diff options
author | clarkzjw <[email protected]> | 2023-02-08 00:40:09 -0800 |
---|---|---|
committer | clarkzjw <[email protected]> | 2023-02-08 00:40:09 -0800 |
commit | 1204730924436ef9e1c7c49c9557837f9a5ed0e8 (patch) | |
tree | 129d79dfd11245751cee6d4082ff5d2f6e941610 /ansible/roles/common | |
parent | 9635ac4dedf69de5bff65785bcc16bef80b52d75 (diff) | |
download | mail-1204730924436ef9e1c7c49c9557837f9a5ed0e8.tar.gz |
Diffstat (limited to 'ansible/roles/common')
-rw-r--r-- | ansible/roles/common/defaults/main.yml | 3 | ||||
-rw-r--r-- | ansible/roles/common/files/ffdhe2048.pem | 8 | ||||
-rw-r--r-- | ansible/roles/common/files/ffdhe3072.pem | 11 | ||||
-rw-r--r-- | ansible/roles/common/files/ffdhe4096.pem | 13 | ||||
-rw-r--r-- | ansible/roles/common/files/inputrc | 61 | ||||
-rwxr-xr-x | ansible/roles/common/files/net-listeners.py | 334 | ||||
-rwxr-xr-x | ansible/roles/common/files/ssh-transfer-only.sh | 11 | ||||
-rw-r--r-- | ansible/roles/common/files/vimrc.local | 25 | ||||
-rw-r--r-- | ansible/roles/common/handlers/main.yml | 20 | ||||
-rw-r--r-- | ansible/roles/common/tasks/main.yml | 301 |
10 files changed, 787 insertions, 0 deletions
diff --git a/ansible/roles/common/defaults/main.yml b/ansible/roles/common/defaults/main.yml new file mode 100644 index 0000000..2410e85 --- /dev/null +++ b/ansible/roles/common/defaults/main.yml | |||
@@ -0,0 +1,3 @@ | |||
1 | --- | ||
2 | grub: | ||
3 | extras: "" | ||
diff --git a/ansible/roles/common/files/ffdhe2048.pem b/ansible/roles/common/files/ffdhe2048.pem new file mode 100644 index 0000000..9b182b7 --- /dev/null +++ b/ansible/roles/common/files/ffdhe2048.pem | |||
@@ -0,0 +1,8 @@ | |||
1 | -----BEGIN DH PARAMETERS----- | ||
2 | MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz | ||
3 | +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a | ||
4 | 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 | ||
5 | YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi | ||
6 | 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD | ||
7 | ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== | ||
8 | -----END DH PARAMETERS----- | ||
diff --git a/ansible/roles/common/files/ffdhe3072.pem b/ansible/roles/common/files/ffdhe3072.pem new file mode 100644 index 0000000..fb31ccd --- /dev/null +++ b/ansible/roles/common/files/ffdhe3072.pem | |||
@@ -0,0 +1,11 @@ | |||
1 | -----BEGIN DH PARAMETERS----- | ||
2 | MIIBiAKCAYEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz | ||
3 | +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a | ||
4 | 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 | ||
5 | YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi | ||
6 | 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD | ||
7 | ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3 | ||
8 | 7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32 | ||
9 | nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZsYu | ||
10 | N///////////AgEC | ||
11 | -----END DH PARAMETERS----- | ||
diff --git a/ansible/roles/common/files/ffdhe4096.pem b/ansible/roles/common/files/ffdhe4096.pem new file mode 100644 index 0000000..3cf0fcb --- /dev/null +++ b/ansible/roles/common/files/ffdhe4096.pem | |||
@@ -0,0 +1,13 @@ | |||
1 | -----BEGIN DH PARAMETERS----- | ||
2 | MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz | ||
3 | +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a | ||
4 | 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 | ||
5 | YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi | ||
6 | 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD | ||
7 | ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3 | ||
8 | 7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32 | ||
9 | nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e | ||
10 | 8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx | ||
11 | iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K | ||
12 | zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI= | ||
13 | -----END DH PARAMETERS----- | ||
diff --git a/ansible/roles/common/files/inputrc b/ansible/roles/common/files/inputrc new file mode 100644 index 0000000..47e7275 --- /dev/null +++ b/ansible/roles/common/files/inputrc | |||
@@ -0,0 +1,61 @@ | |||
1 | # do not bell on tab-completion | ||
2 | #set bell-style none | ||
3 | |||
4 | set meta-flag on | ||
5 | set input-meta on | ||
6 | set convert-meta off | ||
7 | set output-meta on | ||
8 | |||
9 | # Completed names which are symbolic links to | ||
10 | # directories have a slash appended. | ||
11 | set mark-symlinked-directories on | ||
12 | |||
13 | $if mode=emacs | ||
14 | |||
15 | # for linux console and RH/Debian xterm | ||
16 | "\e[1~": beginning-of-line | ||
17 | "\e[4~": end-of-line | ||
18 | # commented out keymappings for pgup/pgdown to reach begin/end of history | ||
19 | #"\e[5~": beginning-of-history | ||
20 | #"\e[6~": end-of-history | ||
21 | "\e[5~": history-search-backward | ||
22 | "\e[6~": history-search-forward | ||
23 | "\e[3~": delete-char | ||
24 | "\e[2~": quoted-insert | ||
25 | "\e[5C": forward-word | ||
26 | "\e[5D": backward-word | ||
27 | "\e[1;5C": forward-word | ||
28 | "\e[1;5D": backward-word | ||
29 | |||
30 | # for rxvt | ||
31 | "\e[8~": end-of-line | ||
32 | "\eOc": forward-word | ||
33 | "\eOd": backward-word | ||
34 | |||
35 | # for non RH/Debian xterm, can't hurt for RH/DEbian xterm | ||
36 | "\eOH": beginning-of-line | ||
37 | "\eOF": end-of-line | ||
38 | |||
39 | # for freebsd console | ||
40 | "\e[H": beginning-of-line | ||
41 | "\e[F": end-of-line | ||
42 | $endif | ||
43 | |||
44 | # cd d<TAB> will match documents or Documents | ||
45 | set completion-ignore-case on | ||
46 | |||
47 | # front-of-command up and down completion | ||
48 | "\e[A":history-search-backward | ||
49 | "\e[B":history-search-forward | ||
50 | |||
51 | # This is the magic command. | ||
52 | # Enables sane tcsh-like ctrl-d completion showing. | ||
53 | Control-d:delete-char-or-list | ||
54 | |||
55 | # oddly, menu-complete makes the menu *not* appear | ||
56 | # just cycle through each option | ||
57 | Tab:menu-complete | ||
58 | |||
59 | # stop asking if I "Really want to see 102 completions" | ||
60 | set completion-query-items 350 | ||
61 | set page-completions off | ||
diff --git a/ansible/roles/common/files/net-listeners.py b/ansible/roles/common/files/net-listeners.py new file mode 100755 index 0000000..f8b39cd --- /dev/null +++ b/ansible/roles/common/files/net-listeners.py | |||
@@ -0,0 +1,334 @@ | |||
1 | #!/usr/bin/env python3 | ||
2 | |||
3 | """ Output a colorized list of listening addresses with owners. | ||
4 | |||
5 | This tool parses files in /proc directly to obtain the list | ||
6 | of IPv4 and IPv6 addresses listening on tcp, tcp6, udp, and udp6 ports | ||
7 | also with pids of processes responsible for the listening. | ||
8 | |||
9 | Due to permission restrictions on Linux, script must be run as root | ||
10 | to determine which pids match which listening sockets. | ||
11 | |||
12 | This is also something like: | ||
13 | osqueryi "select po.pid, rtrim(p.cmdline), po.family, po.local_address, po.local_port from process_open_sockets as po JOIN processes as p ON po.pid=p.pid WHERE po.state='LISTEN';" | ||
14 | |||
15 | """ | ||
16 | |||
17 | import collections | ||
18 | import subprocess | ||
19 | import codecs | ||
20 | import socket | ||
21 | import struct | ||
22 | import glob | ||
23 | import sys | ||
24 | import re | ||
25 | import os | ||
26 | |||
27 | TERMINAL_WIDTH = "/usr/bin/tput cols" # could also be "stty size" | ||
28 | |||
29 | ONLY_LOWEST_PID = False | ||
30 | |||
31 | # oooh, look, a big dirty global dict collecting all our data without being | ||
32 | # passed around! call the programming police! | ||
33 | inodes = {} | ||
34 | |||
35 | |||
36 | class Color: | ||
37 | HEADER = '\033[95m' | ||
38 | OKBLUE = '\033[94m' | ||
39 | OKGREEN = '\033[92m' | ||
40 | WARNING = '\033[93m' | ||
41 | FAIL = '\033[91m' | ||
42 | BOLD = '\033[1m' | ||
43 | UNDERLINE = '\033[4m' | ||
44 | END = '\033[0m' | ||
45 | |||
46 | |||
47 | COLOR_HEADER = Color.HEADER | ||
48 | COLOR_OKAY = Color.OKBLUE | ||
49 | COLOR_WARNING = Color.FAIL | ||
50 | COLOR_END = Color.END | ||
51 | |||
52 | # This should capture: | ||
53 | # 127.0.0.0/8 | ||
54 | # 192.168.0.0/16 | ||
55 | # 10.0.0.0/8 | ||
56 | # 169.254.0.0/16 | ||
57 | # 172.16.0.0/12 | ||
58 | # ::1 | ||
59 | # fe80::/10 | ||
60 | # fc00::/7 | ||
61 | # fd00::/8 | ||
62 | NON_ROUTABLE_REGEX = r"""^((127\.) | | ||
63 | (192\.168\.) | | ||
64 | (10\.) | | ||
65 | (169\.254\.) | | ||
66 | (172\.1[6-9]\.) | | ||
67 | (172\.2[0-9]\.) | | ||
68 | (172\.3[0-1]\.) | | ||
69 | (::1) | | ||
70 | ([fF][eE]80) | ||
71 | ([fF][cCdD]))""" | ||
72 | likelyLocalOnly = re.compile(NON_ROUTABLE_REGEX, re.VERBOSE) | ||
73 | |||
74 | |||
75 | def run(thing): | ||
76 | """ Run any string as an async command invocation. """ | ||
77 | # We don't use subprocess.check_output because we want to run all | ||
78 | # processes async | ||
79 | return subprocess.Popen(thing.split(), stdout=subprocess.PIPE) | ||
80 | |||
81 | |||
82 | def readOutput(ranCommand): | ||
83 | """ Return array of rows split by newline from previous invocation. """ | ||
84 | stdout, stderr = ranCommand.communicate() | ||
85 | return stdout.decode('utf-8').strip().splitlines() | ||
86 | |||
87 | |||
88 | def procListeners(): | ||
89 | """ Wrapper to parse all IPv4 tcp udp, and, IPv6 tcp6 udp6 listeners. """ | ||
90 | |||
91 | def processProc(name): | ||
92 | """ Process IPv4 and IPv6 versions of listeners based on ``name``. | ||
93 | |||
94 | ``name`` is either 'udp' or 'tcp' so we parse, for each ``name``: | ||
95 | - /proc/net/[name] | ||
96 | - /proc/net/[name]6 | ||
97 | |||
98 | As in: | ||
99 | - /proc/net/tcp | ||
100 | - /proc/net/tcp6 | ||
101 | - /proc/net/udp | ||
102 | - /proc/net/udp6 | ||
103 | """ | ||
104 | |||
105 | def ipv6(addr): | ||
106 | """ Convert /proc IPv6 hex address into standard IPv6 notation. """ | ||
107 | # turn ASCII hex address into binary | ||
108 | addr = codecs.decode(addr, "hex") | ||
109 | |||
110 | # unpack into 4 32-bit integers in big endian / network byte order | ||
111 | addr = struct.unpack('!LLLL', addr) | ||
112 | |||
113 | # re-pack as 4 32-bit integers in system native byte order | ||
114 | addr = struct.pack('@IIII', *addr) | ||
115 | |||
116 | # now we can use standard network APIs to format the address | ||
117 | addr = socket.inet_ntop(socket.AF_INET6, addr) | ||
118 | return addr | ||
119 | |||
120 | def ipv4(addr): | ||
121 | """ Convert /proc IPv4 hex address into standard IPv4 notation. """ | ||
122 | # Instead of codecs.decode(), we can just convert a 4 byte hex | ||
123 | # string to an integer directly using python radix conversion. | ||
124 | # Basically, int(addr, 16) EQUALS: | ||
125 | # aOrig = addr | ||
126 | # addr = codecs.decode(addr, "hex") | ||
127 | # addr = struct.unpack(">L", addr) | ||
128 | # assert(addr == (int(aOrig, 16),)) | ||
129 | addr = int(addr, 16) | ||
130 | |||
131 | # system native byte order, 4-byte integer | ||
132 | addr = struct.pack("=L", addr) | ||
133 | addr = socket.inet_ntop(socket.AF_INET, addr) | ||
134 | return addr | ||
135 | |||
136 | isUDP = name == "udp" | ||
137 | |||
138 | # Iterate four files: /proc/net/{tcp,udp}{,6} | ||
139 | # ipv4 has no prefix, while ipv6 has 6 appended. | ||
140 | for ver in ["", "6"]: | ||
141 | with open(f"/proc/net/{name}{ver}", 'r') as proto: | ||
142 | proto = proto.read().splitlines() | ||
143 | proto = proto[1:] # drop header row | ||
144 | |||
145 | for cxn in proto: | ||
146 | cxn = cxn.split() | ||
147 | |||
148 | # /proc/net/udp{,6} uses different constants for LISTENING | ||
149 | if isUDP: | ||
150 | # These constants are based on enum offsets inside | ||
151 | # the Linux kernel itself. They aren't likely to ever | ||
152 | # change since they are hardcoded in utilities. | ||
153 | isListening = cxn[3] == "07" | ||
154 | else: | ||
155 | isListening = cxn[3] == "0A" | ||
156 | |||
157 | # Right now this is a single-purpose tool so if process is | ||
158 | # not listening, we avoid further processing of this row. | ||
159 | if not isListening: | ||
160 | continue | ||
161 | |||
162 | ip, port = cxn[1].split(':') | ||
163 | if ver: | ||
164 | ip = ipv6(ip) | ||
165 | else: | ||
166 | ip = ipv4(ip) | ||
167 | |||
168 | port = int(port, 16) | ||
169 | inode = cxn[9] | ||
170 | |||
171 | # We just use a list here because creating a new sub-dict | ||
172 | # for each entry was noticably slower than just indexing | ||
173 | # into lists. | ||
174 | inodes[int(inode)] = [ip, port, f"{name}{ver}"] | ||
175 | |||
176 | processProc("tcp") | ||
177 | processProc("udp") | ||
178 | |||
179 | |||
180 | def appendToInodePidMap(fd, inodePidMap): | ||
181 | """ Take a full path to /proc/[pid]/fd/[fd] for reading. | ||
182 | |||
183 | Populates both pid and full command line of pid owning an inode we | ||
184 | are interested in. | ||
185 | |||
186 | Basically finds if any inodes on this pid is a listener we previously | ||
187 | recorded into our ``inodes`` dict. """ | ||
188 | _, _, pid, _, _ = fd.split('/') | ||
189 | try: | ||
190 | target = os.readlink(fd) | ||
191 | except FileNotFoundError: | ||
192 | # file vanished, can't do anything else | ||
193 | return | ||
194 | |||
195 | if target.startswith("socket"): | ||
196 | ostype, inode = target.split(':') | ||
197 | # strip brackets from fd string (it looks like: [fd]) | ||
198 | inode = int(inode[1:-1]) | ||
199 | inodePidMap[inode].append(int(pid)) | ||
200 | |||
201 | |||
202 | def addProcessNamesToInodes(): | ||
203 | """ Loop over every fd in every process in /proc. | ||
204 | |||
205 | The only way to map an fd back to a process is by looking | ||
206 | at *every* processes fd and extracting backing inodes. | ||
207 | |||
208 | It's basically like a big awkward database join where you don't | ||
209 | have an index on the field you want. | ||
210 | |||
211 | Also, due to Linux permissions (and Linux security concerns), | ||
212 | only the root user can read fd listing of processes not owned | ||
213 | by the current user. """ | ||
214 | |||
215 | # glob glob glob it all | ||
216 | allFDs = glob.iglob("/proc/*/fd/*") | ||
217 | inodePidMap = collections.defaultdict(list) | ||
218 | |||
219 | for fd in allFDs: | ||
220 | appendToInodePidMap(fd, inodePidMap) | ||
221 | |||
222 | for inode in inodes: | ||
223 | if inode in inodePidMap: | ||
224 | for pid in inodePidMap[inode]: | ||
225 | try: | ||
226 | with open(f"/proc/{pid}/cmdline", 'r') as cmd: | ||
227 | # /proc command line arguments are delimited by | ||
228 | # null bytes, so undo that here... | ||
229 | cmdline = cmd.read().split('\0') | ||
230 | inodes[inode].append((pid, cmdline)) | ||
231 | except BaseException: | ||
232 | # files can vanish on us at any time (and that's okay!) | ||
233 | # But, since the file is gone, we want the entire fd | ||
234 | # entry gone too: | ||
235 | pass # del inodes[inode] | ||
236 | |||
237 | |||
238 | def checkListenersProc(): | ||
239 | terminalWidth = run(TERMINAL_WIDTH) | ||
240 | |||
241 | procListeners() | ||
242 | addProcessNamesToInodes() | ||
243 | tried = inodes | ||
244 | |||
245 | try: | ||
246 | cols = readOutput(terminalWidth)[0] | ||
247 | cols = int(cols) | ||
248 | except BaseException: | ||
249 | cols = 80 | ||
250 | |||
251 | # Print our own custom output header... | ||
252 | proto = "Proto" | ||
253 | addr = "Listening" | ||
254 | pid = "PID" | ||
255 | process = "Process" | ||
256 | print(f"{COLOR_HEADER}{proto:^5} {addr:^25} {pid:>5} {process:^30}") | ||
257 | |||
258 | # Could sort by anything: ip, port, proto, pid, command name | ||
259 | # (or even the fd integer if that provided any insight whatsoever) | ||
260 | def compareByPidOrPort(what): | ||
261 | k, v = what | ||
262 | # v = [ip, port, proto, pid, cmd] | ||
263 | # - OR - | ||
264 | # v = [ip, port, proto] | ||
265 | |||
266 | # If we're not running as root we can't pid and command mappings for | ||
267 | # the processes of other users, so sort the pids we did find at end | ||
268 | # of list and show UNKNOWN entries first | ||
269 | # (because the lines will be shorter most likely so the bigger visual | ||
270 | # weight should be lower in the display table) | ||
271 | try: | ||
272 | # Pid available! Sort by first pid, subsort by IP then port. | ||
273 | return (1, v[3], v[0], v[1]) | ||
274 | except BaseException: | ||
275 | # No pid available! Sort by port number then IP then... port again. | ||
276 | return (0, v[1], v[0], v[1]) | ||
277 | |||
278 | # Sort results by pid... | ||
279 | for name, vals in sorted(tried.items(), key=compareByPidOrPort): | ||
280 | attachedPids = vals[3:] | ||
281 | if attachedPids: | ||
282 | desc = [f"{pid:5} {' '.join(cmd)}" for pid, cmd in vals[3:]] | ||
283 | else: | ||
284 | # If not running as root, we won't have pid or process, so use | ||
285 | # defaults | ||
286 | desc = ["UNKNOWN (must be root for global pid mappings)"] | ||
287 | |||
288 | port = vals[1] | ||
289 | try: | ||
290 | # Convert port integer to service name if possible | ||
291 | port = socket.getservbyport(port) | ||
292 | except BaseException: | ||
293 | # If no match, just use port number directly. | ||
294 | pass | ||
295 | |||
296 | addr = f"{vals[0]}:{port}" | ||
297 | proto = vals[2] | ||
298 | |||
299 | # If IP address looks like it could be visible to the world, | ||
300 | # throw up a color. | ||
301 | # Note: due to port forwarding and NAT and other issues, | ||
302 | # this clearly isn't exhaustive. | ||
303 | if re.match(likelyLocalOnly, addr): | ||
304 | colorNotice = COLOR_OKAY | ||
305 | else: | ||
306 | colorNotice = COLOR_WARNING | ||
307 | |||
308 | isFirstLine = True | ||
309 | for line in desc: | ||
310 | if isFirstLine: | ||
311 | output = f"{colorNotice}{proto:5} {addr:25} {line}" | ||
312 | isFirstLine = False | ||
313 | else: | ||
314 | output = f"{' ':31} {line}" | ||
315 | |||
316 | # Be a polite terminal citizen by limiting our width to user's width | ||
317 | # (colors take up non-visible space, so add it to our col count) | ||
318 | print(output[:cols + (len(colorNotice) if isFirstLine else 0)]) | ||
319 | |||
320 | if ONLY_LOWEST_PID: | ||
321 | break | ||
322 | |||
323 | print(COLOR_END) | ||
324 | |||
325 | |||
326 | if __name__ == "__main__": | ||
327 | # cheap hack garbage way of setting one option | ||
328 | # if we need more options, obviously pull in argparse | ||
329 | if len(sys.argv) > 1: | ||
330 | ONLY_LOWEST_PID = True | ||
331 | else: | ||
332 | ONLY_LOWEST_PID = False | ||
333 | |||
334 | checkListenersProc() | ||
diff --git a/ansible/roles/common/files/ssh-transfer-only.sh b/ansible/roles/common/files/ssh-transfer-only.sh new file mode 100755 index 0000000..c1f0624 --- /dev/null +++ b/ansible/roles/common/files/ssh-transfer-only.sh | |||
@@ -0,0 +1,11 @@ | |||
1 | #!/usr/bin/env bash | ||
2 | |||
3 | # Only allow ssh commands starting with 'scp' or 'rsync' | ||
4 | case $SSH_ORIGINAL_COMMAND in | ||
5 | scp*) | ||
6 | $SSH_ORIGINAL_COMMAND ;; | ||
7 | rsync*) | ||
8 | $SSH_ORIGINAL_COMMAND ;; | ||
9 | *) | ||
10 | echo "Not allowed with this key: $SSH_ORIGINAL_COMMAND" ;; | ||
11 | esac | ||
diff --git a/ansible/roles/common/files/vimrc.local b/ansible/roles/common/files/vimrc.local new file mode 100644 index 0000000..378d91e --- /dev/null +++ b/ansible/roles/common/files/vimrc.local | |||
@@ -0,0 +1,25 @@ | |||
1 | set encoding=utf-8 | ||
2 | |||
3 | set ignorecase | ||
4 | set smartcase | ||
5 | |||
6 | set title | ||
7 | |||
8 | set backupdir=~/.vim-tmp,/tmp | ||
9 | set directory=~/.vim-tmp,/tmp | ||
10 | |||
11 | set ruler | ||
12 | |||
13 | filetype plugin indent on | ||
14 | |||
15 | set ai | ||
16 | set expandtab | ||
17 | set tabstop=4 | ||
18 | set shiftwidth=4 | ||
19 | |||
20 | autocmd BufEnter * :syntax sync fromstart | ||
21 | |||
22 | set hlsearch | ||
23 | colorscheme peachpuff | ||
24 | |||
25 | set wildignore+=*/tmp/*,*.so,*.swp,*.zip,*.o,*.dSYM,tags | ||
diff --git a/ansible/roles/common/handlers/main.yml b/ansible/roles/common/handlers/main.yml new file mode 100644 index 0000000..ade4fea --- /dev/null +++ b/ansible/roles/common/handlers/main.yml | |||
@@ -0,0 +1,20 @@ | |||
1 | --- | ||
2 | - name: reload sshd | ||
3 | service: | ||
4 | name: sshd | ||
5 | state: reloaded | ||
6 | |||
7 | - name: reload grub | ||
8 | command: update-grub | ||
9 | |||
10 | - name: double disable systemd ntp client | ||
11 | command: timedatectl set-ntp false | ||
12 | |||
13 | - name: clear motd cache | ||
14 | file: | ||
15 | path: "{{ item }}" | ||
16 | state: absent | ||
17 | loop: | ||
18 | - /var/cache/motd-news | ||
19 | - /run/motd.dynamic | ||
20 | - /run/motd.dynamic.new | ||
diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml new file mode 100644 index 0000000..23de53c --- /dev/null +++ b/ansible/roles/common/tasks/main.yml | |||
@@ -0,0 +1,301 @@ | |||
1 | --- | ||
2 | # You can manually view how your OS-provided packages are supported with: | ||
3 | # ubuntu-support-status --show-all | ||
4 | - name: update packages | ||
5 | apt: | ||
6 | update_cache: yes | ||
7 | upgrade: safe | ||
8 | cache_valid_time: 3600 | ||
9 | |||
10 | |||
11 | - name: fix inputrc | ||
12 | copy: | ||
13 | src: inputrc | ||
14 | dest: /etc/inputrc | ||
15 | owner: root | ||
16 | group: root | ||
17 | mode: 0644 | ||
18 | |||
19 | - name: fix vimrc | ||
20 | copy: | ||
21 | src: vimrc.local | ||
22 | dest: /etc/vim/ | ||
23 | owner: root | ||
24 | group: root | ||
25 | mode: 0644 | ||
26 | |||
27 | |||
28 | - include_role: | ||
29 | name: ramdisk | ||
30 | |||
31 | |||
32 | - name: remove ubuntu call home reporting cron | ||
33 | cron: | ||
34 | cron_file: popularity-contest | ||
35 | state: absent | ||
36 | |||
37 | |||
38 | - name: remove low port restriction | ||
39 | sysctl: | ||
40 | name: net.ipv4.ip_unprivileged_port_start | ||
41 | value: 0 | ||
42 | state: present | ||
43 | sysctl_set: yes | ||
44 | |||
45 | |||
46 | # 3 means enable for outgoing and incoming connections | ||
47 | # 2 means enable for incoming connections | ||
48 | # 1 means enable for outgoing connections | ||
49 | # 0 means disabled | ||
50 | # Linux 3.13 (2014-01-19) and newer | ||
51 | - name: enable server and client TCP_FASTOPEN | ||
52 | sysctl: | ||
53 | name: net.ipv4.tcp_fastopen | ||
54 | value: 3 | ||
55 | state: present | ||
56 | sysctl_set: yes | ||
57 | |||
58 | |||
59 | # These were taken from: | ||
60 | # https://wiki.mozilla.org/Security/Server_Side_TLS#Pre-defined_DHE_groups | ||
61 | - name: populate known-good dhparams | ||
62 | copy: | ||
63 | src: "{{ item }}" | ||
64 | dest: "/etc/ssl/{{ item }}" | ||
65 | loop: | ||
66 | - ffdhe2048.pem | ||
67 | - ffdhe3072.pem | ||
68 | - ffdhe4096.pem | ||
69 | |||
70 | |||
71 | - name: configure /etc/hostname | ||
72 | hostname: | ||
73 | name: "{{ inventory_hostname }}" | ||
74 | |||
75 | #- name: Add IP address of all hosts to all hosts | ||
76 | # lineinfile: | ||
77 | # state: present | ||
78 | # dest: /etc/hosts | ||
79 | # regexp: '.*{{ item }}$' | ||
80 | # line: "{{ hostvars[item].ansible_default_ipv4.address }} {{item}}" | ||
81 | # when: hostvars[item].ansible_default_ipv4.address is defined | ||
82 | # with_items: "{{ groups['all'] }}" | ||
83 | |||
84 | |||
85 | - name: configure sshd to only listen on IPv4 | ||
86 | lineinfile: | ||
87 | dest: /etc/ssh/sshd_config | ||
88 | regexp: '^#?AddressFamily' | ||
89 | line: "AddressFamily inet" # no ipv6 | ||
90 | state: present | ||
91 | notify: reload sshd | ||
92 | |||
93 | |||
94 | # Capture example: | ||
95 | #- replace: | ||
96 | # path: /etc/hosts | ||
97 | # regexp: '(\s+)old\.host\.name(\s+.*)?$' | ||
98 | # replace: '\1new.host.name\2' | ||
99 | # backup: yes | ||
100 | |||
101 | |||
102 | - name: fix motd | ||
103 | replace: | ||
104 | path: /etc/default/motd-news | ||
105 | regexp: 'https://motd.ubuntu.com' | ||
106 | replace: 'https://matt.sh/motd' | ||
107 | notify: | ||
108 | - clear motd cache | ||
109 | |||
110 | |||
111 | # Verify against: | ||
112 | # systemctl list-timers | ||
113 | - name: disable more automated call home reporting | ||
114 | systemd: | ||
115 | name: "{{ item }}" | ||
116 | state: stopped | ||
117 | enabled: False | ||
118 | loop: | ||
119 | - apt-daily-upgrade.timer | ||
120 | - apt-daily.timer | ||
121 | - motd-news.timer | ||
122 | |||
123 | |||
124 | - name: remove ubuntu self-advertising | ||
125 | file: | ||
126 | path: "/etc/update-motd.d/{{ item }}" | ||
127 | state: absent | ||
128 | loop: | ||
129 | - 91-release-upgrade | ||
130 | - 80-livepatch | ||
131 | - 10-help-text | ||
132 | notify: | ||
133 | - clear motd cache | ||
134 | |||
135 | |||
136 | # Ubuntu's pam_motd.so shows you /etc/legal | ||
137 | # on login if you don't have ~/.cache/motd.legal-displayed | ||
138 | # There is no way to disable the creation of that file in ~/.cache on login, | ||
139 | # but we can wipe out the message for new users. | ||
140 | - name: remove login disclaimer | ||
141 | file: | ||
142 | path: /etc/legal | ||
143 | state: absent | ||
144 | |||
145 | |||
146 | - name: place net-listeners.py | ||
147 | copy: | ||
148 | src: net-listeners.py | ||
149 | dest: /usr/local/bin/ | ||
150 | owner: root | ||
151 | group: root | ||
152 | mode: 0755 | ||
153 | |||
154 | - name: place scp/rsync-only ssh restriction capability | ||
155 | copy: | ||
156 | src: ssh-transfer-only.sh | ||
157 | dest: /usr/local/bin/ | ||
158 | owner: root | ||
159 | group: root | ||
160 | mode: 0755 | ||
161 | |||
162 | # can't setsid 04755 scripts, so enable script with global passwordless sudo | ||
163 | - name: enable all user running of net-listeners.py | ||
164 | lineinfile: | ||
165 | path: /etc/sudoers.d/net-listeners | ||
166 | regexp: "listeners.py" | ||
167 | line: "ALL ALL = (root) NOPASSWD: /usr/local/bin/net-listeners.py" | ||
168 | create: yes | ||
169 | mode: 0440 | ||
170 | |||
171 | - name: add uptime and uname to login motd | ||
172 | lineinfile: | ||
173 | dest: /etc/update-motd.d/00-header | ||
174 | line: "{{ item }}" | ||
175 | state: present | ||
176 | loop: | ||
177 | - printf "\n$(w -us)\n" | ||
178 | |||
179 | - name: add listening watcher to global login config | ||
180 | lineinfile: | ||
181 | dest: /etc/bash.bashrc | ||
182 | line: "{{ item }}" | ||
183 | state: present | ||
184 | loop: | ||
185 | # Only show output when running a login, not when starting a sudo shell | ||
186 | - "[[ -z $SUDO_UID ]] && sudo /usr/local/bin/net-listeners.py" | ||
187 | |||
188 | - name: ensure system grub template has serial access | ||
189 | lineinfile: | ||
190 | dest: /etc/default/grub | ||
191 | regexp: '^GRUB_CMDLINE_LINUX=' | ||
192 | line: 'GRUB_CMDLINE_LINUX="console=ttyS0 {{ grub.extras }}"' | ||
193 | state: present | ||
194 | notify: reload grub | ||
195 | |||
196 | |||
197 | # This is an ops opinion. For more advanced needs, modify here or just template | ||
198 | # the entire sshd_config directly. | ||
199 | - name: configure sshd to only listen on local IP | ||
200 | lineinfile: | ||
201 | dest: /etc/ssh/sshd_config | ||
202 | regexp: '^#?ListenAddress' | ||
203 | line: "ListenAddress {{ hostvars[inventory_hostname]['ansible_' + network.interface.private]['ipv4']['address'] }}" | ||
204 | state: present | ||
205 | notify: reload sshd | ||
206 | |||
207 | |||
208 | - name: install system tools | ||
209 | apt: | ||
210 | pkg: | ||
211 | # acl is required for ansible to "become_user" as someone non-root because | ||
212 | # of permissions on its temporary files. Ansible will setfacl on temp files | ||
213 | # so it doesn't have to 0666 everything just so a new user can modify things. | ||
214 | - acl | ||
215 | |||
216 | # you aren't a linux server without sending nightly summary emails | ||
217 | - logwatch | ||
218 | |||
219 | # apt helpers for repo installs not included by default for some reason | ||
220 | - software-properties-common | ||
221 | |||
222 | # production CA bundles so we don't get unknown CA errors | ||
223 | - ca-certificates | ||
224 | |||
225 | # Maintains high numbers in /proc/sys/kernel/random/entropy_avail | ||
226 | - rng-tools | ||
227 | |||
228 | # should we use a more modern thing than collect? distributed osquery? | ||
229 | - collectd | ||
230 | |||
231 | # make sure 'install_recommends: no' or this installs lots of other stuff | ||
232 | - vim-nox | ||
233 | |||
234 | # rrdtool only installed so we can be lazy and generate graphs on-demand | ||
235 | # with: /usr/share/doc/collectd-core/examples/collectd2html.pl | ||
236 | # TODO: enable centralized reporting system | ||
237 | - rrdtool | ||
238 | |||
239 | # netstat, mii-tool, etc | ||
240 | - net-tools | ||
241 | install_recommends: no | ||
242 | state: latest | ||
243 | |||
244 | # use a modern ntp client+server. | ||
245 | # | ||
246 | # systemd actually has a built-in ntp client called 'systemd-timesyncd' | ||
247 | # You can view its status with: | ||
248 | # journalctl -u systemd-timesyncd | ||
249 | # timedatectl | ||
250 | # | ||
251 | # Installing chrony will disable systemd-timesyncd | ||
252 | # (represented in apt with "Replaces: time-daemon") | ||
253 | # but it doesn't _actually_ disable it according to timedatectl (bug?) | ||
254 | # so we also manually run 'timedatectl set-ntp false' just to confirm. | ||
255 | # A good writeup about systemd-timesyncd lives at: | ||
256 | # https://wiki.archlinux.org/index.php/systemd-timesyncd | ||
257 | # | ||
258 | # You can view your live chrony status with: | ||
259 | # chronyc tracking | ||
260 | # chronyc sources | ||
261 | # chronyc sourcestats | ||
262 | # | ||
263 | # ...and that's a lot more detail than the built-in garabage systemd-timesyncd | ||
264 | # client will tell you about how your system time is being managed. | ||
265 | # | ||
266 | # chrony is both an ntp client with a remote administration interface | ||
267 | # and an ntp server, but by default chrony does not enable remote admin | ||
268 | # or ntp serving without additional explicit configuration (chrony.conf). | ||
269 | # | ||
270 | # For more details about becoming an ntp server and remote time administartion, | ||
271 | # see sections 2.2 and 2.5 of: | ||
272 | # https://chrony.tuxfamily.org/faq.html#_how_do_i_make_an_ntp_server_from_an_ntp_client | ||
273 | - name: install ntp client | ||
274 | apt: | ||
275 | pkg: chrony | ||
276 | state: latest | ||
277 | notify: | ||
278 | - double disable systemd ntp client | ||
279 | |||
280 | # If ansible facts aren't enough, we can get puppet and chef facts too: | ||
281 | #- name: install facter | ||
282 | # apt: | ||
283 | # pkg: facter | ||
284 | # state: latest | ||
285 | # | ||
286 | #- name: install ohai | ||
287 | # apt: | ||
288 | # pkg: facter | ||
289 | # state: latest | ||
290 | |||
291 | |||
292 | # cleanup | ||
293 | - name: cleanup packaging | ||
294 | apt: | ||
295 | autoclean: yes | ||
296 | autoremove: yes | ||
297 | |||
298 | # If needed, build and provide: | ||
299 | # | ||
300 | # Build for nsjail: | ||
301 | # apt install protobu* bison flex pkg-config libprotobuf-dev | ||