Closes the other major named gap from this phase: until now, there was no way for a human to actually log in -- only /alerting's RoleService credential could be minted. GET /auth/oidc/login and GET /auth/oidc/callback drive the real coreos/go-oidc flow already wired in enterprise/internal/oidc: CSRF state in a short-lived cookie, code exchange, ID token verification, upserting a users row, resolving tenant/role from exactly one tenant_memberships row (refusing outright on zero or more than one, rather than guessing), and issuing a real session cookie. Unlike everything else built this phase, this one is genuinely verified end to end: the tests spin up coreos/go-oidc's own oidctest fake IdP, which signs real RS256 ID tokens, and drive the full login->callback-> session-cookie round trip through actual signature verification -- no live database or Docker needed, so nothing here is asserted without having actually been run in this session. Also fixes a real bug caught while wiring this into enterprise-auth's main.go: assigning a nil *oidc.Provider to the handler's interface field would have produced a non-nil interface wrapping a nil pointer (Go's classic typed-nil trap), silently breaking the "OIDC not configured" no-op path -- New() now takes the concrete pointer type and checks it before ever converting to the interface, with a regression test pinning the fix down. Still missing: SAML's equivalent (ACS endpoint), a tenant-picker UI for multi-membership identities, and any admin UI to actually create a tenant_memberships row (today that's manual SQL, documented in the runbook's new bootstrap walkthrough).
97 lines
3.0 KiB
Go
97 lines
3.0 KiB
Go
// Package config loads enterprise-auth's configuration from environment
|
|
// variables, same convention as every other Go service in this repo.
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
)
|
|
|
|
type Config struct {
|
|
HTTPListenAddr string
|
|
Postgres PostgresConfig
|
|
OIDC OIDCConfig
|
|
SAML SAMLConfig
|
|
SessionSigningKey []byte
|
|
// PostLoginRedirectURL is where the browser lands after
|
|
// internal/loginhandler sets a session cookie -- web's base URL in
|
|
// a real deployment.
|
|
PostLoginRedirectURL string
|
|
}
|
|
|
|
type PostgresConfig struct {
|
|
Addr string
|
|
Database string
|
|
Username string
|
|
Password string
|
|
}
|
|
|
|
// OIDCConfig is optional -- a deployment might configure OIDC, SAML,
|
|
// both, or (during early rollout) neither yet. Load() doesn't fail if
|
|
// these are unset; internal/oidc.New is only called once IssuerURL is
|
|
// actually present.
|
|
type OIDCConfig struct {
|
|
IssuerURL string
|
|
ClientID string
|
|
ClientSecret string
|
|
RedirectURL string
|
|
}
|
|
|
|
// SAMLConfig is likewise optional. Note this only records *presence* --
|
|
// enough for /auth/features (internal/authhandler) to report
|
|
// saml_enabled -- it does not itself fetch/parse IDPMetadataURL into the
|
|
// *saml.EntityDescriptor internal/saml.New requires; that fetch (and the
|
|
// login/ACS HTTP handlers that would use it) is deferred, same as OIDC's
|
|
// login/callback handlers -- see cmd/enterprise-auth/main.go's doc
|
|
// comment.
|
|
type SAMLConfig struct {
|
|
EntityID string
|
|
ACSURL string
|
|
IDPMetadataURL string
|
|
}
|
|
|
|
func Load() (Config, error) {
|
|
cfg := Config{
|
|
HTTPListenAddr: getenv("HTTP_LISTEN_ADDR", ":8082"),
|
|
Postgres: PostgresConfig{
|
|
Addr: getenv("POSTGRES_ADDR", "localhost:5432"),
|
|
Database: getenv("POSTGRES_DATABASE", "sentry_metadata"),
|
|
Username: getenv("POSTGRES_USERNAME", "sentry"),
|
|
Password: getenv("POSTGRES_PASSWORD", ""),
|
|
},
|
|
OIDC: OIDCConfig{
|
|
IssuerURL: getenv("OIDC_ISSUER_URL", ""),
|
|
ClientID: getenv("OIDC_CLIENT_ID", ""),
|
|
ClientSecret: getenv("OIDC_CLIENT_SECRET", ""),
|
|
RedirectURL: getenv("OIDC_REDIRECT_URL", ""),
|
|
},
|
|
SAML: SAMLConfig{
|
|
EntityID: getenv("SAML_ENTITY_ID", ""),
|
|
ACSURL: getenv("SAML_ACS_URL", ""),
|
|
IDPMetadataURL: getenv("SAML_IDP_METADATA_URL", ""),
|
|
},
|
|
PostLoginRedirectURL: getenv("POST_LOGIN_REDIRECT_URL", "http://localhost:3000"),
|
|
}
|
|
|
|
// Required, unlike OIDC/SAML above: every enterprise-auth deployment
|
|
// issues and validates session/service tokens (internal/session),
|
|
// even one that hasn't configured any IdP yet. 32 bytes matches
|
|
// internal/session.MinSigningKeyBytes -- not imported here to avoid
|
|
// a config->session dependency for one constant, but the two values
|
|
// must be kept in sync.
|
|
signingKey := getenv("ENTERPRISE_SESSION_SIGNING_KEY", "")
|
|
if len(signingKey) < 32 {
|
|
return Config{}, fmt.Errorf("ENTERPRISE_SESSION_SIGNING_KEY must be set to at least 32 bytes (got %d)", len(signingKey))
|
|
}
|
|
cfg.SessionSigningKey = []byte(signingKey)
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func getenv(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|