sso: provision vaultwarden users
This commit is contained in:
parent
727d8cfd48
commit
5437cebb9e
98
scripts/vaultwarden_cred_sync.py
Normal file
98
scripts/vaultwarden_cred_sync.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from typing import Any, Iterable
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from atlas_portal import settings
|
||||||
|
from atlas_portal.keycloak import admin_client
|
||||||
|
from atlas_portal.vaultwarden import invite_user
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_keycloak_users(page_size: int = 200) -> Iterable[dict[str, Any]]:
|
||||||
|
client = admin_client()
|
||||||
|
if not client.ready():
|
||||||
|
raise RuntimeError("keycloak admin client not configured")
|
||||||
|
|
||||||
|
url = f"{settings.KEYCLOAK_ADMIN_URL}/admin/realms/{settings.KEYCLOAK_REALM}/users"
|
||||||
|
first = 0
|
||||||
|
while True:
|
||||||
|
headers = client.headers()
|
||||||
|
params = {"first": str(first), "max": str(page_size)}
|
||||||
|
with httpx.Client(timeout=settings.HTTP_CHECK_TIMEOUT_SEC) as http:
|
||||||
|
resp = http.get(url, params=params, headers=headers)
|
||||||
|
resp.raise_for_status()
|
||||||
|
payload = resp.json()
|
||||||
|
|
||||||
|
if not isinstance(payload, list) or not payload:
|
||||||
|
return
|
||||||
|
|
||||||
|
for item in payload:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
yield item
|
||||||
|
|
||||||
|
if len(payload) < page_size:
|
||||||
|
return
|
||||||
|
first += page_size
|
||||||
|
|
||||||
|
|
||||||
|
def _email_for_user(user: dict[str, Any]) -> str:
|
||||||
|
email = (user.get("email") if isinstance(user.get("email"), str) else "") or ""
|
||||||
|
if email.strip():
|
||||||
|
return email.strip()
|
||||||
|
username = (user.get("username") if isinstance(user.get("username"), str) else "") or ""
|
||||||
|
username = username.strip()
|
||||||
|
if not username:
|
||||||
|
return ""
|
||||||
|
return f"{username}@{settings.MAILU_DOMAIN}"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
processed = 0
|
||||||
|
created = 0
|
||||||
|
skipped = 0
|
||||||
|
failures = 0
|
||||||
|
|
||||||
|
for user in _iter_keycloak_users():
|
||||||
|
username = (user.get("username") if isinstance(user.get("username"), str) else "") or ""
|
||||||
|
username = username.strip()
|
||||||
|
if not username:
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
enabled = user.get("enabled")
|
||||||
|
if enabled is False:
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if user.get("serviceAccountClientId") or username.startswith("service-account-"):
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
email = _email_for_user(user)
|
||||||
|
if not email:
|
||||||
|
print(f"skip {username}: missing email", file=sys.stderr)
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
processed += 1
|
||||||
|
result = invite_user(email)
|
||||||
|
if result.ok:
|
||||||
|
created += 1
|
||||||
|
print(f"ok {username}: {result.status}")
|
||||||
|
else:
|
||||||
|
failures += 1
|
||||||
|
print(f"err {username}: {result.status} {result.detail}", file=sys.stderr)
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"done processed={processed} created_or_present={created} skipped={skipped} failures={failures}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 0 if failures == 0 else 2
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@ -71,7 +71,7 @@ spec:
|
|||||||
name: atlas-portal-db
|
name: atlas-portal-db
|
||||||
key: PORTAL_DATABASE_URL
|
key: PORTAL_DATABASE_URL
|
||||||
- name: HTTP_CHECK_TIMEOUT_SEC
|
- name: HTTP_CHECK_TIMEOUT_SEC
|
||||||
value: "10"
|
value: "20"
|
||||||
- name: ACCESS_REQUEST_SUBMIT_RATE_LIMIT
|
- name: ACCESS_REQUEST_SUBMIT_RATE_LIMIT
|
||||||
value: "30"
|
value: "30"
|
||||||
- name: ACCESS_REQUEST_SUBMIT_RATE_WINDOW_SEC
|
- name: ACCESS_REQUEST_SUBMIT_RATE_WINDOW_SEC
|
||||||
|
|||||||
@ -13,4 +13,13 @@ resources:
|
|||||||
- frontend-service.yaml
|
- frontend-service.yaml
|
||||||
- backend-deployment.yaml
|
- backend-deployment.yaml
|
||||||
- backend-service.yaml
|
- backend-service.yaml
|
||||||
|
- vaultwarden-cred-sync-cronjob.yaml
|
||||||
- ingress.yaml
|
- ingress.yaml
|
||||||
|
|
||||||
|
configMapGenerator:
|
||||||
|
- name: vaultwarden-cred-sync-script
|
||||||
|
namespace: bstein-dev-home
|
||||||
|
files:
|
||||||
|
- vaultwarden_cred_sync.py=../../scripts/vaultwarden_cred_sync.py
|
||||||
|
options:
|
||||||
|
disableNameSuffixHash: true
|
||||||
|
|||||||
57
services/bstein-dev-home/vaultwarden-cred-sync-cronjob.yaml
Normal file
57
services/bstein-dev-home/vaultwarden-cred-sync-cronjob.yaml
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# services/bstein-dev-home/vaultwarden-cred-sync-cronjob.yaml
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: vaultwarden-cred-sync
|
||||||
|
namespace: bstein-dev-home
|
||||||
|
spec:
|
||||||
|
schedule: "*/15 * * * *"
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
successfulJobsHistoryLimit: 1
|
||||||
|
failedJobsHistoryLimit: 3
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
backoffLimit: 0
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
serviceAccountName: bstein-dev-home
|
||||||
|
restartPolicy: Never
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/arch: arm64
|
||||||
|
node-role.kubernetes.io/worker: "true"
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: harbor-bstein-robot
|
||||||
|
containers:
|
||||||
|
- name: sync
|
||||||
|
image: registry.bstein.dev/bstein/bstein-dev-home-backend:0.1.1-49 # {"$imagepolicy": "bstein-dev-home:bstein-dev-home-backend"}
|
||||||
|
imagePullPolicy: Always
|
||||||
|
command:
|
||||||
|
- python
|
||||||
|
- /scripts/vaultwarden_cred_sync.py
|
||||||
|
env:
|
||||||
|
- name: KEYCLOAK_ENABLED
|
||||||
|
value: "true"
|
||||||
|
- name: KEYCLOAK_REALM
|
||||||
|
value: atlas
|
||||||
|
- name: KEYCLOAK_ADMIN_URL
|
||||||
|
value: http://keycloak.sso.svc.cluster.local
|
||||||
|
- name: KEYCLOAK_ADMIN_REALM
|
||||||
|
value: atlas
|
||||||
|
- name: KEYCLOAK_ADMIN_CLIENT_ID
|
||||||
|
value: bstein-dev-home-admin
|
||||||
|
- name: KEYCLOAK_ADMIN_CLIENT_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: bstein-dev-home-keycloak-admin
|
||||||
|
key: client_secret
|
||||||
|
- name: HTTP_CHECK_TIMEOUT_SEC
|
||||||
|
value: "20"
|
||||||
|
volumeMounts:
|
||||||
|
- name: vaultwarden-cred-sync-script
|
||||||
|
mountPath: /scripts
|
||||||
|
readOnly: true
|
||||||
|
volumes:
|
||||||
|
- name: vaultwarden-cred-sync-script
|
||||||
|
configMap:
|
||||||
|
name: vaultwarden-cred-sync-script
|
||||||
|
defaultMode: 0555
|
||||||
@ -2,7 +2,7 @@
|
|||||||
apiVersion: batch/v1
|
apiVersion: batch/v1
|
||||||
kind: Job
|
kind: Job
|
||||||
metadata:
|
metadata:
|
||||||
name: keycloak-ldap-federation-4
|
name: keycloak-ldap-federation-5
|
||||||
namespace: sso
|
namespace: sso
|
||||||
spec:
|
spec:
|
||||||
backoffLimit: 2
|
backoffLimit: 2
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
apiVersion: batch/v1
|
apiVersion: batch/v1
|
||||||
kind: Job
|
kind: Job
|
||||||
metadata:
|
metadata:
|
||||||
name: keycloak-realm-settings-9
|
name: keycloak-realm-settings-10
|
||||||
namespace: sso
|
namespace: sso
|
||||||
spec:
|
spec:
|
||||||
backoffLimit: 0
|
backoffLimit: 0
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user