Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add oidc.claim #635

Merged
merged 5 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cmd/incusd/api_1.0.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ func api10Get(d *Daemon, r *http.Request) response.Response {
// Get the authentication methods.
authMethods := []string{api.AuthenticationMethodTLS}

oidcIssuer, oidcClientID, _ := s.GlobalConfig.OIDCServer()
oidcIssuer, oidcClientID, _, _ := s.GlobalConfig.OIDCServer()
if oidcIssuer != "" && oidcClientID != "" {
authMethods = append(authMethods, api.AuthenticationMethodOIDC)
}
Expand Down Expand Up @@ -998,13 +998,13 @@ func doApi10UpdateTriggers(d *Daemon, nodeChanged, clusterChanged map[string]str
}

if oidcChanged {
oidcIssuer, oidcClientID, oidcAudience := clusterConfig.OIDCServer()
oidcIssuer, oidcClientID, oidcAudience, oidcClaim := clusterConfig.OIDCServer()

if oidcIssuer == "" || oidcClientID == "" {
d.oidcVerifier = nil
} else {
var err error
d.oidcVerifier, err = oidc.NewVerifier(oidcIssuer, oidcClientID, oidcAudience)
d.oidcVerifier, err = oidc.NewVerifier(oidcIssuer, oidcClientID, oidcAudience, oidcClaim)
if err != nil {
return fmt.Errorf("Failed creating verifier: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/incusd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -1380,7 +1380,7 @@ func (d *Daemon) init() error {

d.gateway.HeartbeatOfflineThreshold = d.globalConfig.OfflineThreshold()
lokiURL, lokiUsername, lokiPassword, lokiCACert, lokiInstance, lokiLoglevel, lokiLabels, lokiTypes := d.globalConfig.LokiServer()
oidcIssuer, oidcClientID, oidcAudience := d.globalConfig.OIDCServer()
oidcIssuer, oidcClientID, oidcAudience, oidcClaim := d.globalConfig.OIDCServer()
syslogSocketEnabled := d.localConfig.SyslogSocket()
openfgaAPIURL, openfgaAPIToken, openfgaStoreID, openFGAAuthorizationModelID := d.globalConfig.OpenFGA()
instancePlacementScriptlet := d.globalConfig.InstancesPlacementScriptlet()
Expand All @@ -1406,7 +1406,7 @@ func (d *Daemon) init() error {

// Setup OIDC authentication.
if oidcIssuer != "" && oidcClientID != "" {
d.oidcVerifier, err = oidc.NewVerifier(oidcIssuer, oidcClientID, oidcAudience)
d.oidcVerifier, err = oidc.NewVerifier(oidcIssuer, oidcClientID, oidcAudience, oidcClaim)
if err != nil {
return err
}
Expand Down
4 changes: 4 additions & 0 deletions doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2383,3 +2383,7 @@ This adds the ability to use a signed `JSON Web Token` (`JWT`) instead of using
In this scenario, the client derives a `JWT` from their own TLS client certificate providing it as a `bearer` token through the `Authorization` HTTP header.

The `JWT` must have the certificate's fingerprint as its `Subject` and must be signed by the client's private key.

## `oidc_claim`

This introduces a new `oidc.claim` server configuration key which can be used to specify what OpenID Connect claim to use as the username.
7 changes: 7 additions & 0 deletions doc/config_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1787,6 +1787,13 @@ Specify the volume using the syntax `POOL/VOLUME`.
This value is required by some providers.
```

```{config:option} oidc.claim server-oidc
:scope: "global"
:shortdesc: "OpenID Connect claim to use as the username"
:type: "string"

```

```{config:option} oidc.client.id server-oidc
:scope: "global"
:shortdesc: "OpenID Connect client ID"
Expand Down
15 changes: 13 additions & 2 deletions internal/server/auth/oidc/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Verifier struct {
clientID string
issuer string
audience string
claim string
cookieKey []byte
}

Expand Down Expand Up @@ -129,6 +130,16 @@ func (o *Verifier) Auth(ctx context.Context, w http.ResponseWriter, r *http.Requ
}
}

if o.claim != "" {
claim := claims.Claims[o.claim]
username, ok := claim.(string)
if claim == nil || !ok || username == "" {
return "", fmt.Errorf("OIDC user is missing required claim %q", o.claim)
}

return username, nil
}

user, ok := claims.Claims["email"]
if ok && user != nil && user.(string) != "" {
return user.(string), nil
Expand Down Expand Up @@ -295,13 +306,13 @@ func getAccessTokenVerifier(issuer string) (op.AccessTokenVerifier, error) {
}

// NewVerifier returns a Verifier.
func NewVerifier(issuer string, clientid string, audience string) (*Verifier, error) {
func NewVerifier(issuer string, clientid string, audience string, claim string) (*Verifier, error) {
cookieKey, err := uuid.New().MarshalBinary()
if err != nil {
return nil, fmt.Errorf("Failed to create UUID: %w", err)
}

verifier := &Verifier{issuer: issuer, clientID: clientid, audience: audience, cookieKey: cookieKey}
verifier := &Verifier{issuer: issuer, clientID: clientid, audience: audience, cookieKey: cookieKey, claim: claim}
verifier.accessTokenVerifier, _ = getAccessTokenVerifier(issuer)

return verifier, nil
Expand Down
12 changes: 10 additions & 2 deletions internal/server/cluster/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ func (c *Config) RemoteTokenExpiry() string {
}

// OIDCServer returns all the OpenID Connect settings needed to connect to a server.
func (c *Config) OIDCServer() (string, string, string) {
return c.m.GetString("oidc.issuer"), c.m.GetString("oidc.client.id"), c.m.GetString("oidc.audience")
func (c *Config) OIDCServer() (string, string, string, string) {
return c.m.GetString("oidc.issuer"), c.m.GetString("oidc.client.id"), c.m.GetString("oidc.audience"), c.m.GetString("oidc.claim")
}

// ClusterHealingThreshold returns the configured healing threshold, i.e. the
Expand Down Expand Up @@ -685,6 +685,14 @@ var ConfigSchema = config.Schema{
// shortdesc: Expected audience value for the application
"oidc.audience": {},

// gendoc:generate(entity=server, group=oidc, key=oidc.claim)
//
// ---
// type: string
// scope: global
// shortdesc: OpenID Connect claim to use as the username
"oidc.claim": {},

// OVN networking global keys.

// gendoc:generate(entity=server, group=miscellaneous, key=network.ovn.integration_bridge)
Expand Down
8 changes: 8 additions & 0 deletions internal/server/metadata/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -1962,6 +1962,14 @@
"type": "string"
}
},
{
"oidc.claim": {
"longdesc": "",
"scope": "global",
"shortdesc": "OpenID Connect claim to use as the username",
"type": "string"
}
},
{
"oidc.client.id": {
"longdesc": "",
Expand Down
1 change: 1 addition & 0 deletions internal/version/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ var APIExtensions = []string{
"storage_lvm_cluster",
"shared_custom_block_volumes",
"auth_tls_jwt",
"oidc_claim",
}

// APIExtensionsCount returns the number of available API extensions.
Expand Down
Loading