From f1720b0ea88931c780c05bf4396f29b12786cd33 Mon Sep 17 00:00:00 2001 From: Stefan Schwarz Date: Fri, 28 Apr 2023 19:26:57 +0200 Subject: [PATCH] changes form forked role: renamed var: semaphore_port -> semaphore_listenport introduced vars: semaphore_listenip semaphore_default_user_make_admin: true|false added vars for nginx reverse config (new vars) including dist-specfic vars: semaphore_nginx_deploy_reverseconfig semaphore_nginx_template semaphore_nginx_remove_default_config semaphore_nginx_config_filename semaphore_nginx_use_nginx_common_snippet semaphore_nginx_snippet_directory semaphore_nginx_tls_hardening_snippet semaphore_tls_hsts_enable: true|false semaphore_tls_hsts_time: integer semaphore_nginx_ssl_certificate semaphore_nginx_ssl_certificate_key semaphore_nginx_default_config semaphore_nginx_config_src_dir semaphore_nginx_config_dst_dir improved query if default user already exists (query specifically the semaphore_default_user not if any user exists) option to make default user admin (var: semaphore_default_user_make_admin) changed default config to builtin-default of semaphore (you can omit --config on commands this way): semaphore_config_path: "/etc/semaphore/semaphore.json" -> "/etc/semaphore/config.json" --- README.md | 16 ++++++++-- defaults/main.yml | 36 +++++++++++++++++++---- handlers/main.yml | 5 ++++ tasks/main.yml | 41 +++++++++++++++++++++++--- tasks/nginx.yml | 21 +++++++++++++ templates/nginx-semaphore.j2 | 57 ++++++++++++++++++++++++++++++++++++ vars/vars-Debian.yml | 5 ++++ vars/vars-RedHat.yml | 5 ++++ 8 files changed, 174 insertions(+), 12 deletions(-) create mode 100644 tasks/nginx.yml create mode 100644 templates/nginx-semaphore.j2 create mode 100644 vars/vars-Debian.yml create mode 100644 vars/vars-RedHat.yml diff --git a/README.md b/README.md index be4288b..4243498 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Ansible role to install and configure the [Ansible UI Semaphore](https://github. ## Requirements -None. But for a production environment you should install a webserver as proxy for ssl termination. +None. But for a production environment you should install a webserver as proxy for ssl termination (role is prepared for nginx). ## Example playbook @@ -31,7 +31,7 @@ None of the variables below are required. | Variable | Default | Comment | | :--- | :--- | :--- | -| `semaphore_version` | `v2.8.77` | the version to download, also see `semaphore_download_url` and `semaphore_download_checksum` | +| `semaphore_version` | latest available version | the version to download (example: 2.8.77), also see `semaphore_download_url` and `semaphore_download_checksum` | | `semaphore_mysql_install` | `true` | whether to install mysql on the host, installs with the password `mysql_root_password` | | `semaphore_mysql_create_db` | `true` | whether to create the mysql db and user | | `semaphore_mysql_host`:`semaphore_mysql_port` | `127.0.0.1`:`3306` | the mysql host | @@ -39,14 +39,22 @@ None of the variables below are required. | `semaphore_mysql_user` | semaphore | the mysql user | | `semaphore_mysql_password` | semaphore | the mysql user password | | `semaphore_user` | semaphore | the user and systemd identifier semaphore runs as | -| `semaphore_port` | `3000` | the port semaphore binds to | +| `semaphore_listenip` | `127.0.0.1` | the IP semaphore binds to | +| `semaphore_listenport` | `3000` | the port semaphore binds to | | `semaphore_path` | /opt/semaphore | destination for the binary | | `semaphore_addn_config` | `{}` | for all options see the [source](https://github.com/ansible-semaphore/semaphore/blob/master/util/config.go#L36-L72) | | `semaphore_config_path` | /etc/semaphore/semaphore.json | config file | | `semaphore_default_user` | admin | login name of the default user | +| `semaphore_default_user_make_admin` | true | make default user admin | | `semaphore_default_user_name` | `semaphore_default_user` | his human readable name | | `semaphore_default_user_password` | admin | the password | | `semaphore_default_user_mail` | admin@example.com | and mail adress | +| `semaphore_default_user_password` | `admin` | change to a secure value! | +| :--- | :--- | :--- | +| `semaphore_nginx_deploy_reverseconfig` | false | set to true to enable nginx | +| `semaphore_nginx_config_filename` | `semaphore` | filename of nginx vhost-config | +| `semaphore_nginx_ssl_certificate` | `/etc/letsencrypt/live/{{ semaphore_hostname }}/fullchain.pem` | path to tls certificate | +| `semaphore_nginx_ssl_certificate_key` | `/etc/letsencrypt/live/{{ semaphore_hostname }}/privkey.pem` | path to tls key | For all options see [defaults/main.yml](defaults/main.yml) @@ -61,3 +69,5 @@ Molecule is used for testing, the webinterface of the centos machine will be exp ## License MIT + +Role forked from https://github.com/morbidick/ansible-role-semaphore diff --git a/defaults/main.yml b/defaults/main.yml index 430af49..ecd85bd 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -16,19 +16,25 @@ semaphore_mysql_allowed_host: "localhost" # the default user that should be created semaphore_default_user: "admin" +# create default user with admin priviledges +# (otherwise you need to execute "/opt/semaphore/semaphore "): +semaphore_default_user_make_admin: true semaphore_default_user_name: "{{ semaphore_default_user }}" semaphore_default_user_password: "admin" semaphore_default_user_mail: "admin@example.com" # sempahore binary source -semaphore_version: "2.8.77" +# if not set anywhere, defaults to latest version: +# semaphore_version: "2.8.77" + semaphore_download_url: "https://github.com/ansible-semaphore/semaphore/releases/download/v{{ semaphore_version }}/semaphore_{{ semaphore_version }}_linux_amd64.tar.gz" -semaphore_download_checksum: "sha256:a67a6ef4a49bf7613e87628bf35db7d239050a99d7123a278c6cc85b2bff7997" +# if not set dynamically determined: +# semaphore_download_checksum: "sha256:a67a6ef4a49bf7613e87628bf35db7d239050a99d7123a278c6cc85b2bff7997" # binary paths and service identifiers semaphore_user: "semaphore" semaphore_path: "/opt/semaphore" -semaphore_config_path: "/etc/semaphore/semaphore.json" +semaphore_config_path: "/etc/semaphore/config.json" semaphore_tmp_path: "{{ semaphore_path }}/tmp" semaphore_executable: "{{ semaphore_path }}/semaphore" semaphore_command: "{{ semaphore_executable }} --config {{ semaphore_config_path | quote }}" @@ -37,7 +43,8 @@ semaphore_identifier: "{{ semaphore_user }}" semaphore_systemd_unit_path: "/etc/systemd/system/{{ semaphore_identifier }}.service" # semaphore config options -semaphore_port: 3000 +semaphore_listenip: "127.0.0.1" +semaphore_listenport: 3000 semaphore_addn_config: {} # mysql command lines to create default user @@ -60,5 +67,24 @@ semaphore_config_object: user: "{{ semaphore_mysql_user }}" pass: "{{ semaphore_mysql_password }}" name: "{{ semaphore_mysql_db }}" - port: ":{{ semaphore_port }}" + port: ":{{ semaphore_listenport }}" tmp_path: "{{ semaphore_tmp_path }}" + +# use nginx to make a ssl reverse-proxy-config: +semaphore_nginx_deploy_reverseconfig: true +semaphore_nginx_template: "nginx-semaphore.j2" +semaphore_nginx_remove_default_config: false +semaphore_nginx_config_filename: semaphore + +# if you use https://github.com/selfhostx/ansible/tree/main/roles/nginx_common than set to "true" here: +semaphore_nginx_use_nginx_common_snippet: false +semaphore_nginx_snippet_directory: "{{ nginx_snippet_directory | default('/etc/nginx/snippets') }}" +semaphore_nginx_tls_hardening_snippet: "{{ semaphore_nginx_snippet_directory }}/tls-hardening.conf" + +# enable HSTS (not used when semaphore_nginx_use_nginx_common_snippet: true): +semaphore_tls_hsts_enable: true +semaphore_tls_hsts_time: 31536000 + +# path to ssl certificates, defaults to letsencrypt paths: +semaphore_nginx_ssl_certificate: "/etc/letsencrypt/live/{{ semaphore_hostname }}/fullchain.pem" +semaphore_nginx_ssl_certificate_key: "/etc/letsencrypt/live/{{ semaphore_hostname }}/privkey.pem" diff --git a/handlers/main.yml b/handlers/main.yml index f8a91d2..c5aa4ae 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -4,3 +4,8 @@ name: "{{ semaphore_identifier }}" daemon_reload: true state: restarted + +- name: Restart nginx + ansible.builtin.service: + name: nginx + state: restarted diff --git a/tasks/main.yml b/tasks/main.yml index 4b47f26..2a3e32e 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,8 +1,17 @@ --- +- name: Include OS-specific variables + ansible.builtin.include_vars: "vars-{{ ansible_os_family }}.yml" + tags: + - always + - name: Ensure MySQL Server ansible.builtin.import_tasks: setup-mysql.yml when: semaphore_mysql_install or semaphore_mysql_create_db +- name: Nginx Config + ansible.builtin.import_tasks: nginx.yml + when: semaphore_nginx_deploy_reverseconfig + - name: Install dependencies ansible.builtin.package: name: "{{ item }}" @@ -22,6 +31,27 @@ name: "{{ semaphore_user }}" createhome: false +- name: Get Semaphore Release Info + ansible.builtin.uri: + url: 'https://api.github.com/repos/ansible-semaphore/semaphore/releases/latest' + return_content: true + register: 'semaphore_version_github' + +- name: Determine current Semaphore version + ansible.builtin.set_fact: + semaphore_version: "{{ semaphore_version_github['json']['tag_name'][1:] }}" + +# FIXME implement lookup of checksum from semaphore_checksums_github.content +#- name: Get checksum file of current Semaphore version +# ansible.builtin.uri: +# url: "https://github.com/ansible-semaphore/semaphore/releases/download/v{{ semaphore_version }}/semaphore_{{ semaphore_version }}_checksums.txt" +# return_content: true +# register: 'semaphore_checksums_github' + +#- name: Set checksum from file +# ansible.builtin.set_fact: +# semaphore_download_checksum: "{{ semaphore_checksums_github['json']['tag_name'][2:] }}" + - name: Create application directories ansible.builtin.file: path: "{{ item }}" @@ -94,15 +124,18 @@ notify: - Semaphore restart -- name: Check users - ansible.builtin.command: "{{ semaphore_command }} user list" +- name: Check if default user already exists (fails if not) + ansible.builtin.command: "{{ semaphore_command }} user get --login {{ semaphore_default_user | quote }}" register: semaphore_users + ignore_errors: true changed_when: false - name: Create default user - ansible.builtin.command: "{{ semaphore_command }} user add --name {{ semaphore_default_user_name | quote }} --login {{ semaphore_default_user | quote }} --email {{ semaphore_default_user_mail | quote }} --password {{ semaphore_default_user_password | quote }}" - when: semaphore_users.stdout == "" + ansible.builtin.command: "{{ semaphore_command }} user add {% if semaphore_default_user_make_admin %}--admin{% endif %} --name {{ semaphore_default_user_name | quote }} --login {{ semaphore_default_user | quote }} --email {{ semaphore_default_user_mail | quote }} --password {{ semaphore_default_user_password | quote }}" + # when: semaphore_users.stdout == "" + when: semaphore_users.rc != 0 changed_when: true + no_log: true - name: Deploy systemd service file ansible.builtin.template: diff --git a/tasks/nginx.yml b/tasks/nginx.yml new file mode 100644 index 0000000..1ae71b2 --- /dev/null +++ b/tasks/nginx.yml @@ -0,0 +1,21 @@ +--- +- name: Remove default config (when enabled) + ansible.builtin.file: + path: "{{ semaphore_nginx_default_config }}" + state: absent + notify: Restart nginx + when: semaphore_nginx_remove_default_config + +- name: Ensure nginx config is present + ansible.builtin.template: + dest: "{{ semaphore_nginx_config_src_dir }}/{{ semaphore_nginx_config_filename }}" + mode: "0644" + src: "{{ semaphore_nginx_template }}" + notify: Restart nginx + +- name: Enable nginx config via symbolic link + ansible.builtin.file: + src: "{{ semaphore_nginx_config_src_dir }}/{{ semaphore_nginx_config_filename }}" + dest: "{{ semaphore_nginx_config_dst_dir }}/{{ semaphore_nginx_config_filename }}" + state: link + notify: Restart nginx diff --git a/templates/nginx-semaphore.j2 b/templates/nginx-semaphore.j2 new file mode 100644 index 0000000..d1a2146 --- /dev/null +++ b/templates/nginx-semaphore.j2 @@ -0,0 +1,57 @@ +server { + listen 80 {% if semaphore_nginx_remove_default_config %}default_server{% endif %}; + listen [::]:80 {% if semaphore_nginx_remove_default_config %}default_server{% endif %}; + server_name {{ semaphore_hostname }}; + + location / { + # remember: 301 = moved permanently (search engines will react), 302 = moved temporarily (search engines will hold back) + return 301 https://$server_name$request_uri; + } +} + +server { + listen 443 ssl http2 {% if semaphore_nginx_remove_default_config %}default_server{% endif %}; + listen [::]:443 ssl http2 {% if semaphore_nginx_remove_default_config %}default_server{% endif %}; + server_name {{ semaphore_hostname }}; + + client_max_body_size 0; + chunked_transfer_encoding on; + +{% if semaphore_nginx_use_nginx_common_snippet %} + include {{ semaphore_nginx_tls_hardening_snippet }}; +{% else %} + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers CHACHA20:-AES:AESGCM:AESCCM:!kRSA:!PSK:!aECDSA:!aDSS:!aNULL:!eNULL:!SHA1:!MD5; + ssl_ecdh_curve X25519:secp521r1:secp384r1; +{% if semaphore_tls_hsts_enable %} + add_header Strict-Transport-Security "max-age={{ semaphore_tls_hsts_time }}"; +{% endif %} +{% endif %} + + ssl_certificate {{ semaphore_nginx_ssl_certificate }}; + ssl_certificate_key {{ semaphore_nginx_ssl_certificate_key }}; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_pass http://{{ semaphore_listenip }}:{{ semaphore_listenport }}; + proxy_read_timeout 30; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /api/ws { + proxy_pass http://{{ semaphore_listenip }}:{{ semaphore_listenport }}/api/ws; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Origin ""; + } +} diff --git a/vars/vars-Debian.yml b/vars/vars-Debian.yml new file mode 100644 index 0000000..5890456 --- /dev/null +++ b/vars/vars-Debian.yml @@ -0,0 +1,5 @@ +--- +# vars specific for Debian systems +semaphore_nginx_default_config: "/etc/nginx/sites-enabled/default" +semaphore_nginx_config_src_dir: "/etc/nginx/sites-available" +semaphore_nginx_config_dst_dir: "/etc/nginx/sites-enabled" diff --git a/vars/vars-RedHat.yml b/vars/vars-RedHat.yml new file mode 100644 index 0000000..b1f6cfb --- /dev/null +++ b/vars/vars-RedHat.yml @@ -0,0 +1,5 @@ +--- +# vars specific for RedHat systems +semaphore_nginx_default_config: "/etc/nginx/conf.d/default.conf" +# semaphore_nginx_config_src_dir: "" +semaphore_nginx_config_dst_dir: "/etc/nginx/conf.d"