feat: add homelab topology documentation, docker deployments, and authentik sso configuration scripts
This commit is contained in:
162
scripts/setup_synology_authentik_sso.py
Normal file
162
scripts/setup_synology_authentik_sso.py
Normal file
@@ -0,0 +1,162 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user