Google Workspace SSO Setup for Guardian Control Plane
This guide sets up Google Workspace SSO for the Guardian control-plane and console stack.
- User login/session for
control-planeandconsole. - Member management and local agent onboarding from the console.
- Agent runtime identity stays unchanged: agents still authenticate to
llm-gatewaywith RFC 9421 signatures usingdid:key.
1. Decide your external URLs first
Section titled “1. Decide your external URLs first”Use one control-plane host and choose whether the API is rooted at / or a prefix like /api.
Recommended (UI + API on same host):
ingress.hosts.controlPlane=guardian.example.comingress.controlPlanePath=/apicontrolPlane.apiBasePath=/apiguardianUI.runtime.apiURL=https://guardian.example.com/api- Google callback URL:
https://guardian.example.com/api/v1/auth/callback/google
If ingress.controlPlanePath=/, callback becomes:
https://guardian.example.com/v1/auth/callback/google
ingress.controlPlanePath and controlPlane.apiBasePath must match.
2. Create Google OAuth client (Workspace)
Section titled “2. Create Google OAuth client (Workspace)”- In Google Cloud Console, create/select the project for Guardian.
- Configure OAuth consent screen.
- For Workspace-only access, choose internal user type.
- Add scopes:
openid,profile,email. - Create OAuth Client ID:
- Application type:
Web application - Authorized redirect URI: your callback URL from step 1.
- Application type:
- Save:
client_idclient_secret
3. Generate required Guardian secrets
Section titled “3. Generate required Guardian secrets”openssl rand -base64 48 | tr -d '\n' # controlPlane.auth.stateSecretopenssl rand -base64 48 | tr -d '\n' # controlPlane.auth.registrationTokenSecretstateSecret: signs OAuth state/nonce envelope.registrationTokenSecret: mints short-lived member registration tokens for localviper did register.registrationTokenSecretis also used by llm-gateway to validate registration tokens.- Configure
controlPlane.auth.bearer.*for CLI/tool OIDC bearer auth.
4. Configure Helm values
Section titled “4. Configure Helm values”Option A (recommended): reference an existing Kubernetes Secret:
controlPlane: apiBasePath: /api auth: enabled: true existingSecret: guardian-control-plane-auth registrationTokenIssuer: control-plane bearer: enabled: true issuerURL: https://accounts.google.com clientID: "<google-device-client-id>" scopes: - openid - profile - email cookieSecure: true allowedEmailDomains: - example.com bootstrapAdminEmail: admin@example.com uiBaseURL: https://guardian.example.com google: enabled: true issuerURL: https://accounts.google.com redirectURL: https://guardian.example.com/api/v1/auth/callback/google scopes: - openid - profile - emailCreate that Secret (default key names shown):
kubectl -n guardian create secret generic guardian-control-plane-auth \ --from-literal=stateSecret='<strong-random-secret>' \ --from-literal=registrationTokenSecret='<strong-random-secret>' \ --from-literal=googleClientID='<google-client-id>' \ --from-literal=googleClientSecret='<google-client-secret>'Option B: inline values (chart creates the Secret for you):
controlPlane: apiBasePath: /api auth: enabled: true cookieSecure: true allowedEmailDomains: - example.com bootstrapAdminEmail: admin@example.com stateSecret: "<strong-random-secret>" registrationTokenSecret: "<strong-random-secret>" registrationTokenIssuer: control-plane bearer: enabled: true issuerURL: https://accounts.google.com clientID: "<google-device-client-id>" scopes: - openid - profile - email uiBaseURL: https://guardian.example.com google: enabled: true issuerURL: https://accounts.google.com clientID: "<google-client-id>" clientSecret: "<google-client-secret>" redirectURL: https://guardian.example.com/api/v1/auth/callback/google scopes: - openid - profile - email
ingress: enabled: true controlPlanePath: /api hosts: controlPlane: guardian.example.com
guardianUI: enabled: true runtime: apiURL: https://guardian.example.com/api basePath: /Notes:
- Keep
cookieSecure: truein production (HTTPS required). allowedEmailDomainsis the server-side domain gate.bootstrapAdminEmailis promoted to admin on first successful login.- Secret key names are configurable via
controlPlane.auth.secretKeys.*. llmGateway.registration.enabled=trueusesregistrationTokenSecretfrom the same auth secret.
5. Deploy
Section titled “5. Deploy”helm dependency update deploy/charts/guardian-stackhelm upgrade --install guardian deploy/charts/guardian-stack \ --namespace guardian \ --create-namespace \ -f <your-values>.yaml6. Validate SSO
Section titled “6. Validate SSO”- Provider discovery returns Google:
curl -sS https://guardian.example.com/api/v1/auth/providers | jqExpected provider:
{ "id": "google", "display_name": "Google"}- Open the console and click
Continue with Google. - Complete login and confirm console loads authenticated pages.
- Confirm user profile endpoint in browser/devtools:
GET /api/v1/mereturns200.
7. Validate member local agent onboarding
Section titled “7. Validate member local agent onboarding”- Sign in as a member user.
- In console, open
Register Local Agent. - Generate the registration command.
- On the member machine:
viper did addviper did assign --app <app>viper did register --app <app> --provisioning-token "<generated-token>"Replace <app> with the target agent application (claude, codex, opencode, openclaw, or proxy).
- Confirm the agent appears in the console
Agentsview.
The control-plane now generates user-scoped agent IDs server-side, so users do not provide custom agent_id.
8. Non-Helm env var equivalents
Section titled “8. Non-Helm env var equivalents”If you run control-plane without Helm, these map to the same config:
CONTROL_PLANE_CONFIG_FILE=/path/to/runtime.toml(optional)CONTROL_PLANE_AUTH_ENABLED=trueCONTROL_PLANE_AUTH_ALLOWED_EMAIL_DOMAINS=example.comCONTROL_PLANE_AUTH_BOOTSTRAP_ADMIN_EMAIL=admin@example.comCONTROL_PLANE_AUTH_STATE_SECRET=<secret>CONTROL_PLANE_AUTH_REGISTRATION_TOKEN_SECRET=<secret>CONTROL_PLANE_AUTH_REGISTRATION_TOKEN_ISSUER=control-planeCONTROL_PLANE_AUTH_UI_BASE_URL=https://guardian.example.comCONTROL_PLANE_AUTH_GOOGLE_ENABLED=trueCONTROL_PLANE_AUTH_GOOGLE_ISSUER_URL=https://accounts.google.comCONTROL_PLANE_AUTH_GOOGLE_CLIENT_ID=<client-id>CONTROL_PLANE_AUTH_GOOGLE_CLIENT_SECRET=<client-secret>CONTROL_PLANE_AUTH_GOOGLE_REDIRECT_URL=https://guardian.example.com/api/v1/auth/callback/googleCONTROL_PLANE_AUTH_GOOGLE_SCOPES=openid,profile,emailCONTROL_PLANE_AUTH_BEARER_ENABLED=trueCONTROL_PLANE_AUTH_BEARER_ISSUER_URL=https://accounts.google.comCONTROL_PLANE_AUTH_BEARER_CLIENT_ID=<google-device-client-id>CONTROL_PLANE_AUTH_BEARER_SCOPES=openid,profile,email
9. Common issues
Section titled “9. Common issues”redirect_uri_mismatch: Google redirect URI does not exactly matchcontrolPlane.auth.google.redirectURL.provider is not configured: auth enabled butgoogle.enabledor client settings are missing.email domain is not allowed: user email domain is not inallowedEmailDomains.- Login loop/no session cookie: HTTPS/cookie domain/path mismatch.
- Callback 404:
ingress.controlPlanePathandcontrolPlane.apiBasePathare not aligned.
10. Extending to future providers
Section titled “10. Extending to future providers”Current production path is Google Workspace. The control-plane auth provider model is adapter-friendly, so Entra ID/Okta can be added later without changing member/agent onboarding flow or RFC 9421 agent identity behavior.