From 0a673e300800b91342499cec9cd482b5d2d9c603 Mon Sep 17 00:00:00 2001 From: binary Date: Tue, 10 Nov 2020 20:46:04 +0100 Subject: Fully working init for alpine vm --- playbooks/network.yml | 8 +++ roles/pf/tasks/main.yml | 19 ++++++ roles/serial/files/serial_macro.py | 14 +++++ roles/serial/tasks/main.yml | 12 ++++ roles/ssh/tasks/main.yml | 19 +++++- roles/ssh/templates/sshd_config.j2 | 93 ++++++++++++++++++++++++++++++ roles/vmm/files/init_vm_alpine.yml | 115 +++++++++++++++++++++++++++++++++++++ roles/vmm/files/init_vm_serial.py | 88 ---------------------------- roles/vmm/tasks/init_vm.yml | 38 ------------ roles/vmm/tasks/init_vm_alpine.yml | 50 ++++++++++++++++ 10 files changed, 329 insertions(+), 127 deletions(-) create mode 100644 roles/serial/files/serial_macro.py create mode 100644 roles/ssh/templates/sshd_config.j2 create mode 100644 roles/vmm/files/init_vm_alpine.yml delete mode 100644 roles/vmm/files/init_vm_serial.py delete mode 100644 roles/vmm/tasks/init_vm.yml create mode 100644 roles/vmm/tasks/init_vm_alpine.yml diff --git a/playbooks/network.yml b/playbooks/network.yml index 7d59334..c3bb76f 100644 --- a/playbooks/network.yml +++ b/playbooks/network.yml @@ -2,6 +2,14 @@ # site.yml # Deploy configuration to all servers. +--- + +- hosts: localhost + tasks: + - include_role: + name: ssh + tasks_from: generate_dns.yml + - hosts: openbsd roles: - ssh diff --git a/roles/pf/tasks/main.yml b/roles/pf/tasks/main.yml index c47a721..51471c5 100644 --- a/roles/pf/tasks/main.yml +++ b/roles/pf/tasks/main.yml @@ -11,5 +11,24 @@ group: "{{ group_root }}" mode: "0600" +- name: Enable pf + shell: /sbin/pfctl -e + ignore_errors: true + - name: Restart pf shell: /sbin/pfctl -f /etc/pf.conf + +- name: Test ssh connection on new pf rule + wait_for: + port: 22 + delay: 2 + state: started + +- name: Add cron job for pf + cron: + cron_file: /etc/crontab + name: "Reload pf configuration" + user: root + job: "/sbin/pfctl -f /etc/pf.conf > /dev/nul 2>&1" + minute: "*" + diff --git a/roles/serial/files/serial_macro.py b/roles/serial/files/serial_macro.py new file mode 100644 index 0000000..eb47226 --- /dev/null +++ b/roles/serial/files/serial_macro.py @@ -0,0 +1,14 @@ +#!/bin/python3 + +import serial +import time + + +def send_cmd(ser, delay, cmd): + ser.write(f"{cmd}\n".encode("utf-8")) + time.sleep(delay) + +def send_cmds(ser, cmds): + for cmd in cmds: + send_cmd(ser, cmd[0], cmd[1]) + diff --git a/roles/serial/tasks/main.yml b/roles/serial/tasks/main.yml index 95a8ff5..2db546d 100644 --- a/roles/serial/tasks/main.yml +++ b/roles/serial/tasks/main.yml @@ -5,3 +5,15 @@ - name: Check pyserial installation command: python3 -m pip install pyserial + +- name: Ensure python scripts directory exists + file: + path: /data/python + recurse: true + state: directory + +- name: Copy custom serial library + copy: + src: serial_macro.py + dest: /data/python/serial_macro.py + diff --git a/roles/ssh/tasks/main.yml b/roles/ssh/tasks/main.yml index 38300df..0fc2dee 100644 --- a/roles/ssh/tasks/main.yml +++ b/roles/ssh/tasks/main.yml @@ -3,4 +3,21 @@ --- -- include: generate_dns.yml +- name: Generate sshd configuration + template: + src: templates/sshd_config.j2 + dest: /etc/ssh/sshd_config + owner: root + group: "{{ group_root }}" + mode: "0644" + +- name: Restart sshd + service: + name: sshd + state: restarted + +- name: Check ssh connection + wait_for: + port: 22 + delay: 1 + state: started diff --git a/roles/ssh/templates/sshd_config.j2 b/roles/ssh/templates/sshd_config.j2 new file mode 100644 index 0000000..4f7f608 --- /dev/null +++ b/roles/ssh/templates/sshd_config.j2 @@ -0,0 +1,93 @@ +# $OpenBSD: sshd_config,v 1.103 2018/04/09 20:41:22 tj Exp $ + +# This is the sshd server system-wide configuration file. See +# sshd_config(5) for more information. + +# The strategy used for options in the default sshd_config shipped with +# OpenSSH is to specify options with their default value where +# possible, but leave them commented. Uncommented options override the +# default value. + +#Port 22 +#AddressFamily any +#ListenAddress 0.0.0.0 +#ListenAddress :: + +#HostKey /etc/ssh/ssh_host_rsa_key +#HostKey /etc/ssh/ssh_host_ecdsa_key +#HostKey /etc/ssh/ssh_host_ed25519_key + +# Ciphers and keying +#RekeyLimit default none + +# Logging +#SyslogFacility AUTH +#LogLevel INFO + +# Authentication: + +#LoginGraceTime 2m +PermitRootLogin yes +#StrictModes yes +#MaxAuthTries 6 +#MaxSessions 10 + +#PubkeyAuthentication yes + +# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2 +# but this is overridden so installations will only check .ssh/authorized_keys +AuthorizedKeysFile .ssh/authorized_keys + +#AuthorizedPrincipalsFile none + +#AuthorizedKeysCommand none +#AuthorizedKeysCommandUser nobody + +# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts +#HostbasedAuthentication no +# Change to yes if you don't trust ~/.ssh/known_hosts for +# HostbasedAuthentication +#IgnoreUserKnownHosts no +# Don't read the user's ~/.rhosts and ~/.shosts files +#IgnoreRhosts yes + +# To disable tunneled clear text passwords, change to no here! +PasswordAuthentication no +PermitEmptyPasswords no + +# Change to no to disable s/key passwords +#ChallengeResponseAuthentication yes + +#AllowAgentForwarding yes +#AllowTcpForwarding yes +#GatewayPorts no +#X11Forwarding no +#X11DisplayOffset 10 +#X11UseLocalhost yes +#PermitTTY yes +#PrintMotd yes +#PrintLastLog yes +#TCPKeepAlive yes +#PermitUserEnvironment no +#Compression delayed +ClientAliveInterval 180 +#ClientAliveCountMax 3 +#UseDNS no +#PidFile /var/run/sshd.pid +#MaxStartups 10:30:100 +#PermitTunnel no +#ChrootDirectory none +#VersionAddendum none + +# no default banner path +#Banner none + +# override default of no subsystems +Subsystem sftp /usr/libexec/sftp-server + +# Example of overriding settings on a per-user basis +#Match User anoncvs +# X11Forwarding no +# AllowTcpForwarding no +# PermitTTY no +# ForceCommand cvs server diff --git a/roles/vmm/files/init_vm_alpine.yml b/roles/vmm/files/init_vm_alpine.yml new file mode 100644 index 0000000..d9dc0d5 --- /dev/null +++ b/roles/vmm/files/init_vm_alpine.yml @@ -0,0 +1,115 @@ +#!/bin/python3 + +import serial +import subprocess +import sys +import os + +from serial_macro import * + +USAGE = f"USAGE: {sys.argv[0]} vm_guest gate ip mask ssh_key" + +def init_network(): + send_cmds(ser, [ + [1, "setup-interfaces"], + [1, ""], + [1, f"{IP}"], + [1, f"{MASK}"], + [1, f"{GATE}"], + [1, "no"], + [1, "ifdown -a"], + [10, "ifup -a"], + [1, "rc-update add networking"] + ]) + + +def init_dns(): + send_cmds(ser, [ + [1, f"setup-dns"], + [1, ""], + [1, f"{DNS}"], + ]) + + +def init_disk(): + send_cmds(ser, [ + [10, "apk add e2fsprogs sfdisk syslinux"], + [1, "setup-disk"], + [1, ""], + [10, "sys"], + [30, "y"], + ]) + + +def init_ssh(): + send_cmds(ser, [ + [5, "apk add openssh"], + [1, "mkdir /root/.ssh"], + [1, f"echo '{SSHKEY}' > /root/.ssh/authorized_keys"], + [1, f"echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config"], + [1, "/etc/init.d/sshd restart"] + ]) + +def init_packages(): + send_cmds(ser, [ + [1, "echo https://mirror.ungleich.ch/mirror/packages/alpine/latest-stable/main/ > /etc/apk/repositories "], + [5, "apk update"] + ]) + + +def main(): + + global ser + global IP + global GATE + global MASK + global DNS + global SSHKEY + + COM = "/dev/" + BAUD = 115200 + TIMEOUT = 1 + + if len(sys.argv) != 7: + sys.stderr.write(USAGE) + sys.exit(1) + + GUEST = "vm-tmp" + HOST = sys.argv[1] + + cmd = f"vmctl show | grep {GUEST} | tr -s ' ' | cut -d ' ' -f7" + _buffer = subprocess.check_output(cmd, shell=True).decode().rstrip() + print(_buffer) + + if _buffer == "": + sys.exit(1) + COM += _buffer + + IP = sys.argv[2] + GATE = sys.argv[3] + MASK = sys.argv[4] + DNS = sys.argv[5] + SSHKEY = sys.argv[6] + + ser = serial.Serial(COM, BAUD, timeout=TIMEOUT) + send_cmd(ser, 1, "root") + # first boot :: live + init_network() + init_dns() + init_packages() + init_disk() + send_cmd(ser, 70, "reboot") + ser.close() + + ser = serial.Serial(COM, BAUD, timeout=TIMEOUT) + send_cmd(ser, 1, "root") + # second boot :: disk + init_network() + init_dns() + init_ssh() + init_packages() + ser.close() + + +if __name__ == "__main__": + main() diff --git a/roles/vmm/files/init_vm_serial.py b/roles/vmm/files/init_vm_serial.py deleted file mode 100644 index 46e34d7..0000000 --- a/roles/vmm/files/init_vm_serial.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/python3 - -import serial -import subprocess -import sys -import os - -import time - - -USAGE = f"USAGE: {sys.argv[0]} vm_guest gate ip mask ssh_key" - - -def send_cmd(ser, delay, cmd): - ser.write(f"{cmd}\n".encode("utf-8")) - time.sleep(delay) - -def send_cmds(ser, cmds): - for cmd in cmds: - send_cmd(ser, cmd[0], cmd[1]) - - -def main(): - - COM = "/dev/" - BAUD = 115200 - TIMEOUT = 1 - - if len(sys.argv) != 7: - sys.stderr.write(USAGE) - sys.exit(1) - - GUEST = "vm-tmp" - HOST = sys.argv[1] - - cmd = f"vmctl show | grep {GUEST} | tr -s ' ' | cut -d ' ' -f7" - _buffer = subprocess.check_output(cmd, shell=True).decode().rstrip() - print(_buffer) - - if _buffer == "": - sys.exit(1) - COM += _buffer - - IP = sys.argv[2] - GATE = sys.argv[3] - MASK = sys.argv[4] - DNS = sys.argv[5] - SSHKEY = sys.argv[6] - - ser = serial.Serial(COM, BAUD, timeout=TIMEOUT) - - send_cmd(ser, 1, "root") - - # virtual interface - send_cmds(ser, [ - [1, "setup-interfaces"], - [1, ""], - [1, f"{IP}"], - [1, f"{MASK}"], - [1, f"{GATE}"], - [1, "no"], - [1, "ifdown -a"], - [10, "ifup -a"] - ]) - - # dns - send_cmds(ser, [ - [1, "setup-dns"], - [1, f"{HOST}"], - [1, f"{DNS}"] - ]) - - # ssh - send_cmds(ser, [ - [5, "apk add openssh"], - [1, "mkdir /root/.ssh"], - [1, f"echo '{SSHKEY}' > /root/.ssh/authorized_keys"], - [1, f"echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config"], - [1, "/etc/init.d/sshd restart"] - ]) - - ser.close() - - print(COM) - - -if __name__ == "__main__": - main() diff --git a/roles/vmm/tasks/init_vm.yml b/roles/vmm/tasks/init_vm.yml deleted file mode 100644 index 6b5cf4d..0000000 --- a/roles/vmm/tasks/init_vm.yml +++ /dev/null @@ -1,38 +0,0 @@ - -# vmm ~~ tasks/init_vm.yml - ---- - -- include_role: - name: serial - -- name: Stop vm if running - shell: vmctl stop "{{ guest }}" ; vmctl stop vm-tmp - ignore_errors: true - -- set_fact: - iso: "{{ vms | selectattr('name', 'equalto', guest) | map(attribute='iso') | first }}" - -- debug: - var: iso - -- include: set_facts.yml - -- name: Start temporary vm - shell: vmctl start -r {{ iso_latest }} -d {{ disk_file }} -n {{ vmm.switch.name }} -m 1G vm-tmp - -- name: Pause 30 seconds for vm boot - pause: - seconds: 30 - -- name: Init vm via script - script: init_vm_serial.py \ - {{ guest }} \ - {{ hostvars[guest].ip.out }} \ - {{ hypervisor.gateway }} \ - {{ hypervisor.mask }} \ - {{ _i.dns[0] }} \ - "{{ lookup('file', inventory_dir + '/files/pubkeys/rgoncalves.pub') }}" - args: - executable: "/usr/local/bin/python3" - diff --git a/roles/vmm/tasks/init_vm_alpine.yml b/roles/vmm/tasks/init_vm_alpine.yml new file mode 100644 index 0000000..0c66a44 --- /dev/null +++ b/roles/vmm/tasks/init_vm_alpine.yml @@ -0,0 +1,50 @@ + +# vmm ~~ tasks/init_vm_alpine.yml + +--- + +- set_fact: + iso: "{{ vms | selectattr('name', 'equalto', guest) | map(attribute='iso') | first }}" + +- include: set_facts.yml + +- name: Check for existing drive + stat: + path: "{{ disk_file }}" + register: st_disk + +- fail: + msg: "No empty disk detected ! You need to generated disks via hypervisor playbook" + when: not st_disk.stat.exists + +- fail: + msg: "Existing installation detected ! Manual action on host required" + when: st_disk.stat.size > 500000 + +- include_role: + name: serial + +- name: Copy vm init script + copy: + src: init_vm_alpine.py + dest: /data/python/init_vm_alpine.py + +- name: Stop vm if running + shell: vmctl stop "{{ guest }}" ; vmctl stop vm-tmp + ignore_errors: true + +- name: Start temporary vm + shell: vmctl start -r {{ iso_latest }} -d {{ disk_file }} -n {{ vmm.switch.name }} -m 1G vm-tmp + +- name: Pause 30 seconds for vm boot + pause: + seconds: 30 + +- name: Init vm via script + command: python3 /data/python/init_vm_alpine.py \ + {{ guest }} \ + {{ hostvars[guest].ip.out }} \ + {{ hypervisor.gateway }} \ + {{ hypervisor.mask }} \ + {{ _i.dns[0] }} \ + "{{ lookup('file', inventory_dir + '/files/pubkeys/rgoncalves.pub') }}" -- cgit v1.2.3