diff --git a/defaults/main.yml b/defaults/main.yml index ea55373d..6b32705c 100755 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -234,3 +234,5 @@ openwisp2_radius_periodic_tasks: true openwisp2_usage_metric_collection_periodic_tasks: true # point {{ inventory_name }} to localhost in /etc/hosts openwisp2_inventory_hostname_localhost: true +# installs and configure openvpn to provide a Management IP to all devices +openwisp2_openvpn_setup: true \ No newline at end of file diff --git a/meta/runtime.yml b/meta/runtime.yml new file mode 100644 index 00000000..96d46335 --- /dev/null +++ b/meta/runtime.yml @@ -0,0 +1,4 @@ +--- +requires_ansible: ">=2.13" +collections: + - community.general diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml index 05304a25..f813b79a 100644 --- a/molecule/default/molecule.yml +++ b/molecule/default/molecule.yml @@ -28,7 +28,7 @@ provisioner: verify: ../resources/verify.yml config_options: defaults: - stdout_callback: yaml + result_format: yaml bin_ansible_callbacks: true inventory: host_vars: diff --git a/molecule/local/molecule.yml b/molecule/local/molecule.yml index 4ff9991d..13b13bba 100644 --- a/molecule/local/molecule.yml +++ b/molecule/local/molecule.yml @@ -4,6 +4,7 @@ dependency: name: galaxy options: role-file: molecule/resources/requirements.yml + requirements-file: molecule/resources/collections.yml driver: name: docker lint: | @@ -60,7 +61,7 @@ provisioner: verify: ../resources/verify.yml config_options: defaults: - stdout_callback: yaml + result_format: yaml bin_ansible_callbacks: true verifier: name: ansible diff --git a/molecule/resources/collections.yml b/molecule/resources/collections.yml new file mode 100644 index 00000000..62f9bcc5 --- /dev/null +++ b/molecule/resources/collections.yml @@ -0,0 +1,4 @@ +--- +collections: + - name: community.general + version: ">=10.5.0" \ No newline at end of file diff --git a/tasks/django.yml b/tasks/django.yml index fd45abf1..29d8c33b 100644 --- a/tasks/django.yml +++ b/tasks/django.yml @@ -228,3 +228,9 @@ or "changed" in load_initial_data_result.stdout args: chdir: "{{ openwisp2_path }}" + +- name: Get openwisp2_vpn_id and openwisp2_vpn_name + set_fact: + openwisp2_vpn_id: "{{ load_initial_data_result.stdout | regex_search('openwisp2_vpn_id\\s+=\\s+([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})','\\1') | first }}" + openwisp2_vpn_name: "{{ load_initial_data_result.stdout | regex_search('openwisp2_vpn_name\\s+=\\s+(.*)','\\1') | first }}" + when: '"created" in load_initial_data_result.stdout and openwisp2_openvpn_setup' diff --git a/tasks/main.yml b/tasks/main.yml index c69d055d..7b685a90 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -40,5 +40,8 @@ - import_tasks: cron.yml tags: [openwisp2, cron] +- import_tasks: openvpn.yml + tags: [openwisp2, openvpn] + - import_tasks: complete.yml tags: [openwisp2] diff --git a/tasks/openvpn.yml b/tasks/openvpn.yml new file mode 100644 index 00000000..9bb20b11 --- /dev/null +++ b/tasks/openvpn.yml @@ -0,0 +1,72 @@ +--- +# Making sure bare necessary services are running +- name: Update supervisor configuration + command: supervisorctl update openwisp2 + register: supervisord_update_openwisp2 + when: openwisp2_openvpn_setup and openwisp2_vpn_id is defined + +- name: Restart openwisp2 uwsgi server + command: "supervisorctl restart openwisp2" + when: openwisp2_openvpn_setup and openwisp2_vpn_id is defined + +- name: Ensure nginx is started + service: + name: nginx + state: started + when: openwisp2_nginx_install + +- name: Install OpenVPN if needed + apt: + name: + - openvpn + state: present + update_cache: true + when: openwisp2_openvpn_setup and openwisp2_vpn_id is defined + # Note: openwisp2_vpn_id will be defined only in the ansible run that creates it. + +- name: Configure logrotate for OpenVPN + template: + src: logrotate.d/openvpn.j2 + dest: /etc/logrotate.d/openvpn + mode: '0644' + when: openwisp2_openvpn_setup and openwisp2_vpn_id is defined + +- name: Retrieve Access Token with default credentials + ansible.builtin.uri: + url: "https://{{ inventory_hostname }}/api/v1/users/token/" + method: POST + status_code: 200 + headers: + Content-Type: application/json + body_format: json + body: + username: "admin" + password: "admin" + validate_certs: false + follow_redirects: all + when: openwisp2_openvpn_setup and openwisp2_vpn_id is defined + register: login_task_default_result + +- name: Retrieve OpenVPN Server configuration + ansible.builtin.get_url: + url: "https://{{ inventory_hostname }}/api/v1/controller/vpn/{{ openwisp2_vpn_id }}/configuration/" + dest: "/etc/openvpn/{{ openwisp2_vpn_name }}-config.tar.gz" + validate_certs: false + headers: + Content-Type: application/json + Authorization: Bearer {{ login_task_default_result.json.token }} + when: openwisp2_openvpn_setup and openwisp2_vpn_id is defined and openwisp2_vpn_name is defined + +- name: Unarchive OpenVPN Server configuration + unarchive: + src: "/etc/openvpn/{{ openwisp2_vpn_name }}-config.tar.gz" + dest: /etc/openvpn/server/ + remote_src: yes + when: openwisp2_openvpn_setup and openwisp2_vpn_id is defined and openwisp2_vpn_name is defined + +- name: Enable and start OpenVPN service + systemd: + name: "openvpn-server@{{ openwisp2_vpn_name }}" + enabled: true + state: started + when: openwisp2_openvpn_setup and openwisp2_vpn_name is defined \ No newline at end of file diff --git a/templates/load_initial_data.py b/templates/load_initial_data.py index 6428d267..3d42b6d4 100644 --- a/templates/load_initial_data.py +++ b/templates/load_initial_data.py @@ -20,6 +20,9 @@ Credentials = load_model("connection", "Credentials") Template = load_model("config", "Template") +Ca = load_model("pki", "Ca") +Vpn = load_model("config", "Vpn") + User = get_user_model() changed = False @@ -38,8 +41,8 @@ print("default site updated") # Get SSH key pair -ssh_private_key = os.environ.get("PRIVATE_KEY") -ssh_pub_key = os.environ.get("PUBLIC_KEY") +ssh_private_key = os.environ.get("PRIVATE_KEY", "") +ssh_pub_key = os.environ.get("PUBLIC_KEY", "") # Create a default credentials object if ssh_private_key and Credentials.objects.count() == 0: @@ -81,3 +84,118 @@ config_file["contents"] += "\n" + ssh_pub_key template_obj.save() print(f"changed {template_obj.name} to add default SSH credential") + + +# Create Ca +if len(Ca.objects.all()) == 0: + ca_instance = Ca.objects.create( + name="{{ inventory_hostname }} CA", + ) + print("created {{ inventory_hostname }} Certificate Authority") +else: + # If Ca already exists, get the first one + ca_instance = Ca.objects.first() + +# Create Vpn +if len(Vpn.objects.all()) == 0: + vpn_instance = Vpn.objects.create( + name="{{ inventory_hostname }}-vpn", + host="{{ inventory_hostname }}", + backend="openwisp_controller.vpn_backends.OpenVpn", + ca=ca_instance, + config={ + "openvpn": [{ + "server": "10.42.0.0 255.255.255.0", + "name": "{{ inventory_hostname }}-vpn", + "mode": "server", + 'proto': 'udp', + "port": 1194, + "dev_type": "tun", + "dev": "tun0", + "local": "", + "comp_lzo": "adaptive", + "auth": "SHA1", + "data_ciphers": [ + { + "cipher": "AES-256-GCM", + "optional": False + }, + { + "cipher": "AES-128-GCM", + "optional": False + } + ], + "data_ciphers_fallback": "AES-256-GCM", + "cipher": "AES-256-GCM", + "engine": "", + "ca": "ca.pem", + "cert": "cert.pem", + "key": "key.pem", + "pkcs12": "", + "tls_auth": "", + "ns_cert_type": "", + "mtu_disc": "no", + "mtu_test": False, + "fragment": 0, + "mssfix": 1450, + "keepalive": "", + "persist_tun": False, + "persist_key": False, + "tun_ipv6": False, + "up": "", + "up_delay": 0, + "down": "", + "script_security": 1, + "user": "", + "group": "", + "mute": 0, + "status": "", + "status_version": 1, + "mute_replay_warnings": False, + "secret": "", + "reneg_sec": 3600, + "tls_timeout": 2, + "tls_cipher": "", + "remote_cert_tls": "", + "float": False, + "auth_nocache": False, + "fast_io": False, + "log": "", + "verb": 1, + "topology": "subnet", + "tls_server": True, + "dh": "dh.pem", + "crl_verify": "", + "duplicate_cn": False, + "client_to_client": False, + "client_cert_not_required": False, + "username_as_common_name": False, + "auth_user_pass_verify": "" + }], + "files": [ + { + "path": "ca.pem", + "mode": "0644", + "contents": "{{ '{{ ca }}' }}" + }, + { + "path": "cert.pem", + "mode": "0644", + "contents": "{{ '{{ cert }}' }}" + }, + { + "path": "key.pem", + "mode": "0644", + "contents": "{{ '{{ key }}' }}" + }, + { + "path": "dh.pem", + "mode": "0644", + "contents": "{{ '{{ dh }}' }}" + } + ] + } + ) + print(f"created {{ inventory_hostname }} Vpn Server") + print(f"openwisp2_vpn_name = {vpn_instance.name}") + print(f"openwisp2_vpn_id = {vpn_instance.id}") \ No newline at end of file diff --git a/templates/logrotate.d/openvpn.j2 b/templates/logrotate.d/openvpn.j2 new file mode 100644 index 00000000..2ff15554 --- /dev/null +++ b/templates/logrotate.d/openvpn.j2 @@ -0,0 +1,9 @@ +/var/log/openvpn/*.log { + daily + missingok + rotate 30 + compress + delaycompress + notifempty + copytruncate + }