diff --git a/changelogs/fragments/155-add-opnsense-confi-role.yml b/changelogs/fragments/155-add-opnsense-confi-role.yml new file mode 100644 index 00000000..2aaeec5b --- /dev/null +++ b/changelogs/fragments/155-add-opnsense-confi-role.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - puzzle.opnsense.opnsense_configure - Addition of an ansible role to the collection diff --git a/molecule/opnsense_config/converge.yml b/molecule/opnsense_config/converge.yml new file mode 100644 index 00000000..0305f4d3 --- /dev/null +++ b/molecule/opnsense_config/converge.yml @@ -0,0 +1,64 @@ +--- +- name: converge + hosts: all + become: true + vars: + system: + access: + users: + - username: simple_user + password: pass1234 + high_availability: + synchronize_interface: LAN + synchronize_config_to_ip: 224.0.0.240 + synchronize_peer_ip: 224.0.0.241 + disable_preempt: true + disconnect_dialup_interfaces: true + synchronize_states: true + remote_system_username: opnsense + remote_system_password: v3rys3cure + services_to_synchronize: + - aliases + - rules + - ipsec + settings: + general: + hostname: "firewall01" + domain: "test.local" + timezone: "Europe/Zurich" + logging: + preserve_logs: 10 + interfaces: + assignments: + - device: em0 + identifier: opt2 + description: VAGRANT + - device: em1 + identifier: lan + description: LAN + - device: em2 + identifier: wan + description: WAN + - device: em3 + identifier: opt1 + description: DMZ + firewall: + aliases: + - name: TestAliasTypeHost + type: host + statistics: false + description: Test Alias with type Host + content: 10.0.0.1 + - name: TestAliasTypeNetwork + type: network + statistics: false + description: Test Alias with type Network + content: 10.0.0.0/24 + rules: + - interface: lan + description: Block SSH on LAN + destination: + port: 22 + action: block + roles: + - role: puzzle.opnsense.opnsense_configure diff --git a/molecule/opnsense_config/molecule.yml b/molecule/opnsense_config/molecule.yml new file mode 100644 index 00000000..e8e1672a --- /dev/null +++ b/molecule/opnsense_config/molecule.yml @@ -0,0 +1,77 @@ +--- +scenario: + name: opnsense_config + test_sequence: + # - dependency not relevant unless we have requirements + - destroy + - syntax + - create + - converge + - idempotence + - verify + - destroy + +driver: + name: vagrant + parallel: true + +platforms: + - name: "22.7" + hostname: false + box: puzzle/opnsense + box_version: "22.7" + memory: 1024 + cpus: 2 + instance_raw_config_args: + - 'vm.guest = :freebsd' + - 'ssh.sudo_command = "%c"' + - 'ssh.shell = "/bin/sh"' + - name: "23.1" + box: puzzle/opnsense + hostname: false + box_version: "23.1" + memory: 1024 + cpus: 2 + instance_raw_config_args: + - 'vm.guest = :freebsd' + - 'ssh.sudo_command = "%c"' + - 'ssh.shell = "/bin/sh"' + - name: "23.7" + box: puzzle/opnsense + hostname: false + box_version: "23.7" + memory: 1024 + cpus: 2 + instance_raw_config_args: + - 'vm.guest = :freebsd' + - 'ssh.sudo_command = "%c"' + - 'ssh.shell = "/bin/sh"' + - name: "24.1" + box: puzzle/opnsense + hostname: false + box_version: "24.1" + memory: 1024 + cpus: 2 + instance_raw_config_args: + - 'vm.guest = :freebsd' + - 'ssh.sudo_command = "%c"' + - 'ssh.shell = "/bin/sh"' + - name: "24.7" + box: puzzle/opnsense + hostname: false + box_version: "24.7" + memory: 1024 + cpus: 2 + instance_raw_config_args: + - 'vm.guest = :freebsd' + - 'ssh.sudo_command = "%c"' + - 'ssh.shell = "/bin/sh"' + +provisioner: + name: ansible + env: + ANSIBLE_VERBOSITY: 3 +verifier: + name: ansible + options: + become: true diff --git a/molecule/opnsense_config/verify.yml b/molecule/opnsense_config/verify.yml new file mode 100644 index 00000000..b447dcf4 --- /dev/null +++ b/molecule/opnsense_config/verify.yml @@ -0,0 +1,6 @@ +--- +- name: Verify connectivity to server + hosts: all + tasks: + - name: Ping the server + ansible.builtin.ping: diff --git a/roles/opnsense_configure/README.md b/roles/opnsense_configure/README.md new file mode 100644 index 00000000..6ca4a040 --- /dev/null +++ b/roles/opnsense_configure/README.md @@ -0,0 +1,116 @@ +opnsense_configure - OPNsense configuration role +========= + +This role provides a generic approach to configure OPNsense instances by populating host variables +according to this roles defaults specification. + +Role Variables +-------------- + +The variables must be structured in a way that each puzzle.opnsense module has its own block. Each module related block +is then structured just like the corresponding module parameters as documented in the modules themselves. +The top level structure must be structured as follows: +```yaml +--- +system: + access: + users: [] # list of users, where the users follows the system_access_users module parameter structure + high_availability: + # system_high_availability_settings module parameters + settings: + general: + # system_settings_general module parameters + logging: + # system_settings_logging module parameters + +interfaces: + assignments: [] # list of interface assignments, where the users follows the interfaces_assignments module parameter structure + +firewall: + aliases: [] # list of aliases, where the users follows the firewall_alias module parameter structure + rules: [] # list of rules, where the users follows the firewall_rules module parameter structure +``` + + +Example Playbook +---------------- + +The usage of the role is straight forward, however the main thought should go into the building of the +host variables. An example execution could look like this: + +```yaml +--- +- name: converge + hosts: all + become: true + vars: + system: + access: + users: + - username: simple_user + password: pass1234 + high_availability: + synchronize_interface: LAN + synchronize_config_to_ip: 224.0.0.240 + synchronize_peer_ip: 224.0.0.241 + disable_preempt: true + disconnect_dialup_interfaces: true + synchronize_states: true + remote_system_username: opnsense + remote_system_password: v3rys3cure + services_to_synchronize: + - aliases + - rules + - ipsec + settings: + general: + hostname: "firewall01" + domain: "test.local" + timezone: "Europe/Zurich" + logging: + preserve_logs: 10 + interfaces: + assignments: + - device: em0 + identifier: opt2 + description: VAGRANT + - device: em1 + identifier: lan + description: LAN + - device: em2 + identifier: wan + description: WAN + - device: em3 + identifier: opt1 + description: DMZ + firewall: + aliases: + - name: TestAliasTypeHost + type: host + statistics: false + description: Test Alias with type Host + content: 10.0.0.1 + - name: TestAliasTypeNetwork + type: network + statistics: false + description: Test Alias with type Network + content: 10.0.0.0/24 + rules: + - interface: lan + description: Block SSH on LAN + destination: + port: 22 + action: block + roles: + - role: puzzle.opnsense.opnsense_configure + +``` + +License +------- + +GPLv3 + +Author Information +------------------ + - Fabio Bertagna (github.com/dongiovanni83) diff --git a/roles/opnsense_configure/defaults/main.yml b/roles/opnsense_configure/defaults/main.yml new file mode 100644 index 00000000..863caf37 --- /dev/null +++ b/roles/opnsense_configure/defaults/main.yml @@ -0,0 +1,45 @@ +--- +# defaults file for opnsense_configure + +# +# System variables should be provided in this structure +# +# system: +# access: +# users: [] # see system_access_users task args for user entry structure +# high_availability: +# disable_preempt: +# disconnect_dialup_interfaces: +# synchronize_states: +# synchronize_interface: +# sync_compatibility: +# synchronize_peer_ip: +# synchronize_config_to_ip: +# remote_system_username: +# remote_system_password: +# services_to_synchronize: +# settings: +# general: +# hostname: +# domain: +# timezone: +# logging: +# max_log_file_size_mb: +# preserve_logs: + +system: + settings: + access: +# Interface related variables: +# +# interfaces: +# assignments: [] +interfaces: + +# +# Firewall related variables should be provided in this structure +# +# firewall: +# aliases: [] +# rules: [] +firewall: \ No newline at end of file diff --git a/roles/opnsense_configure/meta/main.yml b/roles/opnsense_configure/meta/main.yml new file mode 100644 index 00000000..6dfb7b34 --- /dev/null +++ b/roles/opnsense_configure/meta/main.yml @@ -0,0 +1,9 @@ +galaxy_info: + author: Fabio Bertagna + company: Puzzle ITC + license: GPL-3.0-only + min_ansible_version: 2.1 + galaxy_tags: + - opnsense + +dependencies: [ ] diff --git a/roles/opnsense_configure/tasks/main.yml b/roles/opnsense_configure/tasks/main.yml new file mode 100644 index 00000000..274389c4 --- /dev/null +++ b/roles/opnsense_configure/tasks/main.yml @@ -0,0 +1,111 @@ +--- +# tasks file for opnsense_configure + +- name: Configure general system settings + puzzle.opnsense.system_settings_general: + hostname: "{{ system.settings.general.hostname | default(omit) }}" + domain: "{{ system.settings.general.domain | default(omit) }}" + timezone: "{{ system.settings.general.timezone | default(omit) }}" + when: + - system.settings.general is defined + - > + system.settings.general.hostname is defined or + system.settings.general.domain is defined or + system.settings.general.timezone is defined + +- name: Configure logging system settings + puzzle.opnsense.system_settings_logging: + max_log_file_size_mb: "{{ system.settings.logging.max_log_file_size_mb | default(omit) }}" + preserve_logs: "{{ system.settings.logging.preserve_logs | default(omit) }}" + when: + - system.settings.logging is defined + - > + system.settings.logging.max_log_file_size_mb is defined or + system.settings.logging.preserve_logs is defined + +- name: Configure users + puzzle.opnsense.system_access_users: + username: "{{ user.username }}" + password: "{{ user.password }}" + disabled: "{{ user.disabled | default(omit) }}" + full_name: "{{ user.full_name | default(omit) }}" + email: "{{ user.email | default(omit) }}" + comment: "{{ user.comment | default(omit) }}" + landing_page: "{{ user.landing_page | default(omit) }}" + shell: "{{ user.shell | default(omit) }}" + expires: "{{ user.expires | default(omit) }}" + otp_seed: "{{ user.otp_seed | default(omit) }}" + authorizedkeys: "{{ user.authorizedkeys | default(omit) }}" + groups: "{{ user.groups | default(omit) }}" + apikeys: "{{ user.apikeys | default(omit) }}" + scope: "{{ user.scope | default(omit) }}" + uid: "{{ user.uid | default(omit) }}" + state: "{{ user.state | default(omit) }}" + loop: "{{ system.access.users }}" + loop_control: + loop_var: user + label: "{{ user.username }}" + when: system.access.users is defined + +- name: Configure system HA settings + puzzle.opnsense.system_high_availability_settings: + disable_preempt: "{{ system.high_availability.disable_preempt | default(omit) }}" + disconnect_dialup_interfaces: "{{ system.high_availability.disconnect_dialup_interfaces | default(omit) }}" + synchronize_states: "{{ system.high_availability.synchronize_states | default(omit) }}" + synchronize_interface: "{{ system.high_availability.synchronize_interface }}" + sync_compatibility: "{{ system.high_availability.sync_compatibility | default(omit) }}" + synchronize_peer_ip: "{{ system.high_availability.synchronize_peer_ip | default(omit) }}" + synchronize_config_to_ip: "{{ system.high_availability.synchronize_config_to_ip | default(omit) }}" + remote_system_username: "{{ system.high_availability.remote_system_username | default(omit) }}" + remote_system_password: "{{ system.high_availability.remote_system_password | default(omit) }}" + services_to_synchronize: "{{ system.high_availability.services_to_synchronize | default(omit) }}" + when: system.high_availability is defined + +- name: Configure interface assignments + puzzle.opnsense.interfaces_assignments: + identifier: "{{ interface.identifier }}" + device: "{{ interface.device }}" + description: "{{ interface.description | default(omit) }}" + loop: "{{ interfaces.assignments }}" + loop_control: + loop_var: interface + when: interfaces.assignments is defined + +- name: Configure firewall aliases + puzzle.opnsense.firewall_alias: + name: "{{ alias.name }}" + type: "{{ alias.type }}" + enabled: "{{ alias.enabled | default(omit) }}" + content: "{{ alias.content | default(omit) }}" + description: "{{ alias.description | default(omit) }}" + interface: "{{ alias.interface | default(omit) }}" + protocol: "{{ alias.protocol | default(omit) }}" + refreshfrequency: + days: "{{ alias.refreshfrequency.days | default(omit) }}" + hours: "{{ alias.refreshfrequency.hours | default(omit) }}" + state: "{{ alias.state | default(omit) }}" + statistics: "{{ alias.statistics | default(omit) }}" + loop: "{{ firewall.aliases }}" + loop_control: + loop_var: alias + when: firewall.aliases is defined + +- name: Configure firewall rules + puzzle.opnsense.firewall_rules: + interface: "{{ rule.interface }}" + action: "{{ rule.action | default(omit) }}" + description: "{{ rule.description | default(omit) }}" + category: "{{ rule.category | default(omit) }}" + direction: "{{ rule.direction | default(omit) }}" + disabled: "{{ rule.disabled | default(omit) }}" + quick: "{{ rule.quick | default(omit) }}" + ipprotocol: "{{ rule.ipprotocol | default(omit) }}" + protocol: "{{ rule.protocol | default(omit) }}" + source: "{{ rule.source | default(omit) }}" + destination: "{{ rule.destination | default(omit) }}" + log: "{{ rule.log | default(omit) }}" + state: "{{ rule.state | default(omit) }}" + loop: "{{ firewall.rules }}" + loop_control: + loop_var: rule + when: firewall.rules is defined \ No newline at end of file