// Package identity — authtoken.go provides the ORCA_OIDC_TOKEN // validation helper used by the SSH-push applier and the txn apply // path (P04, v0.13; C-44). Both paths validate the env-var token // against the issuer's JWKS before applying any state change. // // The token is read from $ORCA_OIDC_TOKEN. The issuer + client ID // come from the OIDC config (oidc.issuer, oidc.client_id). If the // token is missing or invalid, the apply is refused. The verified // claims (sub + groups) are returned so the caller can thread them // into the audit actor field (T5) and the ACL check (T3/T4). package identity import ( "context" "fmt" "os" ) // EnvOIDCToken is the environment variable holding the OIDC ID token // for the SSH-push / txn apply paths (R-021: the IdP issues the // token; Orca never issues its own). const EnvOIDCToken = "ORCA_OIDC_TOKEN" // VerifyOperatorToken reads $ORCA_OIDC_TOKEN and verifies it against // the issuer's JWKS. Returns the verified claims (sub, groups) on // success. Returns an error if the token is missing, expired, or // fails signature verification. // // The issuer + clientID come from the OIDC config block. When issuer // is empty, the function returns an error — the apply path requires // an OIDC issuer to be configured. func VerifyOperatorToken(ctx context.Context, issuer, clientID string) (*IDTokenClaims, error) { raw := os.Getenv(EnvOIDCToken) if raw == "" { return nil, fmt.Errorf("identity: %s env var is not set (operator OIDC token required for apply)", EnvOIDCToken) } if issuer == "" { return nil, fmt.Errorf("identity: oidc.issuer is not configured (required to verify %s)", EnvOIDCToken) } if clientID == "" { clientID = "orca-cli" } claims, err := VerifyIDTokenStatic(ctx, issuer, clientID, raw) if err != nil { return nil, fmt.Errorf("identity: verify %s: %w", EnvOIDCToken, err) } return claims, nil } // OperatorActor renders the verified operator identity for the audit // `actor` field. The convention is "oidc:" so audit entries can // be filtered by human operator. Falls back to "oidc:unknown" when // claims are nil (e.g. when the caller could not verify the token but // still wants to record an audit entry). func OperatorActor(claims *IDTokenClaims) string { if claims == nil || claims.Subject == "" { return "oidc:unknown" } return "oidc:" + claims.Subject }