oidc: one verifier per issuer, because IdPs mint one per application
Authentik derives the issuer from the application slug, so two applications mean two issuers - and a token's `iss` must match whoever signed it. A single pinned issuer could therefore only ever serve one of the two clients. So there is a verifier per issuer, and each accepts only the client belonging to it. That is tighter than the previous arrangement as well as more general: a token minted for the phone cannot be replayed at the admin login, and vice versa, because they arrive at different verifiers with different audiences. ECHOLOT_OIDC_APP_ISSUER is optional - empty means both clients share ECHOLOT_OIDC_ISSUER, which is what IdPs with one global issuer do. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
5d7f59a66a
commit
c7750fbf0b
@@ -184,22 +184,33 @@ func serve(cfg *config.Config) error {
|
||||
// Identity is optional. Without an issuer the server simply has no sign-in, and
|
||||
// uploads=account can never be satisfied — which is the honest outcome, not a silent
|
||||
// downgrade to anonymous.
|
||||
var idp *oidc.Verifier
|
||||
if cfg.OIDCIssuer != "" && (cfg.OIDCClientID != "" || cfg.OIDCAppClientID != "") {
|
||||
// One verifier per issuer. An IdP may mint a distinct issuer per application — Authentik
|
||||
// derives it from the application slug — and a token's `iss` must match whoever signed it.
|
||||
// Each verifier accepts only the client belonging to its own issuer, so a token minted for
|
||||
// the phone cannot be replayed at the admin login and vice versa.
|
||||
var idp, adminIdP *oidc.Verifier
|
||||
appIssuer := cfg.OIDCAppIssuer
|
||||
if appIssuer == "" {
|
||||
appIssuer = cfg.OIDCIssuer // IdPs with one global issuer
|
||||
}
|
||||
if appIssuer != "" && cfg.OIDCAppClientID != "" {
|
||||
idp = oidc.New(oidc.Config{
|
||||
Issuer: cfg.OIDCIssuer,
|
||||
ClientID: cfg.OIDCClientID,
|
||||
AppClientID: cfg.OIDCAppClientID,
|
||||
AdminGroup: cfg.OIDCAdminGroup,
|
||||
Issuer: appIssuer, AppClientID: cfg.OIDCAppClientID, AdminGroup: cfg.OIDCAdminGroup,
|
||||
}, nil)
|
||||
slog.Info("identity provider configured", "issuer", cfg.OIDCIssuer,
|
||||
"admin_client_id", cfg.OIDCClientID, "app_client_id", cfg.OIDCAppClientID,
|
||||
"admin_group", cfg.OIDCAdminGroup)
|
||||
slog.Info("identity: app client", "issuer", appIssuer, "client_id", cfg.OIDCAppClientID)
|
||||
}
|
||||
if cfg.OIDCIssuer != "" && cfg.OIDCClientID != "" {
|
||||
adminIdP = oidc.New(oidc.Config{
|
||||
Issuer: cfg.OIDCIssuer, ClientID: cfg.OIDCClientID, AdminGroup: cfg.OIDCAdminGroup,
|
||||
}, nil)
|
||||
slog.Info("identity: admin client", "issuer", cfg.OIDCIssuer,
|
||||
"client_id", cfg.OIDCClientID, "admin_group", cfg.OIDCAdminGroup)
|
||||
if cfg.OIDCAdminGroup == "" {
|
||||
slog.Warn("no admin group set: nobody will be an admin via OIDC " +
|
||||
"(set ECHOLOT_OIDC_ADMIN_GROUP)")
|
||||
}
|
||||
} else if cfg.UploadsMode == string(runs.ModeAccount) {
|
||||
}
|
||||
if idp == nil && adminIdP == nil && cfg.UploadsMode == string(runs.ModeAccount) {
|
||||
slog.Warn("uploads=account but no identity provider is configured — " +
|
||||
"every upload will be refused")
|
||||
}
|
||||
@@ -216,6 +227,7 @@ func serve(cfg *config.Config) error {
|
||||
AppRange: appRange,
|
||||
PublicControlURL: publicControlURL(cfg),
|
||||
OIDC: idp,
|
||||
AdminOIDC: adminIdP,
|
||||
}
|
||||
// Left nil when there is no raw socket, so the handler answers "not implemented" with a
|
||||
// reason rather than failing somewhere deeper.
|
||||
|
||||
Reference in New Issue
Block a user