Optimized injection layer on lua base, as replace for nginx replace. Also optimized cloudflare cache deletion(no everytime for cleanup). Still CDN is required for logout mechanism via JS and Nextcloud deploy is buggy after changing from nginx to openresty. Propably some variable overwritte topic. Should be solved tomorrow.

This commit is contained in:
Kevin Veen-Birkenbach 2025-07-24 19:13:13 +02:00
parent f62355e490
commit 27973c2773
No known key found for this signature in database
GPG Key ID: 44D8F11FD62F878E
36 changed files with 483 additions and 115 deletions

View File

@ -0,0 +1,30 @@
from ansible.errors import AnsibleFilterError
try:
import tld
from tld.exceptions import TldDomainNotFound, TldBadUrl
except ImportError:
raise AnsibleFilterError("The 'tld' Python package is required for the to_primary_domain filter. Install with 'pip install tld'.")
class FilterModule(object):
''' Custom filter to extract the primary/zone domain from a full domain name '''
def filters(self):
return {
'to_primary_domain': self.to_primary_domain,
}
def to_primary_domain(self, domain):
"""
Converts a full domain or subdomain into its primary/zone domain.
E.g. 'foo.bar.example.co.uk' -> 'example.co.uk'
"""
if not isinstance(domain, str):
raise AnsibleFilterError("Input to to_primary_domain must be a string")
try:
res = tld.get_fld(domain, fix_protocol=True)
if not res:
raise AnsibleFilterError(f"Could not extract primary domain from: {domain}")
return res
except (TldDomainNotFound, TldBadUrl) as exc:
raise AnsibleFilterError(str(exc))

View File

@ -45,7 +45,7 @@ dns_provider: cloudflare # The DNS Prov
certbot_acme_challenge_method: "cloudflare"
certbot_credentials_dir: /etc/certbot
certbot_credentials_file: "{{ certbot_credentials_dir }}/{{ certbot_acme_challenge_method }}.ini"
certbot_dns_api_token: "" # Define in inventory file
certbot_dns_api_token: "" # Define in inventory file: More information here: group_vars/all/docs/CLOUDFLARE_API_TOKEN.md
certbot_dns_propagation_wait_seconds: 40 # How long should the script wait for DNS propagation before continuing
certbot_flavor: san # Possible options: san (recommended, with a dns flavor like cloudflare, or hetzner), wildcard(doesn't function with www redirect), deicated

View File

@ -84,3 +84,5 @@ ports:
turn:
web-app-bigbluebutton: 5349 # Not sure if it's right placed here or if it should be moved to localhost section
web-app-nextcloud: 5350 # Not used yet
federation:
web-app-matrix_synapse: 8448

View File

@ -0,0 +1,61 @@
# Cloudflare API Token for Ansible (`certbot_dns_api_token`)
This document explains how to generate and use a Cloudflare API Token for DNS automation and certificate operations in Ansible (e.g., with Certbot).
## Purpose
The `certbot_dns_api_token` variable must contain a valid Cloudflare API Token.
This token is used for all DNS operations and ACME (SSL/TLS certificate) challenges that require access to your Cloudflare-managed domains.
**Never commit your API token to a public repository. Always keep it secure!**
---
## How to Create a Cloudflare API Token
### 1. Log In to Cloudflare
- Go to: [https://dash.cloudflare.com/](https://dash.cloudflare.com/) and log in.
### 2. Open the API Tokens Page
- Click your profile icon (top right) → **My Profile**
- In the sidebar, choose **API Tokens**
Or use this direct link: [https://dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens)
### 3. Click **Create Token**
### 4. Select **Custom Token**
- Give your token a descriptive name (e.g., `Ansible Certbot Automation`).
### 5. Set Permissions
Add the following permissions:
| Category | Permission | Access |
| -------- | ------------ | -------- |
| Zone | Zone | Read |
| Zone | DNS | Edit |
| Zone | Cache Purge | Purge |
- These permissions are required for DNS record management, CAA/SPF/DKIM handling, cache purging, and certificate provisioning.
### 6. Zone Resources
- **Zone Resources:** Set to `Include → All zones`
(Or restrict to specific zones as needed for your environment.)
### 7. Create and Save the Token
- Click **Continue to summary** and then **Create Token**.
- Copy the API Token. **It will only be shown once!**
---
## Using the Token in Ansible
Set the token in your Ansible inventory or secrets file:
```yaml
certbot_dns_api_token: "cf_your_generated_token_here"

View File

@ -1,4 +1,6 @@
colorscheme-generator @ https://github.com/kevinveenbirkenbach/colorscheme-generator/archive/refs/tags/v0.3.0.zip
numpy
bcrypt
ruamel.yaml
ruamel.yaml
tld
passlib

View File

@ -2,6 +2,7 @@ collections:
- name: kewlfft.aur
- name: community.general
pacman:
# Propably it makes sense to move the following to the requirements.txt to just install it in the python venv
- ansible
- python-passlib
- python-pytest

View File

@ -0,0 +1,33 @@
- name: "Lookup Cloudflare Zone ID for {{ domain }}"
vars:
cf_api_url: "https://api.cloudflare.com/client/v4/zones"
ansible.builtin.uri:
url: "{{ cf_api_url }}?name={{ domain | to_primary_domain }}"
method: GET
headers:
Authorization: "Bearer {{ certbot_dns_api_token }}"
Content-Type: "application/json"
return_content: yes
register: cf_zone_lookup
when: dns_provider == "cloudflare"
- name: "Set fact cf_zone_id"
set_fact:
cf_zone_id: "{{ cf_zone_lookup.json.result[0].id }}"
when:
- dns_provider == "cloudflare"
- cf_zone_lookup.json.result | length > 0
- name: "Purge everything from Cloudflare cache for domain {{ domain }}"
ansible.builtin.uri:
url: "https://api.cloudflare.com/client/v4/zones/{{ cf_zone_id }}/purge_cache"
method: POST
headers:
Authorization: "Bearer {{ certbot_dns_api_token }}"
Content-Type: "application/json"
body:
purge_everything: true
body_format: json
return_content: yes
register: cf_purge
when: dns_provider == "cloudflare"

View File

@ -1,6 +1,9 @@
# run_once_srv_proxy_6_6_domain: deactivated
- name: Cleanup Domain
include_tasks: cleanup.yml
when: mode_cleanup | bool
- name: "include role for {{domain}} to receive certificates and do the modification routines"
- name: "include role for {{ domain }} to receive certificates and do the modification routines"
include_role:
name: srv-web-7-6-composer

View File

@ -0,0 +1,58 @@
# Nginx Location Templates
This directory contains Jinja2 templates for different Nginx `location` blocks, each designed to proxy and optimize different types of web traffic. These templates are used by the `srv-proxy-7-4-core` role to modularize and standardize reverse proxy configuration across a wide variety of applications.
---
## Overview of Files
### `html.conf.j2`
- **Purpose:**
Handles "normal" web traffic such as HTML pages, API endpoints, and general HTTP(S) requests.
- **Features:**
- Proxies requests to the backend service.
- Optionally integrates with OAuth2 proxy for authentication.
- Sets all necessary proxy headers.
- Applies a Content Security Policy header.
- Activates buffering for advanced features such as Lua-based string replacements.
- Supports WebSocket upgrades for hybrid APIs.
---
### `ws.conf.j2`
- **Purpose:**
Handles WebSocket connections, enabling real-time features such as live updates or chats.
- **Features:**
- Sets all headers required for WebSocket upgrades.
- Disables proxy buffering (required for WebSockets).
- Uses `tcp_nodelay` for low latency.
- Proxies traffic to the backend WebSocket server.
---
### `media.conf.j2`
- **Purpose:**
Proxies and caches static media files (images, icons, etc.).
- **Features:**
- Matches image file extensions (jpg, png, gif, webp, ico, svg, etc.).
- Enables browser-side and proxy-side caching for efficient delivery.
- Adds cache control headers and exposes the upstream cache status.
---
## Usage
These templates are intended for inclusion in larger Nginx configuration files via Jinja2.
They modularize your configuration by separating HTML, WebSocket, and media proxying, allowing for clear, reusable, and maintainable reverse proxy logic.
- Use `html.conf.j2` for standard application HTTP/S endpoints.
- Use `ws.conf.j2` for dedicated WebSocket endpoints.
- Use `media.conf.j2` for efficient handling of static media content.
---
## Best Practices
- Only enable WebSocket proxying (`ws.conf.j2`) for routes that actually require it, to avoid breaking buffering for standard HTTP.
- Activate media proxying (`media.conf.j2`) if your application benefits from image caching at the proxy layer.
- Keep templates modular for maintainability and scalability as your application grows.

View File

@ -0,0 +1,2 @@
# TODOS
- ATM it seems like the media proxy isn't used. Propably it could make sense to activate it. -> Research it.

View File

@ -21,13 +21,16 @@ location {{location | default("/")}}
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# deactivate buffering
proxy_buffering off;
proxy_request_buffering off;
# Activate buffering
# Needs to be enabled, so that lua can do str replaces
proxy_buffering on;
proxy_request_buffering on;
# timeouts
proxy_connect_timeout 1s;
proxy_send_timeout 900s;
proxy_read_timeout 900s;
send_timeout 900s;
{% include 'roles/srv-web-7-7-inj-compose/templates/location.lua.j2'%}
}

View File

@ -0,0 +1,14 @@
location {{ location_ws }} {
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 https;
proxy_pass http://127.0.0.1:{{ ws_port }};
# Proxy buffering needs to be disabled for websockets.
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
tcp_nodelay on;
}

View File

@ -0,0 +1,78 @@
# Nginx vHost Templates: Basic vs. WebSocket (ws_generic)
This directory provides two Nginx server templates for reverse proxying Dockerized applications behind Nginx:
- `basic.conf.j2`
- `ws_generic.conf.j2`
---
## When to Use Which Template?
### 1. `basic.conf.j2`
**Use this template for standard HTTP/S applications.**
It is optimized for typical web applications (e.g., static sites, PHP, Node.js, Django, etc.) that do **not** require persistent, bidirectional WebSocket connections.
- **Features:**
- HTTP/2 support, TLS/SSL integration
- Reverse proxy with buffering enabled (`proxy_buffering on`)
- Allows advanced content filtering (e.g., via Lua body/headers)
- Suitable for most REST APIs, web frontends, and admin panels
- **Pros:**
- Enables HTML/body manipulation (for injecting snippets, analytics, CSP, etc.)
- Optimized for efficient caching and GZIP compression
- Good default for "normal" web traffic
- **Cons:**
- **Not** suitable for WebSocket endpoints (buffering can break WS)
- Slightly more latency for streaming data due to buffering
---
### 2. `ws_generic.conf.j2`
**Use this template for applications requiring WebSocket support.**
Designed for services (e.g., chat servers, real-time dashboards) needing fast, persistent connections using the WebSocket protocol.
- **Features:**
- WebSocket-aware: `proxy_buffering off`, special upgrade headers
- Supports standard HTTP/S traffic alongside WebSockets
- Proper handling of connection upgrades and protocol switching
- **Pros:**
- Required for all WebSocket endpoints
- Allows instant, low-latency bidirectional traffic
- Prevents data loss or connection drops due to proxy buffering
- **Cons:**
- Disables body/content filtering and response manipulation
- No buffering means less effective for caching/optimization
- Not suitable for scenarios requiring Lua/JS content injection
---
## Summary Table
| Use Case | Template | Buffering | WebSocket? | Can Filter Content? |
|--------------------------|---------------------|-----------|------------|--------------------|
| Static/Classic Website | `basic.conf.j2` | On | No | Yes |
| REST API | `basic.conf.j2` | On | No | Yes |
| Real-Time Chat/App | `ws_generic.conf.j2`| Off | Yes | No |
| Dashboard w/Live Data | `ws_generic.conf.j2`| Off | Yes | No |
| Needs HTML Injection | `basic.conf.j2` | On | No | Yes |
---
## Good to Know
- **Never enable buffering for true WebSocket connections!**
Use `proxy_buffering off;` (as in `ws_generic.conf.j2`) or connections may fail.
- For most classic web applications, use the **basic template**.
- For apps where you want to inject or modify HTML (e.g., analytics scripts), **only the basic template** supports this.
---
## Author & Project
By [Kevin Veen-Birkenbach](https://www.veen.world)
Part of the [CyMaIS Project](https://s.veen.world/cymais)
Licensed under the [CyMaIS NonCommercial License (CNCL)](https://s.veen.world/cncl)

View File

@ -6,7 +6,7 @@ server
{% include 'roles/web-app-oauth2-proxy/templates/endpoint.conf.j2'%}
{% endif %}
{% include 'roles/srv-web-7-7-inj-compose/templates/global.includes.lua.j2'%}
{% include 'roles/srv-web-7-7-inj-compose/templates/server.conf.j2'%}
{% if proxy_extra_configuration is defined %}
{# Additional Domain Specific Configuration #}
@ -15,9 +15,6 @@ server
{% include 'roles/srv-web-7-7-letsencrypt/templates/ssl_header.j2' %}
{% if applications | get_app_conf(application_id, 'features.logout', False) or domain == primary_domain %}
{% include 'roles/web-svc-logout/templates/logout-proxy.conf.j2' %}
{% endif %}
{% if applications | get_app_conf(application_id, 'features.oauth2', False) %}
{% set acl = applications | get_app_conf(application_id, 'oauth2_proxy.acl', False, {}) %}
@ -25,38 +22,38 @@ server
{# 1. Expose everything by default, then protect blacklisted paths #}
{% set oauth2_proxy_enabled = false %}
{% set location = "/" %}
{% include 'roles/srv-proxy-7-4-core/templates/location/proxy_basic.conf.j2' %}
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
{% for loc in acl.blacklist %}
{% set oauth2_proxy_enabled = true %}
{% set location = loc %}
{% include 'roles/srv-proxy-7-4-core/templates/location/proxy_basic.conf.j2' %}
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
{% endfor %}
{% elif acl.whitelist is defined %}
{# 2. Protect everything by default, then expose whitelisted paths #}
{% set oauth2_proxy_enabled = true %}
{% set location = "/" %}
{% include 'roles/srv-proxy-7-4-core/templates/location/proxy_basic.conf.j2' %}
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
{% for loc in acl.whitelist %}
{% set oauth2_proxy_enabled = false %}
{% set location = loc %}
{% include 'roles/srv-proxy-7-4-core/templates/location/proxy_basic.conf.j2' %}
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
{% endfor %}
{% else %}
{# 3. OAuth2 enabled but no (or empty) ACL — protect all #}
{% set oauth2_proxy_enabled = true %}
{% set location = "/" %}
{% include 'roles/srv-proxy-7-4-core/templates/location/proxy_basic.conf.j2' %}
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
{% endif %}
{% else %}
{# 4. OAuth2 completely disabled — expose all #}
{% set oauth2_proxy_enabled = false %}
{% set location = "/" %}
{% include 'roles/srv-proxy-7-4-core/templates/location/proxy_basic.conf.j2' %}
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
{% endif %}
}

View File

@ -7,7 +7,8 @@ server {
server_name {{ domain }};
{% include 'roles/srv-web-7-7-letsencrypt/templates/ssl_header.j2' %}
{% include 'roles/srv-web-7-7-inj-compose/templates/global.includes.lua.j2' %}
{% include 'roles/srv-web-7-7-inj-compose/templates/server.conf.j2' %}
client_max_body_size {{ client_max_body_size | default('100m') }};
keepalive_timeout 70;
@ -24,26 +25,10 @@ server {
add_header Strict-Transport-Security "max-age=31536000";
{% include 'roles/srv-proxy-7-4-core/templates/location/proxy_basic.conf.j2' %}
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
{% if applications | get_app_conf(application_id, 'features.logout', False) or domain == primary_domain %}
{% include 'roles/web-svc-logout/templates/logout-proxy.conf.j2' %}
{% endif %}
{% if ws_path is defined %}
location {{ ws_path }} {
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 https;
proxy_pass http://127.0.0.1:{{ ws_port }};
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
tcp_nodelay on;
}
{% if location_ws is defined %}
{% include 'roles/srv-proxy-7-4-core/templates/location/ws.conf.j2' %}
{% endif %}
error_page 500 501 502 503 504 /500.html;

View File

@ -8,6 +8,9 @@ events
http
{
include mime.types;
{# default_type application/octet-stream; If html filter does not work, this one needs to be used#}
default_type text/html;
{# caching #}

View File

@ -2,7 +2,7 @@
- name: Create or update Cloudflare A-record for {{ item }}
community.general.cloudflare_dns:
api_token: "{{ cloudflare_api_token }}"
api_token: "{{ certbot_dns_api_token }}"
zone: "{{ item.split('.')[-2:] | join('.') }}"
state: present
type: A

View File

@ -1,12 +1,22 @@
{% set modifier_css_enabled = applications | get_app_conf(application_id, 'features.css', false) | bool %}
{% if modifier_css_enabled %}
{%- include 'roles/srv-web-7-7-inj-css/templates/location.conf.j2' -%}
{% endif %}
lua_need_request_body on;
header_filter_by_lua_block {
local ct = ngx.header.content_type or ""
if ct:lower():find("^text/html") then
ngx.ctx.is_html = true
else
ngx.ctx.is_html = false
end
}
body_filter_by_lua_block {
-- initialize buffer
-- only apply further processing if this is an HTML response
if not ngx.ctx.is_html then
return
end
-- initialize or reuse the buffer
ngx.ctx.buf = ngx.ctx.buf or {}
local chunk, eof = ngx.arg[1], ngx.arg[2]
@ -15,18 +25,22 @@ body_filter_by_lua_block {
end
if not eof then
-- drop intermediate chunks; well emit only on eof
ngx.arg[1] = nil
return
end
-- on eof: concatenate and reset buffer
-- on eof: concatenate all buffered chunks
local whole = table.concat(ngx.ctx.buf)
ngx.ctx.buf = nil
ngx.ctx.buf = nil -- clear buffer
{# whole = string.gsub(whole, "</body>", "<!-- injected text2 -->\n</body>")
ngx.arg[1] = whole #}
-- build head-injection snippets
-- build a list of head-injection snippets
local head_snippets = {}
{% for head_feature in ['css', 'matomo', 'port-ui-desktop', 'javascript', 'logout'] %}
{# Deactivated 'logout' temporary due to chunk size. Needs an CDN. #}
{% for head_feature in ['css', 'matomo', 'port-ui-desktop', 'javascript' ] %}
{% if applications | get_app_conf(application_id, 'features.' ~ head_feature, false) | bool %}
head_snippets[#head_snippets + 1] = [=[
{%- include "roles/srv-web-7-7-inj-" ~ head_feature ~ "/templates/head_sub.j2" -%}
@ -34,19 +48,19 @@ body_filter_by_lua_block {
{% endif %}
{% endfor %}
-- inject into </head>
-- inject all collected snippets right before </head>
local head_payload = table.concat(head_snippets, "\n") .. "</head>"
whole = string.gsub(whole, "</head>", head_payload)
{% if applications | get_app_conf(application_id, 'features.matomo', false) | bool %}
-- build Matomo noscript tracking for body
-- build Matomo noscript snippet for the body
local body_matomo = [=[
{%- include 'roles/srv-web-7-7-inj-matomo/templates/body_sub.j2' -%}
]=]
-- inject before </body>
-- inject it right before </body>
whole = string.gsub(whole, "</body>", body_matomo)
{% endif %}
-- finally send the modified HTML out
ngx.arg[1] = whole
}
}

View File

@ -0,0 +1,9 @@
{% set modifier_css_enabled = applications | get_app_conf(application_id, 'features.css', false) | bool %}
{% if modifier_css_enabled %}
{%- include 'roles/srv-web-7-7-inj-css/templates/location.conf.j2' -%}
{% endif %}
{% set modifier_logout_enabled = applications | get_app_conf(application_id, 'features.logout', False) or domain == primary_domain %}
{% if modifier_logout_enabled %}
{% include 'roles/web-svc-logout/templates/logout-proxy.conf.j2' %}
{% endif %}

View File

@ -85,7 +85,7 @@
// Initial scan
scanAndPatch(document.querySelectorAll('*'));
{#
// MutationObserver for dynamic content
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
@ -97,4 +97,5 @@
});
observer.observe(document.body, { childList: true, subtree: true });
#}
})();

View File

@ -0,0 +1,63 @@
# Caching in OpenResty and Cloudflare
## Overview
When deploying OpenResty as a reverse proxy, content may be cached at multiple layers:
- **Local Proxy Cache:** If you configure Nginx/OpenResty with a cache zone (using directives like `proxy_cache`), responses can be stored locally on disk and served to future clients.
- **Browser Cache:** Browsers cache responses based on HTTP headers like `Cache-Control` or `Expires`.
- **CDN Cache (Cloudflare):** If your domain is proxied through Cloudflare, Cloudflare may cache your content at their edge servers and serve it from there, often without requests reaching your origin server.
## Troubleshooting Cache Issues
Caching can cause problems, especially when you update your web content or configuration but still receive outdated responses. Typical symptoms include:
- Changes to HTML/CSS/JS are not visible immediately.
- Old redirects or headers persist even after config changes.
- Assets do not update after a deployment.
If this happens, always consider all caching layers:
1. **Browser:** Clear the browser cache or use a private window.
2. **OpenResty:** If using a proxy cache, purge or clear the cache directory, or temporarily disable the cache zone.
3. **Cloudflare:** Purge the CDN cache as described below.
## Purging Cloudflare Cache
Cloudflare aggressively caches static content by default. Even after you deploy new files or update your proxy configuration, users may continue to see cached versions until the cache expires.
### Manual Purge via Cloudflare Dashboard
1. Log into your Cloudflare dashboard at [https://dash.cloudflare.com](https://dash.cloudflare.com).
2. Select your domain.
3. Go to **Caching****Configuration**.
4. Click **Purge Cache**.
- Choose **Purge Everything** to delete all cached files (recommended after a deployment).
- Or use **Custom Purge** to specify individual URLs.
### Purge with Cloudflare API
You can also purge the cache programmatically with Cloudflares API:
```bash
curl -X POST "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/purge_cache" \
-H "Authorization: Bearer <YOUR_API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}'
````
* Replace `<ZONE_ID>` with your Cloudflare Zone ID.
* Replace `<YOUR_API_TOKEN>` with a valid API token with cache purge permissions.
To find your Zone ID, go to the overview page for your domain in the Cloudflare dashboard.
**Note:** It can take a few seconds for the cache to be purged globally.
## Recommendations
* Always purge the Cloudflare cache after significant changes to your website or OpenResty/Nginx configuration.
* If you use custom cache rules in OpenResty, consider providing cache-busting mechanisms (e.g., versioned URLs).
* Test changes in a private/incognito window to rule out browser cache.
## Further Reading
* [Cloudflare Purge Cache Documentation](https://developers.cloudflare.com/cache/how-to/purge-cache/)
* [Nginx/OpenResty Proxy Cache Guide](https://openresty.org/en/using-ngx_lua.html#caching)

View File

@ -3,13 +3,13 @@ server {
{% include 'roles/srv-web-7-7-letsencrypt/templates/ssl_header.j2' %}
{% include 'roles/srv-web-7-7-inj-compose/templates/global.includes.lua.j2'%}
{% include 'roles/srv-web-7-7-inj-compose/templates/server.conf.j2'%}
{% include 'roles/srv-proxy-7-4-core/templates/headers/content_security_policy.conf.j2' %}
{% include 'roles/srv-proxy-7-4-core/templates/location/proxy_basic.conf.j2' %}
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
{% set location = '^~ /cool/' %}
{% include 'roles/srv-proxy-7-4-core/templates/location/proxy_basic.conf.j2' %}
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
}

View File

@ -1,6 +1,6 @@
application_id: "web-app-espocrm"
database_type: "mariadb"
ws_path: "/ws"
location_ws: "/ws"
ws_port: "{{ ports.localhost.websocket[application_id] }}"
client_max_body_size: "100m"
vhost_flavour: "ws_generic"

View File

@ -11,7 +11,7 @@
loop_var: domain
vars:
http_port: "{{ ports.localhost.http[application_id] }}"
ws_path: "/api/v1/streaming"
location_ws: "/api/v1/streaming"
ws_port: "{{ ports.localhost.websocket[application_id] }}"
client_max_body_size: "80m"
vhost_flavour: "ws_generic"

View File

@ -3,19 +3,16 @@ server {
{# Could be that this is related to the set_fact use #}
{% set domain = domains[application_id].synapse %}
{% set http_port = ports.localhost.http['web-app-matrix_synapse'] %}
{% set federation_port = ports.public.federation['web-app-matrix_synapse'] %}
server_name {{domains[application_id].synapse}};
{% include 'roles/srv-web-7-7-letsencrypt/templates/ssl_header.j2' %}
# For the federation port
listen 8448 ssl default_server;
listen [::]:8448 ssl default_server;
listen {{ federation_port }} ssl default_server;
listen [::]:{{ federation_port }} ssl default_server;
{% include 'roles/srv-web-7-7-inj-compose/templates/global.includes.lua.j2'%}
{% include 'roles/srv-proxy-7-4-core/templates/location/proxy_basic.conf.j2' %}
{% if applications | get_app_conf(application_id, 'features.logout', False) %}
{% include 'roles/web-svc-logout/templates/logout-proxy.conf.j2' %}
{% endif %}
{% include 'roles/srv-web-7-7-inj-compose/templates/server.conf.j2'%}
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
}

View File

@ -69,6 +69,9 @@ performance:
memory_limit: "{{ ((ansible_memtotal_mb | int) / 30)|int }}M" # Dynamic set memory limit
upload_limit: "5G" # Set upload limit to 5GB for big media files
opcache_memory_consumption: "{{ ((ansible_memtotal_mb | int) / 30)|int }}M" # Dynamic set memory consumption
plugins_enabled: true # Implemented for speeding up testing and debugging process. For productive environments keep it true and steer the apps via the plugins config
plugins:
# List for Nextcloud Plugin Routine
# Decides if plugins should be activated or deactivated

View File

@ -49,6 +49,7 @@
vars:
plugin_key: "{{ plugin_item.key }}"
plugin_value: "{{ plugin_item.value }}"
when: nextcloud_plugins_enabled
- name: Load system configuration
include_tasks: 03_system.yml

View File

@ -29,7 +29,7 @@ NEXTCLOUD_ADMIN_PASSWORD= "{{applications | get_app_conf(application_id, '
# Security
NEXTCLOUD_TRUSTED_DOMAINS= "{{ nextcloud_domains }}"
NEXTCLOUD_TRUSTED_DOMAINS= "{{ domains[application_id] | select | join(',') }}"
# Whitelist local docker gateway in Nextcloud to prevent brute-force throtteling
TRUSTED_PROXIES= "{{ networks.internet.values() | select | join(',') }}"
OVERWRITECLIURL= "{{ domains | get_url(application_id, web_protocol) }}"

View File

@ -6,7 +6,8 @@ server
{% include 'roles/srv-web-7-7-letsencrypt/templates/ssl_header.j2' %}
{% include 'roles/srv-web-7-7-inj-compose/templates/global.includes.lua.j2'%}
{% include 'roles/srv-web-7-7-inj-compose/templates/server.conf.j2'%}
# Remove X-Powered-By, which is an information leak
fastcgi_hide_header X-Powered-By;
@ -18,11 +19,7 @@ server
client_body_buffer_size 400M;
fastcgi_buffers 64 4K;
{% include 'roles/srv-proxy-7-4-core/templates/location/proxy_basic.conf.j2' %}
{% if applications | get_app_conf(application_id, 'features.logout', False) %}
{% include 'roles/web-svc-logout/templates/logout-proxy.conf.j2' %}
{% endif %}
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
location ^~ /.well-known {
rewrite ^/\.well-known/host-meta\.json /public.php?service=host-meta-json last;

View File

@ -6,6 +6,7 @@ container_port: 80
# Database
database_password: "{{ applications | get_app_conf(application_id, 'credentials.database_password', True)}}"
database_type: "mariadb" # Database flavor
nextcloud_plugins_enabled: "{{ applications | get_app_conf(application_id, 'plugins_enabled', True) }}"
# Networking
domain: "{{ domains | get_domain(application_id) }}" # Public domain at which Nextcloud will be accessable
@ -23,15 +24,13 @@ nextcloud_control_node_plugin_tasks_directory: "{{role_path}}/tasks/plugins/"
nextcloud_host_config_additives_directory: "{{ docker_compose.directories.volumes }}cymais/" # This folder is the path to which the additive configurations will be copied
nextcloud_host_include_instructions_file: "{{ docker_compose.directories.volumes }}includes.php" # Path to the instruction file on the host. Responsible for loading the additional configurations
nextcloud_domains: "{{ domains | get_domain(application_id) }}" # This is wrong and should be optimized @todo implement support for multiple domains
# Docker
nextcloud_volume: "{{ applications | get_app_conf(application_id, 'docker.volumes.data', True) }}"
nextcloud_version: "{{ applications | get_app_conf(application_id, 'docker.services.nextcloud.version', True) }}"
nextcloud_image: "{{ applications | get_app_conf(application_id, 'docker.services.nextcloud.image', True) }}"
nextcloud_container: "{{ applications | get_app_conf(application_id, 'docker.services.nextcloud.name', True) }}"
nextcloud_container: "{{ applications | get_app_conf(application_id, 'docker.services.nextcloud.name', True) }}"
nextcloud_proxy_name: "{{ applications | get_app_conf(application_id, 'docker.services.proxy.name', True) }}"
nextcloud_proxy_image: "{{ applications | get_app_conf(application_id, 'docker.services.proxy.image', True) }}"

View File

@ -3,7 +3,7 @@ server {
{% include 'roles/srv-web-7-7-letsencrypt/templates/ssl_header.j2' %}
{% include 'roles/srv-web-7-7-inj-compose/templates/global.includes.lua.j2'%}
{% include 'roles/srv-web-7-7-inj-compose/templates/server.conf.j2'%}
{% include 'roles/srv-proxy-7-4-core/templates/headers/content_security_policy.conf.j2' %}
@ -11,35 +11,18 @@ server {
# Application
##
location @api {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
client_max_body_size 100k; # default is 1M
proxy_connect_timeout 10m;
proxy_send_timeout 10m;
proxy_read_timeout 10m;
send_timeout 10m;
#adapt
proxy_pass http://127.0.0.1:{{ports.localhost.http[application_id]}};
}
{% if applications | get_app_conf(application_id, 'features.logout', False) %}
{% include 'roles/web-svc-logout/templates/logout-proxy.conf.j2' %}
{% endif %}
{% set location = "@html" %}
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2' %}
location / {
try_files /dev/null @api;
try_files /dev/null {{ location }};
}
location = /api/v1/videos/upload-resumable {
client_max_body_size 0;
proxy_request_buffering off;
try_files /dev/null @api;
try_files /dev/null {{ location }};
}
location ~ ^/api/v1/videos/(upload|([^/]+/studio/edit))$ {
@ -47,33 +30,25 @@ server {
client_max_body_size 12G; # default is 1M
add_header X-File-Maximum-Size 8G always; # inform backend of the set value in bytes before mime-encoding (x * 1.4 >= client_max_body_size)
try_files /dev/null @api;
try_files /dev/null {{ location }};
}
location ~ ^/api/v1/(videos|video-playlists|video-channels|users/me) {
client_max_body_size 6M; # default is 1M
add_header X-File-Maximum-Size 4M always; # inform backend of the set value in bytes before mime-encoding (x * 1.4 >= client_max_body_size)
try_files /dev/null @api;
try_files /dev/null {{ location }};
}
##
# Websocket
##
location @api_websocket {
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:{{ports.localhost.http[application_id]}};
}
{% set location_ws = "@websocket" %}
{% include 'roles/srv-proxy-7-4-core/templates/location/ws.conf.j2' %}
location /socket.io {
try_files /dev/null @api_websocket;
try_files /dev/null {{ location_ws }};
}
location /tracker/socket {
@ -81,6 +56,6 @@ server {
# Don't close the websocket before then
proxy_read_timeout 15m; # default is 60s
try_files /dev/null @api_websocket;
try_files /dev/null {{ location_ws }};
}
}

View File

@ -6,7 +6,7 @@ server
{% include 'roles/web-app-oauth2-proxy/templates/endpoint.conf.j2'%}
{% endif %}
{% include 'roles/srv-web-7-7-inj-compose/templates/global.includes.lua.j2'%}
{% include 'roles/srv-web-7-7-inj-compose/templates/server.conf.j2'%}
{% if proxy_extra_configuration is defined %}
{# Additional Domain Specific Configuration #}
@ -17,6 +17,6 @@ server
{% for path in syncope_paths.values() %}
{% set location = web_protocol ~ '://' ~ domains | get_domain(application_id) ~ '/' ~ path ~ '/' %}
{% include 'roles/srv-proxy-7-4-core/templates/location/proxy_basic.conf.j2'%}
{% include 'roles/srv-proxy-7-4-core/templates/location/html.conf.j2'%}
{% endfor %}
}

View File

@ -4,7 +4,7 @@ server
{% include 'roles/srv-web-7-7-letsencrypt/templates/ssl_header.j2' %}
{% include 'roles/srv-web-7-7-inj-compose/templates/global.includes.lua.j2'%}
{% include 'roles/srv-web-7-7-inj-compose/templates/server.conf.j2'%}
{% include 'roles/srv-proxy-7-4-core/templates/headers/content_security_policy.conf.j2' %}
charset utf-8;
@ -15,6 +15,7 @@ server
autoindex on; {# Enable directory listing #}
autoindex_exact_size off; {# Display sizes in a human-readable format #}
autoindex_localtime on; {# Show local time #}
{% include 'roles/srv-web-7-7-inj-compose/templates/location.lua.j2' %}
}
location /.well-known/ {

View File

@ -4,7 +4,7 @@ server
{% include 'roles/srv-web-7-7-letsencrypt/templates/ssl_header.j2' %}
{% include 'roles/srv-web-7-7-inj-compose/templates/global.includes.lua.j2'%}
{% include 'roles/srv-web-7-7-inj-compose/templates/server.conf.j2'%}
{% include 'roles/srv-proxy-7-4-core/templates/headers/content_security_policy.conf.j2' %}
charset utf-8;
@ -13,6 +13,7 @@ server
{
root {{nginx.directories.data.html}};
index index.html index.htm;
{% include 'roles/srv-web-7-7-inj-compose/templates/location.lua.j2' %}
}
location /.well-known/ {

View File

@ -0,0 +1,35 @@
import unittest
from ansible.errors import AnsibleFilterError
# Import the filter plugin
from filter_plugins.to_primary_domain import FilterModule
class TestToPrimaryDomain(unittest.TestCase):
def setUp(self):
self.filtermod = FilterModule()
def test_valid_domains(self):
cases = [
("example.com", "example.com"),
("www.example.com", "example.com"),
("foo.bar.example.com", "example.com"),
("mail.test.example.co.uk", "example.co.uk"),
("test.foo.bar.jp", "bar.jp"),
]
for input_domain, expected in cases:
with self.subTest(domain=input_domain):
self.assertEqual(self.filtermod.to_primary_domain(input_domain), expected)
def test_invalid_domains(self):
invalid_cases = [
"localhost", # not a real domain
1234, # not a string
"", # empty string
]
for input_domain in invalid_cases:
with self.subTest(domain=input_domain):
with self.assertRaises(AnsibleFilterError):
self.filtermod.to_primary_domain(input_domain)
if __name__ == "__main__":
unittest.main()