r"""Automate provisioning of Authentik OAuth2/OIDC Providers & Applications for both Synology NAS units. Pre-configures: 1. Dedicated Authentik OAuth2 Provider for SnarfNAS (GA): - Redirect URI: https://snarfnas.snarfnet.net/ & https://snarfnas.snarfnet.net:5001/ 2. Dedicated Authentik OAuth2 Provider for SN-NAS (SC): - Redirect URI: https://sn-nas.snarfnet.net/ & https://sn-nas.snarfnet.net:5001/ 3. Distinct Authentik Applications ('snarfnas' & 'sn-nas') with brand icons. 4. Outputs exact OIDC values for each NAS in DSM Control Panel. """ import json import secrets import ssl import sys import urllib.request ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE AK_BASE = "https://authentik.snarfnet.net" AK_TOKEN = "SnarfNetPermanentAutomationToken2026" AUTH_FLOW = "38a86ef3-f43a-4756-8f54-ca008a8eef6e" INVAL_FLOW = "7a8145b8-2476-4636-9901-825f826df805" CERT_PK = "6d48ba88-4b51-428f-903c-93334988a941" SCOPES = [ "f521356c-080d-4bd6-b19a-68ab159a5aef", # openid "8395b909-8e8d-4fc1-a620-5592e10df8fe", # email "85b89b43-92d8-425c-b9cc-8e00038cad37", # profile "d2248142-6be2-4833-94f8-e02f5d293b02", # groups ] def ak_request(endpoint, method="GET", data=None): url = f"{AK_BASE.rstrip('/')}/api/v3/{endpoint.lstrip('/')}" body = json.dumps(data).encode("utf-8") if data else None headers = { "Authorization": f"Bearer {AK_TOKEN}", "Content-Type": "application/json", "Accept": "application/json", } req = urllib.request.Request(url, data=body, headers=headers, method=method) try: with urllib.request.urlopen(req, timeout=10, context=ctx) as r: raw = r.read().decode("utf-8") return r.getcode(), json.loads(raw) if raw else {} except urllib.error.HTTPError as e: raw = e.read().decode("utf-8") return e.code, json.loads(raw) if raw else {} def get_or_create_provider(name, redirect_strict, redirect_regex): code, res = ak_request("providers/oauth2/") existing = next((p for p in res.get("results", []) if p.get("name") == name), None) if existing: return existing["pk"], existing["client_id"], existing["client_secret"] client_id = f"synology-{secrets.token_hex(4)}" client_secret = secrets.token_urlsafe(40) provider_payload = { "name": name, "authorization_flow": AUTH_FLOW, "invalidation_flow": INVAL_FLOW, "property_mappings": SCOPES, "client_type": "confidential", "client_id": client_id, "client_secret": client_secret, "include_claims_in_id_token": True, "signing_key": CERT_PK, "redirect_uris": [ {"matching_mode": "strict", "url": redirect_strict}, {"matching_mode": "regex", "url": redirect_regex}, ], "sub_mode": "user_username", "issuer_mode": "per_provider", } code, new_prov = ak_request("providers/oauth2/", method="POST", data=provider_payload) return new_prov["pk"], client_id, client_secret def get_or_create_app(name, slug, provider_id, launch_url, description): code, res = ak_request("core/applications/") existing = next((a for a in res.get("results", []) if a.get("slug") == slug), None) app_payload = { "name": name, "slug": slug, "provider": provider_id, "group": "Storage & Media", "meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/synology.png", "meta_description": description, "meta_launch_url": launch_url, "open_in_new_tab": True, } if existing: ak_request(f"core/applications/{slug}/", method="PATCH", data=app_payload) else: ak_request("core/applications/", method="POST", data=app_payload) def main(): print("=" * 60) print("Synology DSM <-> Authentik Multi-NAS Provisioner") print("=" * 60) # 1. Primary: SnarfNAS (GA) prov1_id, client1_id, secret1 = get_or_create_provider( "Synology SnarfNAS", "https://snarfnas.snarfnet.net/", r"https://snarfnas\.snarfnet\.net.*" ) get_or_create_app( "Synology GA (SnarfNAS)", "snarfnas", prov1_id, "https://snarfnas.snarfnet.net", "Primary Network Attached Storage (DSM 7)" ) print(f"[OK] SnarfNAS Provider (ID {prov1_id}) & Application provisioned.") # 2. Secondary: SN-NAS (SC) prov2_id, client2_id, secret2 = get_or_create_provider( "Synology SN-NAS", "https://sn-nas.snarfnet.net/", r"https://sn-nas\.snarfnet\.net.*" ) get_or_create_app( "Synology SC (SN-NAS)", "sn-nas", prov2_id, "https://sn-nas.snarfnet.net", "Secondary Backup NAS (DSM 7)" ) print(f"[OK] SN-NAS Provider (ID {prov2_id}) & Application provisioned.") print("\n" + "=" * 60) print("1. OIDC VALUES FOR SnarfNAS (https://snarfnas.snarfnet.net):") print(" (Control Panel -> Domain/LDAP -> SSO Client -> OpenID Connect)") print("=" * 60) print(f"Profile Name: Authentik") print(f"Well-Known Discovery: {AK_BASE}/application/o/snarfnas/.well-known/openid-configuration") print(f"Client ID: {client1_id}") print(f"Client Secret: {secret1}") print(f"Scopes: openid email profile") print("\n" + "=" * 60) print("2. OIDC VALUES FOR SN-NAS (https://sn-nas.snarfnet.net):") print(" (Control Panel -> Domain/LDAP -> SSO Client -> OpenID Connect)") print("=" * 60) print(f"Profile Name: Authentik") print(f"Well-Known Discovery: {AK_BASE}/application/o/sn-nas/.well-known/openid-configuration") print(f"Client ID: {client2_id}") print(f"Client Secret: {secret2}") print(f"Scopes: openid email profile") print("=" * 60) if __name__ == "__main__": main()