diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml new file mode 100644 index 0000000..a4daac6 --- /dev/null +++ b/.github/workflows/build-image.yml @@ -0,0 +1,42 @@ +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Azure Login + uses: azure/login@v2 + with : + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Verify Azure Login + run: az account show + + - name: Set up Packer + uses: hashicorp/setup-packer@v3 + + - name: Packer Initialize + run: packer init packer/ubuntu + + + - name: Packer Build + run: packer build -var-file=packer/ubuntu/values.pkrvars.hcl packer/ubuntu + + # - name: Upload Golden Image to Storage Account + # run: | + # # Get the image URI from Packer output + # IMAGE_URI=$(packer build -machine-readable packer/ubuntu | grep 'artifact,0,id' | cut -d',' -f6) + + # # Upload to Azure Storage Account + # az storage blob upload \ + # --account-name ${{ secrets.STORAGE_ACCOUNT_NAME }} \ + # --container-name ${{ secrets.STORAGE_CONTAINER_NAME }} \ + # --name "golden-image-$(date +%Y%m%d-%H%M%S).vhd" \ + # --file "$IMAGE_URI" \ + # --auth-mode login \ No newline at end of file diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..51ad17e --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,11 @@ +on: [pull_request] + +jobs: + validate: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: hashicorp/setup-packer@v3 + - run: packer validate packer/ubuntu + - run: ansible-lint ansible \ No newline at end of file diff --git a/ansible/playbooks/configure.yml b/ansible/playbooks/configure.yml new file mode 100644 index 0000000..16ccae7 --- /dev/null +++ b/ansible/playbooks/configure.yml @@ -0,0 +1,9 @@ +--- +- name: Configure Azure Golden Image + hosts: all + become: yes + vars: + ansible_python_interpreter: /usr/bin/python3 + roles: + - base + - hardening \ No newline at end of file diff --git a/ansible/playbooks/roles/base/defaults/main.yml b/ansible/playbooks/roles/base/defaults/main.yml new file mode 100644 index 0000000..923a571 --- /dev/null +++ b/ansible/playbooks/roles/base/defaults/main.yml @@ -0,0 +1,12 @@ +timezone: UTC + +base_packages: + - git + - curl + - wget + - apt-transport-https + - unzip + - net-tools + - ca-certificates + - gnupg + - software-properties-common \ No newline at end of file diff --git a/ansible/playbooks/roles/base/handlers/main.yml b/ansible/playbooks/roles/base/handlers/main.yml new file mode 100644 index 0000000..e69de29 diff --git a/ansible/playbooks/roles/base/tasks/main.yml b/ansible/playbooks/roles/base/tasks/main.yml new file mode 100644 index 0000000..78643ed --- /dev/null +++ b/ansible/playbooks/roles/base/tasks/main.yml @@ -0,0 +1,32 @@ +--- + +- name: Set Timezone to UTC + command: timedatectl set-timezone {{ timezone | default('UTC') }} + args: + creates: "/etc/timezone" + +- name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 + +- name: Upgrade all packages + apt: + upgrade: dist + +- name: Install base packages + apt: + name: "{{ base_packages }}" + state: present + +- name: Enable automatice security updates + apt: + name: unattended-upgrades + state: present + +- name: Configure unattended-upgrades + copy: + dest: /etc/apt/apt.conf.d/20auto-upgrades + content: | + APT::Periodic::Update-Package-Lists "1"; + APT::Periodic::Unattended-Upgrade "1"; diff --git a/ansible/playbooks/roles/hardening/defaults/main.yml b/ansible/playbooks/roles/hardening/defaults/main.yml new file mode 100644 index 0000000..20141a3 --- /dev/null +++ b/ansible/playbooks/roles/hardening/defaults/main.yml @@ -0,0 +1,7 @@ +--- +ufw_enabled: true +ufw_allow_ssh: true +ufw_ssh_port: 22 +ufw_default_incoming: deny +ufw_default_outgoing: allow +ufw_allowed_ports: [] diff --git a/ansible/playbooks/roles/hardening/handlers/main.yml b/ansible/playbooks/roles/hardening/handlers/main.yml new file mode 100644 index 0000000..9c4c94c --- /dev/null +++ b/ansible/playbooks/roles/hardening/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Restart SSH + service: + name: ssh + state: restarted \ No newline at end of file diff --git a/ansible/playbooks/roles/hardening/tasks/enable.yml b/ansible/playbooks/roles/hardening/tasks/enable.yml new file mode 100644 index 0000000..b915a60 --- /dev/null +++ b/ansible/playbooks/roles/hardening/tasks/enable.yml @@ -0,0 +1,6 @@ +--- +- name: Enable UFW safely + command: ufw --force enable + async: 30 + poll: 0 + when: ufw_enabled | default(true) diff --git a/ansible/playbooks/roles/hardening/tasks/install.yml b/ansible/playbooks/roles/hardening/tasks/install.yml new file mode 100644 index 0000000..dd06297 --- /dev/null +++ b/ansible/playbooks/roles/hardening/tasks/install.yml @@ -0,0 +1,6 @@ +--- +- name: Install UFW + apt: + name: ufw + state: present + update_cache: yes diff --git a/ansible/playbooks/roles/hardening/tasks/main.yml b/ansible/playbooks/roles/hardening/tasks/main.yml new file mode 100644 index 0000000..0a15e68 --- /dev/null +++ b/ansible/playbooks/roles/hardening/tasks/main.yml @@ -0,0 +1,4 @@ +--- +- import_tasks: install.yml +- import_tasks: rules.yml +- import_tasks: enable.yml \ No newline at end of file diff --git a/ansible/playbooks/roles/hardening/tasks/rules.yml b/ansible/playbooks/roles/hardening/tasks/rules.yml new file mode 100644 index 0000000..32ff399 --- /dev/null +++ b/ansible/playbooks/roles/hardening/tasks/rules.yml @@ -0,0 +1,27 @@ +--- +# Allow SSH first to prevent lockout +- name: Allow SSH + ufw: + rule: allow + port: "{{ ssh_port | default(22) }}" + proto: tcp + +# Optional extra ports (if defined in defaults/main.yml) +- name: Allow additional ports + ufw: + rule: allow + port: "{{ item.port }}" + proto: "{{ item.proto | default('tcp') }}" + loop: "{{ ufw_allowed_ports | default([]) }}" + when: ufw_allowed_ports | length > 0 + +# Set default incoming and outgoing policies +- name: Set default incoming policy + ufw: + direction: incoming + policy: "{{ ufw_default_incoming | default('deny') }}" + +- name: Set default outgoing policy + ufw: + direction: outgoing + policy: "{{ ufw_default_outgoing | default('allow') }}" diff --git a/packer/ubuntu/ubuntu.pkr.hcl b/packer/ubuntu/ubuntu.pkr.hcl new file mode 100644 index 0000000..22b8d08 --- /dev/null +++ b/packer/ubuntu/ubuntu.pkr.hcl @@ -0,0 +1,39 @@ +packer { + required_plugins { + azure = { + source = "github.com/hashicorp/azure" + version = "~> 2.0" + } + } + + required_plugins { + ansible = { + source = "github.com/hashicorp/ansible" + version = "~> 1.0" + } + } +} + +source "azure-arm" "ubuntu" { + use_azure_cli_auth = true + + managed_image_resource_group_name = var.resource_group + managed_image_name = "${var.image_name}-golden-${formatdate("DDMMMYY", timestamp())}" + + os_type = var.os_type + image_publisher = "Canonical" + image_offer = "0001-com-ubuntu-server-jammy" + image_sku = var.image_sku + + location = var.location + vm_size = "Standard_B1s" +} + +build { + sources = ["sources.azure-arm.ubuntu"] + + provisioner "ansible" { + playbook_file = "ansible/playbooks/configure.yml" + roles_path= "ansible/roles" + } +} \ No newline at end of file diff --git a/packer/ubuntu/values.pkrvars.hcl b/packer/ubuntu/values.pkrvars.hcl new file mode 100644 index 0000000..92190ef --- /dev/null +++ b/packer/ubuntu/values.pkrvars.hcl @@ -0,0 +1,5 @@ +resource_group = "TResourceGroup" +location= "West Europe" +image_name="ubuntu-22_04-lts" +os_type="Linux" +image_sku="22_04-lts-gen2" \ No newline at end of file diff --git a/packer/ubuntu/variables.pkr.hcl b/packer/ubuntu/variables.pkr.hcl new file mode 100644 index 0000000..05b8f38 --- /dev/null +++ b/packer/ubuntu/variables.pkr.hcl @@ -0,0 +1,19 @@ +variable "image_name" { + default = "ubuntu-22_04-lts" +} + +variable "location" { + default = "West Europe" +} + +variable "resource_group" { + default = "TResourceGroup" +} + +variable "os_type" { + default = "Linux" +} + +variable "image_sku" { + default = "22.04-lts" +} \ No newline at end of file diff --git a/packer/ubuntu/version.json b/packer/ubuntu/version.json new file mode 100644 index 0000000..2e920e0 --- /dev/null +++ b/packer/ubuntu/version.json @@ -0,0 +1,6 @@ +{ + "os": "ubuntu-22.04", + "version": "1.0.0", + "build_date": "" , + "git_commit": "" +} \ No newline at end of file diff --git a/scripts/generate-metadata.sh b/scripts/generate-metadata.sh new file mode 100644 index 0000000..e31530c --- /dev/null +++ b/scripts/generate-metadata.sh @@ -0,0 +1,3 @@ +export VERSION=$(jq -r '.version' ../packer/ubuntu/version.json) +export BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") +export GIT_COMMIT=$(git rev-parse --short HEAD)