diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e5c2748f..94d7abfce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* **role:icingaweb2_module_grafana**: Add JWT support +* **role:grafana**: Add JWT support * Add `playbooks/README.md` documenting all playbooks with their roles in execution order and available skip variables * **role:apache_httpd**: Add platform-specific behavior section, wsgi example, and document localhost endpoints in README * **role:apache_httpd**: Add skip variables section to README linking to relevant playbooks diff --git a/roles/grafana/README.md b/roles/grafana/README.md index 7dfbf1d3f..0e4f0e3cb 100644 --- a/roles/grafana/README.md +++ b/roles/grafana/README.md @@ -47,6 +47,9 @@ grafana__root_url: 'https://monitoring.example.com/grafana' | `grafana__auth_anonymous_enabled` | Whether to allow anonymous (passwordless) access or not. Possible options: `true` or `false` | `false` | | `grafana__auth_anonymous_org_name` | The organization name that should be used for unauthenticated users. | `'Main Org.'` | | `grafana__auth_anonymous_org_role` | The role for unauthenticated users. | `'Viewer'` | +| `grafana__auth_jwt` | Enable JWT-based authentication for Grafana requests. | `false` | +| `grafana__auth_jwt__priv_key_file` | Path to the private key file used to verify JWT signatures for Grafana authentication. | `'/etc/grafana/jwt.key.priv'` | +| `grafana__auth_jwt__pub_key_file` | Path to the public key file used to verify JWT signatures for Grafana authentication. | `'/etc/grafana/jwt.key.pub'` | | `grafana__bitwarden_collection_id` | Will be used to store the token of the created service accounts to this Bitwarden Collection. Can be obtained from the URL in Bitwarden WebGUI. | `'{{ lfops__bitwarden_collection_id | default() }}'` | | `grafana__bitwarden_organization_id` | Will be used to store the token of the created service accounts to this Bitwarden Organization. Can be obtained from the URL in Bitwarden WebGUI. | `'{{ lfops__bitwarden_organization_id | default() }}'` | | `grafana__cookie_samesite` | The [SameSite cookie attribute](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite). Possible options:
* disabled
* lax
* none
* strict | `'lax'` | @@ -71,6 +74,9 @@ grafana__api_url: 'https://grafana01.example.com/grafana' grafana__auth_anonymous_enabled: false grafana__auth_anonymous_org_name: 'Main Org.' grafana__auth_anonymous_org_role: 'Viewer' +grafana__auth_jwt: false +grafana__auth_jwt__priv_key_file: '/etc/grafana/jwt.key.priv' +grafana__auth_jwt__pub_key_file: '/etc/grafana/jwt.key.pub' grafana__cookie_samesite: 'lax' grafana__https_config: cert_file: '/etc/ssl/ssl-certificate.crt' diff --git a/roles/grafana/defaults/main.yml b/roles/grafana/defaults/main.yml index 65b29ff89..2afeaf56e 100644 --- a/roles/grafana/defaults/main.yml +++ b/roles/grafana/defaults/main.yml @@ -3,6 +3,9 @@ grafana__api_url: '{{ grafana__root_url }}' grafana__auth_anonymous_enabled: false grafana__auth_anonymous_org_name: 'Main Org.' grafana__auth_anonymous_org_role: 'Viewer' +grafana__auth_jwt: false +grafana__auth_jwt__priv_key_file: '/etc/grafana/jwt.key.priv' +grafana__auth_jwt__pub_key_file: '/etc/grafana/jwt.key.pub' grafana__bitwarden_collection_id: '{{ lfops__bitwarden_collection_id | default() }}' grafana__bitwarden_organization_id: '{{ lfops__bitwarden_organization_id | default() }}' grafana__cookie_samesite: 'lax' @@ -58,3 +61,4 @@ grafana__serve_from_sub_path: false grafana__service_enabled: true grafana__skip_token_to_bitwarden: false grafana__validate_certs: true + diff --git a/roles/grafana/tasks/main.yml b/roles/grafana/tasks/main.yml index 504266adf..89182c727 100644 --- a/roles/grafana/tasks/main.yml +++ b/roles/grafana/tasks/main.yml @@ -26,6 +26,25 @@ when: - 'grafana__https_config is defined and grafana__https_config | length > 0' + - name: 'generate JWT RSA private key' + community.crypto.openssl_privatekey: + path: '{{ grafana__auth_jwt__priv_key_file }}' + size: 2048 + type: 'RSA' + owner: 'root' + group: 'grafana' + mode: 0o644 + when: 'grafana__auth_jwt | bool' + + - name: 'generate JWT RSA public key' + community.crypto.openssl_publickey: + path: '{{ grafana__auth_jwt__pub_key_file }}' + privatekey_path: '{{ grafana__auth_jwt__priv_key_file }}' + owner: 'root' + group: 'grafana' + mode: 0o644 + when: 'grafana__auth_jwt | bool' + - name: 'deploy /etc/grafana/grafana.ini' ansible.builtin.template: backup: true diff --git a/roles/grafana/templates/etc/grafana/grafana.ini.j2 b/roles/grafana/templates/etc/grafana/grafana.ini.j2 index 63d7e99ea..2d186872e 100644 --- a/roles/grafana/templates/etc/grafana/grafana.ini.j2 +++ b/roles/grafana/templates/etc/grafana/grafana.ini.j2 @@ -1,5 +1,5 @@ # {{ ansible_managed }} -# 2024031901 +# 2026032701 ##################### Grafana Configuration Example ##################### # @@ -606,16 +606,18 @@ hide_version = true #################################### Auth JWT ########################## [auth.jwt] -;enabled = true -;header_name = X-JWT-Assertion -;email_claim = sub -;username_claim = sub +enabled = {{ grafana__auth_jwt | lower }} +header_name = X-JWT-Assertion +email_claim = sub +username_claim = sub ;jwk_set_url = https://foo.bar/.well-known/jwks.json ;jwk_set_file = /path/to/jwks.json ;cache_ttl = 60m ;expected_claims = {"aud": ["foo", "bar"]} -;key_file = /path/to/key/file -;auto_sign_up = false +key_file = {{ grafana__auth_jwt__pub_key_file }} +auto_sign_up = false +url_login = true +skip_org_role_sync = true #################################### Auth LDAP ########################## [auth.ldap] diff --git a/roles/icingaweb2_module_grafana/README.md b/roles/icingaweb2_module_grafana/README.md index 3d732f8a6..d0cbe9e95 100644 --- a/roles/icingaweb2_module_grafana/README.md +++ b/roles/icingaweb2_module_grafana/README.md @@ -5,7 +5,7 @@ Additionally, it deploys the the graph configuration for the [Linuxfabrik Monito This role is tested with the following IcingaWeb2 Grafana Module versions: -* 3.0.1 +* 3.1.3 ## Mandatory Requirements @@ -36,7 +36,7 @@ Example: ```yaml # mandatory icingaweb2_module_grafana__monitoring_plugins_version: '1.2.0.11' -icingaweb2_module_grafana__version: 'v3.1.1' +icingaweb2_module_grafana__version: 'v3.1.3' ``` @@ -44,6 +44,8 @@ icingaweb2_module_grafana__version: 'v3.1.1' | Variable | Description | Default Value | | -------- | ----------- | ------------- | +| `icingaweb2_module_grafana__auth_jwt` | Enable JWT-based authentication for Grafana requests | `'{{ grafana__auth_jwt }}'` | +| `icingaweb2_module_grafana__auth_jwt__priv_key_file` | Path to the private key file used for JWT-based Grafana authentication | `'{{ grafana__auth_jwt__priv_key_file }}'` | | `icingaweb2_module_grafana__custom_graphs_config` | Multiline string. Custom configuration for the Grafana Graphs, will be deployed to `/etc/icingweb2/modules/grafana/graphs.ini` along with the configuration for the [Linuxfabrik Monitoring Plugins](https://github.com/Linuxfabrik/monitoring-plugins) | `''` | | `icingaweb2_module_grafana__default_dashboard` | Name of the default Grafana dashboard | `'Default'` | | `icingaweb2_module_grafana__skip_monitoring_plugins_graphs_config` | Skip the deployment of the graph configuration for [Linuxfabrik Monitoring Plugins](https://github.com/Linuxfabrik/monitoring-plugins). | `false` | @@ -53,6 +55,8 @@ icingaweb2_module_grafana__version: 'v3.1.1' Example: ```yaml # optional +icingaweb2_module_grafana__auth_jwt: false +icingaweb2_module_grafana__auth_jwt__priv_key_file: '/etc/grafana/jwt.key.priv' icingaweb2_module_grafana__custom_graphs_config: |- [icingacli-x509] dashboard = "Default" diff --git a/roles/icingaweb2_module_grafana/defaults/main.yml b/roles/icingaweb2_module_grafana/defaults/main.yml index 5fa9b480b..ee7716b28 100644 --- a/roles/icingaweb2_module_grafana/defaults/main.yml +++ b/roles/icingaweb2_module_grafana/defaults/main.yml @@ -1,3 +1,5 @@ +icingaweb2_module_grafana__auth_jwt: '{{ grafana__auth_jwt }}' +icingaweb2_module_grafana__auth_jwt__priv_key_file: '{{ grafana__auth_jwt__priv_key_file }}' icingaweb2_module_grafana__custom_graphs_config: '' icingaweb2_module_grafana__default_dashboard: 'Default' icingaweb2_module_grafana__monitoring_plugins_version: '{{ lfops__monitoring_plugins_version | default() }}' diff --git a/roles/icingaweb2_module_grafana/tasks/main.yml b/roles/icingaweb2_module_grafana/tasks/main.yml index 7b2658088..49c0a0773 100644 --- a/roles/icingaweb2_module_grafana/tasks/main.yml +++ b/roles/icingaweb2_module_grafana/tasks/main.yml @@ -88,6 +88,16 @@ group: 'icingaweb2' mode: 0o660 + - name: 'copy {{ icingaweb2_module_grafana__auth_jwt__priv_key_file }} to /etc/icingaweb2/modules/grafana/jwt.key.priv' + ansible.builtin.copy: + src: '{{ icingaweb2_module_grafana__auth_jwt__priv_key_file }}' + dest: '/etc/icingaweb2/modules/grafana/jwt.key.priv' + remote_src: true + owner: 'apache' + group: 'icingaweb2' + mode: 0o644 + when: 'icingaweb2_module_grafana__auth_jwt' + tags: - 'icingaweb2_module_grafana' - 'icingaweb2_module_grafana:configure' diff --git a/roles/icingaweb2_module_grafana/templates/etc/icingaweb2/modules/grafana/config.ini.j2 b/roles/icingaweb2_module_grafana/templates/etc/icingaweb2/modules/grafana/config.ini.j2 index 0b87a5816..f45a44f16 100644 --- a/roles/icingaweb2_module_grafana/templates/etc/icingaweb2/modules/grafana/config.ini.j2 +++ b/roles/icingaweb2_module_grafana/templates/etc/icingaweb2/modules/grafana/config.ini.j2 @@ -1,5 +1,5 @@ ; {{ ansible_managed }} -; 2023050802 +; 2026032601 [grafana] accessmode = "iframe" @@ -9,9 +9,18 @@ defaultdashboard = "{{ icingaweb2_module_grafana__default_dashboard }}" defaultdashboardpanelid = "1" defaultdashboarduid = "default" defaultorgid = "1" -host = "{{ (icingaweb2_module_grafana__url | split('://'))[1] }}" -protocol = "{{ (icingaweb2_module_grafana__url | split('://'))[0] }}" +host = "{{ icingaweb2_module_grafana__url | ansible.builtin.urlsplit('hostname') }}{{ icingaweb2_module_grafana__url | ansible.builtin.urlsplit('path') }}" +protocol = "{{ icingaweb2_module_grafana__url | ansible.builtin.urlsplit('scheme') }}" shadows = "0" theme = "{{ icingaweb2_module_grafana__theme }}" timerange = "2d" timerangeAll = "1w/w" +ssl_verifypeer = "0" +ssl_verifyhost = "0" +dashboardlink = "0" +{% if icingaweb2_module_grafana__auth_jwt %} +jwtEnable = "1" +jwtUser = "grafana-admin" +jwtIssuer = "{{ icingaweb2_module_grafana__url }}" +jwtExpires = "30" +{% endif %}