mirror of
				https://github.com/kevinveenbirkenbach/computer-playbook.git
				synced 2025-11-03 19:58:14 +00:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			4ebe7ee918
			...
			0ac9dac658
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 0ac9dac658 | |||
| 7052f8205a | |||
| 8fac2296fe | |||
| ec0d266975 | |||
| 93af817d4b | |||
| 026855b197 | |||
| d0844ce44f | 
@@ -1,2 +0,0 @@
 | 
			
		||||
# Todos
 | 
			
		||||
- Refactor all 4 functions to one
 | 
			
		||||
							
								
								
									
										37
									
								
								filter_plugins/redirect_filters.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								filter_plugins/redirect_filters.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
# roles/<your-role>/filter_plugins/redirect_filters.py
 | 
			
		||||
from ansible.errors import AnsibleFilterError
 | 
			
		||||
 | 
			
		||||
class FilterModule(object):
 | 
			
		||||
    """
 | 
			
		||||
    Custom filters for redirect domain mappings
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def filters(self):
 | 
			
		||||
        return {
 | 
			
		||||
            "add_redirect_if_group": self.add_redirect_if_group,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def add_redirect_if_group(redirect_list, group, source, target, group_names):
 | 
			
		||||
        """
 | 
			
		||||
        Append {"source": source, "target": target} to *redirect_list*
 | 
			
		||||
        **only** if *group* is contained in *group_names*.
 | 
			
		||||
 | 
			
		||||
        Usage in Jinja:
 | 
			
		||||
          {{ redirect_list
 | 
			
		||||
               | add_redirect_if_group('lam',
 | 
			
		||||
                                       'ldap.' ~ primary_domain,
 | 
			
		||||
                                       domains.lam,
 | 
			
		||||
                                       group_names) }}
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            # Make a copy so we don’t mutate the original list in place
 | 
			
		||||
            redirects = list(redirect_list)
 | 
			
		||||
 | 
			
		||||
            if group in group_names:
 | 
			
		||||
                redirects.append({"source": source, "target": target})
 | 
			
		||||
 | 
			
		||||
            return redirects
 | 
			
		||||
 | 
			
		||||
        except Exception as exc:
 | 
			
		||||
            raise AnsibleFilterError(f"add_redirect_if_group failed: {exc}")
 | 
			
		||||
@@ -53,31 +53,31 @@ defaults_domains:
 | 
			
		||||
    - "blog.{{primary_domain}}"
 | 
			
		||||
 | 
			
		||||
## Domain Redirects
 | 
			
		||||
defaults_redirect_domain_mappings:
 | 
			
		||||
- { source: "akaunting.{{primary_domain}}",   target: "{{domains.akaunting}}" }
 | 
			
		||||
- { source: "bbb.{{primary_domain}}",         target: "{{domains.bigbluebutton}}" }
 | 
			
		||||
- { source: "discourse.{{primary_domain}}",   target: "{{domains.discourse}}" }
 | 
			
		||||
- { source: "crm.{{primary_domain}}",         target: "{{domains.espocrm}}" }
 | 
			
		||||
- { source: "funkwhale.{{primary_domain}}",   target: "{{domains.funkwhale}}" }
 | 
			
		||||
- { source: "gitea.{{primary_domain}}",       target: "{{domains.gitea}}" }
 | 
			
		||||
- { source: "keycloak.{{primary_domain}}",    target: "{{domains.keycloak}}" }
 | 
			
		||||
- { 
 | 
			
		||||
    source: "{{ domains.ldap }}", 
 | 
			
		||||
    target: "{% if 'lam' in group_names %}{{ domains.lam }}{% elif 'phpmyldapadmin' in group_names %}{{ domains.phpmyldap }}{% else %}{{ primary_domain }}{% endif %}"
 | 
			
		||||
  }
 | 
			
		||||
- { source: "listmonk.{{primary_domain}}",    target: "{{domains.listmonk}}" }
 | 
			
		||||
- { source: "mailu.{{primary_domain}}",       target: "{{domains.mailu}}" }
 | 
			
		||||
- { source: "moodle.{{primary_domain}}",      target: "{{domains.moodle}}" }
 | 
			
		||||
- { source: "nextcloud.{{primary_domain}}",   target: "{{domains.nextcloud}}" }
 | 
			
		||||
- { source: "openproject.{{primary_domain}}", target: "{{domains.openproject}}" }
 | 
			
		||||
- { source: "peertube.{{primary_domain}}",    target: "{{domains.peertube}}" }
 | 
			
		||||
- { source: "pictures.{{primary_domain}}",    target: "{{domains.pixelfed}}" }
 | 
			
		||||
- { source: "pixelfed.{{primary_domain}}",    target: "{{domains.pixelfed}}" }
 | 
			
		||||
- { source: "short.{{primary_domain}}",       target: "{{domains.yourls}}" }
 | 
			
		||||
- { source: "snipe-it.{{primary_domain}}",    target: "{{domains.snipe_it}}" }
 | 
			
		||||
- { source: "taiga.{{primary_domain}}",       target: "{{domains.taiga}}" }
 | 
			
		||||
- { source: "videos.{{primary_domain}}",      target: "{{domains.peertube}}" }
 | 
			
		||||
- { source: "wordpress.{{primary_domain}}",   target: "{{domains.wordpress[0]}}" }
 | 
			
		||||
defaults_redirect_domain_mappings: >-
 | 
			
		||||
  {{ []
 | 
			
		||||
     | add_redirect_if_group('akaunting',       "akaunting." ~ primary_domain,   domains.akaunting,     group_names)
 | 
			
		||||
     | add_redirect_if_group('bigbluebutton',   "bbb." ~ primary_domain,         domains.bigbluebutton, group_names)
 | 
			
		||||
     | add_redirect_if_group('discourse',       "discourse." ~ primary_domain,   domains.discourse,     group_names)
 | 
			
		||||
     | add_redirect_if_group('espocrm',         "crm." ~ primary_domain,         domains.espocrm,       group_names)
 | 
			
		||||
     | add_redirect_if_group('funkwhale',       "funkwhale." ~ primary_domain,   domains.funkwhale,     group_names)
 | 
			
		||||
     | add_redirect_if_group('gitea',           "gitea." ~ primary_domain,       domains.gitea,         group_names)
 | 
			
		||||
     | add_redirect_if_group('keycloak',        "keycloak." ~ primary_domain,    domains.keycloak,      group_names)
 | 
			
		||||
     | add_redirect_if_group('lam',             domains.ldap,                    domains.lam,           group_names)
 | 
			
		||||
     | add_redirect_if_group('phpmyldapadmin',  domains.ldap,                    domains.phpmyldap,     group_names)
 | 
			
		||||
     | add_redirect_if_group('listmonk',        "listmonk." ~ primary_domain,    domains.listmonk,      group_names)
 | 
			
		||||
     | add_redirect_if_group('mailu',           "mailu." ~ primary_domain,       domains.mailu,         group_names)
 | 
			
		||||
     | add_redirect_if_group('moodle',          "moodle." ~ primary_domain,      domains.moodle,        group_names)
 | 
			
		||||
     | add_redirect_if_group('nextcloud',       "nextcloud." ~ primary_domain,   domains.nextcloud,     group_names)
 | 
			
		||||
     | add_redirect_if_group('openproject',     "openproject." ~ primary_domain, domains.openproject,   group_names)
 | 
			
		||||
     | add_redirect_if_group('peertube',        "peertube." ~ primary_domain,    domains.peertube,      group_names)
 | 
			
		||||
     | add_redirect_if_group('pixelfed',        "pictures." ~ primary_domain,    domains.pixelfed,      group_names)
 | 
			
		||||
     | add_redirect_if_group('pixelfed',        "pixelfed." ~ primary_domain,    domains.pixelfed,      group_names)
 | 
			
		||||
     | add_redirect_if_group('yourls',          "short." ~ primary_domain,       domains.yourls,        group_names)
 | 
			
		||||
     | add_redirect_if_group('snipe-it',        "snipe-it." ~ primary_domain,    domains.snipe_it,      group_names)
 | 
			
		||||
     | add_redirect_if_group('taiga',           "taiga." ~ primary_domain,       domains.taiga,         group_names)
 | 
			
		||||
     | add_redirect_if_group('peertube',        "videos." ~ primary_domain,      domains.peertube,      group_names)
 | 
			
		||||
     | add_redirect_if_group('wordpress',       "wordpress." ~ primary_domain,   domains.wordpress[0],  group_names)
 | 
			
		||||
  }}
 | 
			
		||||
 | 
			
		||||
# Domains which are deprecated and should be cleaned up
 | 
			
		||||
deprecated_domains: []
 | 
			
		||||
@@ -1,14 +1,14 @@
 | 
			
		||||
---
 | 
			
		||||
- name: "stop and remove discourse container if it exist"
 | 
			
		||||
  docker_container:
 | 
			
		||||
    name: "{{applications.discourse.container}}"
 | 
			
		||||
    name: "{{applications[application_id].container}}"
 | 
			
		||||
    state: absent
 | 
			
		||||
  register: container_action
 | 
			
		||||
  failed_when: container_action.failed and 'No such container' not in container_action.msg
 | 
			
		||||
  listen: recreate discourse
 | 
			
		||||
  
 | 
			
		||||
- name: "add central database temporary to {{application_id}}_default"
 | 
			
		||||
  command: docker network connect {{applications.discourse.network}} central-{{ database_type }}
 | 
			
		||||
  command: docker network connect {{applications[application_id].network}} central-{{ database_type }}
 | 
			
		||||
  failed_when: >
 | 
			
		||||
    result.rc != 0 and
 | 
			
		||||
    'already exists in network' not in result.stderr
 | 
			
		||||
@@ -18,6 +18,6 @@
 | 
			
		||||
 | 
			
		||||
- name: rebuild discourse
 | 
			
		||||
  command:
 | 
			
		||||
    cmd: "./launcher rebuild {{applications.discourse.container}}"
 | 
			
		||||
    cmd: "./launcher rebuild {{applications[application_id].container}}"
 | 
			
		||||
    chdir: "{{docker_repository_directory }}"
 | 
			
		||||
  listen: recreate discourse
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
 | 
			
		||||
- name: "cleanup central database from {{application_id}}_default network"
 | 
			
		||||
  command:
 | 
			
		||||
    cmd: "docker network disconnect {{applications.discourse.network}} central-{{ database_type }}"
 | 
			
		||||
    cmd: "docker network disconnect {{applications[application_id].network}} central-{{ database_type }}"
 | 
			
		||||
  ignore_errors: true
 | 
			
		||||
  when: 
 | 
			
		||||
    - mode_reset | bool
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
    dest: "{{docker_compose.directories.instance}}docker-compose.yml"
 | 
			
		||||
  notify:
 | 
			
		||||
    - docker compose project setup
 | 
			
		||||
    - run_once_docker_discourse is not defined
 | 
			
		||||
  when: run_once_docker_discourse is not defined
 | 
			
		||||
 | 
			
		||||
- name: flush, to recreate discourse docker compose
 | 
			
		||||
  meta: flush_handlers
 | 
			
		||||
@@ -78,9 +78,9 @@
 | 
			
		||||
  meta: flush_handlers
 | 
			
		||||
  when: run_once_docker_discourse is not defined
 | 
			
		||||
 | 
			
		||||
- name: "add {{applications.discourse.container}} to network central_postgres"
 | 
			
		||||
- name: "add {{applications[application_id].container}} to network central_postgres"
 | 
			
		||||
  command:
 | 
			
		||||
    cmd: "docker network connect central_postgres {{applications.discourse.container}}"
 | 
			
		||||
    cmd: "docker network connect central_postgres {{applications[application_id].container}}"
 | 
			
		||||
  ignore_errors: true
 | 
			
		||||
  when:
 | 
			
		||||
    - applications | is_feature_enabled('central_database',application_id)
 | 
			
		||||
@@ -88,7 +88,7 @@
 | 
			
		||||
  
 | 
			
		||||
- name: "remove central database from {{application_id}}_default"
 | 
			
		||||
  command:
 | 
			
		||||
    cmd: "docker network disconnect {{applications.discourse.network}} central-{{ database_type }}"
 | 
			
		||||
    cmd: "docker network disconnect {{applications[application_id].network}} central-{{ database_type }}"
 | 
			
		||||
  ignore_errors: true
 | 
			
		||||
  when:
 | 
			
		||||
    - applications | is_feature_enabled('central_database',application_id)
 | 
			
		||||
 
 | 
			
		||||
@@ -143,4 +143,4 @@ run:
 | 
			
		||||
 | 
			
		||||
docker_args:
 | 
			
		||||
  - --network={{application_id}}_default
 | 
			
		||||
  - --name={{applications.discourse.container}}
 | 
			
		||||
  - --name={{applications[application_id].container}}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
application_id:                         "discourse"
 | 
			
		||||
database_password:                      "{{ applications.discourse.credentials.database_password }}"
 | 
			
		||||
database_password:                      "{{ applications[application_id].credentials.database_password }}"
 | 
			
		||||
database_type:                          "postgres"
 | 
			
		||||
docker_repository_directory :           "{{docker_compose.directories.services}}{{applications.discourse.repository}}/"
 | 
			
		||||
discourse_application_yml_destination:  "{{docker_repository_directory }}containers/{{applications.discourse.container}}.yml"
 | 
			
		||||
docker_repository_directory :           "{{docker_compose.directories.services}}{{applications[application_id].repository}}/"
 | 
			
		||||
discourse_application_yml_destination:  "{{docker_repository_directory }}containers/{{applications[application_id].container}}.yml"
 | 
			
		||||
							
								
								
									
										3
									
								
								roles/nginx-docker-reverse-proxy/Todo.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								roles/nginx-docker-reverse-proxy/Todo.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
# Todos
 | 
			
		||||
- Optimize buffering
 | 
			
		||||
- Optimize caching
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
{# Initialize an array to collect each CSP directive line #}
 | 
			
		||||
{%- set csp_parts = [] %}
 | 
			
		||||
{# Create a namespace to hold the accumulated CSP parts #}
 | 
			
		||||
{% set ns = namespace(csp_parts=[]) %}
 | 
			
		||||
 | 
			
		||||
{# List of all directives to process dynamically (except img-src) #}
 | 
			
		||||
{%- set directives = [
 | 
			
		||||
{# List of directives to build dynamically (except img-src) #}
 | 
			
		||||
{% set directives = [
 | 
			
		||||
     'default-src',
 | 
			
		||||
     'connect-src',
 | 
			
		||||
     'frame-ancestors',
 | 
			
		||||
@@ -12,34 +12,34 @@
 | 
			
		||||
     'font-src'
 | 
			
		||||
] %}
 | 
			
		||||
 | 
			
		||||
{# Loop over each directive and build its value from 'self', any unsafe flags, whitelist URLs, and optional Matomo #}
 | 
			
		||||
{%- for directive in directives %}
 | 
			
		||||
  {# Start with the 'self' source #}
 | 
			
		||||
  {%- set tokens = ["'self'"] %}
 | 
			
		||||
{# Build each directive line #}
 | 
			
		||||
{% for directive in directives %}
 | 
			
		||||
  {# Always start with 'self' #}
 | 
			
		||||
  {% set tokens = ["'self'"] %}
 | 
			
		||||
 | 
			
		||||
  {# Add any unsafe flags (unsafe-eval, unsafe-inline) from csp.flags.<directive> #}
 | 
			
		||||
  {%- for flag in applications | get_csp_flags(application_id, directive) %}
 | 
			
		||||
    {%- set tokens = tokens + [flag] %}
 | 
			
		||||
  {%- endfor %}
 | 
			
		||||
  {# Add any unsafe flags for this directive #}
 | 
			
		||||
  {% for flag in applications | get_csp_flags(application_id, directive) %}
 | 
			
		||||
    {% set tokens = tokens + [flag] %}
 | 
			
		||||
  {% endfor %}
 | 
			
		||||
 | 
			
		||||
  {# If Matomo feature is enabled, whitelist its script and connect sources #}
 | 
			
		||||
  {%- if applications | is_feature_enabled('matomo', application_id) and directive in ['script-src','connect-src'] %}
 | 
			
		||||
    {%- set tokens = tokens + ['{{ web_protocol }}://{{ domains.matomo }}'] %}
 | 
			
		||||
  {%- endif %}
 | 
			
		||||
  {# If Matomo is enabled, allow its script and connect endpoints #}
 | 
			
		||||
  {% if applications | is_feature_enabled('matomo', application_id)
 | 
			
		||||
       and directive in ['script-src', 'connect-src'] %}
 | 
			
		||||
    {% set tokens = tokens + [web_protocol ~ '://' ~ domains.matomo] %}
 | 
			
		||||
  {% endif %}
 | 
			
		||||
 | 
			
		||||
  {# Add any extra hosts/URLs from csp.whitelist.<directive> #}
 | 
			
		||||
  {%- for url in applications | get_csp_whitelist(application_id, directive) %}
 | 
			
		||||
    {%- set tokens = tokens + [url] %}
 | 
			
		||||
  {%- endfor %}
 | 
			
		||||
  {# Append any extra whitelist URLs for this directive #}
 | 
			
		||||
  {% for url in applications | get_csp_whitelist(application_id, directive) %}
 | 
			
		||||
    {% set tokens = tokens + [url] %}
 | 
			
		||||
  {% endfor %}
 | 
			
		||||
 | 
			
		||||
  {# Combine into a single directive line and append to csp_parts #}
 | 
			
		||||
  {%- set csp_parts = csp_parts + [directive ~ ' ' ~ (tokens | join(' ')) ~ ';'] %}
 | 
			
		||||
{%- endfor %}
 | 
			
		||||
  {# Store the completed directive line in the namespace #}
 | 
			
		||||
  {% set ns.csp_parts = ns.csp_parts + [directive ~ ' ' ~ (tokens | join(' ')) ~ ';'] %}
 | 
			
		||||
{% endfor %}
 | 
			
		||||
 | 
			
		||||
{# Preserve original img-src directive logic (do not loop) #}
 | 
			
		||||
{%- set img_src = 'img-src * data: blob:' %}
 | 
			
		||||
{%- set csp_parts = csp_parts + [img_src ~ ';'] %}
 | 
			
		||||
{# Add the (static) img-src directive #}
 | 
			
		||||
{% set ns.csp_parts = ns.csp_parts + ['img-src * data: blob:;'] %}
 | 
			
		||||
 | 
			
		||||
{# Emit the assembled Content-Security-Policy header and hide any upstream CSP header #}
 | 
			
		||||
add_header Content-Security-Policy "{{ csp_parts | join(' ') }}" always;
 | 
			
		||||
{# Emit the final header and hide any upstream header #}
 | 
			
		||||
add_header Content-Security-Policy "{{ ns.csp_parts | join(' ') }}" always;
 | 
			
		||||
proxy_hide_header Content-Security-Policy;
 | 
			
		||||
@@ -31,6 +31,3 @@ location {{location | default("/")}}
 | 
			
		||||
  proxy_read_timeout          900s;
 | 
			
		||||
  send_timeout                900s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Load caching
 | 
			
		||||
{% include 'roles/nginx-docker-reverse-proxy/templates/location/proxy_cache.conf.j2' %}
 | 
			
		||||
@@ -11,9 +11,9 @@
 | 
			
		||||
- name: "set oauth2_proxy_application_id (Needed due to lazzy loading issue)"
 | 
			
		||||
  set_fact:
 | 
			
		||||
    oauth2_proxy_application_id: "{{ application_id }}"
 | 
			
		||||
  when: "{{applications[application_id].get('features', {}).get('oauth2', False)}}"
 | 
			
		||||
  when: applications | is_feature_enabled('oauth2',application_id)
 | 
			
		||||
 | 
			
		||||
- name: "include the docker-oauth2-proxy role {{domain}}"
 | 
			
		||||
  include_role:
 | 
			
		||||
    name: docker-oauth2-proxy
 | 
			
		||||
  when: "{{applications[application_id].get('features', {}).get('oauth2', False)}}"
 | 
			
		||||
  when: applications | is_feature_enabled('oauth2',application_id)
 | 
			
		||||
							
								
								
									
										53
									
								
								tests/unit/test_redirect_filters.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								tests/unit/test_redirect_filters.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import unittest
 | 
			
		||||
 | 
			
		||||
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
 | 
			
		||||
sys.path.insert(0, PROJECT_ROOT)
 | 
			
		||||
 | 
			
		||||
from filter_plugins.redirect_filters import FilterModule
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestAddRedirectIfGroup(unittest.TestCase):
 | 
			
		||||
    """Unit-tests for the add_redirect_if_group filter."""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        # Obtain the callable once for reuse
 | 
			
		||||
        self.add_redirect = FilterModule().filters()["add_redirect_if_group"]
 | 
			
		||||
 | 
			
		||||
    def test_appends_redirect_when_group_present(self):
 | 
			
		||||
        original = [{"source": "a", "target": "b"}]
 | 
			
		||||
        result = self.add_redirect(
 | 
			
		||||
            original,
 | 
			
		||||
            group="lam",
 | 
			
		||||
            source="ldap.example.com",
 | 
			
		||||
            target="lam.example.com",
 | 
			
		||||
            group_names=["lam", "other"],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Original list must stay unchanged
 | 
			
		||||
        self.assertEqual(len(original), 1)
 | 
			
		||||
        # Result list must contain the extra entry
 | 
			
		||||
        self.assertEqual(len(result), 2)
 | 
			
		||||
        self.assertIn(
 | 
			
		||||
            {"source": "ldap.example.com", "target": "lam.example.com"}, result
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_keeps_list_unchanged_when_group_absent(self):
 | 
			
		||||
        original = [{"source": "a", "target": "b"}]
 | 
			
		||||
        result = self.add_redirect(
 | 
			
		||||
            original,
 | 
			
		||||
            group="lam",
 | 
			
		||||
            source="ldap.example.com",
 | 
			
		||||
            target="lam.example.com",
 | 
			
		||||
            group_names=["unrelated"],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # No new entries
 | 
			
		||||
        self.assertEqual(result, original)
 | 
			
		||||
        # But ensure a new list object was returned (no in-place mutation)
 | 
			
		||||
        self.assertIsNot(result, original)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    unittest.main()
 | 
			
		||||
		Reference in New Issue
	
	Block a user