Documentation
¶
Overview ¶
Package authproviders persists federated authentication provider configurations (OIDC, SAML) to the database. Each row is one IdP configuration — multiple OIDC providers are supported, each rendered as a separate button on the login page.
The package mirrors the pattern established by pkg/logsinks: a Registry maps each provider type to a typed field schema, a decode function, and a build function. The field schema drives the frontend's dynamic form. Secrets (OIDC client secret, SAML signing key PEM) are redacted in read responses and merged on edit.
SAML signing keys can be auto-generated (self-signed RSA 2048-bit keypair) when the operator doesn't provide their own PEM — no files on disk needed.
Index ¶
- Constants
- Variables
- func MergeSecrets(typ, prevJSON, newJSON string) (string, error)
- func RedactedConfig(typ, cfgJSON string) string
- func SupportedTypes() []string
- func ValidateProvider(name, typ, cfgJSON string) error
- func ValidateType(typ string) bool
- type AuthProvider
- type AuthProviderManager
- func (m *AuthProviderManager) BuildProviders(ctx context.Context) ([]ProviderEntry, error)
- func (m *AuthProviderManager) Create(name, typ string, enabled bool, cfgJSON string, info string) (AuthProvider, error)
- func (m *AuthProviderManager) Delete(id uint) error
- func (m *AuthProviderManager) Get(id uint) (AuthProvider, error)
- func (m *AuthProviderManager) List() ([]AuthProvider, error)
- func (m *AuthProviderManager) ListEnabled() ([]AuthProvider, error)
- func (m *AuthProviderManager) PersistGeneratedSAMLKeys(row AuthProvider, certPEM, keyPEM string) error
- func (m *AuthProviderManager) RevertToService(id uint) error
- func (m *AuthProviderManager) Seed(params *config.ServiceParameters) error
- func (m *AuthProviderManager) Update(id uint, name, typ string, enabled bool, cfgJSON string, info string) (AuthProvider, error)
- type FieldSpec
- type FieldType
- type ProviderEntry
- type ProviderSpec
Constants ¶
const SourceDB = "db"
SourceDB marks a row that has been edited or created through the API.
const SourceService = "service"
SourceService marks a row seeded from the resolved service configuration.
const SourceYAML = "yaml"
SourceYAML is retained for backwards compatibility.
Variables ¶
var ( ErrProviderNotFound = errors.New("auth provider not found") ErrInvalidProviderType = errors.New("invalid auth provider type") ErrInvalidProviderConfig = errors.New("invalid auth provider configuration") )
var Registry = map[string]ProviderSpec{ config.AuthOIDC: { Type: config.AuthOIDC, Description: "OpenID Connect (OAuth2 + OIDC Core). Multi-provider: one button per enabled row.", HasSecret: true, SecretFields: []string{"ClientSecret"}, Fields: []FieldSpec{ {Name: "IssuerURL", Label: "Issuer URL", Type: FieldString, Required: true, Placeholder: "https://keycloak.example.com/realms/myrealm", Help: "OIDC issuer URL. The provider does discovery against /.well-known/openid-configuration."}, {Name: "ClientID", Label: "Client ID", Type: FieldString, Required: true, Placeholder: "osctrl-client"}, {Name: "ClientSecret", Label: "Client secret", Type: FieldPassword, Secret: true, Placeholder: "prefer env vars/secrets in prod", Help: "OIDC client secret. Required unless UsePKCE is true."}, {Name: "RedirectURL", Label: "Redirect URL", Type: FieldString, Required: true, Placeholder: "https://osctrl.example.com:8444/api/v1/auth/oidc/{id}/callback", Help: "Callback URL registered with the IdP. {id} is replaced with the provider row ID at runtime."}, {Name: "Scopes", Label: "Scopes", Type: FieldString, Placeholder: "openid,profile,email", Help: "Comma-separated. 'openid' is always prepended if absent."}, {Name: "UsernameClaim", Label: "Username claim", Type: FieldSelect, Options: []string{"preferred_username", "email", "sub"}, Default: "preferred_username", Help: "OIDC claim used as the AdminUser.Username."}, {Name: "GroupsClaim", Label: "Groups claim", Type: FieldString, Placeholder: "groups", Help: "OIDC claim consulted for group membership."}, {Name: "RequiredGroups", Label: "Required groups", Type: FieldString, Placeholder: "osctrl-admins", Help: "Comma-separated. At least one must be present for login to succeed. Empty disables the gate."}, {Name: "JITProvision", Label: "JIT provision", Type: FieldBoolean, Default: false, Help: "Auto-create an AdminUser on first successful login."}, {Name: "UsePKCE", Label: "Use PKCE", Type: FieldBoolean, Default: false, Help: "Enable PKCE (RFC 7636). Recommended for public clients."}, }, Decode: decodeOIDCConfig, Build: buildOIDCProvider, }, config.AuthSAML: { Type: config.AuthSAML, Description: "SAML 2.0 Web Browser SSO. SP signing keys auto-generated when not provided.", HasSecret: true, SecretFields: []string{"SigningKeyPEM"}, Fields: []FieldSpec{ {Name: "IDPMetadataURL", Label: "IdP metadata URL", Type: FieldString, Placeholder: "https://idp.example.com/metadata", Help: "IdP's published SAML metadata document URL. One of URL or XML below is required."}, {Name: "IDPMetadataXML", Label: "IdP metadata XML", Type: FieldText, Placeholder: "<EntityDescriptor ...>", Help: "Inline IdP metadata XML (air-gapped deployments). Mutually exclusive with URL."}, {Name: "EntityID", Label: "Entity ID", Type: FieldString, Required: true, Placeholder: "https://osctrl.example.com:8444/api/v1/auth/saml/{id}/metadata", Help: "SP entity identifier. {id} is replaced at runtime."}, {Name: "ACSURL", Label: "ACS URL", Type: FieldString, Required: true, Placeholder: "https://osctrl.example.com:8444/api/v1/auth/saml/{id}/acs", Help: "Assertion Consumer Service URL. {id} is replaced at runtime."}, {Name: "UsernameAttribute", Label: "Username attribute", Type: FieldString, Placeholder: "", Help: "SAML attribute whose value becomes the username. Empty = use NameID."}, {Name: "GroupsAttribute", Label: "Groups attribute", Type: FieldString, Placeholder: "groups", Help: "SAML attribute carrying group memberships."}, {Name: "RequiredGroups", Label: "Required groups", Type: FieldString, Placeholder: "osctrl-admins", Help: "Comma-separated. Empty disables the gate."}, {Name: "JITProvision", Label: "JIT provision", Type: FieldBoolean, Default: false, Help: "Auto-create an AdminUser on first successful login."}, {Name: "SigningCertPEM", Label: "SP signing cert (PEM)", Type: FieldText, Placeholder: "-----BEGIN CERTIFICATE-----", Help: "Optional. Leave empty to auto-generate a self-signed keypair."}, {Name: "SigningKeyPEM", Label: "SP signing key (PEM)", Type: FieldPassword, Secret: true, Placeholder: "-----BEGIN RSA PRIVATE KEY-----", Help: "Optional. Leave empty to auto-generate."}, {Name: "ForceAuthn", Label: "Force Authn", Type: FieldBoolean, Default: true, Help: "Re-prompt for credentials even if IdP SSO cookie is alive."}, {Name: "RequireAssertionSigned", Label: "Require signed assertion", Type: FieldBoolean, Default: true, Help: "Must be true for production (threat S2 defense)."}, {Name: "ReplayWindow", Label: "Replay window (min)", Type: FieldInteger, Default: 5, Help: "Maximum clock skew on NotBefore/NotOnOrAfter checks."}, }, Decode: decodeSAMLConfig, Build: buildSAMLProvider, }, }
Registry maps each provider type to its ProviderSpec.
Functions ¶
func MergeSecrets ¶
MergeSecrets replaces "***" placeholders with values from prev.
func RedactedConfig ¶
RedactedConfig replaces secret fields with "***" for the provider Type.
func SupportedTypes ¶
func SupportedTypes() []string
SupportedTypes returns the Registry keys in sorted order.
func ValidateProvider ¶
func ValidateType ¶
ValidateType reports whether the provider type is registered.
Types ¶
type AuthProvider ¶
type AuthProvider struct {
gorm.Model
Name string `gorm:"uniqueIndex"`
Type string `gorm:"index"` // "oidc" or "saml"
Enabled bool
Config string `gorm:"type:text"` // JSON blob
Source string // "service" (seeded) or "db" (edited)
Info string
}
AuthProvider stores one IdP configuration. Config is a JSON-encoded blob whose shape is determined by Type and validated against the Registry. Multiple rows of the same Type are allowed — the login page renders one button per enabled row.
type AuthProviderManager ¶
AuthProviderManager manages the auth_providers table.
func NewAuthProviderManager ¶
func NewAuthProviderManager(backend *gorm.DB) *AuthProviderManager
NewAuthProviderManager initializes the manager and auto-migrates.
func (*AuthProviderManager) BuildProviders ¶
func (m *AuthProviderManager) BuildProviders(ctx context.Context) ([]ProviderEntry, error)
BuildProviders reads all enabled rows, builds live auth.Provider instances, and returns them. Fail-fast: if any enabled provider's Build fails, the error is returned so the caller can log.Fatal.
func (*AuthProviderManager) Create ¶
func (m *AuthProviderManager) Create(name, typ string, enabled bool, cfgJSON string, info string) (AuthProvider, error)
Create inserts a new provider row.
func (*AuthProviderManager) Delete ¶
func (m *AuthProviderManager) Delete(id uint) error
Delete removes a provider by ID.
func (*AuthProviderManager) Get ¶
func (m *AuthProviderManager) Get(id uint) (AuthProvider, error)
Get retrieves one provider by ID.
func (*AuthProviderManager) List ¶
func (m *AuthProviderManager) List() ([]AuthProvider, error)
List returns all providers.
func (*AuthProviderManager) ListEnabled ¶
func (m *AuthProviderManager) ListEnabled() ([]AuthProvider, error)
ListEnabled returns all enabled providers.
func (*AuthProviderManager) PersistGeneratedSAMLKeys ¶
func (m *AuthProviderManager) PersistGeneratedSAMLKeys(row AuthProvider, certPEM, keyPEM string) error
PersistGeneratedSAMLKeys checks if a SAML provider row has auto-generated signing keys (both SigningCertPEM and SigningKeyPEM empty). After Build, the SAML provider has generated keys in memory. This function marshals the generated PEM back into the config JSON so the keys are stable across reloads. It only writes when the row's Source is NOT "db" (seed rows get the keys pinned; operator-edited rows keep whatever the operator set).
func (*AuthProviderManager) RevertToService ¶
func (m *AuthProviderManager) RevertToService(id uint) error
RevertToService flips a row's Source from "db" back to "service".
func (*AuthProviderManager) Seed ¶
func (m *AuthProviderManager) Seed(params *config.ServiceParameters) error
Seed translates the resolved service configuration (OIDC and SAML sections) into AuthProvider rows using create-if-missing semantics.
func (*AuthProviderManager) Update ¶
func (m *AuthProviderManager) Update(id uint, name, typ string, enabled bool, cfgJSON string, info string) (AuthProvider, error)
Update replaces an existing provider row's mutable fields.
type FieldSpec ¶
type FieldSpec struct {
Name string
Label string
Type FieldType
Required bool
Secret bool
Placeholder string
Help string
Options []string
Default any
}
FieldSpec describes one configurable field of a provider type.
type ProviderEntry ¶
type ProviderEntry struct {
ID uint
Name string
Type string
Provider auth.Provider
JITProvision bool
ClientID string // OIDC only
LogoutURL string // SAML only
}
ProviderEntry is one built provider with its metadata.
type ProviderSpec ¶
type ProviderSpec struct {
Type string
Description string
HasSecret bool
SecretFields []string
Fields []FieldSpec
// Decode unmarshals raw JSON Config into the typed config struct.
Decode func(json.RawMessage) (any, error)
// Build instantiates an auth.Provider from a decoded config.
// The context is used for IdP discovery/metadata fetch.
Build func(any, context.Context) (auth.Provider, error)
}
ProviderSpec describes one supported provider type in the Registry.