summaryrefslogtreecommitdiff
path: root/content/posts/2022/2022-04-25-bashtard-introduction.md
blob: bdc001055312c1be90e3efe52ca61912e81da54c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
---
draft: true
title: Configuring my Machines with Bashtard
date: 2022-04-25
tags:
- Bash
- FreeBSD
- GNU+Linux
- Programming
---

Over the past couple weeks I've been spending some time here and there to work
on my own application for configuring my machines. Before this I've tried
Ansible, but found it to be very convoluted to use, and requires a lot of
conditionals if your machines aren't all running the same base system.

So I made something in Bash, with a few abstractions to make certain
interactions less annoying to do manually every time. This used to be called
`tyilnet`, but I've discussed the setup with a few people on IRC, and decided it
would be a fun project to make it a bit more agnostic, so other people could
also easily start using it. This resulted in the creation of [Bashtard]().

It works by simply writing Bash scripts to do the configuration, and provides
abstractions for using the system's package manager, service manager, and some
utilities such as logging and dealing with configured values. Configuration
values can be set on a per-host or per-OS basis. Since I run a varied base of
OSs, including Gentoo, Debian, and FreeBSD, the per-OS configuration comes in
very handy to me.

As for the reason to use Bash, I chose it because most of the systems I run
already have this installed, so it doesn't add a dependency _most of the time_.
I would've liked to do it in POSIX sh, but I feel that when you're reaching a
certain level of complexity, Bash offers some very nice features which can make
your code cleaner, or less likely to contain bugs. Features such as `[[ ]]`,
`local`, and arrays come to mind.

I've been kindly asked to guide potential new users to writing their first
Bashtard script, known as a _playbook_, so if you want to know about how it
works in practice, keep on reading. If you're satisfied with your current
configuration management system, this might not be quite as interesting to you,
so be warned.

The first steps for a new user would obviously to install Bashtard, as it's not
in any OSs package repositories yet. A `Makefile` is supplied in the repository,
which should make this easy enough.

```txt
git clone https://git.tyil.nl/bashtard
cd bashtard
sudo make install
hash -r
```

Once installed, it needs some initialization.

```txt
bashtard init
```

This will create the basic structure in `/etc/bashtard`, including a
`playbooks.d`. Inside this `playbooks.d` directory, any directory is considered
to be a playbook, which requires a `description.txt` and a `playbook.bash`.

```txt
cd /etc/bashtard/playbooks.d
mkdir ssh
cd ssh
echo "OpenSSH configuration" > description.txt
$EDITOR playbook.bash
```

The `playbook.bash` needs to contain 3 functions which are used by `bashtard`, a
`playbook_add()`, `playbook_sync()`, and `playbook_del()`. These will be called
by the `bashtard` subcommand `add`, `sync`, and `del` respectively.

I generally start with the `playbook_sync()` function first, since this is the
function that'll ensure all the configurations are kept in sync with my desires.
I want to have my own `sshd_config`, which needs some templating for the
`Subsystem sftp` line.  There's a `template` function provided by bashtard,
which does some very simple templating. I'll pass it the `sftp` variable to use.

```bash
playbook_sync() {
    template sshd_config \
        "sftp=$(config "ssh.sftp")" \
        > /etc/ssh/sshd_config
}
```

Now to create the actual template. The `template` function looks for templates
in the `share` directory inside the playbook directory.

```txt
mkdir share
$EDITOR share/sshd_config
```

Since I already know what I want my `sshd_config` to look like from previous
installed systems, I'll just use that, but with a variable for the `Subsystem
sftp` value.

```cfg
# Connectivity
Port 22
AddressFamily any
ListenAddress 0.0.0.0
ListenAddress ::

# Fluff
PrintMotd yes

# SFTP
Subsystem sftp ${sftp}

# Authentication
AuthorizedKeysFile /etc/ssh/authorized_keys .ssh/authorized_keys
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication no

# Allow tyil
Match User tyil
    PubkeyAuthentication yes

# Allow public key authentication over VPN
Match Address 10.57.0.0/16
    PubkeyAuthentication yes
    PermitRootLogin prohibit-password
```

The `${sftp}` placeholder will be filled with whatever value is returned by
`config "ssh.sftp"`. And for this to work properly, we will need to define the
variable somewhere. These are written to the `etc` directory inside a playbook.
You can specify defaults in a file called `defaults`, and this can be
overwritten by OS-specific values, which in turn can be overwritten by
host-specific values.

```txt
mkdir etc
$EDITOR etc/defaults
```

The format for these files is a very simple `key=value`. It splits on the first
`=` to determine what the key and value are. This means you can use `=` in your
values, but not your keys.

```txt
ssh.sftp=/usr/lib/openssh/sftp-server
```

This value is correct for Debian and derivatives, but not for my Gentoo or
FreeBSD systems, so I've created OS-specific configuration files for these.

```txt
mkdir etc/os.d
cat /etc/os.d/linux-gentoo
ssh.sftp=/usr/lib64/misc/sftp-server
```

```txt
cat /etc/os.d/freebsd
ssh.sftp=/usr/lib64/misc/sftp-server
```

My `sshd_config` template also specifies the use of a `Motd`, so that needs to
be created as well. This can again be done using the `template` function.

```bash
template "motd" \
    "fqdn=${BASHTARD_PLATFORM[fqdn]}" \
    "time=$(date -u "+%FT%T")" \
    > /etc/motd
```

The `motd` template gets saved at `share/motd`.

```txt
          ████████╗██╗   ██╗██╗██╗        ███╗   ██╗███████╗████████╗
          ╚══██╔══╝╚██╗ ██╔╝██║██║        ████╗  ██║██╔════╝╚══██╔══╝
             ██║    ╚████╔╝ ██║██║        ██╔██╗ ██║█████╗     ██║
             ██║     ╚██╔╝  ██║██║        ██║╚██╗██║██╔══╝     ██║
             ██║      ██║   ██║███████╗██╗██║ ╚████║███████╗   ██║
             ╚═╝      ╚═╝   ╚═╝╚══════╝╚═╝╚═╝  ╚═══╝╚══════╝   ╚═╝

Welcome to ${fqdn}, last updated on ${time}.
```

Lastly, we want to ensure the SSH daemon gets reloaded after every sync, so
let's add that to the `playbook_sync()` function as well.

```bash
svc reload "sshd"
```

The `svc` utility looks for a configuration value that stars with `svc.`,
followed by the service you're trying to act upon, so in this case that would be
`svc.sshd`. We can add this to our configuration files in `etc`. Across all my
machines, `sshd` seems to work as the value, so I only need to add one line to
`etc/defaults`.

```txt
svc.sshd=sshd
```

This should take care of all the things I want automatically synced. The
`playbook_add()` function is intended for all one-time setup required for any
playbook. In this case that means the SSH daemon's service needs to be
activated, since it is not active by default on all my setups.

```bash
playbook_add() {
    svc enable "sshd"
    svc start "sshd"
}
```

However, `add` does not call a `sync`, and I don't want my SSH service to run
with default configuration until a `sync` is initialized. So before enabling and
starting the service, I will call `sync` manually, by running a `playbook_sync`
first. This in turn, however, poses another problem, as `playbook_sync()` wants
to reload the service, which it can't do unless it is already running. To fix
this, I'll add an `if` statement to skip reloading if `bashtard` is running the
`add` command.

```bash
playbook_sync() {
    ...

    [[ $BASHTARD_COMMAND == "add" ]] && return

    svc reload "sshd"
}
```

Now, `bashtard add sshd` will run the `playbook_add()` function, which call the
`playbook_sync()` function before enabling and starting the `sshd` service. All
that is left is the `playbook_del()` function, which only really needs to stop
and disable the service. The templated files can be removed here as well if
desired, of course.

```bash
playbook_del() {
    svc stop "sshd"
    svc disable "sshd"
}
```

Lastly, I configured my `crond` to run `bashtard sync` every 20 minutes, so
whenever I update my configurations, it can take up to 20 minutes to propagate
to all my machines.