diff --git a/roles/ufw/.ansible-lint b/roles/ufw/.ansible-lint new file mode 100644 index 0000000..64a836a --- /dev/null +++ b/roles/ufw/.ansible-lint @@ -0,0 +1,17 @@ +--- +profile: production +strict: true + +# Enable checking of loop variable prefixes in roles +loop_var_prefix: "^(__|{role}_)" + +skip_list: + - var-naming[no-role-prefix] + +warn_list: + - role-name[path] + - var-naming[no-role-prefix] + +exclude_paths: + - .github/ + - ufw-rules.yml diff --git a/roles/ufw/.gitignore b/roles/ufw/.gitignore new file mode 100644 index 0000000..8cd0df3 --- /dev/null +++ b/roles/ufw/.gitignore @@ -0,0 +1,2 @@ +.vscode +.idea \ No newline at end of file diff --git a/roles/ufw/.yamllint b/roles/ufw/.yamllint new file mode 100644 index 0000000..611db54 --- /dev/null +++ b/roles/ufw/.yamllint @@ -0,0 +1,8 @@ +--- +rules: + brackets: + forbid: false + min-spaces-inside: 0 + max-spaces-inside: 2 + min-spaces-inside-empty: -1 + max-spaces-inside-empty: 2 diff --git a/roles/ufw/README.md b/roles/ufw/README.md new file mode 100644 index 0000000..7207f41 --- /dev/null +++ b/roles/ufw/README.md @@ -0,0 +1,37 @@ +template +========= + +Whitelist network ports with UFW + +Requirements +------------ + +None + +Role Variables +-------------- + +None + +Dependencies +------------ + +None + +Example Playbook +---------------- + +```yaml +roles: + - role: genlab.ufw +``` + +License +------- + +BSD + +Author Information +------------------ + +corvus-migratorius@proton.me diff --git a/roles/ufw/defaults/main.yml b/roles/ufw/defaults/main.yml new file mode 100644 index 0000000..7aa973f --- /dev/null +++ b/roles/ufw/defaults/main.yml @@ -0,0 +1,4 @@ +--- +rules: [] +limit_ssh: false +openssh_port: 22 diff --git a/roles/ufw/environment.ci.yml b/roles/ufw/environment.ci.yml new file mode 100644 index 0000000..b91f882 --- /dev/null +++ b/roles/ufw/environment.ci.yml @@ -0,0 +1,10 @@ +--- +name: ansible-ufw +channels: + - conda-forge +dependencies: + - python~=3.12.0 + - pip>=22.2 + - pip: + - -r requirements.txt + - -r requirements.ci.txt diff --git a/roles/ufw/environment.yml b/roles/ufw/environment.yml new file mode 100644 index 0000000..a5a6851 --- /dev/null +++ b/roles/ufw/environment.yml @@ -0,0 +1,9 @@ +--- +name: ansible-ufw +channels: + - conda-forge +dependencies: + - python~=3.12 + - pip>=22.2 + - pip: + - -r requirements.txt diff --git a/roles/ufw/handlers/main.yml b/roles/ufw/handlers/main.yml new file mode 100644 index 0000000..802d7ec --- /dev/null +++ b/roles/ufw/handlers/main.yml @@ -0,0 +1,4 @@ +--- +- name: "Reload-ufw" + community.general.ufw: + state: reloaded diff --git a/roles/ufw/meta/main.yml b/roles/ufw/meta/main.yml new file mode 100644 index 0000000..39c7f50 --- /dev/null +++ b/roles/ufw/meta/main.yml @@ -0,0 +1,17 @@ +--- +galaxy_info: + role_name: ufw + namespace: genlab + author: "Alexander Gorelyshev" + company: "Genlab, LLC" + description: "Whitelist network ports with UFW" + license: "MIT" + min_ansible_version: "2.1" + + platforms: + - name: "Ubuntu" + versions: [ "focal", "jammy" ] + + galaxy_tags: [ ] + +dependencies: [] diff --git a/roles/ufw/molecule/default/converge.yml b/roles/ufw/molecule/default/converge.yml new file mode 100644 index 0000000..26ef0bd --- /dev/null +++ b/roles/ufw/molecule/default/converge.yml @@ -0,0 +1,17 @@ +--- +- name: Converge + hosts: all + vars: + custom_rules: + - port: 80 + - port: 9080 + src: "10.2.1.0/24" + - interface: eth0@if288 + direction: in + comment: "Allow all incoming traffic on eth0@if288" + + roles: + - role: genlab.ufw + disable_ipv6: true + limit_ssh: true + rules: "{{ custom_rules }}" diff --git a/roles/ufw/molecule/default/molecule.yml b/roles/ufw/molecule/default/molecule.yml new file mode 100644 index 0000000..fd2f06d --- /dev/null +++ b/roles/ufw/molecule/default/molecule.yml @@ -0,0 +1,27 @@ +--- +dependency: + name: galaxy + +driver: + name: docker + +platforms: + - name: ubuntu + image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2204}-ansible:latest + pre_build_image: true + command: ${MOLECULE_DOCKER_COMMAND:-""} + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + privileged: true + +provisioner: + name: ansible + +verifier: + name: ansible + +lint: | + set -e + yamllint . + ansible-lint . diff --git a/roles/ufw/molecule/default/verify.yml b/roles/ufw/molecule/default/verify.yml new file mode 100644 index 0000000..3130f87 --- /dev/null +++ b/roles/ufw/molecule/default/verify.yml @@ -0,0 +1,28 @@ +--- +- name: Verify + hosts: all + gather_facts: false + any_errors_fatal: true + + tasks: + - name: "Get the UFW status" + register: ufw_status + changed_when: false + ansible.builtin.command: + cmd: ufw status + + - name: "Verify expected UFW status" + vars: + expected: + - "Status: active" + - "" + - "To Action From" + - "-- ------ ----" + - "22/tcp LIMIT Anywhere " + - "80 ALLOW Anywhere " + - "9080 ALLOW 10.2.1.0/24 " + - "Anywhere on eth0@if288 ALLOW Anywhere # Allow all incoming traffic on eth0@if288" + ansible.builtin.assert: + that: ufw_status.stdout_lines == expected + success_msg: "UFW has the expected state" + fail_msg: "Unexpected UFW state (some rules may have not been applied correctly)" diff --git a/roles/ufw/renovate.json b/roles/ufw/renovate.json new file mode 100644 index 0000000..5db72dd --- /dev/null +++ b/roles/ufw/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ] +} diff --git a/roles/ufw/requirements.ci.txt b/roles/ufw/requirements.ci.txt new file mode 100644 index 0000000..2316ec9 --- /dev/null +++ b/roles/ufw/requirements.ci.txt @@ -0,0 +1,6 @@ +ansible-lint +molecule~=24.12.0 +molecule-plugins[docker] +docker==7.1.0 +requests==2.31.0 # pinned to the latest version not breaking Docker SDK +yamllint diff --git a/roles/ufw/requirements.txt b/roles/ufw/requirements.txt new file mode 100644 index 0000000..90d4055 --- /dev/null +++ b/roles/ufw/requirements.txt @@ -0,0 +1 @@ +ansible diff --git a/roles/ufw/tasks/main.yml b/roles/ufw/tasks/main.yml new file mode 100644 index 0000000..21973d5 --- /dev/null +++ b/roles/ufw/tasks/main.yml @@ -0,0 +1,54 @@ +--- +- name: "Ensure that ufw is installed" + ansible.builtin.apt: + name: ufw + update_cache: true + +- name: "Disable IPv6" + when: (disable_ipv6 is defined) and (disable_ipv6 is true) + ansible.builtin.lineinfile: + path: /etc/default/ufw + regexp: ^IPV6 + line: IPV6=no + +- name: "Deny incoming connections" + notify: Reload-ufw + community.general.ufw: + direction: incoming + proto: any + policy: deny + +- name: "Allow outgoing connections" + notify: Reload-ufw + community.general.ufw: + direction: outgoing + proto: any + policy: allow + +- name: "Allow SSH access" + notify: Reload-ufw + community.general.ufw: + rule: "{{ limit_ssh | ternary('limit', 'allow') }}" + port: "{{ openssh_port }}" + proto: tcp + +- name: "Set whitelist rules" + notify: Reload-ufw + loop: "{{ rules }}" + community.general.ufw: + rule: "{{ item.rule | default('allow') }}" + comment: "{{ item.comment | default(omit) }}" + port: "{{ item.port | default(omit) }}" + proto: "{{ item.proto | default('any') }}" + src: "{{ item.src | default('any') }}" + dest: "{{ item.dest | default(omit) }}" + interface: "{{ item.interface | default(omit) }}" + direction: "{{ item.direction | default(omit) }}" + route: "{{ item.route | default(false) }}" + +- name: "Enable the ufw service" + community.general.ufw: + state: enabled + +- name: "Flush handlers" + ansible.builtin.meta: flush_handlers diff --git a/roles/ufw/vars/main.yml b/roles/ufw/vars/main.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/roles/ufw/vars/main.yml @@ -0,0 +1 @@ +---