GoTrue SSRF Protection Blocks Private IPs
Problem
GoTrue's custom OAuth provider API (/admin/custom-providers) validates URLs and blocks private IPs (RFC 1918) to prevent SSRF attacks. When trying to register a custom OAuth provider pointing to an internal service (e.g., idm.grigri.cloud resolving to 192.168.193.4), the API returns:
400: URL cannot resolve to private network addresses
Root Cause
GoTrue's ValidateOAuthURL function in internal/utilities/url_validator.go performs DNS resolution and checks each IP address against:
- Loopback addresses (127.0.0.0/8, ::1)
- Private network addresses (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Link-local addresses (169.254.0.0/16, fe80::/10)
- Cloud metadata endpoints (169.254.169.254)
- Multicast addresses
There is no configuration option to whitelist specific hosts or disable this validation.
Attempted Solutions
Environment Variable
Tried GOTRUE_CUSTOM_OAUTH_PRIVATE_HOSTS environment variable - doesn't exist in GoTrue v2.196.0.
Provider Type
Tried both oidc and oauth2 provider types - both perform the same URL validation.
External DNS
Tried using the public IP address instead of internal DNS - the public IP may not be routable from within the cluster, and this defeats the purpose of internal service discovery.
Workaround
The custom provider is inserted automatically by the db-migrate init container on every pod start, using an idempotent INSERT ... ON CONFLICT DO UPDATE. The Kanidm OAuth client credentials come from the readest-kanidm-oauth2-credentials secret (created by the kaniop KanidmOAuth2Client CR), so the entire flow is GitOps-managed.
The init container runs (in apps/readest/templates/configmap-init-sql.yaml):
INSERT INTO auth.custom_oauth_providers (
provider_type, identifier, name, client_id, client_secret,
authorization_url, token_url, userinfo_url, scopes, pkce_enabled, enabled
) VALUES (
'oauth2', 'custom:kanidm', 'Kanidm',
:'kanidm_client_id', :'kanidm_client_secret',
'https://idm.grigri.cloud/ui/oauth2',
'https://idm.grigri.cloud/oauth2/token',
'https://idm.grigri.cloud/oauth2/openid/readest/userinfo',
ARRAY['openid','profile','email'], true, true
) ON CONFLICT (identifier) DO UPDATE
SET client_id = :'kanidm_client_id',
client_secret = :'kanidm_client_secret',
enabled = true;
If the automatic insertion fails (e.g. the auth.custom_oauth_providers table doesn't exist yet because the schema migration hasn't run), you can insert it manually:
INSERT INTO auth.custom_oauth_providers (
provider_type, identifier, name, client_id, client_secret,
authorization_url, token_url, userinfo_url, scopes, pkce_enabled, enabled
) VALUES (
'oauth2', 'custom:kanidm', 'Kanidm',
'<client_id>', '<client_secret>',
'https://idm.grigri.cloud/ui/oauth2',
'https://idm.grigri.cloud/oauth2/token',
'https://idm.grigri.cloud/oauth2/openid/readest/userinfo',
ARRAY['openid', 'profile', 'email'], true, true
);
Impact
- The custom provider is now managed by GitOps (auto-inserted on every pod start)
- Credentials sourced from kaniop-managed secret — rotates automatically
- Cannot use GoTrue's admin API to update or delete the provider
- No validation of the URLs (could point to invalid endpoints)
Verification
Verify the provider was inserted correctly:
SELECT identifier, name, provider_type, enabled
FROM auth.custom_oauth_providers;
Or via GoTrue admin API:
curl -H "Authorization: Bearer <service_role_key>" \
http://localhost:9999/admin/custom-providers
Future Improvements
If GoTrue adds support for private hosts (e.g., via environment variable or config), we can: - Use the GoTrue API to register the provider - Manage the provider configuration via GitOps - Get URL validation and error handling
References
- GoTrue source:
internal/utilities/url_validator.go - GoTrue issue: https://github.com/supabase/auth/issues/1234 (example)