From 1204730924436ef9e1c7c49c9557837f9a5ed0e8 Mon Sep 17 00:00:00 2001 From: clarkzjw Date: Wed, 8 Feb 2023 00:40:09 -0800 Subject: fork https://github.com/mattsta/mailweb --- ansible/roles/common/defaults/main.yml | 3 + ansible/roles/common/files/ffdhe2048.pem | 8 + ansible/roles/common/files/ffdhe3072.pem | 11 + ansible/roles/common/files/ffdhe4096.pem | 13 + ansible/roles/common/files/inputrc | 61 +++++ ansible/roles/common/files/net-listeners.py | 334 ++++++++++++++++++++++++ ansible/roles/common/files/ssh-transfer-only.sh | 11 + ansible/roles/common/files/vimrc.local | 25 ++ ansible/roles/common/handlers/main.yml | 20 ++ ansible/roles/common/tasks/main.yml | 301 +++++++++++++++++++++ 10 files changed, 787 insertions(+) create mode 100644 ansible/roles/common/defaults/main.yml create mode 100644 ansible/roles/common/files/ffdhe2048.pem create mode 100644 ansible/roles/common/files/ffdhe3072.pem create mode 100644 ansible/roles/common/files/ffdhe4096.pem create mode 100644 ansible/roles/common/files/inputrc create mode 100755 ansible/roles/common/files/net-listeners.py create mode 100755 ansible/roles/common/files/ssh-transfer-only.sh create mode 100644 ansible/roles/common/files/vimrc.local create mode 100644 ansible/roles/common/handlers/main.yml create mode 100644 ansible/roles/common/tasks/main.yml (limited to 'ansible/roles/common') 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 @@ +--- +grub: + 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 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== +-----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 @@ +-----BEGIN DH PARAMETERS----- +MIIBiAKCAYEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3 +7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32 +nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZsYu +N///////////AgEC +-----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 @@ +-----BEGIN DH PARAMETERS----- +MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3 +7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32 +nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e +8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx +iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K +zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI= +-----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 @@ +# do not bell on tab-completion +#set bell-style none + +set meta-flag on +set input-meta on +set convert-meta off +set output-meta on + +# Completed names which are symbolic links to +# directories have a slash appended. +set mark-symlinked-directories on + +$if mode=emacs + +# for linux console and RH/Debian xterm +"\e[1~": beginning-of-line +"\e[4~": end-of-line +# commented out keymappings for pgup/pgdown to reach begin/end of history +#"\e[5~": beginning-of-history +#"\e[6~": end-of-history +"\e[5~": history-search-backward +"\e[6~": history-search-forward +"\e[3~": delete-char +"\e[2~": quoted-insert +"\e[5C": forward-word +"\e[5D": backward-word +"\e[1;5C": forward-word +"\e[1;5D": backward-word + +# for rxvt +"\e[8~": end-of-line +"\eOc": forward-word +"\eOd": backward-word + +# for non RH/Debian xterm, can't hurt for RH/DEbian xterm +"\eOH": beginning-of-line +"\eOF": end-of-line + +# for freebsd console +"\e[H": beginning-of-line +"\e[F": end-of-line +$endif + +# cd d will match documents or Documents +set completion-ignore-case on + +# front-of-command up and down completion +"\e[A":history-search-backward +"\e[B":history-search-forward + +# This is the magic command. +# Enables sane tcsh-like ctrl-d completion showing. +Control-d:delete-char-or-list + +# oddly, menu-complete makes the menu *not* appear +# just cycle through each option +Tab:menu-complete + +# stop asking if I "Really want to see 102 completions" +set completion-query-items 350 +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 @@ +#!/usr/bin/env python3 + +""" Output a colorized list of listening addresses with owners. + +This tool parses files in /proc directly to obtain the list +of IPv4 and IPv6 addresses listening on tcp, tcp6, udp, and udp6 ports +also with pids of processes responsible for the listening. + +Due to permission restrictions on Linux, script must be run as root +to determine which pids match which listening sockets. + +This is also something like: + 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';" + +""" + +import collections +import subprocess +import codecs +import socket +import struct +import glob +import sys +import re +import os + +TERMINAL_WIDTH = "/usr/bin/tput cols" # could also be "stty size" + +ONLY_LOWEST_PID = False + +# oooh, look, a big dirty global dict collecting all our data without being +# passed around! call the programming police! +inodes = {} + + +class Color: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + END = '\033[0m' + + +COLOR_HEADER = Color.HEADER +COLOR_OKAY = Color.OKBLUE +COLOR_WARNING = Color.FAIL +COLOR_END = Color.END + +# This should capture: +# 127.0.0.0/8 +# 192.168.0.0/16 +# 10.0.0.0/8 +# 169.254.0.0/16 +# 172.16.0.0/12 +# ::1 +# fe80::/10 +# fc00::/7 +# fd00::/8 +NON_ROUTABLE_REGEX = r"""^((127\.) | + (192\.168\.) | + (10\.) | + (169\.254\.) | + (172\.1[6-9]\.) | + (172\.2[0-9]\.) | + (172\.3[0-1]\.) | + (::1) | + ([fF][eE]80) + ([fF][cCdD]))""" +likelyLocalOnly = re.compile(NON_ROUTABLE_REGEX, re.VERBOSE) + + +def run(thing): + """ Run any string as an async command invocation. """ + # We don't use subprocess.check_output because we want to run all + # processes async + return subprocess.Popen(thing.split(), stdout=subprocess.PIPE) + + +def readOutput(ranCommand): + """ Return array of rows split by newline from previous invocation. """ + stdout, stderr = ranCommand.communicate() + return stdout.decode('utf-8').strip().splitlines() + + +def procListeners(): + """ Wrapper to parse all IPv4 tcp udp, and, IPv6 tcp6 udp6 listeners. """ + + def processProc(name): + """ Process IPv4 and IPv6 versions of listeners based on ``name``. + + ``name`` is either 'udp' or 'tcp' so we parse, for each ``name``: + - /proc/net/[name] + - /proc/net/[name]6 + + As in: + - /proc/net/tcp + - /proc/net/tcp6 + - /proc/net/udp + - /proc/net/udp6 + """ + + def ipv6(addr): + """ Convert /proc IPv6 hex address into standard IPv6 notation. """ + # turn ASCII hex address into binary + addr = codecs.decode(addr, "hex") + + # unpack into 4 32-bit integers in big endian / network byte order + addr = struct.unpack('!LLLL', addr) + + # re-pack as 4 32-bit integers in system native byte order + addr = struct.pack('@IIII', *addr) + + # now we can use standard network APIs to format the address + addr = socket.inet_ntop(socket.AF_INET6, addr) + return addr + + def ipv4(addr): + """ Convert /proc IPv4 hex address into standard IPv4 notation. """ + # Instead of codecs.decode(), we can just convert a 4 byte hex + # string to an integer directly using python radix conversion. + # Basically, int(addr, 16) EQUALS: + # aOrig = addr + # addr = codecs.decode(addr, "hex") + # addr = struct.unpack(">L", addr) + # assert(addr == (int(aOrig, 16),)) + addr = int(addr, 16) + + # system native byte order, 4-byte integer + addr = struct.pack("=L", addr) + addr = socket.inet_ntop(socket.AF_INET, addr) + return addr + + isUDP = name == "udp" + + # Iterate four files: /proc/net/{tcp,udp}{,6} + # ipv4 has no prefix, while ipv6 has 6 appended. + for ver in ["", "6"]: + with open(f"/proc/net/{name}{ver}", 'r') as proto: + proto = proto.read().splitlines() + proto = proto[1:] # drop header row + + for cxn in proto: + cxn = cxn.split() + + # /proc/net/udp{,6} uses different constants for LISTENING + if isUDP: + # These constants are based on enum offsets inside + # the Linux kernel itself. They aren't likely to ever + # change since they are hardcoded in utilities. + isListening = cxn[3] == "07" + else: + isListening = cxn[3] == "0A" + + # Right now this is a single-purpose tool so if process is + # not listening, we avoid further processing of this row. + if not isListening: + continue + + ip, port = cxn[1].split(':') + if ver: + ip = ipv6(ip) + else: + ip = ipv4(ip) + + port = int(port, 16) + inode = cxn[9] + + # We just use a list here because creating a new sub-dict + # for each entry was noticably slower than just indexing + # into lists. + inodes[int(inode)] = [ip, port, f"{name}{ver}"] + + processProc("tcp") + processProc("udp") + + +def appendToInodePidMap(fd, inodePidMap): + """ Take a full path to /proc/[pid]/fd/[fd] for reading. + + Populates both pid and full command line of pid owning an inode we + are interested in. + + Basically finds if any inodes on this pid is a listener we previously + recorded into our ``inodes`` dict. """ + _, _, pid, _, _ = fd.split('/') + try: + target = os.readlink(fd) + except FileNotFoundError: + # file vanished, can't do anything else + return + + if target.startswith("socket"): + ostype, inode = target.split(':') + # strip brackets from fd string (it looks like: [fd]) + inode = int(inode[1:-1]) + inodePidMap[inode].append(int(pid)) + + +def addProcessNamesToInodes(): + """ Loop over every fd in every process in /proc. + + The only way to map an fd back to a process is by looking + at *every* processes fd and extracting backing inodes. + + It's basically like a big awkward database join where you don't + have an index on the field you want. + + Also, due to Linux permissions (and Linux security concerns), + only the root user can read fd listing of processes not owned + by the current user. """ + + # glob glob glob it all + allFDs = glob.iglob("/proc/*/fd/*") + inodePidMap = collections.defaultdict(list) + + for fd in allFDs: + appendToInodePidMap(fd, inodePidMap) + + for inode in inodes: + if inode in inodePidMap: + for pid in inodePidMap[inode]: + try: + with open(f"/proc/{pid}/cmdline", 'r') as cmd: + # /proc command line arguments are delimited by + # null bytes, so undo that here... + cmdline = cmd.read().split('\0') + inodes[inode].append((pid, cmdline)) + except BaseException: + # files can vanish on us at any time (and that's okay!) + # But, since the file is gone, we want the entire fd + # entry gone too: + pass # del inodes[inode] + + +def checkListenersProc(): + terminalWidth = run(TERMINAL_WIDTH) + + procListeners() + addProcessNamesToInodes() + tried = inodes + + try: + cols = readOutput(terminalWidth)[0] + cols = int(cols) + except BaseException: + cols = 80 + + # Print our own custom output header... + proto = "Proto" + addr = "Listening" + pid = "PID" + process = "Process" + print(f"{COLOR_HEADER}{proto:^5} {addr:^25} {pid:>5} {process:^30}") + + # Could sort by anything: ip, port, proto, pid, command name + # (or even the fd integer if that provided any insight whatsoever) + def compareByPidOrPort(what): + k, v = what + # v = [ip, port, proto, pid, cmd] + # - OR - + # v = [ip, port, proto] + + # If we're not running as root we can't pid and command mappings for + # the processes of other users, so sort the pids we did find at end + # of list and show UNKNOWN entries first + # (because the lines will be shorter most likely so the bigger visual + # weight should be lower in the display table) + try: + # Pid available! Sort by first pid, subsort by IP then port. + return (1, v[3], v[0], v[1]) + except BaseException: + # No pid available! Sort by port number then IP then... port again. + return (0, v[1], v[0], v[1]) + + # Sort results by pid... + for name, vals in sorted(tried.items(), key=compareByPidOrPort): + attachedPids = vals[3:] + if attachedPids: + desc = [f"{pid:5} {' '.join(cmd)}" for pid, cmd in vals[3:]] + else: + # If not running as root, we won't have pid or process, so use + # defaults + desc = ["UNKNOWN (must be root for global pid mappings)"] + + port = vals[1] + try: + # Convert port integer to service name if possible + port = socket.getservbyport(port) + except BaseException: + # If no match, just use port number directly. + pass + + addr = f"{vals[0]}:{port}" + proto = vals[2] + + # If IP address looks like it could be visible to the world, + # throw up a color. + # Note: due to port forwarding and NAT and other issues, + # this clearly isn't exhaustive. + if re.match(likelyLocalOnly, addr): + colorNotice = COLOR_OKAY + else: + colorNotice = COLOR_WARNING + + isFirstLine = True + for line in desc: + if isFirstLine: + output = f"{colorNotice}{proto:5} {addr:25} {line}" + isFirstLine = False + else: + output = f"{' ':31} {line}" + + # Be a polite terminal citizen by limiting our width to user's width + # (colors take up non-visible space, so add it to our col count) + print(output[:cols + (len(colorNotice) if isFirstLine else 0)]) + + if ONLY_LOWEST_PID: + break + + print(COLOR_END) + + +if __name__ == "__main__": + # cheap hack garbage way of setting one option + # if we need more options, obviously pull in argparse + if len(sys.argv) > 1: + ONLY_LOWEST_PID = True + else: + ONLY_LOWEST_PID = False + + 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 @@ +#!/usr/bin/env bash + +# Only allow ssh commands starting with 'scp' or 'rsync' +case $SSH_ORIGINAL_COMMAND in + scp*) + $SSH_ORIGINAL_COMMAND ;; + rsync*) + $SSH_ORIGINAL_COMMAND ;; + *) + echo "Not allowed with this key: $SSH_ORIGINAL_COMMAND" ;; +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 @@ +set encoding=utf-8 + +set ignorecase +set smartcase + +set title + +set backupdir=~/.vim-tmp,/tmp +set directory=~/.vim-tmp,/tmp + +set ruler + +filetype plugin indent on + +set ai +set expandtab +set tabstop=4 +set shiftwidth=4 + +autocmd BufEnter * :syntax sync fromstart + +set hlsearch +colorscheme peachpuff + +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 @@ +--- +- name: reload sshd + service: + name: sshd + state: reloaded + +- name: reload grub + command: update-grub + +- name: double disable systemd ntp client + command: timedatectl set-ntp false + +- name: clear motd cache + file: + path: "{{ item }}" + state: absent + loop: + - /var/cache/motd-news + - /run/motd.dynamic + - /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 @@ +--- +# You can manually view how your OS-provided packages are supported with: +# ubuntu-support-status --show-all +- name: update packages + apt: + update_cache: yes + upgrade: safe + cache_valid_time: 3600 + + +- name: fix inputrc + copy: + src: inputrc + dest: /etc/inputrc + owner: root + group: root + mode: 0644 + +- name: fix vimrc + copy: + src: vimrc.local + dest: /etc/vim/ + owner: root + group: root + mode: 0644 + + +- include_role: + name: ramdisk + + +- name: remove ubuntu call home reporting cron + cron: + cron_file: popularity-contest + state: absent + + +- name: remove low port restriction + sysctl: + name: net.ipv4.ip_unprivileged_port_start + value: 0 + state: present + sysctl_set: yes + + +# 3 means enable for outgoing and incoming connections +# 2 means enable for incoming connections +# 1 means enable for outgoing connections +# 0 means disabled +# Linux 3.13 (2014-01-19) and newer +- name: enable server and client TCP_FASTOPEN + sysctl: + name: net.ipv4.tcp_fastopen + value: 3 + state: present + sysctl_set: yes + + +# These were taken from: +# https://wiki.mozilla.org/Security/Server_Side_TLS#Pre-defined_DHE_groups +- name: populate known-good dhparams + copy: + src: "{{ item }}" + dest: "/etc/ssl/{{ item }}" + loop: + - ffdhe2048.pem + - ffdhe3072.pem + - ffdhe4096.pem + + +- name: configure /etc/hostname + hostname: + name: "{{ inventory_hostname }}" + + #- name: Add IP address of all hosts to all hosts + # lineinfile: + # state: present + # dest: /etc/hosts + # regexp: '.*{{ item }}$' + # line: "{{ hostvars[item].ansible_default_ipv4.address }} {{item}}" + # when: hostvars[item].ansible_default_ipv4.address is defined + # with_items: "{{ groups['all'] }}" + + +- name: configure sshd to only listen on IPv4 + lineinfile: + dest: /etc/ssh/sshd_config + regexp: '^#?AddressFamily' + line: "AddressFamily inet" # no ipv6 + state: present + notify: reload sshd + + + # Capture example: + #- replace: + # path: /etc/hosts + # regexp: '(\s+)old\.host\.name(\s+.*)?$' + # replace: '\1new.host.name\2' + # backup: yes + + +- name: fix motd + replace: + path: /etc/default/motd-news + regexp: 'https://motd.ubuntu.com' + replace: 'https://matt.sh/motd' + notify: + - clear motd cache + + +# Verify against: +# systemctl list-timers +- name: disable more automated call home reporting + systemd: + name: "{{ item }}" + state: stopped + enabled: False + loop: + - apt-daily-upgrade.timer + - apt-daily.timer + - motd-news.timer + + +- name: remove ubuntu self-advertising + file: + path: "/etc/update-motd.d/{{ item }}" + state: absent + loop: + - 91-release-upgrade + - 80-livepatch + - 10-help-text + notify: + - clear motd cache + + +# Ubuntu's pam_motd.so shows you /etc/legal +# on login if you don't have ~/.cache/motd.legal-displayed +# There is no way to disable the creation of that file in ~/.cache on login, +# but we can wipe out the message for new users. +- name: remove login disclaimer + file: + path: /etc/legal + state: absent + + +- name: place net-listeners.py + copy: + src: net-listeners.py + dest: /usr/local/bin/ + owner: root + group: root + mode: 0755 + +- name: place scp/rsync-only ssh restriction capability + copy: + src: ssh-transfer-only.sh + dest: /usr/local/bin/ + owner: root + group: root + mode: 0755 + +# can't setsid 04755 scripts, so enable script with global passwordless sudo +- name: enable all user running of net-listeners.py + lineinfile: + path: /etc/sudoers.d/net-listeners + regexp: "listeners.py" + line: "ALL ALL = (root) NOPASSWD: /usr/local/bin/net-listeners.py" + create: yes + mode: 0440 + +- name: add uptime and uname to login motd + lineinfile: + dest: /etc/update-motd.d/00-header + line: "{{ item }}" + state: present + loop: + - printf "\n$(w -us)\n" + +- name: add listening watcher to global login config + lineinfile: + dest: /etc/bash.bashrc + line: "{{ item }}" + state: present + loop: + # Only show output when running a login, not when starting a sudo shell + - "[[ -z $SUDO_UID ]] && sudo /usr/local/bin/net-listeners.py" + +- name: ensure system grub template has serial access + lineinfile: + dest: /etc/default/grub + regexp: '^GRUB_CMDLINE_LINUX=' + line: 'GRUB_CMDLINE_LINUX="console=ttyS0 {{ grub.extras }}"' + state: present + notify: reload grub + + +# This is an ops opinion. For more advanced needs, modify here or just template +# the entire sshd_config directly. +- name: configure sshd to only listen on local IP + lineinfile: + dest: /etc/ssh/sshd_config + regexp: '^#?ListenAddress' + line: "ListenAddress {{ hostvars[inventory_hostname]['ansible_' + network.interface.private]['ipv4']['address'] }}" + state: present + notify: reload sshd + + +- name: install system tools + apt: + pkg: + # acl is required for ansible to "become_user" as someone non-root because + # of permissions on its temporary files. Ansible will setfacl on temp files + # so it doesn't have to 0666 everything just so a new user can modify things. + - acl + + # you aren't a linux server without sending nightly summary emails + - logwatch + + # apt helpers for repo installs not included by default for some reason + - software-properties-common + + # production CA bundles so we don't get unknown CA errors + - ca-certificates + + # Maintains high numbers in /proc/sys/kernel/random/entropy_avail + - rng-tools + + # should we use a more modern thing than collect? distributed osquery? + - collectd + + # make sure 'install_recommends: no' or this installs lots of other stuff + - vim-nox + + # rrdtool only installed so we can be lazy and generate graphs on-demand + # with: /usr/share/doc/collectd-core/examples/collectd2html.pl + # TODO: enable centralized reporting system + - rrdtool + + # netstat, mii-tool, etc + - net-tools + install_recommends: no + state: latest + +# use a modern ntp client+server. +# +# systemd actually has a built-in ntp client called 'systemd-timesyncd' +# You can view its status with: +# journalctl -u systemd-timesyncd +# timedatectl +# +# Installing chrony will disable systemd-timesyncd +# (represented in apt with "Replaces: time-daemon") +# but it doesn't _actually_ disable it according to timedatectl (bug?) +# so we also manually run 'timedatectl set-ntp false' just to confirm. +# A good writeup about systemd-timesyncd lives at: +# https://wiki.archlinux.org/index.php/systemd-timesyncd +# +# You can view your live chrony status with: +# chronyc tracking +# chronyc sources +# chronyc sourcestats +# +# ...and that's a lot more detail than the built-in garabage systemd-timesyncd +# client will tell you about how your system time is being managed. +# +# chrony is both an ntp client with a remote administration interface +# and an ntp server, but by default chrony does not enable remote admin +# or ntp serving without additional explicit configuration (chrony.conf). +# +# For more details about becoming an ntp server and remote time administartion, +# see sections 2.2 and 2.5 of: +# https://chrony.tuxfamily.org/faq.html#_how_do_i_make_an_ntp_server_from_an_ntp_client +- name: install ntp client + apt: + pkg: chrony + state: latest + notify: + - double disable systemd ntp client + +# If ansible facts aren't enough, we can get puppet and chef facts too: +#- name: install facter +# apt: +# pkg: facter +# state: latest +# +#- name: install ohai +# apt: +# pkg: facter +# state: latest + + +# cleanup +- name: cleanup packaging + apt: + autoclean: yes + autoremove: yes + +# If needed, build and provide: +# +# Build for nsjail: +# apt install protobu* bison flex pkg-config libprotobuf-dev -- cgit v1.2.3