Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
// 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
|
// uploads=account can never be satisfied — which is the honest outcome, not a silent
|
||||||
// downgrade to anonymous.
|
// downgrade to anonymous.
|
||||||
var idp *oidc.Verifier
|
// One verifier per issuer. An IdP may mint a distinct issuer per application — Authentik
|
||||||
if cfg.OIDCIssuer != "" && (cfg.OIDCClientID != "" || cfg.OIDCAppClientID != "") {
|
// 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{
|
idp = oidc.New(oidc.Config{
|
||||||
Issuer: cfg.OIDCIssuer,
|
Issuer: appIssuer, AppClientID: cfg.OIDCAppClientID, AdminGroup: cfg.OIDCAdminGroup,
|
||||||
ClientID: cfg.OIDCClientID,
|
|
||||||
AppClientID: cfg.OIDCAppClientID,
|
|
||||||
AdminGroup: cfg.OIDCAdminGroup,
|
|
||||||
}, nil)
|
}, nil)
|
||||||
slog.Info("identity provider configured", "issuer", cfg.OIDCIssuer,
|
slog.Info("identity: app client", "issuer", appIssuer, "client_id", cfg.OIDCAppClientID)
|
||||||
"admin_client_id", cfg.OIDCClientID, "app_client_id", cfg.OIDCAppClientID,
|
}
|
||||||
"admin_group", cfg.OIDCAdminGroup)
|
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 == "" {
|
if cfg.OIDCAdminGroup == "" {
|
||||||
slog.Warn("no admin group set: nobody will be an admin via OIDC " +
|
slog.Warn("no admin group set: nobody will be an admin via OIDC " +
|
||||||
"(set ECHOLOT_OIDC_ADMIN_GROUP)")
|
"(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 — " +
|
slog.Warn("uploads=account but no identity provider is configured — " +
|
||||||
"every upload will be refused")
|
"every upload will be refused")
|
||||||
}
|
}
|
||||||
@@ -216,6 +227,7 @@ func serve(cfg *config.Config) error {
|
|||||||
AppRange: appRange,
|
AppRange: appRange,
|
||||||
PublicControlURL: publicControlURL(cfg),
|
PublicControlURL: publicControlURL(cfg),
|
||||||
OIDC: idp,
|
OIDC: idp,
|
||||||
|
AdminOIDC: adminIdP,
|
||||||
}
|
}
|
||||||
// Left nil when there is no raw socket, so the handler answers "not implemented" with a
|
// Left nil when there is no raw socket, so the handler answers "not implemented" with a
|
||||||
// reason rather than failing somewhere deeper.
|
// reason rather than failing somewhere deeper.
|
||||||
|
|||||||
@@ -74,6 +74,12 @@ type Config struct {
|
|||||||
OIDCIssuer string // ECHOLOT_OIDC_ISSUER / --oidc-issuer
|
OIDCIssuer string // ECHOLOT_OIDC_ISSUER / --oidc-issuer
|
||||||
OIDCClientID string // ECHOLOT_OIDC_CLIENT_ID / --oidc-client-id (confidential, admin UI)
|
OIDCClientID string // ECHOLOT_OIDC_CLIENT_ID / --oidc-client-id (confidential, admin UI)
|
||||||
OIDCAppClientID string // ECHOLOT_OIDC_APP_CLIENT_ID / --oidc-app-client-id (public, the phone app)
|
OIDCAppClientID string // ECHOLOT_OIDC_APP_CLIENT_ID / --oidc-app-client-id (public, the phone app)
|
||||||
|
// Issuer for the app's client, when the IdP gives each application its own.
|
||||||
|
//
|
||||||
|
// Authentik derives the issuer from the application slug, so two applications mean two
|
||||||
|
// issuers — and a token's `iss` must match the one that minted it. Empty means both clients
|
||||||
|
// share ECHOLOT_OIDC_ISSUER, which is what IdPs with a single global issuer do.
|
||||||
|
OIDCAppIssuer string // ECHOLOT_OIDC_APP_ISSUER / --oidc-app-issuer
|
||||||
OIDCAdminGroup string // ECHOLOT_OIDC_ADMIN_GROUP / --oidc-admin-group
|
OIDCAdminGroup string // ECHOLOT_OIDC_ADMIN_GROUP / --oidc-admin-group
|
||||||
|
|
||||||
// Break-glass admin username; the password lives hashed in the state store.
|
// Break-glass admin username; the password lives hashed in the state store.
|
||||||
@@ -167,6 +173,7 @@ func Load(args []string) (*Config, *Actions, error) {
|
|||||||
fs.StringVar(&c.OIDCIssuer, "oidc-issuer", envOr("OIDC_ISSUER", ""), "OpenID Connect issuer URL; empty disables sign-in")
|
fs.StringVar(&c.OIDCIssuer, "oidc-issuer", envOr("OIDC_ISSUER", ""), "OpenID Connect issuer URL; empty disables sign-in")
|
||||||
fs.StringVar(&c.OIDCClientID, "oidc-client-id", envOr("OIDC_CLIENT_ID", ""), "confidential OIDC client id for the admin UI")
|
fs.StringVar(&c.OIDCClientID, "oidc-client-id", envOr("OIDC_CLIENT_ID", ""), "confidential OIDC client id for the admin UI")
|
||||||
fs.StringVar(&c.OIDCAppClientID, "oidc-app-client-id", envOr("OIDC_APP_CLIENT_ID", ""), "public OIDC client id used by the Android app (PKCE)")
|
fs.StringVar(&c.OIDCAppClientID, "oidc-app-client-id", envOr("OIDC_APP_CLIENT_ID", ""), "public OIDC client id used by the Android app (PKCE)")
|
||||||
|
fs.StringVar(&c.OIDCAppIssuer, "oidc-app-issuer", envOr("OIDC_APP_ISSUER", ""), "issuer for the app client when the IdP uses per-application issuers; empty = same as --oidc-issuer")
|
||||||
fs.StringVar(&c.OIDCAdminGroup, "oidc-admin-group", envOr("OIDC_ADMIN_GROUP", ""), "group claim required for admin access; empty means nobody is an admin via OIDC")
|
fs.StringVar(&c.OIDCAdminGroup, "oidc-admin-group", envOr("OIDC_ADMIN_GROUP", ""), "group claim required for admin access; empty means nobody is an admin via OIDC")
|
||||||
fs.StringVar(&c.OIDCClientSecret, "oidc-client-secret", secretOr("OIDC_CLIENT_SECRET", ""), "secret for the confidential admin client; prefer ECHOLOT_OIDC_CLIENT_SECRET_FILE")
|
fs.StringVar(&c.OIDCClientSecret, "oidc-client-secret", secretOr("OIDC_CLIENT_SECRET", ""), "secret for the confidential admin client; prefer ECHOLOT_OIDC_CLIENT_SECRET_FILE")
|
||||||
fs.StringVar(&c.AdminBaseURL, "admin-base-url", envOr("ADMIN_BASE_URL", ""), "public URL of the admin UI, for the OIDC redirect (e.g. https://admin.example.net)")
|
fs.StringVar(&c.AdminBaseURL, "admin-base-url", envOr("ADMIN_BASE_URL", ""), "public URL of the admin UI, for the OIDC redirect (e.g. https://admin.example.net)")
|
||||||
|
|||||||
@@ -59,8 +59,12 @@ type Server struct {
|
|||||||
// Granted server->client sends (spec §5). Both consume an asymmetric grant.
|
// Granted server->client sends (spec §5). Both consume an asymmetric grant.
|
||||||
DownTrain func(sess *session.Session, g *session.Grant, count, sizeBytes, intervalUs int) (int, error)
|
DownTrain func(sess *session.Session, g *session.Grant, count, sizeBytes, intervalUs int) (int, error)
|
||||||
BigSend func(sess *session.Session, g *session.Grant, sizes []int, df bool) ([]dataplane.BigSendResult, error)
|
BigSend func(sess *session.Session, g *session.Grant, sizes []int, df bool) ([]dataplane.BigSendResult, error)
|
||||||
// OIDC verifies ID tokens when the operator has configured an issuer (may be nil).
|
// OIDC verifies ID tokens presented by the *app* (may be nil).
|
||||||
OIDC *oidc.Verifier
|
OIDC *oidc.Verifier
|
||||||
|
// AdminOIDC verifies tokens from the admin UI's own client. Separate because an IdP may
|
||||||
|
// give each application its own issuer — Authentik derives it from the application slug —
|
||||||
|
// and a verifier pins exactly one issuer and the clients belonging to it.
|
||||||
|
AdminOIDC *oidc.Verifier
|
||||||
// Runs stores uploaded measurement documents (may be nil: uploads unsupported).
|
// Runs stores uploaded measurement documents (may be nil: uploads unsupported).
|
||||||
Runs *runs.Store
|
Runs *runs.Store
|
||||||
// FragSend emits one datagram as hand-built IP fragments in a chosen order (may be nil:
|
// FragSend emits one datagram as hand-built IP fragments in a chosen order (may be nil:
|
||||||
|
|||||||
Reference in New Issue
Block a user