diff --git a/images/ansible/ansible.cfg b/images/ansible/ansible.cfg new file mode 100644 index 00000000..fadf537b --- /dev/null +++ b/images/ansible/ansible.cfg @@ -0,0 +1,2 @@ +[defaults] +roles_path = roles diff --git a/images/ansible/playbooks/barebones.yml b/images/ansible/playbooks/barebones.yml new file mode 100644 index 00000000..2b36e2fa --- /dev/null +++ b/images/ansible/playbooks/barebones.yml @@ -0,0 +1,8 @@ +--- +- name: AX Barebones Image + hosts: all + become: yes + vars_files: + - ../vars/barebones.yml + roles: + - common \ No newline at end of file diff --git a/images/ansible/roles/common/handlers/main.yml b/images/ansible/roles/common/handlers/main.yml new file mode 100644 index 00000000..822887e3 --- /dev/null +++ b/images/ansible/roles/common/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart ssh + service: + name: ssh + state: restarted diff --git a/images/ansible/roles/common/tasks/main.yml b/images/ansible/roles/common/tasks/main.yml new file mode 100644 index 00000000..b95a01b6 --- /dev/null +++ b/images/ansible/roles/common/tasks/main.yml @@ -0,0 +1,200 @@ +--- +- name: Wait for cloud-init + command: /usr/bin/cloud-init status --wait + changed_when: false + +- name: Create swap + include_tasks: swap.yml + when: ansible_swaptotal_mb == 0 + +- name: Update & upgrade + apt: + update_cache: yes + upgrade: dist + environment: + DEBIAN_FRONTEND: noninteractive + +- name: Install required packages + apt: + name: + - fail2ban + - ufw + - net-tools + - zsh + - zsh-syntax-highlighting + - zsh-autosuggestions + - jq + - build-essential + - python3-pip + - unzip + - git + - p7zip + - libpcap-dev + - rubygems + - ruby-dev + - grc + - nmap + state: present + install_recommends: no + +- name: Allow SSH and custom port in UFW + ufw: + rule: allow + port: "{{ item }}" + loop: + - '22' + - '2266' + +- name: Enable UFW + ufw: + state: enabled + policy: deny + +- name: Create op user + user: + name: op + groups: sudo + shell: /usr/bin/zsh + create_home: yes + append: yes + +- name: Create op directories + file: + path: "/home/op/{{ item }}" + state: directory + owner: op + group: users + mode: '0755' + loop: + - .ssh + - c2 + - recon + - lists + - go + - bin + - .config + - .cache + - work + - .config/amass + +- name: Remove default MOTD files + file: + path: "{{ item }}" + state: absent + loop: "{{ q('fileglob', '/etc/update-motd.d/*') }}" + +- name: Install Oh My Zsh (non-interactively) + shell: > + sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended + become: yes + become_user: op + args: + creates: /home/op/.oh-my-zsh + +- name: Create sudo_as_admin_successful + file: + path: /home/op/.sudo_as_admin_successful + state: touch + owner: op + group: users + +- name: Create legal MOTD cache + file: + path: /home/op/.cache/motd.legal-displayed + state: touch + owner: op + group: users + +- name: Set passwords + user: + name: "{{ item.name }}" + password: "{{ op_random_password | password_hash('sha512') }}" + loop: + - { name: 'op' } + - { name: 'ubuntu' } + - { name: 'root' } + +- name: Copy config files from tmp (matching original Packer) + copy: + src: "/tmp/configs/{{ item.src }}" + dest: "{{ item.dest }}" + owner: "{{ item.owner | default('root') }}" + group: "{{ item.group | default('root') }}" + mode: "{{ item.mode | default('0644') }}" + backup: yes + remote_src: true + loop: + - { src: 'sudoers', dest: '/etc/sudoers', owner: 'root', group: 'root', mode: '0440' } + - { src: 'bashrc', dest: '/home/op/.bashrc', owner: 'op', group: 'users', mode: '0644' } + - { src: 'zshrc', dest: '/home/op/.zshrc', owner: 'op', group: 'users', mode: '0644' } + - { src: 'sshd_config', dest: '/etc/ssh/sshd_config', owner: 'root', group: 'root', mode: '0600' } + - { src: '00-header', dest: '/etc/update-motd.d/00-header', owner: 'root', group: 'root', mode: '0755' } + - { src: 'authorized_keys', dest: '/home/op/.ssh/authorized_keys', owner: 'op', group: 'users', mode: '0600' } + - { src: 'tmux-splash.sh', dest: '/home/op/bin/tmux-splash.sh', owner: 'op', group: 'users', mode: '0755' } + +- name: Set op user file ownership + file: + path: /home/op + owner: op + group: users + recurse: yes + +- name: Install Golang + unarchive: + src: "https://golang.org/dl/go{{ golang_version }}.linux-amd64.tar.gz" + dest: /usr/local + remote_src: yes + creates: /usr/local/go + mode: '0755' + +- name: Install Docker + shell: curl -fsSL https://get.docker.com | sh + args: + creates: /usr/bin/docker + +- name: Add op user to docker group + user: + name: op + groups: docker + append: yes + +- name: Install uv system-wide using installer script + shell: | + curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="/usr/local/bin" sh + args: + creates: "/usr/local/bin/uv" + +- name: Install Interlace using uv tool for user op + become_user: op + shell: uv tool install git+https://github.com/codingo/Interlace.git + register: uv_install_result + changed_when: "'already installed' not in uv_install_result.stderr" + failed_when: uv_install_result.rc != 0 and 'already installed' not in uv_install_result.stderr + +- name: Optimize SSH config + lineinfile: + path: /etc/ssh/sshd_config + regexp: "^{{ item.key }} " + line: "{{ item.key }} {{ item.value }}" + validate: /usr/sbin/sshd -t -f %s + loop: + - { key: 'ClientAliveInterval', value: '60' } + - { key: 'ClientAliveCountMax', value: '60' } + - { key: 'MaxSessions', value: '100' } + notify: restart ssh + +- name: Tune sysctl for high concurrency + sysctl: + name: "{{ item.name }}" + value: "{{ item.value }}" + state: present + reload: no + loop: + - { name: 'net.nf_conntrack_max', value: '1048576' } + - { name: 'net.core.somaxconn', value: '1048576' } + - { name: 'net.ipv4.ip_local_port_range', value: '1024 65535' } + +- name: Clean apt + apt: + autoclean: yes + autoremove: yes diff --git a/images/ansible/roles/common/tasks/swap.yml b/images/ansible/roles/common/tasks/swap.yml new file mode 100644 index 00000000..417d2f20 --- /dev/null +++ b/images/ansible/roles/common/tasks/swap.yml @@ -0,0 +1,15 @@ +- command: fallocate -l 2G /swap + args: + creates: /swap + +- file: + path: /swap + mode: '0600' + +- command: mkswap /swap + +- command: swapon /swap + +- lineinfile: + path: /etc/fstab + line: '/swap none swap sw 0 0' diff --git a/images/ansible/vars/barebones.yml b/images/ansible/vars/barebones.yml new file mode 100644 index 00000000..f7a01b09 --- /dev/null +++ b/images/ansible/vars/barebones.yml @@ -0,0 +1,3 @@ +--- +op_random_password: "{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}" +golang_version: "1.22.5" # Override via Packer var diff --git a/images/pkr.hcl/builders/do.pkr.hcl b/images/pkr.hcl/builders/do.pkr.hcl index 8f5ef0b5..ed7b9704 100644 --- a/images/pkr.hcl/builders/do.pkr.hcl +++ b/images/pkr.hcl/builders/do.pkr.hcl @@ -1,3 +1,12 @@ +packer { + required_plugins { + ansible = { + version = ">= 1.1.4" + source = "github.com/hashicorp/ansible" + } + } +} + variable "golang_version" { type = string } @@ -18,11 +27,20 @@ source "digitalocean" "packer" { ssh_username = "root" snapshot_name = var.snapshot_name api_token = var.do_key - image = "ubuntu-22-04-x64" + image = "ubuntu-24-04-x64" region = var.region size = var.default_size } +variable "AXIOM_ROOT" { + type = string + default = "/root/.axiom" +} + +locals { + axiom_root = var.AXIOM_ROOT +} + build { sources = [ "source.digitalocean.packer" diff --git a/images/pkr.hcl/provisioners/ansible-barebones.pkr.hcl b/images/pkr.hcl/provisioners/ansible-barebones.pkr.hcl new file mode 100644 index 00000000..fde2e0c6 --- /dev/null +++ b/images/pkr.hcl/provisioners/ansible-barebones.pkr.hcl @@ -0,0 +1,20 @@ + provisioner "file" { + source = "./configs" + destination = "/tmp/configs" + } + + provisioner "ansible" { + playbook_file = "${var.AXIOM_ROOT}/images/ansible/playbooks/barebones.yml" + + ansible_env_vars = [ + "ANSIBLE_CONFIG=${var.AXIOM_ROOT}/images/ansible/ansible.cfg" + ] + } + + provisioner "shell" { + inline = [ + "echo CkNvbmdyYXR1bGF0aW9ucywgeW91ciBidWlsZCBpcyBhbG1vc3QgZG9uZSEKCiDilojilojilojilojilojilZcg4paI4paI4pWXICDilojilojilZcgICAg4paI4paI4paI4paI4paI4paI4pWXIOKWiOKWiOKVlyAgIOKWiOKWiOKVl+KWiOKWiOKVl+KWiOKWiOKVlyAgICAg4paI4paI4paI4paI4paI4paI4pWXCuKWiOKWiOKVlOKVkOKVkOKWiOKWiOKVl+KVmuKWiOKWiOKVl+KWiOKWiOKVlOKVnSAgICDilojilojilZTilZDilZDilojilojilZfilojilojilZEgICDilojilojilZHilojilojilZHilojilojilZEgICAgIOKWiOKWiOKVlOKVkOKVkOKWiOKWiOKVlwrilojilojilojilojilojilojilojilZEg4pWa4paI4paI4paI4pWU4pWdICAgICDilojilojilojilojilojilojilZTilZ3ilojilojilZEgICDilojilojilZHilojilojilZHilojilojilZEgICAgIOKWiOKWiOKVkSAg4paI4paI4pWRCuKWiOKWiOKVlOKVkOKVkOKWiOKWiOKVkSDilojilojilZTilojilojilZcgICAgIOKWiOKWiOKVlOKVkOKVkOKWiOKWiOKVl+KWiOKWiOKVkSAgIOKWiOKWiOKVkeKWiOKWiOKVkeKWiOKWiOKVkSAgICAg4paI4paI4pWRICDilojilojilZEK4paI4paI4pWRICDilojilojilZHilojilojilZTilZ0g4paI4paI4pWXICAgIOKWiOKWiOKWiOKWiOKWiOKWiOKVlOKVneKVmuKWiOKWiOKWiOKWiOKWiOKWiOKVlOKVneKWiOKWiOKVkeKWiOKWiOKWiOKWiOKWiOKWiOKVl+KWiOKWiOKWiOKWiOKWiOKWiOKVlOKVnQrilZrilZDilZ0gIOKVmuKVkOKVneKVmuKVkOKVnSAg4pWa4pWQ4pWdICAgIOKVmuKVkOKVkOKVkOKVkOKVkOKVnSAg4pWa4pWQ4pWQ4pWQ4pWQ4pWQ4pWdIOKVmuKVkOKVneKVmuKVkOKVkOKVkOKVkOKVkOKVneKVmuKVkOKVkOKVkOKVkOKVkOKVnQoKTWFpbnRhaW5lcjogMHh0YXZpYW4KCvCdk7LwnZO38J2TvPCdk7nwnZOy8J2Tu/Cdk67wnZOtIPCdk6vwnZSCIPCdk6rwnZSB8J2TsvCdk7jwnZO2OiDwnZO98J2TsfCdk64g8J2TrfCdlILwnZO38J2TqvCdk7bwnZOy8J2TrCDwnZOy8J2Tt/Cdk6/wnZO78J2TqvCdk7zwnZO98J2Tu/Cdk77wnZOs8J2TvfCdk77wnZO78J2TriDwnZOv8J2Tu/Cdk6rwnZO28J2TrvCdlIDwnZO48J2Tu/Cdk7Qg8J2Tr/Cdk7jwnZO7IPCdk67wnZO/8J2TrvCdk7vwnZSC8J2Tq/Cdk7jwnZOt8J2UgiEgLSBA8J2TufCdk7vwnZSCMPCdk6zwnZOsIEAw8J2UgfCdk73wnZOq8J2Tv/Cdk7LwnZOq8J2TtwoKUmVhZCB0aGVzZSB3aGlsZSB5b3UncmUgd2FpdGluZyB0byBnZXQgc3RhcnRlZCA6KQoKICAgIC0gTmV3IFdpa2k6IGh0dHBzOi8vYXgtZnJhbWV3b3JrLmdpdGJvb2suaW8vd2lraS8KICAgIC0gRXhpc3RpbmcgVXNlcnM6IGh0dHBzOi8vYXgtZnJhbWV3b3JrLmdpdGJvb2suaW8vd2lraS9vdmVydmlldy9leGlzdGluZy11c2VycwogICAgLSBCcmluZyBZb3VyIE93biBQcm92aXNpb25lcjogaHR0cHM6Ly9heC1mcmFtZXdvcmsuZ2l0Ym9vay5pby93aWtpL2Z1bmRhbWVudGFscy9icmluZy15b3VyLW93bi1wcm92aXNpb25lciAKICAgIC0gRmlsZXN5c3RlbSBVdGlsaXRpZXM6IGh0dHBzOi8vYXgtZnJhbWV3b3JrLmdpdGJvb2suaW8vd2lraS9mdW5kYW1lbnRhbHMvZmlsZXN5c3RlbS11dGlsaXRpZXMKICAgIC0gRmxlZXRzOiBodHRwczovL2F4LWZyYW1ld29yay5naXRib29rLmlvL3dpa2kvZnVuZGFtZW50YWxzL2ZsZWV0cwogICAgLSBTY2FuczogaHR0cHM6Ly9heC1mcmFtZXdvcmsuZ2l0Ym9vay5pby93aWtpL2Z1bmRhbWVudGFscy9zY2FuCg== | base64 -d" + ] + } + +}