Compare commits

..

8 Commits

Author SHA1 Message Date
uumas
39b35b30a9 grafana: Support additional networks 2025-07-13 19:09:44 +03:00
uumas
6baab11851 service: Support proxy forward auth using OAuth2 Proxy 2025-07-13 19:09:08 +03:00
uumas
543a34f60d Add oauth2_proxy role 2025-07-13 19:08:10 +03:00
uumas
4e4f824958 service: Support additional networks for database 2025-07-13 19:05:37 +03:00
uumas
a8a7dfc688 container: Restart container when secret changed 2025-07-11 20:44:39 +03:00
uumas
f52ba4eced service: Allow socat without reverse proxy config 2025-07-11 20:41:04 +03:00
uumas
303d3a384a Add grafana role 2025-07-10 00:56:23 +03:00
uumas
d6083ec2be image: Add readme 2025-07-10 00:54:18 +03:00
20 changed files with 279 additions and 37 deletions

View File

@@ -52,4 +52,4 @@
state: quadlet
quadlet_file_mode: "0600"
quadlet_options: "{{ _container_quadlet_options }}"
notify: "Restart container service {{ container_name }}"
notify: Restart container service {{ container_name }}

View File

@@ -6,3 +6,4 @@
skip_existing: "{{ item.value is not defined }}"
no_log: true
loop: "{{ container_secrets }}"
notify: Restart container service {{ container_name }}

1
roles/grafana/README.md Normal file
View File

@@ -0,0 +1 @@
Installs and configures grafana

View File

@@ -0,0 +1,15 @@
---
grafana_additional_networks: []
grafana_oauth_name: ""
grafana_oauth_client_id: ""
grafana_oauth_auth_url: ""
grafana_oauth_token_url: ""
grafana_oauth_api_url: ""
grafana_oauth_scopes:
- openid
- profile
- email
grafana_oauth_role_attribute_path: ""
grafana_oauth_allow_sign_up: true
grafana_oauth_auto_login: true

View File

@@ -0,0 +1,80 @@
---
argument_specs:
main:
description: Installs and configures grafana
options:
grafana_domain:
description: The domain grafana should be available on
type: str
required: true
grafana_additional_networks:
description: >-
A list of additional podman networks for the grafana container (in
addition to grafana network).
type: list
required: false
default: []
elements: str
grafana_oauth_name:
description: >-
Name that refers to the generic OAuth2 authentication from the Grafana
user interface. Required to enable OAuth authentication.
type: str
required: false
default: ""
grafana_oauth_client_id:
description: >-
Client ID provided by your OAuth2 app. Required if OAuth is enabled.
type: str
required: false
default: ""
grafana_oauth_auth_url:
description: Authorization endpoint of your OAuth2 provider. Required if OAuth is enabled.
type: str
required: false
default: ""
grafana_oauth_token_url:
description: Endpoint used to obtain the OAuth2 access token.
type: str
required: false
default: ""
grafana_oauth_api_url:
description: Endpoint used to obtain user information compatible with OpenID UserInfo.
type: str
required: false
default: ""
grafana_oauth_scopes:
description: List of OAuth2 scopes.
type: list
required: false
elements: str
default:
- openid
- profile
- email
grafana_oauth_role_attribute_path:
description: >-
JMESPath expression to use for Grafana role lookup. Grafana will first
evaluate the expression using the OAuth2 ID token. If no role is found,
the expression will be evaluated using the user information obtained
from the UserInfo endpoint. The result of the evaluation should be
a valid Grafana role (Viewer, Editor, Admin or GrafanaAdmin).
type: str
required: false
default: ""
grafana_oauth_allow_sign_up:
description: >-
Controls Grafana user creation through the generic OAuth2 login. Only
existing Grafana users can log in with generic OAuth if set to false.
type: bool
required: false
default: true
grafana_oauth_auto_login:
description: >-
Whether to enable users to bypass the login screen and automatically
log in. This setting is ignored if you configure multiple auth
providers to use auto-login.
type: bool
required: false
default: true

View File

@@ -0,0 +1,40 @@
---
- name: Grafana
ansible.builtin.import_role:
name: service
vars:
service_name: grafana
service_container_image: "docker.io/grafana/grafana:latest"
service_container_mounts:
- type: volume
source: data
destination: /var/lib/grafana
service_container_http_port: 3000
service_domains:
- "{{ grafana_domain }}"
service_database_type: postgres
service_postgres_tag: 17-alpine
service_container_additional_networks: "{{ grafana_additional_networks }}"
service_container_env:
GF_DATABASE_TYPE: postgres
GF_DATABASE_HOST: grafana-postgres:5432
GF_DATABASE_NAME: grafana
GF_DATABASE_USER: grafana
GF_DATABASE_PASSWORD__FILE: /run/secrets/postgres
GF_SERVER_DOMAIN: "{{ grafana_domain }}"
GF_SERVER_ROOT_URL: "https://{{ grafana_domain }}"
GF_AUTH_GENERIC_OAUTH_ENABLED: "{{ 'true' if grafana_oauth_name | length > 0 else 'false' }}"
GF_AUTH_GENERIC_OAUTH_NAME: "{{ grafana_oauth_name }}"
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "{{ grafana_oauth_client_id }}"
GF_AUTH_GENERIC_OAUTH_AUTH_URL: "{{ grafana_oauth_auth_url }}"
GF_AUTH_GENERIC_OAUTH_TOKEN_URL: "{{ grafana_oauth_token_url }}"
GF_AUTH_GENERIC_OAUTH_API_URL: "{{ grafana_oauth_userinfo_url }}"
GF_AUTH_GENERIC_OAUTH_SCOPES: "{{ grafana_oauth_scopes | join(' ') }}"
GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP: "{{ 'true' if grafana_oauth_allow_sign_up else 'false' }}"
GF_AUTH_GENERIC_OAUTH_AUTO_LOGIN: "{{ 'true' if grafana_oauth_auto_login else 'false' }}"
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH: "{{ grafana_oauth_role_attribute_path }}"
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_STRICT: "true"
GF_AUTH_GENERIC_OAUTH_ALLOW_ASSIGN_GRAFANA_ADMIN: "true"
GF_AUTH_GENERIC_OAUTH_USE_PKCE: "true"
GF_AUTH_GENERIC_OAUTH_LOGIN_ATTRIBUTE_PATH: preferred_username

2
roles/image/README.md Normal file
View File

@@ -0,0 +1,2 @@
Sets up podman image with systemd unit (quadlet)
The image unit filename is `image_name` with / replaced by _

View File

@@ -0,0 +1 @@
Sets up a oauth2-proxy container

View File

@@ -0,0 +1,17 @@
---
argument_specs:
main:
description: "Sets up a oauth2-proxy container"
options:
oauth2_proxy_oidc_issuer_url:
description: the OpenID Connect issuer URL
type: str
required: true
oauth2_proxy_client_id:
description: the OAuth client ID
type: str
required: true
oauth2_proxy_client_secret:
description: the OAuth client secret
type: str
required: true

View File

@@ -0,0 +1,24 @@
---
- name: OAuth2 Proxy
ansible.builtin.import_role:
name: service
vars:
service_name: oauth2-proxy
service_container_image: "quay.io/oauth2-proxy/oauth2-proxy:latest-alpine"
service_container_http_port: 4180
service_container_command:
- --config
- /oauth2-proxy.cfg
- --client-secret-file
- /run/secrets/client_secret
service_container_mounts:
- type: template
source: oauth2-proxy.cfg.j2
destination: /oauth2-proxy.cfg
service_container_secrets:
- name: cookie_secret
length: 32
type: env
target: OAUTH2_PROXY_COOKIE_SECRET
- name: client_secret
value: "{{ oauth2_proxy_client_secret }}"

View File

@@ -0,0 +1,11 @@
# OAuth2 Proxy Configuration
http_address = "0.0.0.0:4180"
# OIDC Provider Configuration
provider = "oidc"
oidc_issuer_url = "{{ oauth2_proxy_oidc_issuer_url }}"
client_id = "{{ oauth2_proxy_client_id }}"
code_challenge_method = "S256"
skip_provider_button = "true"
email_domains = "*"

View File

@@ -2,8 +2,10 @@
service_container_command: []
service_domains: []
service_container_http_port: 0
service_vhost_locations: []
service_proxy_pass_host_header: true
service_proxy_auth_type: none
service_container_additional_networks: []
service_container_user: ""
@@ -13,6 +15,7 @@ service_container_secrets: []
service_container_env: {}
service_database_type: none
service_database_additional_networks: []
service_database_secret_type: mount
service_database_secret_target: "{{ service_database_type }}"
service_postgres_image: docker.io/library/postgres

View File

@@ -27,11 +27,22 @@ argument_specs:
- Required if service_domains is not empty.
type: int
required: false
default: 0
service_proxy_pass_host_header:
description: Passed to vhost role as vhost_proxy_pass_header
type: bool
required: false
default: true
service_proxy_auth_type:
description: >-
Set to oauth2-proxy to use OAuth2 Proxy for vhost authentication.
The oauth2-proxy role must be run separately.
type: str
required: false
default: none
choices:
- none
- oauth2-proxy
service_vhost_locations:
description: Passed to vhost role as vhost_locations
type: list
@@ -65,7 +76,9 @@ argument_specs:
required: false
default: ""
service_container_additional_networks:
description: A list of additional podman networks for the service container (in addition to service name network).
description: >-
A list of additional podman networks for the service container (in
addition to service name network).
type: list
required: false
default: []
@@ -211,6 +224,14 @@ argument_specs:
- none
required: false
default: none
service_database_additional_networks:
description: >-
A list of additional podman networks for the database container (in
addition to service name network).
type: list
required: false
default: []
elements: str
service_database_secret_type:
description: Secret type for database secret for service container
type: str

View File

@@ -9,8 +9,7 @@
- type: volume
source: "{{ _service_database_name }}"
destination: /var/lib/postgresql/data
container_networks:
- "{{ service_name }}"
container_networks: "{{ _service_database_networks }}"
container_secrets:
- name: "{{ _service_database_name }}"
target: "{{ service_database_type }}"

View File

@@ -40,6 +40,10 @@
container_wants: "{{ _service_container_wants }}"
container_auto_update: "{{ service_auto_update }}"
- name: Socat for {{ service_name }}
ansible.builtin.include_tasks: socat.yaml
when: service_container_http_port > 0
- name: Reverse proxy for {{ service_name }}
ansible.builtin.include_tasks: proxy.yaml
when: service_domains | length > 0

View File

@@ -1,29 +1,4 @@
---
- name: Socat socket for {{ service_name }}
ansible.builtin.template:
src: socat.socket.j2
dest: /etc/systemd/system/{{ service_name }}-socat.socket
mode: "0644"
notify: Restart socat socket for {{ service_name }}
- name: Socat container for {{ service_name }}
ansible.builtin.import_role:
name: container
vars:
container_name: "{{ service_name }}-socat"
container_image: "docker.io/alpine/socat:latest"
container_command:
- "ACCEPT-FD:3,fork"
- "TCP:{{ service_name }}:{{ service_container_http_port }}"
container_user: nobody
container_networks:
- "{{ service_name }}"
container_requires:
- "{{ service_name }}-socat.socket"
- "{{ service_name }}.service"
container_auto_start: false
container_auto_update: "{{ service_auto_update }}"
- name: Reverse proxy for {{ service_name }}
ansible.builtin.import_role:
name: uumas.general.vhost
@@ -34,4 +9,7 @@
vhost_proxy_target_netproto: unix
vhost_proxy_target_socket: "/run/{{ service_name }}-socat.sock"
vhost_proxy_headers: "{{ _service_proxy_headers }}"
vhost_locations: "{{ service_vhost_locations }}"
vhost_proxy_auth_socket: "{{ _service_oauth2_socket }}"
vhost_proxy_auth_uri: /oauth2/auth
vhost_proxy_auth_unauthorized_redir: "/oauth2/sign_in?rd={scheme}://{host}{uri}"
vhost_locations: "{{ _service_vhost_locations }}"

View File

@@ -0,0 +1,26 @@
---
- name: Socat socket for {{ service_name }}
ansible.builtin.template:
src: socat.socket.j2
dest: /etc/systemd/system/{{ service_name }}-socat.socket
mode: "0644"
notify: Restart socat socket for {{ service_name }}
- name: Socat container for {{ service_name }}
ansible.builtin.import_role:
name: container
vars:
container_name: "{{ service_name }}-socat"
container_image: "docker.io/alpine/socat:latest"
container_command:
- "ACCEPT-FD:3,fork"
- "TCP:{{ service_name }}:{{ service_container_http_port }}"
container_user: nobody
container_networks:
- "{{ service_name }}"
container_requires:
- "{{ service_name }}-socat.socket"
- "{{ service_name }}.service"
container_auto_start: false
container_auto_update: "{{ service_auto_update }}"

View File

@@ -0,0 +1,8 @@
---
_service_setup_database: "{{ service_database_type != 'none' }}"
_service_database_name: "{{ service_name }}-{{ service_database_type }}"
_service_database_networks: >-
{{
[service_name] +
service_database_additional_networks
}}

View File

@@ -1,7 +1,4 @@
---
_service_setup_database: "{{ service_database_type != 'none' }}"
_service_database_name: "{{ service_name }}-{{ service_database_type }}"
_service_container_networks: "{{ [service_name] + service_container_additional_networks }}"
_service_container_requires: >-
@@ -18,7 +15,3 @@ _service_container_wants: >-
| map(attribute='name')
| map('regex_replace', '$', '.service')
}}
_service_replacement_host_header:
Host: "{{ service_name }}:{{ service_container_http_port }}"
_service_proxy_headers: "{{ _service_replacement_host_header if not service_proxy_pass_host_header else {} }}"

View File

@@ -0,0 +1,18 @@
---
_service_replacement_host_header:
Host: "{{ service_name }}:{{ service_container_http_port }}"
_service_proxy_headers: "{{ _service_replacement_host_header if not service_proxy_pass_host_header else {} }}"
_service_oauth2_proxy: "{{ service_proxy_auth_type == 'oauth2-proxy' }}"
_service_oauth2_socket: >-
{{ '/run/oauth2-proxy-socat.sock' if _service_oauth2_proxy else '' }}
_service_oauth2_proxy_location:
path: /oauth2/*
proxy_target_socket: "{{ _service_oauth2_socket }}"
proxy_auth_socket: ""
_service_vhost_locations: >-
{{
service_vhost_locations +
([_service_oauth2_proxy_location] if _service_oauth2_proxy else [])
}}