mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-11-18 19:16:27 +00:00
Compare commits
3 Commits
a327adf8db
...
feature/ke
| Author | SHA1 | Date | |
|---|---|---|---|
| 6fcf6a1ab6 | |||
| 4d9890406e | |||
| 59b652958f |
@@ -14,6 +14,32 @@ Designed for Archlinux systems, this role periodically checks whether web resour
|
||||
- **Domain Extraction:** Parses all `.conf` files in the NGINX config folder to determine the list of domains to check.
|
||||
- **Automated Execution:** Registers a systemd service and timer for recurring health checks.
|
||||
- **Error Notification:** Integrates with `sys-ctl-alm-compose` for alerting on failure.
|
||||
- **Ignore List Support:** Optional variable to suppress network block reports from specific external domains.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Variables
|
||||
|
||||
- **`HEALTH_CSP_IGNORE_NETWORK_BLOCKS_FROM`** (list, default: `[]`)
|
||||
Optional list of domains whose network block failures (e.g., ORB) should be ignored during CSP checks.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
HEALTH_CSP_IGNORE_NETWORK_BLOCKS_FROM:
|
||||
- pxscdn.com
|
||||
- cdn.example.org
|
||||
```
|
||||
|
||||
This will run the CSP checker with:
|
||||
|
||||
```bash
|
||||
checkcsp start --short --ignore-network-blocks-from pxscdn.com -- cdn.example.org <domains...>
|
||||
```
|
||||
|
||||
### Systemd Integration
|
||||
|
||||
The role configures a systemd service and timer which executes the CSP crawler periodically against all NGINX domains.
|
||||
|
||||
## License
|
||||
|
||||
@@ -24,4 +50,4 @@ Infinito.Nexus NonCommercial License
|
||||
|
||||
Kevin Veen-Birkenbach
|
||||
Consulting & Coaching Solutions
|
||||
[https://www.veen.world](https://www.veen.world)
|
||||
[https://www.veen.world](https://www.veen.world)
|
||||
|
||||
5
roles/sys-ctl-hlth-csp/defaults/main.yml
Normal file
5
roles/sys-ctl-hlth-csp/defaults/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
# List of domains whose network block failures (e.g., ORB) should be ignored
|
||||
# during CSP checks. This is useful for suppressing known external resources
|
||||
# (e.g., third-party CDNs) that cannot be influenced but otherwise cause
|
||||
# unnecessary alerts in the crawler reports.
|
||||
HEALTH_CSP_IGNORE_NETWORK_BLOCKS_FROM: []
|
||||
@@ -21,11 +21,20 @@ def extract_domains(config_path):
|
||||
print(f"Directory {config_path} not found.", file=sys.stderr)
|
||||
return None
|
||||
|
||||
def run_checkcsp(domains):
|
||||
def run_checkcsp(domains, ignore_network_blocks_from):
|
||||
"""
|
||||
Executes the 'checkcsp' command with the given domains.
|
||||
Executes the 'checkcsp' command with the given domains and optional ignores.
|
||||
"""
|
||||
cmd = ["checkcsp", "start", "--short"] + domains
|
||||
cmd = ["checkcsp", "start", "--short"]
|
||||
|
||||
# pass through ignore list only if not empty
|
||||
if ignore_network_blocks_from:
|
||||
cmd.append("--ignore-network-blocks-from")
|
||||
cmd.extend(ignore_network_blocks_from)
|
||||
cmd.append("--")
|
||||
|
||||
cmd += domains
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, check=True)
|
||||
return result.returncode
|
||||
@@ -45,6 +54,12 @@ def main():
|
||||
required=True,
|
||||
help="Directory containing NGINX .conf files"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ignore-network-blocks-from",
|
||||
nargs="*",
|
||||
default=[],
|
||||
help="Optional: one or more domains whose network block failures should be ignored"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
domains = extract_domains(args.nginx_config_dir)
|
||||
@@ -55,7 +70,7 @@ def main():
|
||||
print("No domains found to check.")
|
||||
sys.exit(0)
|
||||
|
||||
rc = run_checkcsp(domains)
|
||||
rc = run_checkcsp(domains, args.ignore_network_blocks_from)
|
||||
sys.exit(rc)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
system_service_timer_enabled: true
|
||||
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
|
||||
system_service_tpl_timeout_start_sec: "{{ CURRENT_PLAY_DOMAINS_ALL | timeout_start_sec_for_domains }}"
|
||||
system_service_tpl_exec_start: "{{ system_service_script_exec }} --nginx-config-dir={{ NGINX.DIRECTORIES.HTTP.SERVERS }}"
|
||||
system_service_tpl_exec_start: >-
|
||||
{{ system_service_script_exec }}
|
||||
--nginx-config-dir={{ NGINX.DIRECTORIES.HTTP.SERVERS }}
|
||||
--ignore-network-blocks-from {{ HEALTH_CSP_IGNORE_NETWORK_BLOCKS_FROM | join(' ') }}
|
||||
|
||||
- include_tasks: utils/run_once.yml
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
load_dependencies: True # When set to false the dependencies aren't loaded. Helpful for developing
|
||||
load_dependencies: True # When set to false the dependencies aren't loaded. Helpful for developing
|
||||
actions:
|
||||
import_realm: True # Import REALM
|
||||
import_realm: True # Import REALM
|
||||
create_automation_client: True
|
||||
features:
|
||||
matomo: true
|
||||
css: true
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
shell: "{{ KEYCLOAK_EXEC_KCADM }} get client-scopes -r {{ KEYCLOAK_REALM }} --format json"
|
||||
register: all_scopes
|
||||
changed_when: false
|
||||
failed_when: "'HTTP 401' in (all_scopes.stderr | default(''))"
|
||||
|
||||
- name: Extract RBAC scope id
|
||||
set_fact:
|
||||
|
||||
63
roles/web-app-keycloak/tasks/05a_service_account.yml
Normal file
63
roles/web-app-keycloak/tasks/05a_service_account.yml
Normal file
@@ -0,0 +1,63 @@
|
||||
# Creates a confidential client with service account, fetches the secret,
|
||||
# and grants realm-management/realm-admin to its service-account user.
|
||||
|
||||
- name: "Ensure automation client exists (confidential + service accounts)"
|
||||
shell: |
|
||||
{{ KEYCLOAK_EXEC_KCADM }} create clients -r {{ KEYCLOAK_REALM }} \
|
||||
-s clientId={{ KEYCLOAK_AUTOMATION_CLIENT_ID }} \
|
||||
-s protocol=openid-connect \
|
||||
-s publicClient=false \
|
||||
-s serviceAccountsEnabled=true \
|
||||
-s directAccessGrantsEnabled=false
|
||||
register: create_client
|
||||
changed_when: create_client.rc == 0
|
||||
failed_when: create_client.rc != 0 and ('already exists' not in (create_client.stderr | lower))
|
||||
|
||||
- name: "Resolve automation client id"
|
||||
shell: >
|
||||
{{ KEYCLOAK_EXEC_KCADM }} get clients -r {{ KEYCLOAK_REALM }}
|
||||
--query 'clientId={{ KEYCLOAK_AUTOMATION_CLIENT_ID }}' --fields id --format json | jq -r '.[0].id'
|
||||
register: auto_client_id_cmd
|
||||
changed_when: false
|
||||
|
||||
- name: "Fail if client id could not be resolved"
|
||||
assert:
|
||||
that:
|
||||
- "(auto_client_id_cmd.stdout | trim) is match('^[0-9a-f-]+$')"
|
||||
fail_msg: "Automation client id could not be resolved."
|
||||
|
||||
- name: "Read client secret"
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
shell: >
|
||||
{{ KEYCLOAK_EXEC_KCADM }} get clients/{{ auto_client_id_cmd.stdout | trim }}/client-secret
|
||||
-r {{ KEYCLOAK_REALM }} --format json | jq -r .value
|
||||
register: auto_client_secret_cmd
|
||||
changed_when: false
|
||||
|
||||
- name: "Expose client secret as a fact"
|
||||
set_fact:
|
||||
KEYCLOAK_AUTOMATION_CLIENT_SECRET: "{{ auto_client_secret_cmd.stdout | trim }}"
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Grant {{ KEYCLOAK_AUTOMATION_GRANT_ROLE }} to service account"
|
||||
shell: >
|
||||
{{ KEYCLOAK_EXEC_KCADM }} add-roles -r {{ KEYCLOAK_REALM }}
|
||||
--uusername service-account-{{ KEYCLOAK_AUTOMATION_CLIENT_ID }}
|
||||
--cclientid realm-management
|
||||
--rolename {{ KEYCLOAK_AUTOMATION_GRANT_ROLE }}
|
||||
register: grant_role
|
||||
changed_when: grant_role.rc == 0
|
||||
failed_when: grant_role.rc != 0 and ('already exists' not in (grant_role.stderr | lower))
|
||||
|
||||
- name: "Verify client-credentials login works"
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
shell: >
|
||||
{{ KEYCLOAK_EXEC_KCADM }} config credentials
|
||||
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }}
|
||||
--realm {{ KEYCLOAK_REALM }}
|
||||
--client {{ KEYCLOAK_AUTOMATION_CLIENT_ID }}
|
||||
--client-secret {{ KEYCLOAK_AUTOMATION_CLIENT_SECRET }} &&
|
||||
{{ KEYCLOAK_EXEC_KCADM }} get realms/{{ KEYCLOAK_REALM }} --format json | jq -r '.realm'
|
||||
register: verify_cc
|
||||
changed_when: false
|
||||
failed_when: (verify_cc.rc != 0) or ((verify_cc.stdout | trim) != (KEYCLOAK_REALM | trim))
|
||||
@@ -36,6 +36,42 @@
|
||||
--password {{ KEYCLOAK_MASTER_API_USER_PASSWORD }}
|
||||
changed_when: false
|
||||
|
||||
- name: Verify kcadm session works (quick read)
|
||||
shell: >
|
||||
{{ KEYCLOAK_EXEC_KCADM }} get realms --format json | jq -r '.[0].realm' | head -n1
|
||||
register: kcadm_verify
|
||||
changed_when: false
|
||||
failed_when: >
|
||||
(kcadm_verify.rc != 0)
|
||||
or ('HTTP 401' in (kcadm_verify.stderr | default('')))
|
||||
or ((kcadm_verify.stdout | trim) == '')
|
||||
|
||||
# --- Create & grant automation service account (Option A) ---
|
||||
- name: "Ensure automation service account client (Option A)"
|
||||
include_tasks: 05a_service_account.yml
|
||||
when: applications | get_app_conf(application_id, 'actions.create_automation_client', True)
|
||||
|
||||
# --- Switch session to the service account for all subsequent API work ---
|
||||
- name: kcadm login (realm) using service account
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
shell: >
|
||||
{{ KEYCLOAK_EXEC_KCADM }} config credentials
|
||||
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }}
|
||||
--realm {{ KEYCLOAK_REALM }}
|
||||
--client {{ KEYCLOAK_AUTOMATION_CLIENT_ID }}
|
||||
--client-secret {{ KEYCLOAK_AUTOMATION_CLIENT_SECRET }}
|
||||
changed_when: false
|
||||
|
||||
- name: Verify kcadm session works (exact realm via service account)
|
||||
shell: >
|
||||
{{ KEYCLOAK_EXEC_KCADM }} get realms/{{ KEYCLOAK_REALM }} --format json | jq -r '.realm'
|
||||
register: kcadm_verify_sa
|
||||
changed_when: false
|
||||
failed_when: >
|
||||
(kcadm_verify_sa.rc != 0)
|
||||
or ('HTTP 401' in (kcadm_verify_sa.stderr | default('')))
|
||||
or ((kcadm_verify_sa.stdout | trim) != (KEYCLOAK_REALM | trim))
|
||||
|
||||
- name: "Update Client settings"
|
||||
vars:
|
||||
kc_object_kind: "client"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# General
|
||||
application_id: "web-app-keycloak" # Internal Infinito.Nexus application id
|
||||
database_type: "postgres" # Database which will be used
|
||||
application_id: "web-app-keycloak" # Internal Infinito.Nexus application id
|
||||
database_type: "postgres" # Database which will be used
|
||||
|
||||
# Keycloak
|
||||
|
||||
@@ -34,9 +34,16 @@ KEYCLOAK_ADMIN_PASSWORD: "{{ applications | get_app_conf(application_
|
||||
|
||||
## Docker
|
||||
KEYCLOAK_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.name') }}" # Name of the keycloak docker container
|
||||
KEYCLOAK_EXEC_KCADM: "docker exec -i {{ KEYCLOAK_CONTAINER }} /opt/keycloak/bin/kcadm.sh" # Init script for keycloak
|
||||
KEYCLOAK_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.image') }}" # Keycloak docker image
|
||||
KEYCLOAK_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.version') }}" # Keycloak docker version
|
||||
KEYCLOAK_KCADM_CONFIG: "/opt/keycloak/data/kcadm.config"
|
||||
KEYCLOAK_EXEC_KCADM: "docker exec -i {{ KEYCLOAK_CONTAINER }} /opt/keycloak/bin/kcadm.sh --config {{ KEYCLOAK_KCADM_CONFIG }}"
|
||||
|
||||
## Automation Service Account (Option A)
|
||||
KEYCLOAK_AUTOMATION_CLIENT_ID: "infinito-automation"
|
||||
KEYCLOAK_AUTOMATION_GRANT_ROLE: "realm-admin" # or granular roles if you prefer
|
||||
# Will be discovered dynamically and set as a fact during the run:
|
||||
# KEYCLOAK_AUTOMATION_CLIENT_SECRET
|
||||
|
||||
## Server
|
||||
KEYCLOAK_SERVER_HOST: "127.0.0.1:{{ ports.localhost.http[application_id] }}"
|
||||
|
||||
Reference in New Issue
Block a user