From 973546647df3fe505e913c42af9ed3547083593c Mon Sep 17 00:00:00 2001 From: thesabear Date: Tue, 9 Jun 2026 10:20:07 +0300 Subject: [PATCH] new files from source --- .directory | 2 + Ansible/playbooks/clean-docker.yml | 57 +++ Ansible/playbooks/ping.yml | 9 + Ansible/playbooks/remove_apps.yml | 65 +++ Ansible/playbooks/remove_repo.yml | 8 + Ansible/playbooks/upgrade-packages.yml | 89 ++++ Docker/Actual/docker-compose.yaml | 18 + Docker/Airsonic/docker-compose.yaml | 19 + Docker/Authentik/docker-compose.yaml | 75 +++ Docker/Bookstack/docker-compose.yaml | 38 ++ Docker/Calibre-Web/docker-compose.yaml | 15 + Docker/Calibre/docker-compose.yaml | 18 + Docker/CloudCommander/docker-compose.yaml | 10 + Docker/Diun/docker-compose.yaml | 26 + Docker/Docker-Monitoring/docker-compose.yaml | 24 + Docker/Duplicati/docker-compose.yaml | 15 + Docker/Gitlab/docker-compose.yaml | 20 + Docker/Gotify/docker-compose.yaml | 16 + Docker/Grafana-Monitoring/docker-compose.yaml | 60 +++ Docker/Grocy/docker-compose.yaml | 14 + Docker/Home-Assistant/docker-compose.yaml | 33 ++ Docker/IT-Tools/docker-compose.yaml | 7 + Docker/Immich/docker-compose.yaml | 79 ++++ Docker/Jenkins/docker-compose.yaml | 17 + Docker/Jitsi/docker-compose.yaml | 447 ++++++++++++++++++ Docker/Joplin/docker-compose.yaml | 39 ++ Docker/MariaDB/docker-compose.yaml | 18 + Docker/Minio/docker-compose.yaml | 33 ++ Docker/Nextcloud/docker-compose.yaml | 14 + Docker/Ollama/docker-compose.yaml | 14 + Docker/Organizr/docker-compose.yaml | 13 + Docker/Paperless-NGX/docker-compose.yaml | 75 +++ Docker/Semaphore/docker-compose.yaml | 39 ++ Docker/Speedtest-Tracker/docker-compose.yaml | 19 + Docker/Uptime Kuma/docker-compose.yaml | 14 + Docker/Vaultwarden/docker-compose.yaml | 15 + Docker/Whoogle/docker-compose.yaml | 11 + .../Ansible/.vault_pass.txt | 1 + Infrastrucutre as Code/Ansible/README.md | 3 + Infrastrucutre as Code/Ansible/ansible.cfg | 8 + .../Ansible/group_vars/all.yml | 2 + .../Ansible/group_vars/all/vault_token.yml | 14 + .../host_vars/docker-host/host_tags.yml | 31 ++ .../host_vars/haproxy-vm/host_tags.yml | 32 ++ Infrastrucutre as Code/Ansible/inventory.yml | 14 + .../Ansible/inventory_initialization.yml | 8 + .../Ansible/playbooks/compliance.yml | 29 ++ .../Ansible/playbooks/initialization.yml | 87 ++++ .../Ansible/playbooks/provisioning.yml | 29 ++ .../Ansible/requirements.yml | 9 + .../bootstrap_roles/base_users/tasks/main.yml | 15 + .../disk_resize/tasks/main.yml | 28 ++ .../docker_gitlab_setup/defaults/main.yml | 23 + .../docker_gitlab_setup/handlers/main.yml | 5 + .../docker_gitlab_setup/tasks/main.yml | 75 +++ .../endlessh/defaults/main.yml | 4 + .../bootstrap_roles/endlessh/tasks/main.yml | 67 +++ .../endlessh/templates/endlessh.service.j2 | 20 + .../endlessh/templates/endlessh_config.j2 | 5 + .../firewall_ssh/defaults/main.yml | 4 + .../firewall_ssh/tasks/main.yml | 69 +++ .../hostname_network/tasks/main.yml | 29 ++ .../packages/defaults/main.yml | 10 + .../bootstrap_roles/packages/tasks/main.yml | 32 ++ .../portainer_agent/tasks/main.yml | 39 ++ .../root_password/defaults/main.yml | 2 + .../root_password/tasks/main.yml | 16 + .../bootstrap_roles/zsh_custom/tasks/main.yml | 82 ++++ .../zsh_custom/templates/zshrc.j2 | 21 + .../cis_hardening/banners/defaults/main.yml | 11 + .../cis_hardening/banners/tasks/main.yml | 26 + .../roles/cis_hardening/defaults/main.yml | 6 + .../file_permissions/defaults/main.yml | 16 + .../file_permissions/tasks/main.yml | 11 + .../filesystem_hardening/defaults/main.yml | 4 + .../filesystem_hardening/tasks/main.yml | 34 ++ .../roles/cis_hardening/handlers/main.yml | 17 + .../kernel_hardening/defaults/main.yml | 7 + .../tasks/blacklist_module.yml | 18 + .../kernel_hardening/tasks/main.yml | 78 +++ .../password_policy/defaults/main.yml | 9 + .../password_policy/tasks/main.yml | 60 +++ .../service_disable/defaults/main.yml | 10 + .../service_disable/tasks/main.yml | 10 + .../ssh_hardening/defaults/main.yml | 32 ++ .../ssh_hardening/tasks/main.yml | 82 ++++ .../roles/cis_hardening/tasks/main.yml | 29 ++ .../secure_vault/haproxy/defaults/main.yml | 55 +++ .../secure_vault/haproxy/handlers/main.yml | 5 + .../roles/secure_vault/haproxy/tasks/main.yml | 107 +++++ .../haproxy/templates/haproxy.cfg.j2 | 86 ++++ .../secure_vault/technitium/defaults/main.yml | 28 ++ .../secure_vault/technitium/handlers/main.yml | 8 + .../secure_vault/technitium/tasks/main.yml | 125 +++++ .../templates/technitium.service.j2 | 16 + .../secure_vault/technitium/vars/main.yml | 4 + .../vault_agent/defaults/main.yml | 41 ++ .../vault_agent/files/generic_ca.ctmpl | 3 + .../vault_agent/files/generic_cert.ctmpl | 7 + .../vault_agent/files/haproxy.pem.ctmpl | 6 + .../vault_agent/files/haproxy_ca.ctmpl | 3 + .../vault_agent/files/technitium_ca.ctmpl | 3 + .../vault_agent/files/technitium_cert.ctmpl | 3 + .../vault_agent/files/technitium_key.ctmpl | 3 + .../vault_agent/files/thesabear-client.ctmpl | 6 + .../vault_agent/handlers/main.yml | 3 + .../secure_vault/vault_agent/tasks/main.yml | 230 +++++++++ .../templates/vault-agent-config.hcl.j2 | 30 ++ .../templates/vault-agent.service.j2 | 13 + .../Ansible/roles_metadata.yml | 123 +++++ Infrastrucutre as Code/Packer/README.md | 20 + .../rocky10-proxmox-template/.vault_pass.txt | 1 + .../rocky10-proxmox-template/http/ks.cfg | 82 ++++ .../rocky10-proxmox-template/packer.pkr.hcl | 105 ++++ .../scripts/setup-admin-user.sh | 108 +++++ .../scripts/setup-ansible-user.sh | 33 ++ .../secure-packer-build.sh | 27 ++ .../terraform.pkrvar.hcl | 27 ++ .../variables.pkr.hcl | 15 + Infrastrucutre as Code/README.md | 176 +++++++ Infrastrucutre as Code/SSO/README.md | 7 + Infrastrucutre as Code/Terraform/README.md | 3 + .../Terraform/host_tags.yml.tpl | 20 + Infrastrucutre as Code/Terraform/main.tf | 108 +++++ Infrastrucutre as Code/Terraform/providers.tf | 16 + .../Terraform/terraform.tfvars | 78 +++ Infrastrucutre as Code/Terraform/variables.tf | 32 ++ LICENSE | 21 + Proxmox/Proxmox API Authentication.md | 18 + Proxmox/Proxmox Certificate Management.md | 313 ++++++++++++ Proxmox/Proxmox Cheat-Sheet.md | 393 +++++++++++++++ README.md | 66 +++ 132 files changed, 5335 insertions(+) create mode 100644 .directory create mode 100644 Ansible/playbooks/clean-docker.yml create mode 100644 Ansible/playbooks/ping.yml create mode 100644 Ansible/playbooks/remove_apps.yml create mode 100644 Ansible/playbooks/remove_repo.yml create mode 100644 Ansible/playbooks/upgrade-packages.yml create mode 100644 Docker/Actual/docker-compose.yaml create mode 100644 Docker/Airsonic/docker-compose.yaml create mode 100644 Docker/Authentik/docker-compose.yaml create mode 100644 Docker/Bookstack/docker-compose.yaml create mode 100644 Docker/Calibre-Web/docker-compose.yaml create mode 100644 Docker/Calibre/docker-compose.yaml create mode 100644 Docker/CloudCommander/docker-compose.yaml create mode 100644 Docker/Diun/docker-compose.yaml create mode 100644 Docker/Docker-Monitoring/docker-compose.yaml create mode 100644 Docker/Duplicati/docker-compose.yaml create mode 100644 Docker/Gitlab/docker-compose.yaml create mode 100644 Docker/Gotify/docker-compose.yaml create mode 100644 Docker/Grafana-Monitoring/docker-compose.yaml create mode 100644 Docker/Grocy/docker-compose.yaml create mode 100644 Docker/Home-Assistant/docker-compose.yaml create mode 100644 Docker/IT-Tools/docker-compose.yaml create mode 100644 Docker/Immich/docker-compose.yaml create mode 100644 Docker/Jenkins/docker-compose.yaml create mode 100644 Docker/Jitsi/docker-compose.yaml create mode 100644 Docker/Joplin/docker-compose.yaml create mode 100644 Docker/MariaDB/docker-compose.yaml create mode 100644 Docker/Minio/docker-compose.yaml create mode 100644 Docker/Nextcloud/docker-compose.yaml create mode 100644 Docker/Ollama/docker-compose.yaml create mode 100644 Docker/Organizr/docker-compose.yaml create mode 100644 Docker/Paperless-NGX/docker-compose.yaml create mode 100644 Docker/Semaphore/docker-compose.yaml create mode 100644 Docker/Speedtest-Tracker/docker-compose.yaml create mode 100644 Docker/Uptime Kuma/docker-compose.yaml create mode 100644 Docker/Vaultwarden/docker-compose.yaml create mode 100644 Docker/Whoogle/docker-compose.yaml create mode 100644 Infrastrucutre as Code/Ansible/.vault_pass.txt create mode 100644 Infrastrucutre as Code/Ansible/README.md create mode 100644 Infrastrucutre as Code/Ansible/ansible.cfg create mode 100644 Infrastrucutre as Code/Ansible/group_vars/all.yml create mode 100644 Infrastrucutre as Code/Ansible/group_vars/all/vault_token.yml create mode 100755 Infrastrucutre as Code/Ansible/host_vars/docker-host/host_tags.yml create mode 100755 Infrastrucutre as Code/Ansible/host_vars/haproxy-vm/host_tags.yml create mode 100644 Infrastrucutre as Code/Ansible/inventory.yml create mode 100644 Infrastrucutre as Code/Ansible/inventory_initialization.yml create mode 100644 Infrastrucutre as Code/Ansible/playbooks/compliance.yml create mode 100644 Infrastrucutre as Code/Ansible/playbooks/initialization.yml create mode 100644 Infrastrucutre as Code/Ansible/playbooks/provisioning.yml create mode 100644 Infrastrucutre as Code/Ansible/requirements.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/base_users/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/disk_resize/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/docker_gitlab_setup/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/docker_gitlab_setup/handlers/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/docker_gitlab_setup/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/templates/endlessh.service.j2 create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/templates/endlessh_config.j2 create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/firewall_ssh/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/firewall_ssh/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/hostname_network/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/packages/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/packages/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/portainer_agent/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/root_password/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/root_password/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/zsh_custom/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/bootstrap_roles/zsh_custom/templates/zshrc.j2 create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/banners/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/banners/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/file_permissions/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/file_permissions/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/filesystem_hardening/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/filesystem_hardening/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/handlers/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/kernel_hardening/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/kernel_hardening/tasks/blacklist_module.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/kernel_hardening/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/password_policy/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/password_policy/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/service_disable/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/service_disable/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/ssh_hardening/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/ssh_hardening/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/cis_hardening/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/handlers/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/templates/haproxy.cfg.j2 create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/handlers/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/templates/technitium.service.j2 create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/vars/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/defaults/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/generic_ca.ctmpl create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/generic_cert.ctmpl create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/haproxy.pem.ctmpl create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/haproxy_ca.ctmpl create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/technitium_ca.ctmpl create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/technitium_cert.ctmpl create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/technitium_key.ctmpl create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/thesabear-client.ctmpl create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/handlers/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/tasks/main.yml create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/templates/vault-agent-config.hcl.j2 create mode 100644 Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/templates/vault-agent.service.j2 create mode 100644 Infrastrucutre as Code/Ansible/roles_metadata.yml create mode 100644 Infrastrucutre as Code/Packer/README.md create mode 100644 Infrastrucutre as Code/Packer/rocky10-proxmox-template/.vault_pass.txt create mode 100644 Infrastrucutre as Code/Packer/rocky10-proxmox-template/http/ks.cfg create mode 100644 Infrastrucutre as Code/Packer/rocky10-proxmox-template/packer.pkr.hcl create mode 100755 Infrastrucutre as Code/Packer/rocky10-proxmox-template/scripts/setup-admin-user.sh create mode 100644 Infrastrucutre as Code/Packer/rocky10-proxmox-template/scripts/setup-ansible-user.sh create mode 100755 Infrastrucutre as Code/Packer/rocky10-proxmox-template/secure-packer-build.sh create mode 100644 Infrastrucutre as Code/Packer/rocky10-proxmox-template/terraform.pkrvar.hcl create mode 100644 Infrastrucutre as Code/Packer/rocky10-proxmox-template/variables.pkr.hcl create mode 100644 Infrastrucutre as Code/README.md create mode 100644 Infrastrucutre as Code/SSO/README.md create mode 100644 Infrastrucutre as Code/Terraform/README.md create mode 100644 Infrastrucutre as Code/Terraform/host_tags.yml.tpl create mode 100644 Infrastrucutre as Code/Terraform/main.tf create mode 100644 Infrastrucutre as Code/Terraform/providers.tf create mode 100644 Infrastrucutre as Code/Terraform/terraform.tfvars create mode 100644 Infrastrucutre as Code/Terraform/variables.tf create mode 100644 LICENSE create mode 100644 Proxmox/Proxmox API Authentication.md create mode 100644 Proxmox/Proxmox Certificate Management.md create mode 100644 Proxmox/Proxmox Cheat-Sheet.md create mode 100644 README.md diff --git a/.directory b/.directory new file mode 100644 index 0000000..b422ca1 --- /dev/null +++ b/.directory @@ -0,0 +1,2 @@ +[Desktop Entry] +Icon=/home/thesabear/Downloads/App Images/devops_iac_publicreo.png diff --git a/Ansible/playbooks/clean-docker.yml b/Ansible/playbooks/clean-docker.yml new file mode 100644 index 0000000..26b074b --- /dev/null +++ b/Ansible/playbooks/clean-docker.yml @@ -0,0 +1,57 @@ +--- +- hosts: Docker + become: true + become_method: sudo + become_user: root + vars: + GOTIFY_IP: xxx #[Your gotify host IP goes here] + GOTIFY_PORT: xxx #[Your gotify port goes here] + GOTIFY_TOKEN: xxx #[Your app token goes here] + ansible_python_interpreter: /usr/bin/python3 + + tasks: + + - name: prune non-dangling images + community.docker.docker_prune: + containers: false + images: true + images_filters: + dangling: false + networks: false + volumes: false + builder_cache: false + + - name: Check if reboot is required + stat: + path: /var/run/reboot-required + register: reboot_required_file + + - name: Show Output of required reboots + debug: msg="{{ reboot_required_file.stat.exists == true }}" + + - name: Rebooting machine + shell: sleep 2 && shutdown -r now "Ansible updates triggered" + async: 1 + poll: 0 + ignore_errors: true + when: reboot_required_file.stat.exists + + - name: Waiting for the machine to come back + local_action: wait_for host={{ ansible_host }} state=started port=22 delay=30 + become: false + when: reboot_required_file.stat.exists + + - name: notify via Gotify + uri: + url: 'http://{{ GOTIFY_IP }}:{{ GOTIFY_PORT }}/message?token={{ GOTIFY_TOKEN }}' + method: POST + body: + title: + content: clean docker hosts + message: + content: The clean docker playbook has completed for {{ ansible_host }} and is now updated. A required reboot is {{ reboot_required_file.stat.exists == true }}. + priority: + content: '5' + body_format: form-multipart + register: result +... \ No newline at end of file diff --git a/Ansible/playbooks/ping.yml b/Ansible/playbooks/ping.yml new file mode 100644 index 0000000..24c3ec3 --- /dev/null +++ b/Ansible/playbooks/ping.yml @@ -0,0 +1,9 @@ +--- +- name: ping test + hosts: all + become: true + vars: + ansible_python_interpreter: /usr/bin/python3 + tasks: + - name: test ping + ansible.builtin.ping: \ No newline at end of file diff --git a/Ansible/playbooks/remove_apps.yml b/Ansible/playbooks/remove_apps.yml new file mode 100644 index 0000000..67ebe8c --- /dev/null +++ b/Ansible/playbooks/remove_apps.yml @@ -0,0 +1,65 @@ +### This is just a sample showing the way of removing apps from a VM using Ansible +--- +- hosts: all + become: yes + vars: + GOTIFY_IP: xxx #[Your gotify host IP goes here] + GOTIFY_PORT: xxx #[Your gotify port goes here] + GOTIFY_TOKEN: xxx #[Your app token goes here] + + tasks: + + - name: Ansible apt remove crowdsec + apt: + name: crowdsec + state: absent + + - name: Ansible apt remove zabbix + apt: + name: zabbix + state: absent + + - name: Ansible apt remove zabbix-agent + apt: + name: zabbix-agent + state: absent + + - name: Remove dependencies that are no longer required and purge their configuration files + ansible.builtin.apt: + autoremove: yes + purge: true + + - name: Check if reboot is required + stat: + path: /var/run/reboot-required + register: reboot_required_file + + - name: Show Output of required reboots + debug: msg="{{ reboot_required_file.stat.exists == true }}" + + - name: Rebooting machine + shell: sleep 2 && shutdown -r now "Ansible updates triggered" + async: 1 + poll: 0 + ignore_errors: true + when: reboot_required_file.stat.exists + + - name: Waiting for the machine to come back + local_action: wait_for host={{ ansible_host }} state=started port=22 delay=30 + become: no + when: reboot_required_file.stat.exists + + - name: notify via Gotify + uri: + url: 'http://{{ GOTIFY_IP }}:{{ GOTIFY_PORT }}/message?token={{ GOTIFY_TOKEN }}' + method: POST + body: + title: + content: APT Packages Update + message: + content: The remove apps playbook has completed for {{ ansible_host }} and is now updated. A required reboot is {{ reboot_required_file.stat.exists == true }}. + priority: + content: '5' + body_format: form-multipart + register: result +... \ No newline at end of file diff --git a/Ansible/playbooks/remove_repo.yml b/Ansible/playbooks/remove_repo.yml new file mode 100644 index 0000000..1af26bd --- /dev/null +++ b/Ansible/playbooks/remove_repo.yml @@ -0,0 +1,8 @@ +--- +- hosts: all + become: true + tasks: + - name: Remove specified repository from sources list + ansible.builtin.apt_repository: + repo: xxx #put the reponame of the repo you no longer want here + state: absent diff --git a/Ansible/playbooks/upgrade-packages.yml b/Ansible/playbooks/upgrade-packages.yml new file mode 100644 index 0000000..035ab11 --- /dev/null +++ b/Ansible/playbooks/upgrade-packages.yml @@ -0,0 +1,89 @@ +--- +- name: Patching and Updating Linux Systems + hosts: all + become: true + vars: + GOTIFY_IP: xxx #[Your gotify host IP goes here] + GOTIFY_PORT: xxx #[Your gotify port goes here] + GOTIFY_TOKEN: xxx #[Your app token goes here] + ansible_python_interpreter: /usr/bin/python3 + + tasks: + - name: Update Debian-based Systems + when: ansible_os_family == 'Debian' or ansible_os_family == 'Ubuntu' + apt: + update_cache: yes + upgrade: dist + + - name: Update RHEL-based Systems + when: ansible_os_family == 'RedHat' + yum: + name: '*' + state: latest + + - name: List installed and updated packages + shell: grep -E "^$(date +%Y-%m-%d).+ (install|upgrade) " /var/log/dpkg.log |cut -d " " -f 3-5 + register: result + + - name: Check if Docker is installed + command: which docker + register: docker_installed + ignore_errors: true + changed_when: false + + - name: Upgrade Python pip modules for Docker support (only if Docker is installed) + when: docker_installed.rc == 0 and ansible_os_family == 'RedHat' + pip: + name: + - docker + - requests + state: latest + executable: pip3 + + - name: Show Output of updated files + debug: msg="{{ result.stdout_lines }}" + + - name: Check if reboot is required + stat: + path: /var/run/reboot-required + register: reboot_required_file + + - name: Show Output of required reboots + debug: msg="{{ reboot_required_file.stat.exists == true }}" + + - name: Rebooting machine + shell: sleep 2 && shutdown -r now "Ansible updates triggered" + async: 1 + poll: 0 + ignore_errors: true + when: reboot_required_file.stat.exists + + - name: Waiting for the machine to come back + local_action: wait_for host={{ ansible_host }} state=started port=22 delay=30 + become: false + when: reboot_required_file.stat.exists +### + - name: Remove DNF dependencies that are no longer required. + when: ansible_os_family == 'RedHat' + ansible.builtin.dnf: + autoremove: yes + + - name: Remove APT dependencies that are no longer required. + when: ansible_os_family == 'Debian' or ansible_os_family == 'Ubuntu' + ansible.builtin.apt: + autoremove: yes + + - name: notify via Gotify + uri: + url: 'http://{{ GOTIFY_IP }}:{{ GOTIFY_PORT }}/message?token={{ GOTIFY_TOKEN }}' + method: POST + body: + title: + content: All Packages Update + message: + content: The upgrade-packages playbook has completed for {{ ansible_host }} and is now updated. A required reboot is {{ reboot_required_file.stat.exists == true }}. + priority: + content: '5' + body_format: form-multipart + register: result +... \ No newline at end of file diff --git a/Docker/Actual/docker-compose.yaml b/Docker/Actual/docker-compose.yaml new file mode 100644 index 0000000..97c074a --- /dev/null +++ b/Docker/Actual/docker-compose.yaml @@ -0,0 +1,18 @@ +services: + actual_server: + image: actualbudget/actual-server:latest + container_name: actual + ports: + - 5006:5006 + environment: + - PUID=PUID + - PGID=PGID + - TZ=timezone + # Uncomment any of the lines below to set configuration options. + # - ACTUAL_HTTPS_KEY=/data/selfhost.key + # - ACTUAL_HTTPS_CERT=/data/selfhost.crt + # See all options and more details at https://actualbudget.github.io/docs/Installing/Configuration + # !! If you are not using any of these options, remove the 'environment:' tag entirely. + volumes: + - /path/to/actual-data:/data + restart: unless-stopped \ No newline at end of file diff --git a/Docker/Airsonic/docker-compose.yaml b/Docker/Airsonic/docker-compose.yaml new file mode 100644 index 0000000..16a756f --- /dev/null +++ b/Docker/Airsonic/docker-compose.yaml @@ -0,0 +1,19 @@ + airsonic-advanced: + image: lscr.io/linuxserver/airsonic-advanced:latest + container_name: airsonic-advanced + environment: + - PUID=PUID + - PGID=PGID # Note that if you want to use Airsonic's Java jukebox player, then PGID will need to match the group of your sound device (e.g. /dev/snd). + - TZ=timezone + volumes: + - /path/toairsonic-advanced:/config + - /path/to/music:/music + - /path/to/playlists:/playlists + - /path/to/audiobooks:/audiobooks + - - + {{ + vm_tags + | select('in', + roles_metadata + | dict2items + | selectattr('value.' ~ stage_name, 'defined') + | selectattr('value.' ~ stage_name) + | map(attribute='key') + | list + ) + | list + }} diff --git a/Infrastrucutre as Code/Ansible/playbooks/initialization.yml b/Infrastrucutre as Code/Ansible/playbooks/initialization.yml new file mode 100644 index 0000000..3cdd4e1 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/playbooks/initialization.yml @@ -0,0 +1,87 @@ +--- +- name: Initialization stage + hosts: all + become: true + gather_facts: true + + vars: + stage_name: initialization + inventory_init: "/path/to/Infrastrucutre as Code/ansible/inventory_initialization.yml" + inventory_active: "/path/to/Infrastrucutre as Code/ansible/inventory.yml" + + vars_files: + - /path/to/Infrastrucutre as Code/ansible/roles_metadata.yml + - /path/to/Infrastrucutre as Code/ansible/group_vars/all/vault_token.yml + + pre_tasks: + - name: Ensure active inventory exists + ansible.builtin.file: + path: "{{ inventory_active }}" + state: touch + delegate_to: localhost + run_once: true + + - name: Load existing active inventory + ansible.builtin.slurp: + src: "{{ inventory_active }}" + register: active_inv_raw + delegate_to: localhost + run_once: true + + - name: Parse active inventory YAML (or initialize empty) + ansible.builtin.set_fact: + active_inventory: >- + {{ + (active_inv_raw.content | b64decode | from_yaml) + if (active_inv_raw.content | b64decode | from_yaml is mapping) + else {'all': {'hosts': {}}} + }} + run_once: true + + tasks: + - name: Run initialization roles for uninitialized hosts + ansible.builtin.include_role: + name: "{{ item }}" + loop: >- + {{ + vm_tags + | select('in', + roles_metadata + | dict2items + | selectattr('value.' ~ stage_name, 'defined') + | selectattr('value.' ~ stage_name) + | map(attribute='key') + | list + ) + | list + }} + + post_tasks: + - name: Append or update host in active inventory + ansible.builtin.set_fact: + active_inventory: >- + {{ + active_inventory | + combine({ + 'all': { + 'hosts': ( + active_inventory.all.hosts | + combine({ + inventory_hostname: { + 'ansible_host': hostvars[inventory_hostname]['ansible_host'], + 'ansible_port': hostvars[inventory_hostname]['ssh_port'] | default(22), + 'initialized': true + } + }) + ) + } + }, recursive=True) + }} + delegate_to: localhost + + - name: Write updated active inventory back to file + ansible.builtin.copy: + dest: "{{ inventory_active }}" + content: "{{ active_inventory | to_nice_yaml }}" + delegate_to: localhost + run_once: true diff --git a/Infrastrucutre as Code/Ansible/playbooks/provisioning.yml b/Infrastrucutre as Code/Ansible/playbooks/provisioning.yml new file mode 100644 index 0000000..bfa899d --- /dev/null +++ b/Infrastrucutre as Code/Ansible/playbooks/provisioning.yml @@ -0,0 +1,29 @@ +--- +- name: Provisioning stage + hosts: all + become: true + gather_facts: true + + vars: + stage_name: provisioning + + vars_files: + - /path/to/Infrastrucutre as Code/ansible/roles_metadata.yml + + tasks: + - name: Run roles for this stage + ansible.builtin.include_role: + name: "{{ item }}" + loop: >- + {{ + vm_tags + | select('in', + roles_metadata + | dict2items + | selectattr('value.' ~ stage_name, 'defined') + | selectattr('value.' ~ stage_name) + | map(attribute='key') + | list + ) + | list + }} diff --git a/Infrastrucutre as Code/Ansible/requirements.yml b/Infrastrucutre as Code/Ansible/requirements.yml new file mode 100644 index 0000000..d461551 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/requirements.yml @@ -0,0 +1,9 @@ +collections: + - name: community.hashi_vault + - name: community.general + - name: ansible.posix + - name: community.crypto + - name: community.dns + - name: middleware_automation.keycloak + - name: community.network + - name: community.docker diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/base_users/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/base_users/tasks/main.yml new file mode 100644 index 0000000..8b40659 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/base_users/tasks/main.yml @@ -0,0 +1,15 @@ +--- +- name: Configure default home directory and shell for system users + block: + - name: Set default home directory and shell for system users + ansible.builtin.lineinfile: + path: /etc/default/useradd + regexp: "^{{ setting.key }}=" + line: "{{ setting.key }}={{ setting.value }}" + create: yes + backup: yes + loop: + - { key: "HOME", value: "/nonexistent" } + - { key: "SHELL", value: "/sbin/nologin" } + loop_control: + loop_var: setting diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/disk_resize/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/disk_resize/tasks/main.yml new file mode 100644 index 0000000..50348e0 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/disk_resize/tasks/main.yml @@ -0,0 +1,28 @@ +- name: Expand disk and root filesystem + block: + - name: Attempt to grow partition (safe if already full) + command: growpart /dev/sda 2 + register: growpart_result + ignore_errors: true + + - name: Determine if partition actually grew + set_fact: + partition_grew: >- + {{ growpart_result.rc == 0 and + (growpart_result.stdout is defined and 'NOCHANGE' not in growpart_result.stdout) }} + + - name: Resize LVM physical volume if needed + command: pvresize /dev/sda2 + when: partition_grew + register: pvresize_result + changed_when: "'Physical volume' in pvresize_result.stdout and 'resized' in pvresize_result.stdout" + + - name: Extend logical volume if needed + command: lvextend -l +100%FREE /dev/mapper/rocky-lv_root + when: partition_grew + register: lvextend_result + changed_when: lvextend_result.rc == 0 and "'0 extents found" not in lvextend_result.stderr + + - name: Grow the XFS filesystem if needed + command: xfs_growfs / + when: partition_grew and (lvextend_result is changed or lvextend_result is not defined) diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/docker_gitlab_setup/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/docker_gitlab_setup/defaults/main.yml new file mode 100644 index 0000000..1c7be9b --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/docker_gitlab_setup/defaults/main.yml @@ -0,0 +1,23 @@ +--- +docker_old_root: "/var/lib/docker" +docker_new_root_base: "/opt/docker/docker_data" +docker_new_root: "{{ docker_new_root_base }}/docker" + +docker_volumes_dir: "/opt/docker/docker_volumes" +docker_homelab_dir: "/opt/docker/homelab" + +docker_volumes_owner: "user" +docker_volumes_group: "group" + +git_repo_url: "git@git.example.com:USER/REPO.git" +git_repo_version: "main" + +ssh_key_path: "/home/USER/.ssh/id_rsa" +git_repo_owner: "user" +git_repo_group: "group" + +docker_daemon_conf: "/etc/docker/daemon.json" +daemon_json_template: | + { + "data-root": "{{ docker_new_root }}" + } diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/docker_gitlab_setup/handlers/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/docker_gitlab_setup/handlers/main.yml new file mode 100644 index 0000000..3627303 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/docker_gitlab_setup/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart docker + service: + name: docker + state: restarted diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/docker_gitlab_setup/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/docker_gitlab_setup/tasks/main.yml new file mode 100644 index 0000000..4c4c490 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/docker_gitlab_setup/tasks/main.yml @@ -0,0 +1,75 @@ +--- +# 1. Ensure SSH key exists so git clone works +- name: Ensure .ssh directory exists + file: + path: "{{ ssh_key_path | dirname }}" + state: directory + owner: "user" + group: "user" + mode: "0700" + +- name: Ensure SSH key exists (don't overwrite) + copy: + src: "{{ ssh_key_path }}" + dest: "{{ ssh_key_path }}" + owner: "user" + group: "user" + mode: "0600" + +# 2. Ensure docker_data directory exists +- name: Ensure new Docker data root base exists + file: + path: "{{ docker_new_root_base }}" + state: directory + owner: root + group: root + mode: "0755" + +# 3. Stop Docker ONLY if /var/lib/docker still exists +- name: Stop Docker if old root exists + service: + name: docker + state: stopped + when: docker_old_root is exists + +# 4. Move /var/lib/docker β†’ new location (idempotent) +- name: Move Docker data root to new location + command: "mv {{ docker_old_root }} {{ docker_new_root }}" + args: + creates: "{{ docker_new_root }}" + when: docker_old_root is exists + +# 5. Ensure daemon.json points to new data-root +- name: Configure Docker daemon.json + copy: + dest: "{{ docker_daemon_conf }}" + content: "{{ daemon_json_template }}" + owner: root + group: root + mode: "0644" + notify: restart docker + +# 6. Ensure docker_volumes exists +- name: Ensure docker_volumes directory exists + file: + path: "{{ docker_volumes_dir }}" + state: directory + owner: "{{ docker_volumes_owner }}" + group: "{{ docker_volumes_group }}" + mode: "0755" + +# 7. Ensure homelab directory exists +- name: Ensure homelab directory exists + file: + path: "{{ docker_homelab_dir }}" + state: directory + owner: "{{ git_repo_owner }}" + group: "{{ git_repo_group }}" + mode: "0755" + +# 8. Start Docker if it was stopped +- name: Start Docker + service: + name: docker + state: started + when: docker_new_root is exists diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/defaults/main.yml new file mode 100644 index 0000000..4ee19bf --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/defaults/main.yml @@ -0,0 +1,4 @@ +--- +endlessh_port: 22 +endlessh_repo: https://github.com/skeeto/endlessh.git +endlessh_clone_path: /usr/local/src/endlessh diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/tasks/main.yml new file mode 100644 index 0000000..98fb869 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/tasks/main.yml @@ -0,0 +1,67 @@ +--- +- name: Ensure endlessh group exists + ansible.builtin.group: + name: endlessh + system: true + +- name: Ensure endlessh user exists + ansible.builtin.user: + name: endlessh + group: endlessh + system: true + shell: /usr/sbin/nologin + create_home: false + +- name: Ensure Endlessh repo is cloned and updated + ansible.builtin.git: + repo: "{{ endlessh_repo }}" + dest: "{{ endlessh_clone_path }}" + version: master + update: yes + register: git_result + +- name: Build Endlessh if repo was updated + ansible.builtin.command: make + args: + chdir: "{{ endlessh_clone_path }}" + when: git_result.after != git_result.before + register: make_result + # no changed_when needed; we'll rely on the copy step to track changes + +- name: Copy Endlessh binary to /usr/local/bin if updated + ansible.builtin.copy: + src: "{{ endlessh_clone_path }}/endlessh" + dest: /usr/local/bin/endlessh + mode: "0755" + remote_src: true + when: git_result.after != git_result.before + register: copy_result + +- name: Create Endlessh config directory + ansible.builtin.file: + path: /etc/endlessh + state: directory + mode: "0755" + +- name: Deploy Endlessh config + ansible.builtin.template: + src: endlessh_config.j2 + dest: /etc/endlessh/config + mode: "0644" + +- name: Deploy Endlessh systemd service + ansible.builtin.template: + src: endlessh.service.j2 + dest: /etc/systemd/system/endlessh.service + mode: "0644" + +- name: Reload systemd daemon + ansible.builtin.systemd: + daemon_reload: true + +- name: Restart Endlessh if binary updated + ansible.builtin.systemd: + name: endlessh + enabled: true + state: restarted + when: copy_result is changed diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/templates/endlessh.service.j2 b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/templates/endlessh.service.j2 new file mode 100644 index 0000000..1ffecc1 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/templates/endlessh.service.j2 @@ -0,0 +1,20 @@ +[Unit] +Description=Endlessh SSH Tarpit +After=network.target + +[Service] +ExecStart=/usr/local/bin/endlessh -f /etc/endlessh/config +Restart=always +User=endlessh +Group=endlessh +AmbientCapabilities=CAP_NET_BIND_SERVICE +NoNewPrivileges=true +ProtectSystem=full +ProtectHome=true +PrivateTmp=true +RestrictAddressFamilies=AF_INET AF_INET6 +ReadWritePaths=/etc/endlessh +LockPersonality=true + +[Install] +WantedBy=multi-user.target diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/templates/endlessh_config.j2 b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/templates/endlessh_config.j2 new file mode 100644 index 0000000..948322b --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/endlessh/templates/endlessh_config.j2 @@ -0,0 +1,5 @@ +Port {{ endlessh_port }} +Delay 10000 +MaxLineLength 32 +MaxClients 4096 +LogLevel 1 diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/firewall_ssh/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/firewall_ssh/defaults/main.yml new file mode 100644 index 0000000..f0de0ba --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/firewall_ssh/defaults/main.yml @@ -0,0 +1,4 @@ +--- +# Customize if needed; this defaults to port 22 for endlessh +endlessh_port: 22 +# The SSH port will be dynamically computed as: 10000 + (vmid % 100) diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/firewall_ssh/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/firewall_ssh/tasks/main.yml new file mode 100644 index 0000000..21f3336 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/firewall_ssh/tasks/main.yml @@ -0,0 +1,69 @@ +--- +- name: Configure firewall and SSH port + block: + - name: Fail if vmid is not defined + ansible.builtin.fail: + msg: "'vmid' must be defined to calculate dynamic SSH port" + when: vmid is not defined + + - name: Calculate SSH port from vmid + ansible.builtin.set_fact: + ssh_port: "{{ 10000 + (vmid | int % 100) }}" + + - name: Enable and start firewalld + ansible.builtin.service: + name: firewalld + state: started + enabled: true + + - name: Set firewalld default zone to drop + ansible.posix.firewalld: + zone: public + state: enabled + permanent: yes + target: DROP + + - name: Reload firewalld to apply permanent changes + ansible.builtin.command: firewall-cmd --reload + + - name: Allow custom SSH port in firewalld + ansible.posix.firewalld: + port: "{{ ssh_port }}/tcp" + permanent: true + state: enabled + immediate: true + + - name: Allow Endlessh port in firewalld + ansible.posix.firewalld: + port: "{{ endlessh_port }}/tcp" + permanent: true + state: enabled + immediate: true + + - name: Ensure custom SSH port is set in sshd_config + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: '^#?Port\s+' + line: "Port {{ ssh_port }}" + backup: yes + validate: "/usr/sbin/sshd -T -f %s" + register: sshd_config_changed + + - name: Gather SELinux facts + ansible.builtin.setup: + filter: ansible_selinux + + - name: Allow SSH to use custom port in SELinux + ansible.builtin.command: > + semanage port -a -t ssh_port_t -p tcp {{ ssh_port }} + register: semanage_result + failed_when: semanage_result.rc not in [0, 1] + changed_when: semanage_result.rc == 0 + when: ansible_selinux.status == "enabled" + + - name: Restart and enable sshd service if needed + ansible.builtin.service: + name: sshd + state: restarted + enabled: true + when: sshd_config_changed is changed diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/hostname_network/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/hostname_network/tasks/main.yml new file mode 100644 index 0000000..7a35314 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/hostname_network/tasks/main.yml @@ -0,0 +1,29 @@ +--- +- name: Set hostname and update /etc/hosts + vars: + default_domain: subdomain.domain + block: + - name: Set hostname fact + ansible.builtin.set_fact: + short_hostname: "{{ vm_name }}" + custom_hostname: "{{ vm_name }}.{{ default_domain }}" + + - name: Set system hostname + ansible.builtin.hostname: + name: "{{ custom_hostname }}" + + - name: Ensure /etc/hosts contains loopback entry for hostname + ansible.builtin.lineinfile: + path: /etc/hosts + regexp: '^127\.0\.1\.1\s+' + line: "127.0.1.1 {{ custom_hostname }} {{ short_hostname }}" + state: present + create: yes + + - name: Ensure /etc/hosts contains primary network IP entry + ansible.builtin.lineinfile: + path: /etc/hosts + regexp: '^{{ ip }}\s+' + line: "{{ ip }} {{ custom_hostname }} {{ short_hostname }}" + state: present + create: yes diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/packages/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/packages/defaults/main.yml new file mode 100644 index 0000000..27d2e39 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/packages/defaults/main.yml @@ -0,0 +1,10 @@ +--- +base_packages: + - firewalld + - policycoreutils-python-utils + - git + - make + - gcc + - cloud-utils-growpart + - lvm2 + - xfsprogs diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/packages/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/packages/tasks/main.yml new file mode 100644 index 0000000..9a06a50 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/packages/tasks/main.yml @@ -0,0 +1,32 @@ +--- +- name: Package management tasks + tags: [packages] + block: + - name: Clean all cached metadata + ansible.builtin.command: dnf clean all + become: true + + - name: Rebuild the DNF cache + ansible.builtin.command: dnf makecache + become: true + + - name: Update all packages to latest + ansible.builtin.dnf: + name: "*" + state: latest + update_cache: true + become: true + + - name: Reboot if kernel was upgraded + ansible.builtin.reboot: + reboot_timeout: 120 + pre_reboot_delay: 10 + when: ansible_facts.packages['kernel'] is defined + become: true + + - name: Install base system packages + ansible.builtin.package: + name: "{{ base_packages }}" + state: present + update_cache: true + become: true diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/portainer_agent/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/portainer_agent/tasks/main.yml new file mode 100644 index 0000000..4e60eed --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/portainer_agent/tasks/main.yml @@ -0,0 +1,39 @@ +--- +- name: Set Portainer Agent installation directory + vars: + portainer_agent_dir: /opt/portainer-agent + block: + - name: Create Portainer Agent directory + become: true + ansible.builtin.file: + path: "{{ portainer_agent_dir }}" + state: directory + owner: user + group: user + mode: "0755" + + - name: Create Portainer Agent docker-compose file + become: true + ansible.builtin.copy: + dest: "{{ portainer_agent_dir }}/docker-compose.yml" + owner: user + group: user + mode: "0644" + content: | + services: + agent: + image: portainer/agent:latest + container_name: portainer_agent + restart: always + ports: + - "9001:9001" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /var/lib/docker/volumes:/var/lib/docker/volumes + + - name: Ensure Portainer Agent is running + become: true + community.docker.docker_compose_v2: + project_src: "{{ portainer_agent_dir }}" + state: present # ensures containers exist + recreate: always # recreates if the definition changed diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/root_password/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/root_password/defaults/main.yml new file mode 100644 index 0000000..9f7201a --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/root_password/defaults/main.yml @@ -0,0 +1,2 @@ +--- +root_password_plain: "{{ lookup('env', 'ROOT_PASSWORD') }}" diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/root_password/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/root_password/tasks/main.yml new file mode 100644 index 0000000..95083ae --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/root_password/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Set root password + block: + - name: Fail if root password is not defined + fail: + msg: "The variable 'root_password_plain' must be provided (e.g., via GitLab CI/CD Variable)" + when: root_password_plain is not defined + + - name: Generate root password hash + set_fact: + root_password_hash: "{{ root_password_plain | password_hash('sha512') }}" + + - name: Set root user password + user: + name: root + password: "{{ root_password_hash }}" diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/zsh_custom/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/zsh_custom/tasks/main.yml new file mode 100644 index 0000000..bb7765a --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/zsh_custom/tasks/main.yml @@ -0,0 +1,82 @@ +--- +- name: Customize Zsh for users + vars: + zsh_users: + - user + + block: + - name: Ensure parent home directory exists (safety) + ansible.builtin.file: + path: "/home/{{ user }}" + state: directory + owner: "{{ user }}" + group: "{{ user }}" + mode: "0755" + loop: "{{ zsh_users }}" + loop_control: + loop_var: user + become: true + + - name: Remove any old oh-my-zsh + ansible.builtin.file: + path: "/home/{{ user }}/.oh-my-zsh" + state: absent + loop: "{{ zsh_users }}" + loop_control: + loop_var: user + become: true + + - name: Clone Oh My Zsh as root + ansible.builtin.git: + repo: "https://github.com/ohmyzsh/ohmyzsh.git" + dest: "/home/{{ user }}/.oh-my-zsh" + version: master + update: yes + force: yes + loop: "{{ zsh_users }}" + loop_control: + loop_var: user + become: true + + - name: Add trusted safe.directory system-wide for Oh My Zsh + ansible.builtin.command: + cmd: git config --system --add safe.directory /home/{{ user }}/.oh-my-zsh + loop: "{{ zsh_users }}" + loop_control: + loop_var: user + become: true + + - name: Install syntax-highlighting plugin + ansible.builtin.git: + repo: "https://github.com/zsh-users/zsh-syntax-highlighting.git" + dest: "/home/{{ user }}/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting" + version: master + update: yes + force: yes + loop: "{{ zsh_users }}" + loop_control: + loop_var: user + become: true + + - name: Deploy customized .zshrc + ansible.builtin.template: + src: zshrc.j2 + dest: "/home/{{ user }}/.zshrc" + owner: "{{ user }}" + group: "{{ user }}" + mode: "0644" + loop: "{{ zsh_users }}" + loop_control: + loop_var: user + become: true + + - name: Fix ownership recursively for Oh My Zsh directory + ansible.builtin.file: + path: "/home/{{ user }}/.oh-my-zsh" + owner: "{{ user }}" + group: "{{ user }}" + recurse: yes + loop: "{{ zsh_users }}" + loop_control: + loop_var: user + become: true diff --git a/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/zsh_custom/templates/zshrc.j2 b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/zsh_custom/templates/zshrc.j2 new file mode 100644 index 0000000..bcf7b7f --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/bootstrap_roles/zsh_custom/templates/zshrc.j2 @@ -0,0 +1,21 @@ +export ZSH="\$HOME/.oh-my-zsh" +ZSH_THEME="robbyrussell" +plugins=(git zsh-autosuggestions zsh-syntax-highlighting) +source \$ZSH/oh-my-zsh.sh + +# Where to save history and how much to keep +HISTFILE=~/.zsh_history +HISTSIZE=10000 # In-memory history +SAVEHIST=10000 # Lines to save to file + +# Recommended options +setopt APPEND_HISTORY # Don’t overwrite history file; append to it +setopt SHARE_HISTORY # Share history across all sessions +setopt HIST_IGNORE_DUPS # Don’t record duplicate commands +setopt HIST_IGNORE_SPACE # Ignore commands starting with a space +setopt HIST_VERIFY # Edit a command before running from history +setopt INC_APPEND_HISTORY # Write history incrementally as commands are run + + +PS1='%F{51}%n%F{255}@%F{227}%m %F{84}%~%f %F{15}$ ' +fastfetch \ No newline at end of file diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/banners/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/banners/defaults/main.yml new file mode 100644 index 0000000..94cd1fa --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/banners/defaults/main.yml @@ -0,0 +1,11 @@ +--- +cis_banner: | + ********************************************************************* + * WARNING: Unauthorized access to this system is prohibited. * + * All activities are monitored and recorded. * + ********************************************************************* + +cis_motd_banner: | + ********************************************************************* + * Welcome to the system. Access is restricted to authorized users. * + ********************************************************************* diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/banners/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/banners/tasks/main.yml new file mode 100644 index 0000000..a1606d3 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/banners/tasks/main.yml @@ -0,0 +1,26 @@ +--- +# Login banners for /etc/motd, /etc/issue, /etc/issue.net + +- name: Ensure /etc/motd contains standard CIS banner + ansible.builtin.copy: + dest: /etc/motd + owner: root + group: root + mode: "0644" + content: "{{ cis_motd_banner }}" + +- name: Ensure /etc/issue contains standard CIS banner + ansible.builtin.copy: + dest: /etc/issue + owner: root + group: root + mode: "0644" + content: "{{ cis_motd_banner }}" + +- name: Ensure /etc/issue.net contains standard CIS banner + ansible.builtin.copy: + dest: /etc/issue.net + owner: root + group: root + mode: "0644" + content: "{{ cis_banner }}" diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/defaults/main.yml new file mode 100644 index 0000000..22644cf --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# Parent-level defaults +cis_tag: cis_hardening +audit_rules_file: /etc/audit/rules.d/cis.rules +rsyslog_remote: "" # set to "logserver.example.com:514" to forward logs +txt_grub_args: "" # additional kernel args if needed, e.g. "audit=1" diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/file_permissions/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/file_permissions/defaults/main.yml new file mode 100644 index 0000000..87f5dd6 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/file_permissions/defaults/main.yml @@ -0,0 +1,16 @@ +--- +# Default ownership and permissions for critical files and directories + +file_permissions_list: + - { path: "/root", mode: "0700", state: "directory" } + - { path: "/etc/passwd", mode: "0644", state: "file" } + - { path: "/etc/shadow", mode: "0400", state: "file" } + - { path: "/etc/group", mode: "0644", state: "file" } + - { path: "/etc/gshadow", mode: "0400", state: "file" } + - { path: "/etc/crontab", mode: "0600", state: "file" } + - { path: "/etc/cron.hourly", mode: "0700", state: "directory" } + - { path: "/etc/cron.daily", mode: "0700", state: "directory" } + - { path: "/etc/cron.weekly", mode: "0700", state: "directory" } + - { path: "/etc/cron.monthly", mode: "0700", state: "directory" } + - { path: "/etc/cron.d", mode: "0700", state: "directory" } + - { path: "/etc/rsyslog.conf", mode: "0640", state: "file" } diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/file_permissions/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/file_permissions/tasks/main.yml new file mode 100644 index 0000000..86a8483 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/file_permissions/tasks/main.yml @@ -0,0 +1,11 @@ +--- +- name: Ensure system files and directories have correct ownership and permissions + ansible.builtin.file: + path: "{{ file_item.path }}" + owner: root + group: root + mode: "{{ file_item.mode }}" + state: "{{ file_item.state }}" + loop: "{{ file_permissions_list }}" + loop_control: + loop_var: file_item diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/filesystem_hardening/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/filesystem_hardening/defaults/main.yml new file mode 100644 index 0000000..0d890e3 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/filesystem_hardening/defaults/main.yml @@ -0,0 +1,4 @@ +tmp_mount_options: "defaults,nosuid,nodev,noexec,relatime" +var_tmp_mount_options: "defaults,nosuid,nodev,noexec,relatime" +home_mount_options: "defaults,nosuid,nodev,relatime" +dev_shm_mount_options: "defaults,nosuid,nodev,noexec,relatime" diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/filesystem_hardening/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/filesystem_hardening/tasks/main.yml new file mode 100644 index 0000000..f39e8b1 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/filesystem_hardening/tasks/main.yml @@ -0,0 +1,34 @@ +--- +# Remount and persist secure options using defaults variables + +- name: Gather mount facts + ansible.builtin.setup: + filter: ansible_mounts + +- name: Ensure /tmp has secure mount options + ansible.builtin.mount: + path: /tmp + fstype: "{{ ansible_mounts | selectattr('mount','equalto','/tmp') | map(attribute='fstype') | first }}" + opts: "{{ tmp_mount_options }}" + state: mounted + +- name: Ensure /var/tmp has secure mount options + ansible.builtin.mount: + path: /var/tmp + fstype: "{{ ansible_mounts | selectattr('mount','equalto','/var/tmp') | map(attribute='fstype') | first }}" + opts: "{{ var_tmp_mount_options }}" + state: mounted + +- name: Ensure /home has secure mount options + ansible.builtin.mount: + path: /home + fstype: "{{ ansible_mounts | selectattr('mount','equalto','/home') | map(attribute='fstype') | first }}" + opts: "{{ home_mount_options }}" + state: mounted + +- name: Ensure /dev/shm has secure mount options + ansible.builtin.mount: + path: /dev/shm + fstype: tmpfs + opts: "{{ dev_shm_mount_options }}" + state: mounted diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/handlers/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/handlers/main.yml new file mode 100644 index 0000000..4f69f71 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/handlers/main.yml @@ -0,0 +1,17 @@ +--- +- name: restart rsyslog + ansible.builtin.service: + name: rsyslog + state: restarted + +- name: restart auditd + ansible.builtin.service: + name: auditd + state: restarted + +- name: reload systemd + ansible.builtin.command: systemctl daemon-reexec + +- name: update initramfs + ansible.builtin.command: dracut -f + when: ansible_facts['os_family'] == 'RedHat' or ansible_facts['distribution'] in ['Rocky','RedHat','CentOS'] diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/kernel_hardening/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/kernel_hardening/defaults/main.yml new file mode 100644 index 0000000..96dc2bf --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/kernel_hardening/defaults/main.yml @@ -0,0 +1,7 @@ +--- +# Kernel defaults (override per host if needed) +modprobe_blacklist_file: /etc/modprobe.d/cis_blacklist.conf + +# Additional kernel modules to disable +blacklist_modules: + - tipc diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/kernel_hardening/tasks/blacklist_module.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/kernel_hardening/tasks/blacklist_module.yml new file mode 100644 index 0000000..51d22b6 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/kernel_hardening/tasks/blacklist_module.yml @@ -0,0 +1,18 @@ +# blacklist_module.yml +- name: Check if module {{ mod }} exists on system + ansible.builtin.shell: "modprobe -n -v {{ mod }}" + register: modprobe_check + ignore_errors: true + +- name: Blacklist module {{ mod }} in {{ modprobe_blacklist_file }} + ansible.builtin.lineinfile: + path: "{{ modprobe_blacklist_file }}" + line: "install {{ mod }} /bin/false" + create: yes + state: present + when: modprobe_check.rc == 0 + +- name: Unload module {{ mod }} if currently loaded + ansible.builtin.shell: "modprobe -r {{ mod }}" + ignore_errors: true + when: modprobe_check.rc == 0 diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/kernel_hardening/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/kernel_hardening/tasks/main.yml new file mode 100644 index 0000000..8e71898 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/kernel_hardening/tasks/main.yml @@ -0,0 +1,78 @@ +--- +# Kernel & sysctl hardening (CIS network/kernel items) + +- name: Ensure IPv4 forwarding is disabled + ansible.builtin.sysctl: + name: net.ipv4.ip_forward + value: "0" + state: present + reload: yes + +- name: Ensure IPv6 forwarding is disabled + ansible.builtin.sysctl: + name: net.ipv6.conf.all.forwarding + value: "0" + state: present + reload: yes + +- name: Ensure source routed packets are not accepted + ansible.builtin.sysctl: + name: net.ipv4.conf.all.accept_source_route + value: "0" + state: present + reload: yes + +- name: Ensure ICMP redirects are not accepted + ansible.builtin.sysctl: + name: net.ipv4.conf.all.accept_redirects + value: "0" + state: present + reload: yes + +- name: Ensure secure ICMP redirects are not accepted + ansible.builtin.sysctl: + name: net.ipv4.conf.all.secure_redirects + value: "0" + state: present + reload: yes + +- name: Ensure Address Space Layout Randomization (ASLR) is enabled + ansible.builtin.sysctl: + name: kernel.randomize_va_space + value: "2" + state: present + reload: yes + +- name: Ensure fs.suid_dumpable is 0 (disable core dumps) + ansible.builtin.sysctl: + name: fs.suid_dumpable + value: "0" + state: present + reload: yes + +- name: Create /etc/modprobe.d/cis_blacklist.conf with common blacklists + ansible.builtin.copy: + dest: /etc/modprobe.d/cis_blacklist.conf + owner: root + group: root + mode: "0644" + content: | + # CIS blacklist + install cramfs /bin/true + install freevxfs /bin/true + install jffs2 /bin/true + install hfs /bin/true + install hfsplus /bin/true + install squashfs /bin/true + install udf /bin/true + +- name: Regenerate initramfs if modprobe blacklist changed + ansible.builtin.command: dracut -f + changed_when: false + when: ansible_facts.get('os_family') == 'RedHat' or ansible_facts.get('distribution') in ['Rocky', 'RedHat', 'CentOS'] + +- name: Blacklist all modules + include_tasks: blacklist_module.yml + loop: "{{ blacklist_modules }}" + loop_control: + loop_var: mod diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/password_policy/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/password_policy/defaults/main.yml new file mode 100644 index 0000000..3672cd3 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/password_policy/defaults/main.yml @@ -0,0 +1,9 @@ +--- +# Password policy defaults +pw_minlen: 14 +pw_minclass: 4 +pw_max_days: 365 +pw_min_days: 7 +pw_warn_age: 7 +faillock_deny: 5 +faillock_unlock_time: 900 diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/password_policy/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/password_policy/tasks/main.yml new file mode 100644 index 0000000..820964c --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/password_policy/tasks/main.yml @@ -0,0 +1,60 @@ +--- +# Password and PAM related hardening + +- name: Ensure password hashing algorithm is SHA512 + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: "^ENCRYPT_METHOD" + line: "ENCRYPT_METHOD SHA512" + create: yes + +- name: Ensure PASS_MAX_DAYS is 365 + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: "^PASS_MAX_DAYS" + line: "PASS_MAX_DAYS 365" + create: yes + +- name: Ensure PASS_MIN_DAYS is 7 + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: "^PASS_MIN_DAYS" + line: "PASS_MIN_DAYS 7" + create: yes + +- name: Ensure PASS_WARN_AGE is 7 + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: "^PASS_WARN_AGE" + line: "PASS_WARN_AGE 7" + create: yes + +- name: Ensure pwquality minlen and requirements + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + regexp: "^minlen" + line: "minlen = 14" + create: yes + +- name: Ensure pwquality minclass (character classes) is 4 + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + regexp: "^minclass" + line: "minclass = 4" + create: yes + +- name: Ensure pam_faillock preauth is configured + ansible.builtin.lineinfile: + path: /etc/pam.d/system-auth + insertafter: "^auth.*required.*pam_unix.so" + line: "auth required pam_faillock.so preauth silent audit deny=5 unlock_time=900" + state: present + backrefs: no + +- name: Ensure pam_faillock authfail/account lines exist + ansible.builtin.blockinfile: + path: /etc/pam.d/system-auth + insertafter: "^auth.*required.*pam_unix.so" + block: | + auth [default=die] pam_faillock.so authfail deny=5 unlock_time=900 audit + account required pam_faillock.so diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/service_disable/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/service_disable/defaults/main.yml new file mode 100644 index 0000000..1efe812 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/service_disable/defaults/main.yml @@ -0,0 +1,10 @@ +--- +unused_services: + - avahi-daemon + - bluetooth + - cups + - rpcbind + - nfslock + - rsh.socket + - telnet.socket + - rsyncd diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/service_disable/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/service_disable/tasks/main.yml new file mode 100644 index 0000000..65d4c18 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/service_disable/tasks/main.yml @@ -0,0 +1,10 @@ +- name: Ensure list of unnecessary services are stopped and masked + ansible.builtin.service: + name: "{{ svc }}" + enabled: no + state: stopped + masked: yes + loop: "{{ unused_services }}" + loop_control: + loop_var: svc + ignore_errors: true diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/ssh_hardening/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/ssh_hardening/defaults/main.yml new file mode 100644 index 0000000..3da2d50 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/ssh_hardening/defaults/main.yml @@ -0,0 +1,32 @@ +--- +# General settings +ssh_protocol: 2 +ssh_permit_root_login: "no" +ssh_password_authentication: "no" +ssh_challenge_response_authentication: "no" +ssh_kerberos_authentication: "no" +ssh_gssapi_authentication: "no" +ssh_use_pam: "yes" +PrintLastLog: "yes" + +# Session security +ssh_x11_forwarding: "no" +ssh_allow_tcp_forwarding: "yes" +ssh_permit_tunnel: "no" +ssh_allow_agent_forwarding: "no" + +# Idle timeout / login attempts +ssh_client_alive_interval: 300 +ssh_client_alive_count_max: 3 +ssh_login_grace_time: 60 +ssh_max_auth_tries: 3 +ssh_max_sessions: 2 +ssh_max_startups: "10:30:60" + +# SSH access restrictions +ssh_allow_users: "adminuser ansibleuser" + +# Strong encryption +ssh_ciphers: "aes256-ctr,aes192-ctr,aes128-ctr" +ssh_macs: "hmac-sha2-512,hmac-sha2-256" +ssh_kex_algorithms: "curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256" diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/ssh_hardening/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/ssh_hardening/tasks/main.yml new file mode 100644 index 0000000..2132f18 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/ssh_hardening/tasks/main.yml @@ -0,0 +1,82 @@ +--- +- name: Ensure OpenSSH server is installed + ansible.builtin.package: + name: openssh-server + state: present + +- name: Ensure SSH daemon is enabled and running + ansible.builtin.service: + name: sshd + enabled: true + state: started + +- name: Backup current sshd_config + ansible.builtin.copy: + src: /etc/ssh/sshd_config + dest: /etc/ssh/sshd_config.bak + owner: root + group: root + mode: "0600" + remote_src: true + +- name: Harden SSH configuration + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: "{{ ssh_item.regexp }}" + line: "{{ ssh_item.line }}" + state: present + backrefs: yes + loop: + - regexp: "^#?Protocol" + line: "Protocol {{ ssh_protocol }}" + - regexp: "^#?PermitRootLogin" + line: "PermitRootLogin {{ ssh_permit_root_login }}" + - regexp: "^#?PasswordAuthentication" + line: "PasswordAuthentication {{ ssh_password_authentication }}" + - regexp: "^#?ChallengeResponseAuthentication" + line: "ChallengeResponseAuthentication {{ ssh_challenge_response_authentication }}" + - regexp: "^#?KerberosAuthentication" + line: "KerberosAuthentication {{ ssh_kerberos_authentication }}" + - regexp: "^#?GSSAPIAuthentication" + line: "GSSAPIAuthentication {{ ssh_gssapi_authentication }}" + - regexp: "^#?UsePAM" + line: "UsePAM {{ ssh_use_pam }}" + - regexp: "^#?X11Forwarding" + line: "X11Forwarding {{ ssh_x11_forwarding }}" + - regexp: "^#?AllowTcpForwarding" + line: "AllowTcpForwarding {{ ssh_allow_tcp_forwarding }}" + - regexp: "^#?PermitTunnel" + line: "PermitTunnel {{ ssh_permit_tunnel }}" + - regexp: "^#?AllowAgentForwarding" + line: "AllowAgentForwarding {{ ssh_allow_agent_forwarding }}" + - regexp: "^#?ClientAliveInterval" + line: "ClientAliveInterval {{ ssh_client_alive_interval }}" + - regexp: "^#?ClientAliveCountMax" + line: "ClientAliveCountMax {{ ssh_client_alive_count_max }}" + - regexp: "^#?LoginGraceTime" + line: "LoginGraceTime {{ ssh_login_grace_time }}" + - regexp: "^#?MaxAuthTries" + line: "MaxAuthTries {{ ssh_max_auth_tries }}" + - regexp: "^#?MaxSessions" + line: "MaxSessions {{ ssh_max_sessions }}" + - regexp: "^#?MaxStartups" + line: "MaxStartups {{ ssh_max_startups }}" + - regexp: "^#?Ciphers" + line: "Ciphers {{ ssh_ciphers }}" + - regexp: "^#?MACs" + line: "MACs {{ ssh_macs }}" + - regexp: "^#?KexAlgorithms" + line: "KexAlgorithms {{ ssh_kex_algorithms }}" + - regexp: "^#?PrintLastLog" + line: "PrintLastLog {{ PrintLastLog }}" + - regexp: '^\s*#?\s*AllowUsers' + line: "AllowUsers {{ ssh_allow_users }}" + - regexp: "^#?Banner" + line: "Banner /etc/issue.net" + loop_control: + loop_var: ssh_item + +- name: Restart SSH service to apply changes + ansible.builtin.service: + name: sshd + state: restarted diff --git a/Infrastrucutre as Code/Ansible/roles/cis_hardening/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/cis_hardening/tasks/main.yml new file mode 100644 index 0000000..cdafb66 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/cis_hardening/tasks/main.yml @@ -0,0 +1,29 @@ +--- +# Parent CIS hardening role: import each subrole with single tag +- import_role: + name: cis_hardening/kernel_hardening + tags: [cis_hardening] + +- import_role: + name: cis_hardening/password_policy + tags: [cis_hardening] + +- import_role: + name: cis_hardening/file_permissions + tags: [cis_hardening] + +- import_role: + name: cis_hardening/service_disable + tags: [cis_hardening] + +- import_role: + name: cis_hardening/banners + tags: [cis_hardening] + +- import_role: + name: cis_hardening/ssh_hardening + tags: [cis_hardening] + +- import_role: + name: cis_hardening/filesystem_hardening + tags: [cis_hardening] diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/defaults/main.yml new file mode 100644 index 0000000..0b362b3 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/defaults/main.yml @@ -0,0 +1,55 @@ +# Vault connection +vault_addr: "http://VAULT_IP:VAULT_PORT" + +# Directory paths +haproxy_certs_dir: "/etc/haproxy/certs" +haproxy_cert_path: "{{ haproxy_pem_path }}" +haproxy_pem_path: "{{ haproxy_certs_dir }}/haproxy.pem" +haproxy_ca_path: "{{ haproxy_certs_dir }}/haproxy_ca.crt" + +# Systemd service names +haproxy_service: "haproxy" + +# HAProxy tuning +haproxy_global_maxconn: 4096 +haproxy_timeout_connect_ms: 5000 +haproxy_timeout_client_ms: 60000 +haproxy_timeout_server_ms: 60000 +haproxy_enable_hsts: true + +vault_agent_templates: + - src: "haproxy.pem.ctmpl" + dest: "{{ haproxy_pem_path }}" + service: "haproxy" + - src: "haproxy_ca.ctmpl" + dest: "{{ haproxy_ca_path }}" + service: "haproxy" + +# === Services routed internally through HAProxy === +securemtls_services: + vault: + hostname: vault.local.DOMAIN_PLACEHOLDER + balance: roundrobin + mode: http + timeout_connect: 5s + timeout_server: 30s + default_server_inter: 3s + default_server_rise: 2 + default_server_fall: 3 + backend_servers: + - address: VAULT_IP + port: VAULT_PORT + proto: http + headers: + - "http-request set-header Host vault.local.DOMAIN_PLACEHOLDER" + - "http-request set-header X-Forwarded-Proto https" + rewrites: + - "http-response replace-header Location ^http://VAULT_IP:VAULT_PORT/(.*) https://vault.local.DOMAIN_PLACEHOLDER/\\1" + + technitium: + hostname: "technitium.local.DOMAIN_PLACEHOLDER" + backend_mode: http + backend_servers: + - address: TECHNITIUM_IP + port: TECHNITIUM_PORT + proto: "http" diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/handlers/main.yml b/Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/handlers/main.yml new file mode 100644 index 0000000..6d501e4 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Reload HAProxy + systemd: + name: haproxy + state: reloaded diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/tasks/main.yml new file mode 100644 index 0000000..a6884c2 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/tasks/main.yml @@ -0,0 +1,107 @@ +--- +- name: Install HAProxy + dnf: + name: haproxy + state: present + +- name: Ensure firewalld is running and enabled + systemd: + name: firewalld + enabled: true + state: started + +- name: Allow inbound HTTPS (443/tcp) + ansible.posix.firewalld: + service: https + permanent: true + state: enabled + immediate: true + +- name: Ensure HAProxy certificate directories exist + file: + path: "{{ cert_dir }}" + state: directory + owner: haproxy + group: haproxy + mode: "0750" + loop: + - "{{ haproxy_certs_dir }}" + - "{{ haproxy_certs_dir }}/clients" + loop_control: + loop_var: cert_dir + +- name: Apply SELinux context for HAProxy certs + command: semanage fcontext -a -t etc_t "{{ haproxy_certs_dir }}(/.*)?" + become: true + register: semanage_fcontext_result + failed_when: false + changed_when: semanage_fcontext_result.rc == 0 + +- name: Restore SELinux context for HAProxy certs + command: restorecon -Rv "{{ haproxy_certs_dir }}" + become: true + changed_when: false + +- name: Allow HAProxy to connect to any port (SELinux) + ansible.posix.seboolean: + name: haproxy_connect_any + state: true + persistent: true + +- name: Wait for HAProxy PEM and CA files + wait_for: + path: "{{ pem_item }}" + state: present + timeout: 120 + loop: + - "{{ haproxy_pem_path }}" + - "{{ haproxy_ca_path }}" + loop_control: + loop_var: pem_item + +- name: Ensure permissions for HAProxy certs + file: + path: "{{ cert_item }}" + owner: haproxy + group: haproxy + mode: "0644" + loop: + - "{{ haproxy_pem_path }}" + - "{{ haproxy_ca_path }}" + loop_control: + loop_var: cert_item + +- name: Deploy HAProxy configuration + template: + src: haproxy.cfg.j2 + dest: /etc/haproxy/haproxy.cfg + owner: root + group: root + mode: "0644" + notify: Reload HAProxy + +- name: Ensure HAProxy service starts after vault-agent + lineinfile: + path: /usr/lib/systemd/system/haproxy.service + regexp: "^After=" + line: "After=network-online.target vault-agent.service" + backup: yes + +- name: Reload systemd daemon + command: systemctl daemon-reload + +- name: Enable and start HAProxy + systemd: + name: haproxy + enabled: yes + state: started + +- name: Check SELinux mode + command: getenforce + register: selinux_state + changed_when: false + +- name: Warn if SELinux is not enforcing + debug: + msg: "⚠️ SELinux is not enforcing β€” HAProxy cert contexts may not be correctly applied." + when: selinux_state.stdout != "Enforcing" diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/templates/haproxy.cfg.j2 b/Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/templates/haproxy.cfg.j2 new file mode 100644 index 0000000..e4b32a7 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/haproxy/templates/haproxy.cfg.j2 @@ -0,0 +1,86 @@ +# ========================================== +# HAProxy global settings +# ========================================== +global + log /dev/log local0 + log /dev/log local1 notice + chroot /var/lib/haproxy + pidfile /run/haproxy.pid + maxconn {{ haproxy_global_maxconn | default(4096) }} + user haproxy + group haproxy + daemon + +defaults + log global + option httplog + option dontlognull + timeout connect {{ haproxy_timeout_connect_ms | default('5000') }} + timeout client {{ haproxy_timeout_client_ms | default('60000') }} + timeout server {{ haproxy_timeout_server_ms | default('60000') }} + option http-server-close + option forwardfor + retries 3 + maxconn 2000 + +# ========================================== +# HAProxy frontends and backends template +# ========================================== + +frontend https-in + mode http + bind *:443 ssl crt {{ haproxy_pem_path }} ca-file {{ haproxy_ca_path }} verify required alpn h2,http/1.1 + option httplog + option forwardfor + timeout client 30s + + http-request set-header X-Client-Cert %[ssl_c_der,base64] + +{% for service_name, service in securemtls_services.items() %} + acl host_{{ service_name }} hdr(host) -i {{ service.hostname }} + use_backend {{ service_name }} if host_{{ service_name }} +{% endfor %} + +# ========================================== +# Backends +# ========================================== +{% for service_name, service in securemtls_services.items() %} +backend {{ service_name }} + balance {{ service.balance | default('roundrobin') }} + mode {{ service.mode | default('http') }} + timeout connect {{ service.timeout_connect | default('5s') }} + timeout server {{ service.timeout_server | default('30s') }} + option {{ service.options | default('http-server-close') }} + default-server inter {{ service.default_server_inter | default('3s') }} + default-server rise {{ service.default_server_rise | default('2') }} + default-server fall {{ service.default_server_fall | default('3') }} + +{% for server in service.backend_servers %} + {% if server.proto == "https" %} + server {{ service_name }}-{{ loop.index }} {{ server.address }}:{{ server.port }} ssl verify none check + {% else %} + server {{ service_name }}-{{ loop.index }} {{ server.address }}:{{ server.port }} check + {% endif %} +{% endfor %} + +{% if service.headers is defined %} +{% for hdr in service.headers %} + {{ hdr }} +{% endfor %} +{% endif %} + +{% if service.rewrites is defined %} +{% for rewrite in service.rewrites %} + {{ rewrite }} +{% endfor %} +{% endif %} + +{% endfor %} + +# ========================================== +# Vault backend for HAProxy +# ========================================== +backend vault_backend + mode tcp + option ssl-hello-chk + server vault1 VAULT_IP:VAULT_PORT ssl verify required ca-file {{ haproxy_ca_path }} crt {{ haproxy_cert_path }} diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/defaults/main.yml new file mode 100644 index 0000000..216ff2f --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/defaults/main.yml @@ -0,0 +1,28 @@ +# Networking +technitium_port: 53 +technitium_tls_port: 853 +technitium_admin_port: TECHNITIUM_PORT +technitium_bind_address: "0.0.0.0" + +# ========================================================== +# Directory paths +# ========================================================== +technitium_certs_dir: "/etc/technitium/certs" +technitium_crt_path: "{{ technitium_certs_dir }}/technitium.crt" +technitium_key_path: "{{ technitium_certs_dir }}/technitium.key" +technitium_ca_path: "{{ technitium_certs_dir }}/technitium_ca.crt" + +# Install paths +technitium_install_dir: "/opt/technitium/dns" +technitium_data_dir: "{{ technitium_install_dir }}/data" + +vault_agent_templates: + - src: "technitium_cert.ctmpl" + dest: "{{ technitium_crt_path }}" + service: "technitium" + - src: "technitium_key.ctmpl" + dest: "{{ technitium_key_path }}" + service: "technitium" + - src: "technitium_ca.ctmpl" + dest: "{{ technitium_ca_path }}" + service: "technitium" diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/handlers/main.yml b/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/handlers/main.yml new file mode 100644 index 0000000..6bcc4fc --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/handlers/main.yml @@ -0,0 +1,8 @@ +- name: Reload systemd + ansible.builtin.systemd: + daemon_reload: yes + +- name: Restart technitium + ansible.builtin.systemd: + name: technitium + state: restarted diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/tasks/main.yml new file mode 100644 index 0000000..b96dd10 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/tasks/main.yml @@ -0,0 +1,125 @@ +--- +# ========================================================== +# Install and configure Technitium DNS Server +# ========================================================== + +- name: Ensure Technitium install directory exists + ansible.builtin.file: + path: "{{ technitium_install_dir }}" + state: directory + owner: root + group: root + mode: "0755" + +- name: Ensure Technitium data directory exists + ansible.builtin.file: + path: "{{ technitium_data_dir }}" + state: directory + owner: root + group: root + mode: "0755" + +- name: Ensure Technitium TLS directory exists + ansible.builtin.file: + path: "{{ technitium_certs_dir }}" + state: directory + owner: vault + group: vault + mode: "0700" + +# ========================================================== +# Install Technitium if not already installed +# ========================================================== +- name: Install Technitium DNS Server + ansible.builtin.command: bash install.sh + args: + chdir: /tmp + creates: "{{ technitium_install_dir }}/start.sh" + environment: + INSTALL_URL: "https://download.technitium.com/dns/install.sh" + register: technitium_install_result + ignore_errors: true + changed_when: technitium_install_result.rc == 0 + +# ========================================================== +# SELinux setup (idempotent) +# ========================================================== +- name: Apply SELinux context for Technitium certs + ansible.builtin.command: semanage fcontext -a -t etc_t '{{ technitium_certs_dir }}(/.*)?' + register: selinux_ctx + changed_when: selinux_ctx.rc == 0 + ignore_errors: true + +- name: Restore SELinux context for Technitium certs + ansible.builtin.command: restorecon -Rv "{{ technitium_certs_dir }}" + changed_when: false + +# ========================================================== +# Firewalld setup +# ========================================================== +- name: Ensure firewalld is running + ansible.builtin.systemd: + name: firewalld + enabled: true + state: started + +- name: Open Technitium DNS and admin ports + ansible.posix.firewalld: + port: "{{ item.port }}/{{ item.proto }}" + permanent: true + state: enabled + immediate: true + loop: + - { port: 53, proto: "udp" } + - { port: 53, proto: "tcp" } + - { port: 853, proto: "tcp" } + - { port: 443, proto: "tcp" } + - { port: 5380, proto: "tcp" } + +# ========================================================== +# Systemd service deployment +# ========================================================== +- name: Deploy Technitium systemd unit + ansible.builtin.template: + src: "technitium.service.j2" + dest: "/etc/systemd/system/technitium.service" + owner: root + group: root + mode: "0644" + notify: + - Reload systemd + - Restart technitium + +- name: Wait for Vault-Agent to render certificates + ansible.builtin.wait_for: + path: "{{ cert_path }}" + state: present + timeout: 60 + loop: + - "{{ technitium_crt_path }}" + - "{{ technitium_key_path }}" + - "{{ technitium_ca_path }}" + loop_control: + loop_var: cert_path + +- name: Fix ownership and permissions for Technitium certs + ansible.builtin.file: + path: "{{ cert.path }}" + owner: vault + group: vault + mode: "{{ cert.mode }}" + loop: + - { path: "{{ technitium_crt_path }}", mode: "0644" } + - { path: "{{ technitium_key_path }}", mode: "0600" } + - { path: "{{ technitium_ca_path }}", mode: "0644" } + loop_control: + loop_var: cert + +# ========================================================== +# Start and enable service +# ========================================================== +- name: Ensure Technitium DNS service is running + ansible.builtin.systemd: + name: technitium + enabled: true + state: started diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/templates/technitium.service.j2 b/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/templates/technitium.service.j2 new file mode 100644 index 0000000..2664bbb --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/templates/technitium.service.j2 @@ -0,0 +1,16 @@ +[Unit] +Description=Technitium DNS Server +After=network.target + +[Service] +Type=simple +WorkingDirectory={{ technitium_install_dir }} +#ExecStart={{ technitium_install_dir }}/start.sh +ExecStart={{ technitium_install_dir }}/start.sh --tls-cert /etc/technitium/certs/technitium.crt --tls-key /etc/technitium/certs/technitium.key +Restart=always +RestartSec=5 +User=root +Group=root + +[Install] +WantedBy=multi-user.target diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/vars/main.yml b/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/vars/main.yml new file mode 100644 index 0000000..ffce5d9 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/technitium/vars/main.yml @@ -0,0 +1,4 @@ +technitium_service_name: "technitium" +technitium_binary_url: "https://download.technitium.com/dns/DnsServerPortable.tar.gz" +technitium_data_dir: "{{ technitium_install_dir }}/data" +technitium_conf_file: "{{ technitium_install_dir }}/Technitium.Dns.Server.json" diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/defaults/main.yml b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/defaults/main.yml new file mode 100644 index 0000000..cfcd822 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/defaults/main.yml @@ -0,0 +1,41 @@ +--- +vault_addr: "http://VAULT_IP:VAULT_PORT" + +vault_agent_config_path: "/etc/vault-agent-config.hcl" +vault_agent_pid_file: "/var/run/vault-agent.pid" +vault_agent_user: "root" +vault_agent_service: "vault-agent" +vault_agent_systemd_unit: "/etc/systemd/system/vault-agent.service" + +# Paths to role_id / secret_id files (written by bootstrap process) +vault_role_id_file: "/etc/vault-agent/role_id" +vault_secret_id_file: "/etc/vault-agent/secret_id" + +# Templates +vault_templates_dir: "/etc/vault-agent-templates" + +vault_agent_templates: + # HAProxy + - src: "haproxy.pem.ctmpl" + dest: "/etc/haproxy/certs/haproxy.pem" + service: haproxy + - src: "haproxy_ca.ctmpl" + dest: "/etc/haproxy/certs/haproxy_ca.crt" + service: haproxy + - src: "thesabear-client.ctmpl" + dest: "/etc/haproxy/certs/clients/thesabear-client.pem" + service: "haproxy" + + # Technitium + - src: "technitium_cert.ctmpl" + dest: "/etc/technitium/certs/technitium.crt" + service: technitium + - src: "technitium_key.ctmpl" + dest: "/etc/technitium/certs/technitium.key" + service: technitium + - src: "technitium_ca.ctmpl" + dest: "/etc/technitium/certs/technitium_ca.crt" + service: technitium + +# Default Vault PKI options (can remain) +pki_mount_path: "pki" diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/generic_ca.ctmpl b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/generic_ca.ctmpl new file mode 100644 index 0000000..27d7de5 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/generic_ca.ctmpl @@ -0,0 +1,3 @@ +{{- with secret "pki/cert/ca" -}} +{{ trim .Data.certificate "\n" }} +{{- end }} diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/generic_cert.ctmpl b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/generic_cert.ctmpl new file mode 100644 index 0000000..d492aca --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/generic_cert.ctmpl @@ -0,0 +1,7 @@ +{% raw %} +{{- with secret "pki/issue/{{ wildcard_role_name }}" "common_name={{ common_name }}" -}} +{{ .Data.certificate }} +{{ .Data.private_key }} +{{ .Data.issuing_ca }} +{{- end }} +{% endraw %} diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/haproxy.pem.ctmpl b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/haproxy.pem.ctmpl new file mode 100644 index 0000000..29081da --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/haproxy.pem.ctmpl @@ -0,0 +1,6 @@ +{{ with secret "pki/issue/securevault-wildcard" "common_name=*.local.DOMAIN_PLACEHOLDER" "ttl=8760h" }} +{{ .Data.certificate }} +{{ .Data.issuing_ca }} +{{ .Data.private_key }} +{{ end }} + diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/haproxy_ca.ctmpl b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/haproxy_ca.ctmpl new file mode 100644 index 0000000..19b5278 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/haproxy_ca.ctmpl @@ -0,0 +1,3 @@ +{{- with secret "pki/issue/securevault-wildcard" "common_name=*.local.DOMAIN_PLACEHOLDER" -}} +{{ .Data.issuing_ca }} +{{- end }} diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/technitium_ca.ctmpl b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/technitium_ca.ctmpl new file mode 100644 index 0000000..0beda22 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/technitium_ca.ctmpl @@ -0,0 +1,3 @@ +{{- with secret "pki_int/issue/securevault-wildcard" "common_name=technitium.local.DOMAIN_PLACEHOLDER" -}} +{{ .Data.issuing_ca }} +{{- end }} diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/technitium_cert.ctmpl b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/technitium_cert.ctmpl new file mode 100644 index 0000000..0bb85c1 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/technitium_cert.ctmpl @@ -0,0 +1,3 @@ +{{- with secret "pki_int/issue/securevault-wildcard" "common_name=technitium.local.DOMAIN_PLACEHOLDER" -}} +{{ .Data.certificate }} +{{- end }} diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/technitium_key.ctmpl b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/technitium_key.ctmpl new file mode 100644 index 0000000..a0b20b4 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/technitium_key.ctmpl @@ -0,0 +1,3 @@ +{{- with secret "pki_int/issue/securevault-wildcard" "common_name=technitium.local.DOMAIN_PLACEHOLDER" -}} +{{ .Data.private_key }} +{{- end }} diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/thesabear-client.ctmpl b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/thesabear-client.ctmpl new file mode 100644 index 0000000..d0a0b41 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/files/thesabear-client.ctmpl @@ -0,0 +1,6 @@ +{{ with secret "pki_int/issue/mtls-client-cert" "common_name=clientname.local.DOMAIN_PLACEHOLDER" "ttl=8760h" }} +{{ .Data.certificate }} +{{ .Data.private_key }} +{{ .Data.issuing_ca }} +{{ end }} + diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/handlers/main.yml b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/handlers/main.yml new file mode 100644 index 0000000..c942a47 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/handlers/main.yml @@ -0,0 +1,3 @@ +--- +- name: Reload systemd + command: systemctl daemon-reload diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/tasks/main.yml b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/tasks/main.yml new file mode 100644 index 0000000..eebdafc --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/tasks/main.yml @@ -0,0 +1,230 @@ +--- +# ========================================================== +# 1️⃣ Install Vault and Dependencies +# ========================================================== +- name: Ensure HashiCorp repository (RHEL9) + ansible.builtin.copy: + dest: /etc/yum.repos.d/hashicorp.repo + mode: "0644" + content: | + [hashicorp] + name=HashiCorp Stable Repository + baseurl=https://rpm.releases.hashicorp.com/RHEL/9/x86_64/stable/ + enabled=1 + gpgcheck=1 + gpgkey=https://rpm.releases.hashicorp.com/gpg + +- name: Install Vault (includes vault-agent) + ansible.builtin.dnf: + name: vault + state: present + +# ========================================================== +# 2️⃣ Firewall Configuration +# ========================================================== +- name: Ensure firewalld is running and enabled + ansible.builtin.systemd: + name: firewalld + enabled: true + state: started + +- name: Allow outbound access to Vault (VAULT_PORT/tcp) + ansible.posix.firewalld: + rich_rule: > + rule family="ipv4" + destination address="VAULT_IP" + port port="VAULT_PORT" protocol="tcp" accept + permanent: true + state: enabled + immediate: true + +# ========================================================== +# 3️⃣ Vault-Agent Directories and Configs +# ========================================================== +- name: Ensure Vault-Agent directories exist + ansible.builtin.file: + path: "{{ vault_dir.path }}" + state: directory + owner: root + group: root + mode: "{{ vault_dir.mode | default('0750') }}" + loop: + - { path: "{{ vault_templates_dir }}" } + - { path: "/etc/vault-agent", mode: "0700" } + loop_control: + loop_var: vault_dir + +- name: Deploy Vault-Agent configuration + ansible.builtin.template: + src: vault-agent-config.hcl.j2 + dest: "{{ vault_agent_config_path }}" + owner: root + group: root + mode: "0600" + +- name: Install Vault-Agent systemd unit + ansible.builtin.template: + src: vault-agent.service.j2 + dest: "{{ vault_agent_systemd_unit }}" + owner: root + group: root + mode: "0644" + notify: Reload systemd + +- name: Enable Vault-Agent but do not start yet + ansible.builtin.systemd: + name: "{{ vault_agent_service }}" + enabled: true + state: stopped + +# ========================================================== +# 4️⃣ Vault-Agent Templates and Cert Paths +# ========================================================== +- name: Copy Vault-Agent templates + ansible.builtin.copy: + src: "{{ vault_item.src }}" + dest: "{{ vault_templates_dir }}/{{ vault_item.src | basename }}" + owner: root + group: root + mode: "0600" + loop: "{{ vault_agent_templates }}" + loop_control: + loop_var: vault_item + +- name: Ensure directories for Vault-rendered files exist + ansible.builtin.file: + path: "{{ vault_template.dest | dirname }}" + state: directory + owner: root + group: root + mode: "0755" + loop: "{{ vault_agent_templates }}" + loop_control: + loop_var: vault_template + +- name: Start Vault-Agent (now that templates exist) + ansible.builtin.systemd: + name: "{{ vault_agent_service }}" + state: started + +# ========================================================== +# 5️⃣ AppRole Authentication Setup +# ========================================================== +- name: Fetch AppRole credentials for this host + delegate_to: localhost + block: + - name: Get role_id + ansible.builtin.command: + cmd: > + vault read -field=role_id auth/approle/role/{{ inventory_hostname }}-role/role-id + register: role_id_raw + changed_when: false + failed_when: role_id_raw.stdout | trim == "" + environment: + VAULT_ADDR: "http://VAULT_IP:VAULT_PORT" + VAULT_TOKEN: "{{ vault_token }}" + + - name: Get secret_id + ansible.builtin.command: + cmd: > + vault write -f -field=secret_id auth/approle/role/{{ inventory_hostname }}-role/secret-id + register: secret_id_raw + changed_when: false + failed_when: secret_id_raw.stdout | trim == "" + environment: + VAULT_ADDR: "http://VAULT_IP:VAULT_PORT" + VAULT_TOKEN: "{{ vault_token }}" + +- name: Write AppRole credentials to Vault-Agent directory + ansible.builtin.copy: + dest: "/etc/vault-agent/{{ cred.name }}" + content: "{{ cred.content }}" + owner: root + group: root + mode: "0600" + loop: + - { name: "role_id", content: "{{ role_id_raw.stdout | trim }}" } + - { name: "secret_id", content: "{{ secret_id_raw.stdout | trim }}" } + loop_control: + loop_var: cred + label: "{{ cred.name }}" + +# ========================================================== +# 6️⃣ Register Service in Vault +# ========================================================== +- name: Login to Vault with AppRole and register VM + delegate_to: localhost + block: + - name: Login to Vault with AppRole + ansible.builtin.command: + cmd: > + vault write -field=token auth/approle/login + role_id={{ role_id_raw.stdout | trim }} + secret_id={{ secret_id_raw.stdout | trim }} + register: vault_login + changed_when: false + environment: + VAULT_ADDR: "http://VAULT_IP:VAULT_PORT" + + - name: Register VM IP in Vault KV + ansible.builtin.uri: + url: "http://VAULT_IP:VAULT_PORT/v1/kv/data/services/{{ inventory_hostname }}" + method: POST + headers: + X-Vault-Token: "{{ vault_login.stdout }}" + body: + data: + ip: "{{ ansible_default_ipv4.address }}" + body_format: json + status_code: [200, 204] + +# ========================================================== +# 7️⃣ SELinux Context for Vault Rendered Paths +# ========================================================== +- name: Check if SELinux is enforcing + ansible.builtin.command: getenforce + register: selinux_status + changed_when: false + failed_when: false + +- name: Gather all Vault-Agent template and rendered paths + ansible.builtin.set_fact: + all_vault_paths: >- + {{ + ( + (vault_agent_templates | default([]) | map(attribute='dest') | map('dirname') | list) + + + (vault_agent_templates | default([]) | map(attribute='dest') | list) + ) | unique + }} + when: vault_agent_templates is defined and vault_agent_templates | length > 0 + +- name: Filter only paths that exist on the host + ansible.builtin.find: + paths: "{{ all_vault_paths }}" + file_type: directory + register: existing_selinux_paths + when: all_vault_paths is defined and all_vault_paths | length > 0 + +- name: Apply SELinux fcontext to all existing Vault paths + ansible.builtin.command: + cmd: semanage fcontext -a -t etc_t '{{ selinux_path_item.path }}(/.*)?' + loop: "{{ existing_selinux_paths.files | default([]) }}" + loop_control: + loop_var: selinux_path_item + ignore_errors: true + when: selinux_status.stdout == "Enforcing" + +- name: Restore SELinux context for all existing Vault paths + ansible.builtin.command: + cmd: restorecon -Rv "{{ selinux_path_item.path }}" + loop: "{{ existing_selinux_paths.files | default([]) }}" + loop_control: + loop_var: selinux_path_item + ignore_errors: true + when: selinux_status.stdout == "Enforcing" + +- name: Warn if SELinux is not enforcing + ansible.builtin.debug: + msg: "⚠️ SELinux is not enforcing β€” Vault-Agent directories may not be properly confined." + when: selinux_status.stdout != "Enforcing" diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/templates/vault-agent-config.hcl.j2 b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/templates/vault-agent-config.hcl.j2 new file mode 100644 index 0000000..4226630 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/templates/vault-agent-config.hcl.j2 @@ -0,0 +1,30 @@ +pid_file = "{{ vault_agent_pid_file }}" + +auto_auth { + method { + type = "approle" + config = { + role_id_file_path = "{{ vault_role_id_file }}" + secret_id_file_path = "{{ vault_secret_id_file }}" + remove_secret_id_file_after_reading = false + } + } + sink { + type = "file" + config = { + path = "/etc/vault-token" + } + } +} + +{% for tpl in vault_agent_templates if tpl.service == hostvars[inventory_hostname].service %} +template { + source = "{{ vault_templates_dir }}/{{ tpl.src }}" + destination = "{{ tpl.dest }}" + perms = "0644" +} +{% endfor %} + +vault { + address = "{{ vault_addr }}" +} diff --git a/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/templates/vault-agent.service.j2 b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/templates/vault-agent.service.j2 new file mode 100644 index 0000000..ca25f6a --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles/secure_vault/vault_agent/templates/vault-agent.service.j2 @@ -0,0 +1,13 @@ +[Unit] +Description=Vault Agent +After=network-online.target +Wants=network-online.target + +[Service] +ExecStart=/usr/bin/vault agent -config={{ vault_agent_config_path }} +Restart=always +RestartSec=5 +User={{ vault_agent_user }} + +[Install] +WantedBy=multi-user.target diff --git a/Infrastrucutre as Code/Ansible/roles_metadata.yml b/Infrastrucutre as Code/Ansible/roles_metadata.yml new file mode 100644 index 0000000..ae1a444 --- /dev/null +++ b/Infrastrucutre as Code/Ansible/roles_metadata.yml @@ -0,0 +1,123 @@ +roles_metadata: + # Bootstrap roles + base_users: + initialization: true + provisioning: false + compliance: false + description: "Base user creation tasks" + + disk_resize: + initialization: true + provisioning: false + compliance: true + description: "Disk resizing tasks" + + docker_gitlab_setup: + initialization: true + provisioning: false + compliance: false + description: "Docker runtime files moving and homelab repo copy" + + endlessh: + initialization: true + provisioning: false + compliance: false + description: "Endlessh setup" + + firewall_ssh: + initialization: true + provisioning: false + compliance: true + description: "Firewall and SSH configuration" + + hostname_network: + initialization: true + provisioning: false + compliance: false + description: "Set hostname and networking" + + packages: + initialization: true + provisioning: false + compliance: true + description: "base packages and update tasks" + + portainer_agent: + initialization: true + provisioning: false + compliance: true + description: "Portainer agent setup" + + root_password: + initialization: true + provisioning: false + compliance: false + description: "Root password setup" + + zsh_custom: + initialization: true + provisioning: false + compliance: false + description: "Custom Zsh setup" + + # CIS Hardening roles + banners: + initialization: false + provisioning: false + compliance: true + description: "Login banners setup" + + file_permissions: + initialization: false + provisioning: false + compliance: true + description: "File permissions hardening" + + filesystem_hardening: + initialization: false + provisioning: false + compliance: true + description: "filesystems hardening" + + kernel_hardening: + initialization: false + provisioning: false + compliance: true + description: "Kernel hardening tasks" + + password_policy: + initialization: false + provisioning: false + compliance: true + description: "Password policy enforcement" + + service_disable: + initialization: false + provisioning: false + compliance: true + description: "Disable unnecessary services" + + ssh_hardening: + initialization: false + provisioning: false + compliance: true + description: "SSH hardening" + + # SecureVault roles + haproxy: + initialization: false + provisioning: true + compliance: true + description: "haproxy deployment" + + vault_agent: + initialization: true + provisioning: false + compliance: false + description: "vault_agent deployment" + + technitium: + initialization: false + provisioning: true + compliance: false + description: "technitium deployment" diff --git a/Infrastrucutre as Code/Packer/README.md b/Infrastrucutre as Code/Packer/README.md new file mode 100644 index 0000000..98d26bd --- /dev/null +++ b/Infrastrucutre as Code/Packer/README.md @@ -0,0 +1,20 @@ +Coming Soon... + +A description of how I use a minimal ISO to built out create a base image with defaults configurations which is perfect for its use as a base for creating Proxmox templates from. + + + + +#ROCKY10-CIS +cd /mnt/storage/template/iso +qm create 5000 --memory 2048 --core 2 --name rocky10-cis --net0 virtio,bridge=vmbr0 +qm importdisk 5000 packer-rocky10.iso storage --format qcow2 +qm set 5000 \ + --scsihw virtio-scsi-pci \ + --scsi0 storage:5000/vm-5000-disk-0.qcow2,discard=on,ssd=1 \ + --cpu host \ + --numa 1 \ + --balloon 0 +qm set 5000 --agent enabled=1,freeze-fs-on-backup=1,fstrim_cloned_disks=1 +qm set 5000 --boot c --bootdisk scsi0 +qm set 5000 --serial0 socket --vga serial0 diff --git a/Infrastrucutre as Code/Packer/rocky10-proxmox-template/.vault_pass.txt b/Infrastrucutre as Code/Packer/rocky10-proxmox-template/.vault_pass.txt new file mode 100644 index 0000000..9e4681f --- /dev/null +++ b/Infrastrucutre as Code/Packer/rocky10-proxmox-template/.vault_pass.txt @@ -0,0 +1 @@ +SuperSecretPasswordToUnlock.vault_pass.txtFile diff --git a/Infrastrucutre as Code/Packer/rocky10-proxmox-template/http/ks.cfg b/Infrastrucutre as Code/Packer/rocky10-proxmox-template/http/ks.cfg new file mode 100644 index 0000000..8475af6 --- /dev/null +++ b/Infrastrucutre as Code/Packer/rocky10-proxmox-template/http/ks.cfg @@ -0,0 +1,82 @@ +#version=RHEL10 +lang en_US.UTF-8 +keyboard us +timezone Europe/Sofia --utc +authselect --enablefeatures=with-faillock +selinux --enforcing +firewall --disabled +services --enabled=sshd,qemu-guest-agent +firstboot --disable +network --bootproto=dhcp --device=link --onboot=on --hostname=rocky10 --activate +ignoredisk --only-use=vda +rootpw --plaintext thisisatemporarypassword +reboot + +# Clear all partitions and create new MBR label +clearpart --all --initlabel --drives=vda --disklabel=msdos + +# /boot with ext4 +part /boot --fstype="ext4" --size=1024 --ondisk=vda --label=BOOT + +# LVM physical volume on rest of disk +part pv.01 --fstype="lvmpv" --ondisk=vda --grow --size=1 --label=PV01 +volgroup rocky pv.01 + +# Logical volumes +logvol /tmp --fstype=xfs --size=2048 --name=lv_tmp --vgname=rocky --fsoptions="defaults,nodev,nosuid" +logvol /home --fstype=xfs --size=2048 --name=lv_home --vgname=rocky --fsoptions="defaults,nodev" +logvol /var --fstype=xfs --size=4096 --name=lv_var --vgname=rocky --fsoptions="defaults,nodev" +logvol /var/tmp --fstype=xfs --size=1024 --name=lv_vartmp --vgname=rocky --fsoptions="defaults,nodev,nosuid,noexec" +logvol /var/log --fstype=xfs --size=2048 --name=lv_varlog --vgname=rocky --fsoptions="defaults,nodev,noexec,nosuid" +logvol /var/log/audit --fstype=xfs --size=2048 --name=lv_audit --vgname=rocky --fsoptions="defaults,nodev,noexec,nosuid" +logvol / --fstype=xfs --name=lv_root --vgname=rocky --grow --size=512 --fsoptions="defaults" + +# BIOS-compatible bootloader +bootloader --location=mbr --boot-drive=vda + +# Repositories +repo --name="base" --baseurl=https://download.rockylinux.org/pub/rocky/10/BaseOS/x86_64/os/ +repo --name="appstream" --baseurl=https://download.rockylinux.org/pub/rocky/10/AppStream/x86_64/os/ + +%packages +@^minimal-environment +openssh-server +openssh-clients +qemu-guest-agent +nano +zsh +git +curl +sudo +util-linux-user +rsync +tree +nmap-ncat +%end + +%post --erroronfail + +# Enable Docker repository +dnf install -y epel-release dnf-plugins-core +dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo + +# Install Docker fastfetch jq +dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin fastfetch jq + +# Enable services +systemctl enable sshd +systemctl enable docker +systemctl restart docker +systemctl restart sshd + +# Enable password authentication for SSH +cat > /etc/ssh/sshd_config.d/99-password-login.conf <", + "e", # edit boot entry + "", # go to linux line end + " inst.ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks.cfg", + " console=tty0 console=ttyS0,115200n8", + " ip=dhcp", + "" # boot with edited entry + ] + + headless = true + qemu_binary = "/usr/bin/qemu-system-x86_64" + accelerator = "kvm" + cpus = 2 + memory = 2048 + + qemuargs = [ + ["-serial", "file:serial.log"], + ["-cpu", "host"], + ["-display", "none"] + ] +} + + +build { + sources = ["source.qemu.rocky10"] + + provisioner "file" { + source = "scripts/setup-admin-user.sh" + destination = "/tmp/setup-admin-user.sh" + } + + provisioner "file" { + source = "scripts/setup-ansible-user.sh" + destination = "/tmp/setup-ansible-user.sh" + } + +# Run setup-admin-user.sh + provisioner "shell" { + inline = [ + "chmod +x /tmp/setup-admin-user.sh", + "/tmp/setup-admin-user.sh \"${var.admin_username}\" \"${var.admin_password_hash}\" \"${var.admin_ssh_key}\"" + ] + } + + provisioner "shell" { + inline = [ + "chmod +x /tmp/setup-ansible-user.sh", + "/tmp/setup-ansible-user.sh \"${var.ansible_username}\" \"${var.ansible_ssh_key}\"" + ] + } +} diff --git a/Infrastrucutre as Code/Packer/rocky10-proxmox-template/scripts/setup-admin-user.sh b/Infrastrucutre as Code/Packer/rocky10-proxmox-template/scripts/setup-admin-user.sh new file mode 100755 index 0000000..12dbe42 --- /dev/null +++ b/Infrastrucutre as Code/Packer/rocky10-proxmox-template/scripts/setup-admin-user.sh @@ -0,0 +1,108 @@ +#!/bin/bash +set -euo pipefail + +USERNAME="$1" +HASHED_PASSWORD="$2" +SSH_KEY="$3" + +echo "Creating admin user: $USERNAME" + +# --- Install dependencies early +if command -v dnf &>/dev/null; then + dnf install -y zsh git curl sudo util-linux-user +elif command -v apt-get &>/dev/null; then + apt-get update + apt-get install -y zsh git curl sudo +else + echo "Unsupported package manager" + exit 1 +fi + +# --- Create user if not exists +if ! id "$USERNAME" &>/dev/null; then + useradd -m -s /bin/zsh "$USERNAME" + echo "${USERNAME}:${HASHED_PASSWORD}" | chpasswd -e +# Disable forced password change and expiration +chage -M 99999 -I -1 -E -1 "$USERNAME" +else + echo "User $USERNAME already exists, skipping creation" +fi + +USER_HOME=$(eval echo "~$USERNAME") + +# --- Create fastfetch config +mkdir -p "$USER_HOME/.config/fastfetch" +cat <<'EOF' > "$USER_HOME/.config/fastfetch/config.jsonc" +{ + "displayMode": "default", + "modules": [ + "os", "host", "kernel", "uptime", "packages", "shell", + "cpu", "memory", + { + "type": "disk", + "folders": ["/", "/home", "/tmp", "/var", "/var/log", "/var/log/audit", "/var/tmp"] + }, + "locale" + ] +} +EOF + +chown -R "$USERNAME:$USERNAME" "$USER_HOME/.config" +chmod -R 700 "$USER_HOME/.config" + +# --- SSH Setup +mkdir -p "$USER_HOME/.ssh" +echo "$SSH_KEY" > "$USER_HOME/.ssh/authorized_keys" +chmod 700 "$USER_HOME/.ssh" +chmod 600 "$USER_HOME/.ssh/authorized_keys" +chown -R "$USERNAME:$USERNAME" "$USER_HOME/.ssh" + +# --- Groups and Sudo +if ! getent group docker >/dev/null; then + groupadd docker +fi + +if getent group sudo >/dev/null; then + usermod -aG sudo,docker "$USERNAME" +elif getent group wheel >/dev/null; then + usermod -aG wheel,docker "$USERNAME" +else + echo "No sudo-equivalent group found, skipping" +fi + +echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/99_$USERNAME" +chmod 440 "/etc/sudoers.d/99_$USERNAME" + +# --- Install Oh My Zsh and config as the user +runuser -l "$USERNAME" -c 'export RUNZSH=no CHSH=no KEEP_ZSHRC=yes && \ + sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended' + +# --- Install autosuggestions plugin +runuser -l "$USERNAME" -c "git clone https://github.com/zsh-users/zsh-autosuggestions \ + ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions" + +# --- Generate custom .zshrc +cat << 'EOF' > "$USER_HOME/.zshrc" +export ZSH="\$HOME/.oh-my-zsh" +ZSH_THEME="robbyrussell" +plugins=(git zsh-autosuggestions) +source \$ZSH/oh-my-zsh.sh + +PS1='%F{51}%n%F{255}@%F{227}%m %F{84}%~%f %F{15}$ ' +fastfetch +EOF + +chown "$USERNAME:$USERNAME" "$USER_HOME/.zshrc" +chmod 644 "$USER_HOME/.zshrc" + +# --- Set shell (do this last to avoid breaking script if user shell changes mid-run) +chsh -s "$(command -v zsh)" "$USERNAME" + +# --- Deny admin from using Ansible +cat < /etc/sudoers.d/99_no_ansible_for_admin +$USERNAME ALL=(ALL) !/usr/bin/ansible, !/usr/bin/ansible-playbook +EOF + +chmod 440 /etc/sudoers.d/99_no_ansible_for_admin + +echo "βœ… User $USERNAME configured successfully" diff --git a/Infrastrucutre as Code/Packer/rocky10-proxmox-template/scripts/setup-ansible-user.sh b/Infrastrucutre as Code/Packer/rocky10-proxmox-template/scripts/setup-ansible-user.sh new file mode 100644 index 0000000..0804ff2 --- /dev/null +++ b/Infrastrucutre as Code/Packer/rocky10-proxmox-template/scripts/setup-ansible-user.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -euo pipefail + +USERNAME="$1" +SSH_KEY="$2" + +echo "Creating ansible user: $USERNAME" + +# --- Create user if not exists +if ! id "$USERNAME" &>/dev/null; then + useradd -m "$USERNAME" + mkdir -p "/home/$USERNAME/.ssh" + echo "$SSH_KEY" > "/home/$USERNAME/.ssh/authorized_keys" + chmod 700 "/home/$USERNAME/.ssh" + chmod 600 "/home/$USERNAME/.ssh/authorized_keys" + chown -R "$USERNAME:$USERNAME" "/home/$USERNAME/.ssh" +else + echo "User $USERNAME already exists, skipping creation" +fi + +# --- Harden the ansible user + +# Disable password (no password login) +passwd -d "$USERNAME" +passwd -l "$USERNAME" + +# Allow full passwordless sudo (needed for playbook tasks) +cat < "/etc/sudoers.d/99_${USERNAME}" +${USERNAME} ALL=(ALL) NOPASSWD: ALL +EOF +chmod 0440 "/etc/sudoers.d/99_${USERNAME}" + +echo "βœ… User $USERNAME hardened for automation-only access" diff --git a/Infrastrucutre as Code/Packer/rocky10-proxmox-template/secure-packer-build.sh b/Infrastrucutre as Code/Packer/rocky10-proxmox-template/secure-packer-build.sh new file mode 100755 index 0000000..bdcdd21 --- /dev/null +++ b/Infrastrucutre as Code/Packer/rocky10-proxmox-template/secure-packer-build.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -euo pipefail + +VAULT_FILE=".vault_pass.txt" +VARS_FILE="terraform.pkrvar.hcl" +PACKER_TEMPLATE="packer.pkr.hcl" + +# Check if vault password file exists +if [[ ! -f "$VAULT_FILE" ]]; then + echo "❌ Vault password file '$VAULT_FILE' not found." + exit 1 +fi + +# Decrypt the vars file temporarily +echo "πŸ”“ Decrypting $VARS_FILE..." +ansible-vault decrypt "$VARS_FILE" --vault-password-file "$VAULT_FILE" + +# Run packer build +echo "πŸ—οΈ Running Packer build..." +packer build -var-file="$VARS_FILE" "$PACKER_TEMPLATE" + +# Re-encrypt the vars file +echo "πŸ”’ Re-encrypting $VARS_FILE..." +ansible-vault encrypt "$VARS_FILE" --vault-password-file "$VAULT_FILE" + +echo "βœ… Done!" diff --git a/Infrastrucutre as Code/Packer/rocky10-proxmox-template/terraform.pkrvar.hcl b/Infrastrucutre as Code/Packer/rocky10-proxmox-template/terraform.pkrvar.hcl new file mode 100644 index 0000000..a173902 --- /dev/null +++ b/Infrastrucutre as Code/Packer/rocky10-proxmox-template/terraform.pkrvar.hcl @@ -0,0 +1,27 @@ +$ANSIBLE_VAULT;1.1;AES256 +37323332646365326338303234343138326439366364323631663634653136363935363865636334 +6534666264666666326235393861356362353932633630390a366334646166623237623233663763 +32323438646339353263366361323632646433643134663666616631316262363539373330633536 +3966623765323965650a613664306466353931643234333837376237303131643932306563346532 +65363634643964613936376661386639303431623534373464636638333238626130316664663666 +33356661313632366531646564366465366530663366326135326133313662323939393566653333 +62393839316330303164646131393162373138613764303864616562666130663035323864383031 +31303536346332393164306162343062616436303138643138653734633630666462633565326234 +66636562663165333863373563373535666533396334363865303237373331633761633838653961 +38383634326530333531656438346238336133613636356238653264303137333839313233656339 +38323730626234643963636662326663353737346332366263373139353962663239663363623736 +38656434386261316136323130646235666139643131313132396536393234373263323735336631 +37623537613164346663363138343238386362646533343837363165353539636539323434393839 +32323633636534623738356338336561653033633939366537653662643430396364383033343663 +37306666353432643636353464333362343162306435363236623866366639663339363339353736 +64663332386662353864303634653539323831396136383565363462316638613530623236623038 +63613262393035303730376164656239646336343133363931613136393362383734363235336637 +34613830633136636130303737636339306332393062656538616133393638613564326534336233 +35623764366232366538363464383334386539333963373066373065323466313033663735626466 +30366430323565333365343036343566663133316236663431393335353930323731633934346636 +38653039653930356633666336333365663833316635353866313965653632363365303165393538 +30666431663838633434363637656365613133323834343630383133396435333061373832633361 +30393730653963303933303934626562373663623265356562376633366238323563623462343162 +32626164663630393831316136303835613362663534383938353965366538653838343539663263 +65663833343834356137366263623331373236323065376536633237613030346135663563333763 +6539613565366337613162366434373430643634393665326666 diff --git a/Infrastrucutre as Code/Packer/rocky10-proxmox-template/variables.pkr.hcl b/Infrastrucutre as Code/Packer/rocky10-proxmox-template/variables.pkr.hcl new file mode 100644 index 0000000..48d1f5a --- /dev/null +++ b/Infrastrucutre as Code/Packer/rocky10-proxmox-template/variables.pkr.hcl @@ -0,0 +1,15 @@ +variable "admin_username" { + default = "admin" +} + +variable "admin_password_hash" { + type = string + sensitive = true +} + +variable "admin_ssh_key" { + type = string +} + +variable "ansible_username" {} +variable "ansible_ssh_key" {} diff --git a/Infrastrucutre as Code/README.md b/Infrastrucutre as Code/README.md new file mode 100644 index 0000000..4439aa7 --- /dev/null +++ b/Infrastrucutre as Code/README.md @@ -0,0 +1,176 @@ +# Infrastructure as Code + +This repository contains the full Infrastructure-as-Code stack that builds, configures, and manages my homelab environment. It brings together **Packer**, **Terraform**, and **Ansible** into a single, structured workflow that provisions VM templates, deploys infrastructure on Proxmox, and configures each service automatically. + +[![GitLab repo](https://img.shields.io/badge/GitLab-Public-blue)](https://gitlab.com/) +[![Terraform](https://img.shields.io/badge/Terraform-Blue?logo=terraform&logoColor=white)](https://www.terraform.io/) +[![Packer](https://img.shields.io/badge/Packer-6C9BFF?logo=hashicorp&logoColor=white)](https://www.packer.io/) +[![Ansible](https://img.shields.io/badge/Ansible-Red?logo=ansible&logoColor=white)](https://www.ansible.com/) + +The goal is to maintain everything as code: reproducible, version-controlled, and modular. + +--- + +## 🧱 Repository Structure + +``` +Infrastrucutre-as-Code/ +β”œβ”€β”€ Packer +β”œβ”€β”€ Terraform +β”œβ”€β”€ Ansible +β”œβ”€β”€ SSO +└── README.md +``` + +--- + +## ![Packer badge](https://img.shields.io/badge/Packer-6C9BFF?logo=hashicorp&logoColor=white) **Packer** +Packer +β”œβ”€β”€ rocky10-proxmox-template +β”‚ β”œβ”€β”€ http +β”‚ β”‚ └── ks.cfg +β”‚ β”œβ”€β”€ scripts +β”‚ β”‚ β”œβ”€β”€ setup-admin-user.sh +β”‚ β”‚ └── setup-ansible-user.sh +β”‚ β”œβ”€β”€ packer.pkr.hcl +β”‚ β”œβ”€β”€ secure-packer-build.sh +β”‚ β”œβ”€β”€ terraform.pkrvar.hcl +β”‚ └── variables.pkr.hcl +└── README.md + +Packer is used to create the base OS templates that all my virtual machines are built from. +This includes: + +- Automated OS installation (using a minimal Rocky10 base image) +- Hardening steps during image creation +- Preconfigured admin and automation users and passwordless SSH access for them +- Preparation for provisioning tools such as Ansible + +Each Packer build lives in its own directory containing: + +- `packer.pkr.hcl` build files +- Kickstart / cloud-init automation +- Hardening & configuration scripts + +These ISO's are uploaded to Proxmox as templates to be used by Terraform and form the baseline for the rest of the IaC pipeline. + +--- + +## ![Terraform badge](https://img.shields.io/badge/Terraform-4C9BFF?logo=terraform&logoColor=white) **Terraform** +Terraform +β”œβ”€β”€ host_tags.yml.tpl +β”œβ”€β”€ main.tf +β”œβ”€β”€ providers.tf +β”œβ”€β”€ README.md +β”œβ”€β”€ terraform.tfvars +└── variables.tf + +Terraform handles the infrastructure provisioning layer. +It manages all Proxmox-related resources, including: + +- VM creation from Packer templates +- Network, firewall, and storage definitions +- Infrastructure for identity and trust components +- Modular, reusable Terraform modules + +Terraform defines **what the homelab *is***. + +--- + +## ![Ansible badge](https://img.shields.io/badge/Ansible-FF5A5A?logo=ansible&logoColor=white) **Ansible** +Ansible +β”œβ”€β”€ group_vars +β”‚ β”œβ”€β”€ all +β”‚ β”‚ └── vault_token.yml +β”‚ └── all.yml +β”œβ”€β”€ host_vars +β”‚ β”œβ”€β”€ docker-host +β”‚ β”‚ └── host_tags.yml +β”‚ └── haproxy-vm +β”‚ └── host_tags.yml +β”œβ”€β”€ playbooks +β”‚ β”œβ”€β”€ compliance.yml +β”‚ β”œβ”€β”€ initialization.yml +β”‚ └── provisioning.yml +β”œβ”€β”€ roles +β”‚ β”œβ”€β”€ bootstrap_roles +β”‚ β”‚ β”œβ”€β”€ base_users +β”‚ β”‚ β”œβ”€β”€ disk_resize +β”‚ β”‚ β”œβ”€β”€ docker_gitlab_setup +β”‚ β”‚ β”œβ”€β”€ endlessh +β”‚ β”‚ β”œβ”€β”€ firewall_ssh +β”‚ β”‚ β”œβ”€β”€ hostname_network +β”‚ β”‚ β”œβ”€β”€ packages +β”‚ β”‚ β”œβ”€β”€ portainer_agent +β”‚ β”‚ β”œβ”€β”€ root_password +β”‚ β”‚ └── zsh_custom +β”‚ β”œβ”€β”€ cis_hardening +β”‚ β”‚ β”œβ”€β”€ banners +β”‚ β”‚ β”œβ”€β”€ defaults +β”‚ β”‚ β”œβ”€β”€ file_permissions +β”‚ β”‚ β”œβ”€β”€ filesystem_hardening +β”‚ β”‚ β”œβ”€β”€ handlers +β”‚ β”‚ β”œβ”€β”€ kernel_hardening +β”‚ β”‚ β”œβ”€β”€ password_policy +β”‚ β”‚ β”œβ”€β”€ service_disable +β”‚ β”‚ β”œβ”€β”€ ssh_hardening +β”‚ β”‚ └── tasks +β”‚ └── secure_vault +β”‚ β”œβ”€β”€ haproxy +β”‚ β”œβ”€β”€ technitium +β”‚ └── vault_agent +β”œβ”€β”€ ansible.cfg +β”œβ”€β”€ inventory_initialization.yml +β”œβ”€β”€ inventory.yml +β”œβ”€β”€ README.md +β”œβ”€β”€ requirements.yml +└── roles_metadata.yml + +Ansible configures the systems after Terraform brings them online. + +Roles and playbooks in this layer include: + +- Service installation and configuration +- Certificate lifecycle management using Hashicrp Vault +- mTLS enforcement for internal services +- Service deployment and automation +- Modular, reusable roles + +Ansible defines **how the homelab *behaves***. + +--- + +## ![SSO badge](https://img.shields.io/badge/SSO-00C2A2?logo=keycloak&logoColor=white) **SSO** + +This directory contains everything related to the Single Sign-On stack. + +This includes: + +- Identity provider access to all locally routed applications and services behind HAProxy +- Integrations with Vault, HAProxy and mTLS layers for true SSO once mTLS trust is verified + +The goal is to centralize and standardize authentication across the environment. + +--- + +## πŸ”§ Workflow Overview + +The full workflow follows a simple, layered model: + +1. **Packer** β†’ build secure, repeatable VM templates +2. **Terraform** β†’ instantiate infrastructure from those templates +3. **Ansible** β†’ configure, secure, and deploy services + +Each layer owns a specific responsibility, keeping the stack clean, modular, and easy to reason about. + +--- + +## πŸ“Œ Notes + +- Everything here is designed to be reproducible. +- No secrets live in this repository β€” Vault injects them during provisioning. +- Each component stands alone while participating in the overall pipeline. + +--- + +If you're exploring, a good path is **Packer β†’ Terraform β†’ Ansible** to see how everything connects. diff --git a/Infrastrucutre as Code/SSO/README.md b/Infrastrucutre as Code/SSO/README.md new file mode 100644 index 0000000..c3aa823 --- /dev/null +++ b/Infrastrucutre as Code/SSO/README.md @@ -0,0 +1,7 @@ +Coming Soon... + +This is the final piece of the IaC pipeline I need to construct. I have tried and tested Authelia, Authentik and Keycloak and none of them can do or fit in with my needs, which are automatically logging in once the mTLS layer provided via HAproxy is verified and access to the local domain granted, for the sign-in to be done. + +I have done some research and it looks like a small python script can be made which will allow for a pre-auth proxy service that sits between HAProxy and the backend apps. + +I have yet to get this fully working and in production. Once it is tested and working this readme and folder will be updated with all the relevant code and information for deployment. \ No newline at end of file diff --git a/Infrastrucutre as Code/Terraform/README.md b/Infrastrucutre as Code/Terraform/README.md new file mode 100644 index 0000000..6ab4b8f --- /dev/null +++ b/Infrastrucutre as Code/Terraform/README.md @@ -0,0 +1,3 @@ +Coming Soon... + +A full explanation of the files in the Terraform folder and how they are laid out in order to consume the Packer built ISO, which has been uploaded into a Proxmox template, for the construction of a VM with specific parameters. \ No newline at end of file diff --git a/Infrastrucutre as Code/Terraform/host_tags.yml.tpl b/Infrastrucutre as Code/Terraform/host_tags.yml.tpl new file mode 100644 index 0000000..b658cc3 --- /dev/null +++ b/Infrastrucutre as Code/Terraform/host_tags.yml.tpl @@ -0,0 +1,20 @@ +# VM metadata from Terraform provisioning +vmid: ${vmid} +vm_name: ${name} +target_node: ${target_node} +template: ${template} +cores: ${cores} +sockets: ${sockets} +memory: ${memory} +bridge: ${bridge} +macaddr: ${macaddr} +disk_size: ${disk_size}GB +disk_storage: ${disk_storage} +ip: ${ip} +vlan: ${vlan} +vm_tags: +%{ for tag in tags ~} + - ${tag} +%{ endfor ~} + + diff --git a/Infrastrucutre as Code/Terraform/main.tf b/Infrastrucutre as Code/Terraform/main.tf new file mode 100644 index 0000000..0a9bf99 --- /dev/null +++ b/Infrastrucutre as Code/Terraform/main.tf @@ -0,0 +1,108 @@ +locals { + ansible_repo_path = var.ansible_repo_path != "" ? var.ansible_repo_path : "${path.module}/../ansible" +} + +resource "proxmox_vm_qemu" "vm" { + for_each = var.vms + agent = 1 + skip_ipv6 = true + vmid = each.value.vmid + name = each.value.name + target_node = each.value.target_node + clone = each.value.template + onboot = each.value.onboot + os_type = "other" + + cpu { + type = "host" + numa = true + cores = each.value.cores + sockets = each.value.sockets + } + + memory = each.value.memory + balloon = 0 + scsihw = "virtio-scsi-pci" + bootdisk = "scsi0" + + disk { + slot = "scsi0" + type = "disk" + storage = each.value.disk_storage + size = each.value.disk_size + format = "qcow2" + discard = true + } + + network { + id = 0 + bridge = each.value.bridge + model = "virtio" + macaddr = each.value.macaddr + tag = each.value.vlan != 0 ? each.value.vlan : null + } + + lifecycle { + ignore_changes = [ + usb, + ] + } + + serial { + id = 0 + } + + tags = length(each.value.tags) > 0 ? join(",", each.value.tags) : null +} + +resource "null_resource" "wait_for_ssh" { + for_each = var.vms + depends_on = [proxmox_vm_qemu.vm] + + connection { + type = "ssh" + host = each.value.ip + user = "ansible" + private_key = file("~/.ssh/auto") + timeout = "5m" + } + + provisioner "local-exec" { + command = </dev/null 2>&1; then + echo "βœ… SSH is up!" + exit 0 + fi + echo "Retry $i: SSH not ready yet..." + sleep 10 + done + echo "❌ SSH did not become available on ${each.value.ip} after timeout." + exit 1 + EOT + } +} + +resource "local_file" "host_tags" { + for_each = var.vms + + content = templatefile("${path.module}/host_tags.yml.tpl", { + vmid = each.value.vmid + name = each.value.name + target_node = each.value.target_node + template = each.value.template + cores = each.value.cores + sockets = each.value.sockets + memory = each.value.memory + bridge = each.value.bridge + macaddr = each.value.macaddr + disk_size = each.value.disk_size + disk_storage = each.value.disk_storage + ip = each.value.ip + vlan = each.value.vlan + tags = each.value.tags + }) + + filename = "${local.ansible_repo_path}/host_vars/${each.value.name}/host_tags.yml" +} diff --git a/Infrastrucutre as Code/Terraform/providers.tf b/Infrastrucutre as Code/Terraform/providers.tf new file mode 100644 index 0000000..01de8ce --- /dev/null +++ b/Infrastrucutre as Code/Terraform/providers.tf @@ -0,0 +1,16 @@ +terraform { + required_version = ">= 0.14" + required_providers { + proxmox = { + source = "telmate/proxmox" + version = "3.0.2-rc03" + } + } +} + +provider "proxmox" { + pm_tls_insecure = true + pm_api_url = "https://host:port/api2/json" + pm_api_token_secret = "pm_api_token_secret" + pm_api_token_id = "pm_api_token_id" +} \ No newline at end of file diff --git a/Infrastrucutre as Code/Terraform/terraform.tfvars b/Infrastrucutre as Code/Terraform/terraform.tfvars new file mode 100644 index 0000000..c51a405 --- /dev/null +++ b/Infrastrucutre as Code/Terraform/terraform.tfvars @@ -0,0 +1,78 @@ +vms = { + haproxy = { + vmid = id number here + name = "vm name" + target_node = "hypervisor node name" + template = "template-name" + onboot = true + cores = 2 + sockets = 1 + memory = 2048 + bridge = "vmbr0" + macaddr = "xx:x:x:x:x:x" + disk_size = 20 + disk_storage = "storage name here" + ip = "xx.xx.xx.x" + gateway = "x.x.x.x" + netmask = "24" + vlan = "0" + startup = { + order = 3 + up = 10 + down = 10 + } + tags = [ + "packages", + "hostname_network", + "root_password", + "disk_resize", + "base_users", + "firewall_ssh", + "endlessh", + "portainer_agent", + "zsh_custom", + "cis_hardening" + ] + } + docker = { + vmid = id number here + name = "vm name" + target_node = "hypervisor node name" + template = "template-name" + onboot = true + cores = 2 + sockets = 1 + memory = 8192 + bridge = "vmbr0" + macaddr = "xx:x:x:x:x:x" + disk_size = 20 + disk_storage = "storage name here" + ip = "xx.xx.xx.x" + gateway = "x.x.x.x" + netmask = "24" + vlan = "0" + startup = { + order = 3 + up = 10 + down = 20 + } + tags = [ + "packages", + "hostname_network", + "root_password", + "disk_resize", + "base_users", + "firewall_ssh", + "endlessh", + "portainer_agent", + "zsh_custom", + "vault_agent", + "cis_hardening", + "docker_gitlab_setup" + "s_nail", + "lynis" + ] + } +} + +ansible_repo_path = "" diff --git a/Infrastrucutre as Code/Terraform/variables.tf b/Infrastrucutre as Code/Terraform/variables.tf new file mode 100644 index 0000000..ff93f29 --- /dev/null +++ b/Infrastrucutre as Code/Terraform/variables.tf @@ -0,0 +1,32 @@ +variable "vms" { + description = "Map of VMs to create" + type = map(object({ + vmid = number + name = string + target_node = string + template = string + onboot = bool + cores = number + sockets = number + memory = number + bridge = string + macaddr = string + disk_size = number + disk_storage = string + ip = string + gateway = string + netmask = string + vlan = string + startup = object({ + order = number + up = number + down = number + }) + tags = list(string) + })) +} + +variable "ansible_repo_path" { + description = "Path to the ansible repo" + type = string +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d428da2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 thesabear + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Proxmox/Proxmox API Authentication.md b/Proxmox/Proxmox API Authentication.md new file mode 100644 index 0000000..cf75865 --- /dev/null +++ b/Proxmox/Proxmox API Authentication.md @@ -0,0 +1,18 @@ +## Create an API Token on Proxmox + +To create a new API Token for your `user` in Proxmox, follow these steps: + +1. Open the Proxmox Web UI and navigate to the **'Datacenter'** in the **'Server View'** menu. +2. Select **'API Token'** in the **'Permissions'** menu. +3. Click on **'Add'**. +4. Select the `user`, and add a **'Token ID'**. +5. (Optional) Disable `Privilege Separation`, and set an `Expire` date. +6. Click on **'Add'**. + +## Check the API Token + +To test the API Token, you can use the following command: + +```sh +curl -H "Authorization: PVEAPIToken=root@pam!monitoring=aaaaaaaaa-bbb-cccc-dddd-ef0123456789" https://your-proxmox-url:8006/api2/json/ +``` \ No newline at end of file diff --git a/Proxmox/Proxmox Certificate Management.md b/Proxmox/Proxmox Certificate Management.md new file mode 100644 index 0000000..17738e9 --- /dev/null +++ b/Proxmox/Proxmox Certificate Management.md @@ -0,0 +1,313 @@ +## Certificates for Intra-Cluster Communication + +Each Proxmox VE cluster creates by default its own (self-signed) Certificate +Authority (CA) and generates a certificate for each node which gets signed by +the aforementioned CA. These certificates are used for encrypted communication +with the cluster's pveproxy service and the Shell/Console feature if SPICE is used. + +The CA certificate and key are stored in the Proxmox Cluster File System (pmxcfs). + +## Certificates for API and Web GUI + +The REST API and web GUI are provided by the pveproxy service, which runs on each node. +You have the following options for the certificate used by pveproxy: + +* By default the node-specific certificate in `/etc/pve/nodes/NODENAME/pve-ssl.pem` is used. This certificate is signed by the cluster CA and therefore not automatically trusted by browsers and operating systems. +* Use an externally provided certificate (e.g. signed by a commercial CA). +* Use ACME (Let's Encrypt) to get a trusted certificate with automatic renewal, this is also integrated in the Proxmox VE API and web interface. + +For options 2 and 3 the file `/etc/pve/local/pveproxy-ssl.pem` (and +`/etc/pve/local/pveproxy-ssl.key`, which needs to be without password) is used. +Keep in mind that `/etc/pve/local` is a node specific symlink to +`/etc/pve/nodes/NODENAME`. + +Certificates are managed with the Proxmox VE Node management command +(see the `pvenode(1)` manpage). + +Do not replace or manually modify the automatically generated node +certificate files in `/etc/pve/local/pve-ssl.pem` and +`/etc/pve/local/pve-ssl.key` or the cluster CA files in +`/etc/pve/pve-root-ca.pem` and `/etc/pve/priv/pve-root-ca.key`. + +## Upload Custom Certificate + +If you already have a certificate which you want to use for a Proxmox VE node +you can upload that certificate simply over the web interface. Note that the +certificates key file, if provided, mustn't be password protected. + +## Trusted certificates via Let's Encrypt (ACME) + +Proxmox VE includes an implementation of the Automatic Certificate +Management Environment ACME protocol, allowing Proxmox VE admins to +use an ACME provider like Let's Encrypt for easy setup of TLS certificates +which are accepted and trusted on modern operating systems and web browsers +out of the box. + +Currently, the two ACME endpoints implemented are the +Let's Encrypt (LE) production and its staging +environment. Our ACME client supports validation of http-01 challenges using +a built-in web server and validation of dns-01 challenges using a DNS plugin +supporting all the DNS API endpoints `acme.sh` does. + +### ACME Account + +You need to register an ACME account per cluster with the endpoint you want to +use. The email address used for that account will serve as contact point for +renewal-due or similar notifications from the ACME endpoint. +You can register and deactivate ACME accounts over the web interface +`Datacenter -> ACME` or using the `pvenode` command line tool. + +```shell + pvenode acme account register account-name mail@example.com +``` + +Because of rate-limits you should use LE staging for experiments or if you use +ACME for the first time. + +### ACME Plugins + +The ACME plugins task is to provide automatic verification that you, and thus +the Proxmox VE cluster under your operation, are the real owner of a domain. This is +the basis building block for automatic certificate management. + +The ACME protocol specifies different types of challenges, for example the +http-01 where a web server provides a file with a certain content to prove +that it controls a domain. Sometimes this isn't possible, either because of +technical limitations or if the address of a record to is not reachable from +the public internet. The dns-01 challenge can be used in these cases. This +challenge is fulfilled by creating a certain DNS record in the domain's zone. + +Proxmox VE supports both of those challenge types out of the box, you can configure +plugins either over the web interface under `Datacenter -> ACME`, or using the +`pvenode acme plugin add` command. + +ACME Plugin configurations are stored in `/etc/pve/priv/acme/plugins.cfg`. +A plugin is available for all nodes in the cluster. + +### Node Domains + +Each domain is node specific. You can add new or manage existing domain entries +under Node -> Certificates, or using the `pvenode config` command. + +After configuring the desired domain(s) for a node and ensuring that the +desired ACME account is selected, you can order your new certificate over the +web-interface. On success the interface will reload after 10 seconds. + +Renewal will happen automatically. + +## ACME HTTP Challenge Plugin + +There is always an implicitly configured standalone plugin for validating +http-01 challenges via the built-in webserver spawned on port 80. + +The name `standalone` means that it can provide the validation on it's +own, without any third party service. So, this plugin works also for cluster +nodes. + +There are a few prerequisites to use it for certificate management with Let's +Encrypts ACME. + +* You have to accept the ToS of Let's Encrypt to register an account. +* Port 80 of the node needs to be reachable from the internet. +* There must be no other listener on port 80. +* The requested (sub)domain needs to resolve to a public IP of the Node. + +## ACME DNS API Challenge Plugin + +On systems where external access for validation via the http-01 method is +not possible or desired, it is possible to use the dns-01 validation method. +This validation method requires a DNS server that allows provisioning of TXT +records via an API. + +### Configuring ACME DNS APIs for validation + +Proxmox VE re-uses the DNS plugins developed for the +[acme.sh project](https://github.com/acmesh-official/acme.sh). Please +refer to its documentation for details on configuration of specific APIs. + +The easiest way to configure a new plugin with the DNS API is using the web +interface (`Datacenter -> ACME`). + +Choose DNS as challenge type. Then you can select your API provider, enter +the credential data to access your account over their API. + +See the `acme.sh` +[How to use DNS API](https://github.com/acmesh-official/acme.sh/wiki/dnsapi#how-to-use-dns-api) +wiki for more detailed information about getting API credentials for your +provider. + +As there are many DNS providers and API endpoints Proxmox VE automatically generates +the form for the credentials for some providers. For the others you will see a +bigger text area, simply copy all the credentials KEY=VALUE pairs in there. + +## DNS Validation through CNAME Alias + +A special alias mode can be used to handle the validation on a different +domain/DNS server, in case your primary/real DNS does not support provisioning +via an API. Manually set up a permanent CNAME record for +`_acme-challenge.domain1.example` pointing to `_acme-challenge.domain2.example` +and set the alias property in the Proxmox VE node configuration file to +`domain2.example` to allow the DNS server of `domain2.example` to validate all +challenges for `domain1.example`. + +### Combination of Plugins + +Combining http-01 and dns-01 validation is possible in case your node is +reachable via multiple domains with different requirements / DNS provisioning +capabilities. Mixing DNS APIs from multiple providers or instances is also +possible by specifying different plugin instances per domain. +Accessing the same service over multiple domains increases complexity and +should be avoided if possible. + +## Automatic renewal of ACME certificates + +If a node has been successfully configured with an ACME-provided certificate +(either via `pvenode` or via the GUI), the certificate will be automatically +renewed by the `pve-daily-update.service`. Currently, renewal will be attempted +if the certificate has expired already, or will expire in the next 30 days. + +## ACME Examples with pvenode + +*Example*: Sample `pvenode` invocation for using Let's Encrypt certificates + +```sh +root@proxmox:~# pvenode acme account register default mail@example.invalid +Directory endpoints: +0) Let's Encrypt V2 (https://acme-v02.api.letsencrypt.org/directory) +1) Let's Encrypt V2 Staging (https://acme-staging-v02.api.letsencrypt.org/directory) +2) Custom +Enter selection: 1 +Terms of Service: https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf +Do you agree to the above terms? [y|N]y +... +Task OK +root@proxmox:~# pvenode config set --acme domains=example.invalid +root@proxmox:~# pvenode acme cert order +Loading ACME account details +Placing ACME order +... +Status is 'valid'! +All domains validated! +... +Downloading certificate +Setting pveproxy certificate and key +Restarting pveproxy +Task OK +``` + +*Example*: Setting up the OVH API for validating a domain + +The account registration steps are the same no matter which plugins are +used, and are not repeated here. + +OVH_AK and OVH_AS need to be obtained from OVH according to the OVH +API documentation + +First you need to get all information so you and Proxmox VE can access the API. + +```sh +root@proxmox:~# cat /path/to/api-token +OVH_AK=XXXXXXXXXXXXXXXX +OVH_AS=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY +root@proxmox:~# source /path/to/api-token +root@proxmox:~# curl -XPOST -H"X-Ovh-Application: $OVH_AK" -H "Content-type: application/json" \ +https://eu.api.ovh.com/1.0/auth/credential -d '{ + "accessRules": [ + {"method": "GET","path": "/auth/time"}, + {"method": "GET","path": "/domain"}, + {"method": "GET","path": "/domain/zone/*"}, + {"method": "GET","path": "/domain/zone/*/record"}, + {"method": "POST","path": "/domain/zone/*/record"}, + {"method": "POST","path": "/domain/zone/*/refresh"}, + {"method": "PUT","path": "/domain/zone/*/record/"}, + {"method": "DELETE","path": "/domain/zone/*/record/*"} +] +}' +{"consumerKey":"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ","state":"pendingValidation","validationUrl":"https://eu.api.ovh.com/auth/?credentialToken=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"} +(open validation URL and follow instructions to link Application Key with account/Consumer Key) +root@proxmox:~# echo "OVH_CK=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" >> /path/to/api-token +``` + +Now you can setup the ACME plugin: + +```sh +root@proxmox:~# pvenode acme plugin add dns example_plugin --api ovh --data /path/to/api_token +root@proxmox:~# pvenode acme plugin config example_plugin +```sh +
+β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
+β”‚ key    β”‚ value                                    β”‚
+β•žβ•β•β•β•β•β•β•β•β•ͺ══════════════════════════════════════════║
+β”‚ api    β”‚ ovh                                      β”‚
+β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
+β”‚ data   β”‚ OVH_AK=XXXXXXXXXXXXXXXX                  β”‚
+β”‚        β”‚ OVH_AS=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY  β”‚
+β”‚        β”‚ OVH_CK=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ  β”‚
+β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
+β”‚ digest β”‚ 867fcf556363ca1bea866863093fcab83edf47a1 β”‚
+β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
+β”‚ plugin β”‚ example_plugin                           β”‚
+β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
+β”‚ type   β”‚ dns                                      β”‚
+β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
+
+ +At last you can configure the domain you want to get certificates for and +place the certificate order for it: + +```sh +root@proxmox:~# pvenode config set -acmedomain0 example.proxmox.com,plugin=example_plugin +root@proxmox:~# pvenode acme cert order +Loading ACME account details +Placing ACME order +Order URL: https://acme-staging-v02.api.letsencrypt.org/acme/order/11111111/22222222 +Getting authorization details from 'https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/33333333' +The validation for example.proxmox.com is pending! +[Wed Apr 22 09:25:30 CEST 2020] Using OVH endpoint: ovh-eu +[Wed Apr 22 09:25:30 CEST 2020] Checking authentication +[Wed Apr 22 09:25:30 CEST 2020] Consumer key is ok. +[Wed Apr 22 09:25:31 CEST 2020] Adding record +[Wed Apr 22 09:25:32 CEST 2020] Added, sleep 10 seconds. +Add TXT record: _acme-challenge.example.proxmox.com +Triggering validation +Sleeping for 5 seconds +Status is 'valid'! +[Wed Apr 22 09:25:48 CEST 2020] Using OVH endpoint: ovh-eu +[Wed Apr 22 09:25:48 CEST 2020] Checking authentication +[Wed Apr 22 09:25:48 CEST 2020] Consumer key is ok. +Remove TXT record: _acme-challenge.example.proxmox.com +All domains validated! +Creating CSR +Checking order status +Order is ready, finalizing order +valid! +Downloading certificate +Setting pveproxy certificate and key +Restarting pveproxy +Task OK +``` + +### Example: Switching from the staging to the regular ACME directory + +Changing the ACME directory for an account is unsupported, but as Proxmox VE +supports more than one account you can just create a new one with the +production (trusted) ACME directory as endpoint. You can also deactivate the +staging account and recreate it. + +*Example*: Changing the default ACME account from staging to directory using `pvenode` + +```sh +root@proxmox:~# pvenode acme account deactivate default +Renaming account file from '/etc/pve/priv/acme/default' to '/etc/pve/priv/acme/_deactivated_default_4' +Task OK +root@proxmox:~# pvenode acme account register default example@proxmox.com +Directory endpoints: +0) Let's Encrypt V2 (https://acme-v02.api.letsencrypt.org/directory) +1) Let's Encrypt V2 Staging (https://acme-staging-v02.api.letsencrypt.org/directory) +2) Custom +Enter selection: 0 +Terms of Service: https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf +Do you agree to the above terms? [y|N]y +... +Task OK +``` \ No newline at end of file diff --git a/Proxmox/Proxmox Cheat-Sheet.md b/Proxmox/Proxmox Cheat-Sheet.md new file mode 100644 index 0000000..be689bc --- /dev/null +++ b/Proxmox/Proxmox Cheat-Sheet.md @@ -0,0 +1,393 @@ +Proxmox Virtual Environment (Proxmox VE or PVE) is a hyper-converged infrastructure open-source software. It is a hosted hypervisor that can run operating systems including Linux and Windows on x64 hardware. It is a Debian-based Linux distribution with a modified Ubuntu LTS kernel and allows deployment and management of virtual machines and containers. Proxmox VE includes a web console and command-line tools, and provides a REST API for third-party tools. Two types of virtualization are supported: container-based with LXC (starting from version 4.0 replacing OpenVZ used in version up to 3.4, included), and full virtualization with KVM. It includes a web-based management interface. + +Proxmox VE is licensed under the GNU Affero General Public License, version 3. + +Repository: [https://git.proxmox.com](https://git.proxmox.com) +Website: [https://pve.proxmox.com](https://pve.proxmox.com) + +## VM Management + +```shell +# list VMs +qm list + +# Create or restore a virtual machine. +qm create your-vm-id + +# start a VM +qm start your-vm-id + +# Suspend virtual machine. +qm suspend your-vm-id + +# shutdown a VM +qm shutdown your-vm-id + +# reboot a VM +qm reboot your-vm-id + +# reset a VM +qm reset your-vm-id + +# stop a VM +qm stop your-vm-id + +# Destroy the VM and all used/owned volumes. +# Removes any VM specific permissions and firewall rules +qm destroy your-vm-id + +# Enter Qemu Monitor interface. +qm monitor your-vm-id + +# Get the virtual machine configuration with both current and pending values. +qm pending your-vm-id + +# Send key event to virtual machine. +qm sendkey your-vm-id your-key-event [OPTIONS] + +# Show command line which is used to start the VM (debug info). +qm showcmd your-vm-id [OPTIONS] + +# Unlock the VM. +qm unlock your-vm-id + +# Clone a VM +qm clone your-vm-id new-vm-id + +# Migrate a VM +qm migrate your-vm-id target-node + +# Show VM status +qm status your-vm-id + +# Clean up resources for a VM +qm cleanup your-vm-id your-clean-shutdown your-guest-requested + +# Create a Template. +qm template your-vm-id [OPTIONS] + +# Set virtual machine options (synchrounous API) +qm set your-vm-id [OPTIONS] +``` + +### Cloudinit + +```shell +# Get automatically generated cloudinit config. +qm cloudinit dump your-vm-id your-vm-type + +# Get the cloudinit configuration with both current and pending values. +qm cloudinit pending your-vm-id + +# Regenerate and change cloudinit config drive. +qm cloudinit update your-vm-id +``` + +### Disk + +```shell +# Import an external disk image as an unused disk in a VM. +# The image format has to be supported by qemu-img(1). +qm disk import your-vm-id your-target-source your-target-storage + +# Move volume to different storage or to a different VM. +qm disk move your-vm-id your-vm-disk [] [OPTIONS] + +# Rescan all storages and update disk sizes and unused disk images. +qm disk rescan [OPTIONS] + +# Extend volume size. +qm disk resize your-vm-id your-vm-disk [OPTIONS] + +# Unlink/delete disk images. +qm disk unlink your-vm-id --idlist [OPTIONS] + +# rescan volumes +qm rescan +``` + +### Snapshot + +```shell +# List all snapshots. +qm listsnapshot your-vm-id + +# Snapshot a VM +qm snapshot your-vm-id + +# Delete a snapshot. +qm delsnapshot your-vm-id + +# Rollback a snapshot +qm rollback your-vm-id + +# Open a terminal using a serial device +# (The VM need to have a serial device configured, for example serial0: socket) +qm terminal your-vm-id [OPTIONS] + +# Proxy VM VNC traffic to stdin/stdout +qm vncproxy your-vm-id +``` + +### Misc + +```shell +# Execute Qemu Guest Agent commands. +qm guest cmd your-vm-id + +# Executes the given command via the guest agent +qm guest exec your-vm-id [] [OPTIONS] + +# Gets the status of the given pid started by the guest-agent +qm guest exec-status your-vm-id + +# Sets the password for the given user to the given password +qm guest passwd your-vm-id [OPTIONS] +``` + +### PV, VG, LV Management + +```shell +# Create a PV +pvcreate + +# Remove a PV +pvremove + +# List all PVs +pvs + +# Create a VG +vgcreate + +# Remove a VG +vgremove + +# List all VGs +vgs + +# Create a LV +lvcreate -L -n + +# Remove a LV +lvremove / + +# List all LVs +lvs +``` + +### Storage Management + +```shell +# Create a new storage. +pvesm add [OPTIONS] + +# Allocate disk images. +pvesm alloc your-vm-id [OPTIONS] + +# Delete volume +pvesm free [OPTIONS] + +# Delete storage configuration. +pvesm remove + +# List storage content. +pvesm list [OPTIONS] + +# An alias for pvesm scan lvm. +pvesm lvmscan + +# An alias for pvesm scan lvmthin. +pvesm lvmthinscan + +# List local LVM volume groups. +pvesm scan lvm + +# List local LVM Thin Pools. +pvesm scan lvmthin + +# Get status for all datastores. +pvesm status [OPTIONS] +``` + +### Template Management + +```shell +# list all templates +pveam available + +# list all templates +pveam list + +# Download appliance templates +pveam download