diff --git a/servers/maas/00-get-ca-certs-commission.sh b/servers/maas/00-get-ca-certs-commission.sh new file mode 100644 index 0000000..44016b3 --- /dev/null +++ b/servers/maas/00-get-ca-certs-commission.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# +# 00-get-ca-certs-commission.sh - Get CA and casub from Artifactory +# +# --- Start MAAS 1.0 script metadata --- +# name: 00-get-ca-certs-commission.sh +# title: Get CA and casub from Artifactory +# description: Get CA and casub from Artifactory +# tags: comissioning +# script_type: commissioning +# --- End MAAS 1.0 script metadata -- + +mkdir -p /usr/local/share/ca-certificates +wget --no-check-certificate -O /usr/local/share/ca-certificates/nso-casub21.crt https://artifactory.company.com/artifactory/generic-local/com//ca-certificates/our-casub.crt +wget --no-check-certificate -O /usr/local/share/ca-certificates/RootCA.crt https://artifactory.company.com/artifactory/generic-local/com/ca-certificates/RootCA.cer +touch /etc/apt/apt.conf.d/99verify-peer.conf +echo "Acquire { https::Verify-Peer false }" >> /etc/apt/apt.conf.d/99verify-peer.conf +apt update && apt install -y ca-certificates +update-ca-certificates diff --git a/servers/maas/45-set-lvm-layout.py b/servers/maas/45-set-lvm-layout.py new file mode 100644 index 0000000..8478ba8 --- /dev/null +++ b/servers/maas/45-set-lvm-layout.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# +# 45-set-lvm-layout - Commissioning script to set custom LVM layout +# +# --- Start MAAS 1.0 script metadata --- +# name: 45-set-lvm-layout +# title: Set LVM layout +# description: Set LVM layout +# script_type: commissioning +# timeout: 60 +# --- End MAAS 1.0 script metadata --- + +import json +import os +import sys + +def read_json_file(path): + try: + with open(path) as fd: + return json.load(fd) + except OSError as e: + sys.exit(f"Failed to read {path}: {e}") + except json.JSONDecodeError as e: + sys.exit(f"Failed to parse {path}: {e}") + +# Read the hardware data from MAAS_RESOURCES_FILE. You can parse the info in it, but this script only sets it so that we can write our static data into it. +hardware = read_json_file(os.environ["MAAS_RESOURCES_FILE"]) + +# Create hardcoded JSON structure for storage layout on sda +# - Partition #1 => 536M FAT32 for EFI +# - Partition #2 => 479G for LVM + +storage_layout = '''{ + "layout": { + "sda": { + "type": "disk", + "ptable": "gpt", + "boot": true, + "partitions": [ + { "name": "sda1", "fs": "fat32", "size": "536M", "bootable": true }, + { "name": "sda2", "size": "479G" } + ] + }, + "vg-root": { + "type": "lvm", + "members": [ "sda2" ], + "volumes": [ + { "name": "lv-root", "size": "50G", "fs": "ext4" }, + { "name": "lv-var", "size": "20G", "fs": "ext4" }, + { "name": "lv-var-lib", "size": "70G", "fs": "ext4" }, + { "name": "lv-run", "size": "30G", "fs": "ext4" } + ] + } + }, + "mounts": { + "/": { "device": "lv-root" }, + "/var": { "device": "lv-var" }, + "/var/lib": { "device": "lv-var-lib" }, + "/run": { "device": "lv-run" }, + "/boot/efi": { "device": "sda1" } + } +}''' + +# Load the above JSON template as a Python object +layoutDetail = json.loads(storage_layout) + +# Put the layout into hardware["storage-extra"], which MAAS will read +hardware["storage-extra"] = layoutDetail + +print("Saving custom storage layout to", os.environ["MAAS_RESOURCES_FILE"]) +print(json.dumps(hardware)) + +# Write the updated hardware JSON back to MAAS_RESOURCES_FILE +with open(os.environ["MAAS_RESOURCES_FILE"], "w") as file: + json.dump(hardware, file) diff --git a/servers/maas/cloud-init.yml b/servers/maas/cloud-init.yml new file mode 100644 index 0000000..9ded42f --- /dev/null +++ b/servers/maas/cloud-init.yml @@ -0,0 +1,92 @@ +#cloud-config + +# Example cloud init userdata script that sets a machine to a certain well known state. + +## Create "devops" user with sudo privileges and SSH key access +users: + - name: devops + gecos: Devops User + sudo: ['ALL=(ALL) NOPASSWD:ALL'] + passwd: "{{ 'REPLACE_DEVOPS_PASSWORD_HERE' | password_hash('sha512') }}" + groups: sudo + lock_passwd: false + shell: /bin/bash + ssh_authorized_keys: + - ssh-rsa AAAAB3NzXXXXX...XXXXGls7 devops@allhosts + +runcmd: + - mkdir -p /home/devops/.ssh + - chown -R devops:devops /home/devops/.ssh + - chmod 0700 /home/devops/.ssh + - chmod 0600 /home/devops/.ssh/authorized_keys + +## Get builduser SSH key for cloning infra baseline +write_files: + - path: /root/.ssh/builduser + content: | + -----BEGIN RSA PRIVATE KEY----- + MIIEpAIBAAKCAQEAv2sPNEAhEljA1KlGjfJz1b96A+a22Xfpa9Q98nWtE74LtYH8 + + REDACTED! REPLACE THIS WITH THE ACTUAL VALUE BEFORE RUNNING! + + 1Bwg3GRwRyM1jlwyrOrqgfbwmLBS4EnuLA+VuVSn6G5ujT8RAYUj2Q== + -----END RSA PRIVATE KEY----- + permissions: '0600' + - path: /home/kcaps//.ssh/config + content: | + Host github.com + IdentityFile = /home/kcaps/.ssh/id_rsa + HostkeyAlgorithms +ssh-rsa + PubkeyAcceptedAlgorithms +ssh-rsa + StrictHostKeyChecking no + permissions: '0600' + - path: /home/kcaps/vault-pass + content: | + REPLACE_DEVOPS_PASSWORD_HERE + permissions: '0600' + - path: /root/inventory.yaml + content: | + --- + all: + vars: + children: + ubuntu: + vars: + ansible_user: root + ansible_python_interpreter: "/usr/bin/python3" + children: + baseline: + hosts: + baseline: + ansible_host: localhost + ansible_connection: local + permissions: '0644' + - path: /root/cloud-init-final.sh + content: | + #!/bin/bash + + ## Setup custom certificates & disable SSL verification for APT + echo 'Acquire { https::Verify-Peer false }' > /etc/apt/apt.conf.d/99verify-peer.conf + chmod 0644 /etc/apt/apt.conf.d/99verify-peer.conf + mkdir -p /usr/local/share/ca-certificates + wget --no-check-certificate -O /usr/local/share/ca-certificates/nso-casub21.crt https://artifactory.company.com/artifactory/generic-local/ca-certificates/our-casub.crt + apt update + apt install -y ca-certificates + update-ca-certificates + + # Clone, adapt, and run Infra baseline on localhost + GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" + git clone ssh://git@github.nsogroup.com/k-caps/scripts.git /home/kcaps/maas/scripts + mv /home/kcaps/maas/inventory.yaml /home/kcaps/maas/scripts/inventory.yaml + mv /home/kcaps/maas/vault-pass /home/kcaps/maas/scripts/vault-pass + + apt install -y ansible + cd /home/kcaps/maas/scripts/ + ansible-playbook /home/kcaps/maas/scripts/playbooks/deploy_baseline.yml -i /home/kcaps/maas/scripts/inventory.yaml -b --vault-pass-file /home/kcaps/maas/scripts/vault-pass + apt remove -y ansible + rm -rf /home/kcaps/maas/scripts/ + rm -f /etc/apt/apt.conf.d/99verify-peer.conf + permissions: '0744' + +runcmd: + - bash /root/cloud-init-final.sh diff --git a/servers/maas/deploy-machine.sh b/servers/maas/deploy-machine.sh new file mode 100644 index 0000000..d4420a0 --- /dev/null +++ b/servers/maas/deploy-machine.sh @@ -0,0 +1,75 @@ +#!/bin/bash +if [ -z "$1" ]; then + echo "You must supply a machine name, such as 'homelab-k8s-worker-02'" + exit 1 +fi + +WORKDIR=/home/kcaps/maas + +echo "Logging into MAAS API..." +maas login admin http://127.0.0.1:5240/MAAS/api/2.0 Lh8KUcfGZpgMmmzVd4:5J9skJbb2TuD3VKjxn:nRx6aPYaHGdfiduywmk7eRNn4YG4jZ + +# Get machine ID by name +mids() { + maas admin machines read | jq -r '(["HOSTNAME","SYSID"] | (., map(length*"-"))),(.[] | [.hostname, .system_id]) | @tsv' | column -t +} +getmid() { + mids | grep $1 | awk '{print $2}' +} +machine_id=$(getmid $1) +check_deploy_status() { + maas admin machine read $machine_id | jq -r .status_name +} + + +echo "Machine ID for $1 is: $machine_id" + +echo "Disabling Swap for $1" +maas admin machine update $machine_id swap_size=0 > /dev/null + +# If there is eth0, rename it to vnic1 - to deploy we need vnic1 vnic2 and bond0 linking them +echo "Reading interface names for machine $machine_id..." +interface_names=$(maas admin interfaces read "$machine_id" | jq -r '.[].name') +interface_count=$(echo "$interface_names" | wc -l) + +if [[ "$interface_count" -eq 1 && "$interface_names" == "eth0" ]]; then + echo "Renaming default interface from eth0 to vnic1" + maas admin interface update $machine_id eth0 name=vnic1 > /dev/null + + echo "Getting vnic2 MAC from mapping file ($WORKDIR/machine_mac_addresses.txt).." + vnic2_mac_address=$(grep $1 machine_mac_addresses.txt | awk '{print $5}') + echo "vnic2 MAC address: $vnic2_mac_address" + + echo "Creating second interface:" + maas admin interfaces create-physical $machine_id name=vnic2 mac_address=$vnic2_mac_address vlan=2 > /dev/null + maas admin interface link-subnet $machine_id vnic2 mode=LINK_UP subnet=2 > /dev/null + maas admin interface update $machine_id vnic2 link_connected=True > /dev/null +fi + +# Start bond creation procedure +if [[ "$interface_count" -eq 2 ]]; then + echo "Getting existing IP addresses from vnic1" + read -r ip_address gateway_ip < <(maas admin interfaces read $machine_id | jq -r '.[].links[] | "\(.ip_address) \(.subnet.gateway_ip)"') + echo "IP address: $ip_address" + echo "Default gateway IP address: $gateway_ip" + + echo "Getting interface IDs for Bond creation..." + parent_interface_ids=$(maas admin interfaces read $machine_id | jq -r '[.[] | "parents=" + (.id | tostring)] | join(" ")') + + echo "Creating 'bond0' Bond interface..." + maas admin interfaces create-bond $machine_id name=bond0 vlan=2 $parent_interface_ids bond_mode=balance-alb bond_miimon=0 bond_xmit_hash_policy=layer3+4 > /dev/null + + echo "Attaching subnet and IP settings to bond0..." + maas admin interface link-subnet $machine_id bond0 subnet=2 mode=STATIC ip_address=$ip_address default_gateway=$gateway_ip > /dev/null + maas admin interface update $machine_id bond0 link_connected=True > /dev/null + + echo "Waiting 10s to ensure bond creation before beginning deploy..." + sleep 10 +fi + +echo "Interfaces and bond set up, beginning deploy..." + +maas admin machine deploy $machine_id user_data="$(base64 -w0 $WORKDIR/cloud-init-baseline.yml)" > /dev/null +echo "Started Deploying machine $1 at $(date)." +echo "Follow progress at:" +echo "http://192.168.1.45:5240/MAAS/r/machine/$machine_id/summary" diff --git a/servers/maas/installation_steps.txt b/servers/maas/installation_steps.txt new file mode 100644 index 0000000..40dfd57 --- /dev/null +++ b/servers/maas/installation_steps.txt @@ -0,0 +1,203 @@ +hostnamectl set-hostname maas +# also edit in /etc/hosts + +# Add artifactory sources list to maas server itself + +mkdir -p /usr/local/share/ca-certificates +wget --no-check-certificate -O /usr/local/share/ca-certificates/our-casub.crt "https://artifactory.company.com/artifactory/generic-local/company/certificate/ca-certificates/our-casub.crt" +wget --no-check-certificate -O /usr/local/share/ca-certificates/RootCA.crt "https://artifactory.company.com/artifactory/generic-local/company/certificate/ca-certificates/RootCA.cer" +echo 'Acquire { https::Verify-Peer "false"; }' > /etc/apt/apt.conf.d/99verify-peer.conf +sed 's|http:|https:|g' /etc/apt/sources.list -i +apt update +update-ca-certificates +rm /etc/apt/apt.conf.d/99verify-peer.conf + +cat > /usr/share/keyrings/maas.asc << 'EOF' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: Hostname: +Version: Hockeypuck 2.2 + +xsFNBFXVlyMBEACqM3iz2EGJE0iE3/AAbNCnbBB25m3AWaSxJk+GJfkAAYWGqAKi +uWceCcetdNKNTKd8frSZFsRB7IceZr0u5sWpSYur6uoMNHzS8Y5cGdyAVrnEZtbd +ak652x13jlX7nrcE9g//lD0w254XW1Loyy5YOGWfUmJkGImndFWtkqd1J7SCVMMW +5l/nS4LwsOx/wTxL5m/cFQLi67JyJGqszKXS88oHT1YFBWPyl1VcXifFwecH/32f +Rr6WGpEAaxGF4dO45WGvJIQs2yiT5f9ha3tuJCbzI58t9BxiR1MMZ9AAPjdNO6JZ +kX2q+/uqgJg9IWNcJ4E+fCgl/hvoB3AURXHmaagH7nMb/6OA/QFSbiR3eciSJ89c +EkK+7d0br+p2+shO/dOV6lUrbidVVjiiTdmYlyXzuPcvPWVYmXjDzsOi0sSZZNMq +8G3/pAavjyGUvZtb781V1j9/8l3o5ScAPzzamT2W4rF+nCh1iHYz7+wP2XDNifE/ +oK7fLNb0ig1G5S4PCqZHUp95LUaJrFczYCPwlERUxIC3B9a+UC3SdZmRuuSENWNs +YxKUlbU07GCrjxtcDhQHGQDVJDUGbqqkA4B/iKrwW3reA5fHo3yocQMX7YR6C2/Q +n+wn/EoEPIB1wkzAQvarnNCCdwjD5AB1VhANEFwUKMWHDEsofKOSTBYvgQARAQAB +zRZMYXVuY2hwYWQgUFBBIGZvciBNQUFTwsF4BBMBAgAiBQJV1ZcjAhsDBgsJCAcD +AgYVCAIJCgsEFgIDAQIeAQIXgAAKCRAE5/3FaE1KHDH8D/9Mdc+4tw8foj6lILCg +fBRi9S37tOyV2m5YvD+qRzefUYgFKXYxleO+H9cjFH2XyHIBwa15dD/Yg+DkcAKb +9f/a1llHNTzLkHiNVQl4tl8qeJPj2Obm53HsjhazIgh0L208GRGJxO4HSBbrBTo8 +FNF00Cl52josZdG1mPCSDuJm1AkeY9q4WeAOnekquz2qjUa+L8J8z+HVPC9rUryE +NXdwCyh3TE0G0occjUAsb5oOu3bcKSbVraq+trhjp9sz7o7O4lc4+cT2gFIWl1Rp +1djzXH8flU/s3U1vl0RcIFEZbuqsuDWukpxozq4M5y7VKq4y5dq7Y0PbMuJ0Dvgn +Bn4fbboMji4LYfgn++vosZv/MXkPIg6wubxdejVdrEoFRFxCcYqW4wObY8vxrvDr +Mjp4HrQ2guN8OJDUYnLdVv9P1MMKDAMrDjRdy3NsBpd7GuA9hXRXBPZ8y74nIwCR +jEDnIz5jsws9PxZIVabieoCI6RibJMw8qpuicM97Ss2Uq5vURvTBQ3f6wYjCMsdt +yqjz6TVJ3zwK9NPfMhXGVrrsxBOxO382r6XXuUbTcXZTDjAkoMsBqfjidlGDGTb3 +Un0LkZJfpXrmZehyvO/GlsoYiFDhGf+EXJzKwRUEuJlIkVEZ72OtuoUMoBrjuADR +lJQUW0ZbcmpOxjK1c6w08nhSvA== +=mlqO +-----END PGP PUBLIC KEY BLOCK----- +EOF + + +# BASHRC: +cat >> /root/.bashrc << 'EOF' +export DEBIAN_FRONTEND=noninteractive +export TZ="Asia/Jerusalem" +export DBUSER=maas +export DBPASS=maaspass-replacebeforerunning +export DBNAME=maasdb +export PROFILE=admin +export EMAIL_ADDRESS=devops@company.com +mids() { + maas $PROFILE machines read | jq -r '(["HOSTNAME","SYSID"] | (., map(length*"-"))),(.[] | [.hostname, .system_id]) | @tsv' | column -t +} +getmid() { + mids | grep $1 | awk '{print $2}' +} +EOF + +# APT +apt update +sudo apt install -y tzdata software-properties-common curl gnupg postgresql postgresql-contrib python3-pip + +ln -fs /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && dpkg-reconfigure -f noninteractive tzdata + +sudo bash -c 'echo "deb [signed-by=/usr/share/keyrings/maas.asc] https://artifactory.company.com/artifactory/deb-maas-remote/ jammy main" > /etc/apt/sources.list.d/maas.list' +sudo apt update && sudo apt install -y maas + +sudo systemctl disable --now systemd-timesyncd + +# POSTGRES +sudo chmod 0647 /etc/postgresql/14/main/pg_hba.conf +echo "host $DBNAME $DBUSER 127.0.0.1/32 trust" >> /etc/postgresql/14/main/pg_hba.confsystemctl restart postgresql +echo "local $DBNAME $DBUSER trust" >> /etc/postgresql/14/main/pg_hba.confsystemctl restart postgresql +sudo chmod 0640 /etc/postgresql/14/main/pg_hba.conf +# sudo -i -u postgres psql -c "CREATE USER \"$DBUSER\" WITH ENCRYPTED PASSWORD '$DBPASS'" + + +## NO HTTPS APT: +# Add a site to nginx - it proxies artifactory but is accessible via HTTP: +# /etc/nginx/sites-available/artifactory +server { + listen 8080; + server_name 192.168.1.150; + + location /artifactory/ { + proxy_pass https://artifactory.company.com/artifactory/; # Use HTTPS for Artifactory + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + + proxy_redirect off; + proxy_buffering off; + + # Disable SSL verification - either that or the trust cer here makes it work. IDK, IDC atm. + proxy_ssl_verify off; + proxy_ssl_trusted_certificate /usr/local/share/ca-certificates/our-casub.crt; + } + + location /ui/native/ { + return 403; + } +} + +sudo ln -s /etc/nginx/sites-available/artifactory /etc/nginx/sites-enabled/ +systemctl restart nginx + +# DNS - once you have a dns record to maas do: +cat > /etc/nginx/sites-available/default << 'EOF' +server { + listen 80 default_server; + listen [::]:80 default_server; + + root /var/www/html; + index index.html index.htm index.nginx-debian.html; + server_name maas.company.com; + + location / { + + # Forward all traffic to MAAS on port 5240 + proxy_pass http://127.0.0.1:5240; + + # Use HTTP/1.1 for keep-alive and upgrade support + proxy_http_version 1.1; + + # Ensure host headers and client IP are preserved + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} +EOF +systemctl reload nginx + + + +## CONFIGURE MAAS: +sudo maas createadmin --username admin --password --email $EMAIL_ADDRESS +echo "maas login admin http://127.0.0.1:5240/MAAS/api/2.0 $(maas apikey --generate --username admin)" >> ~/.bashrc + +# Go to UI, log in, skip everything, go to /MAAS/r/images: +Set "Change Source" URL to: +https://artifactory.company.com/artifactory/maas-images-remote/ephemeral-v3/stable/ +Select desired images + + +maas $PROFILE maas set-config name=upstream_dns value="1.1.1.1" +maas $PROFILE sshkeys create "key=$SSH_KEY" +#maas admin package-repositories create name="Ubuntu archive" url="http://:8080/artifactory/debian-remote" + + +# https://maas.io/docs/about-the-machine-life-cycle + +# easy log tailing for commisions/enlist/deploy : +journalctl -xef | grep -v 'pam_unix(sudo:session)' | grep -v 'maas : PWD=/ ; USER=root ; COMMAND' + +#Add this to: +/etc/default/named + +# startup options for the server +OPTIONS="-u bind -4" + + +# The physical machines that we want to control must be connected to the switch as access and NOT as trunk!!! + + +###### Commisioning scripts: +Save these to your local computer and upload them to maas + + +cat >> ~/.bashrc << 'EOF' +PATH=$PATH:/root +maas login admin http://127.0.0.1:5240/MAAS/api/2.0 redacted:redacted:redacted +mids() { + maas admin machines read | jq -r '(["HOSTNAME","SYSID"] | (., map(length*"-"))),(.[] | [.hostname, .system_id]) | @tsv' | column -t +} +getmid() { + mids | grep $1 | awk '{print $2}' +} +EOF + + + +To get view all machine IDs: +mids # this is a bash alias Kobi defined for root@maas +To directly get an individual machine ID: +mids | grep machine-name | awk '{print $2}' + +To deploy a machine with cloud-init: +maas admin machine deploy $(getmid machine-name) user_data="$(base64 /path/to/cloud-init-baseline.yml)" + diff --git a/servers/maas/machine_mac_addresses.txt b/servers/maas/machine_mac_addresses.txt new file mode 100644 index 0000000..207a12f --- /dev/null +++ b/servers/maas/machine_mac_addresses.txt @@ -0,0 +1,3 @@ +hostname data_ip power_ip vnic1_mac vnic2_mac +homelab-k8s-ctrl-01 192.168.1.21 192.168.2.21 00:25:b5:a0:00:5c 00:25:b5:a0:00:5d +homelab-k8s-wrkr-02 192.168.1.22 192.168.2.22 00:25:b5:a0:00:54 00:25:b5:a0:00:55