2023-06-22 22:12:33 +00:00
|
|
|
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved.
|
2021-06-15 16:27:30 +00:00
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
// Package clientregistry defines Pinniped's OAuth2/OIDC clients.
|
|
|
|
package clientregistry
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2022-07-22 22:19:19 +00:00
|
|
|
"strings"
|
2021-06-15 16:27:30 +00:00
|
|
|
"time"
|
|
|
|
|
Create username scope, required for clients to get username in ID token
- For backwards compatibility with older Pinniped CLIs, the pinniped-cli
client does not need to request the username or groups scopes for them
to be granted. For dynamic clients, the usual OAuth2 rules apply:
the client must be allowed to request the scopes according to its
configuration, and the client must actually request the scopes in the
authorization request.
- If the username scope was not granted, then there will be no username
in the ID token, and the cluster-scoped token exchange will fail since
there would be no username in the resulting cluster-scoped ID token.
- The OIDC well-known discovery endpoint lists the username and groups
scopes in the scopes_supported list, and lists the username and groups
claims in the claims_supported list.
- Add username and groups scopes to the default list of scopes
put into kubeconfig files by "pinniped get kubeconfig" CLI command,
and the default list of scopes used by "pinniped login oidc" when
no list of scopes is specified in the kubeconfig file
- The warning header about group memberships changing during upstream
refresh will only be sent to the pinniped-cli client, since it is
only intended for kubectl and it could leak the username to the
client (which may not have the username scope granted) through the
warning message text.
- Add the user's username to the session storage as a new field, so that
during upstream refresh we can compare the original username from the
initial authorization to the refreshed username, even in the case when
the username scope was not granted (and therefore the username is not
stored in the ID token claims of the session storage)
- Bump the Supervisor session storage format version from 2 to 3
due to the username field being added to the session struct
- Extract commonly used string constants related to OIDC flows to api
package.
- Change some import names to make them consistent:
- Always import github.com/coreos/go-oidc/v3/oidc as "coreosoidc"
- Always import go.pinniped.dev/generated/latest/apis/supervisor/oidc
as "oidcapi"
- Always import go.pinniped.dev/internal/oidc as "oidc"
2022-08-08 23:29:22 +00:00
|
|
|
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
2021-06-15 16:27:30 +00:00
|
|
|
"github.com/ory/fosite"
|
2022-07-14 16:51:11 +00:00
|
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
|
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
|
|
|
|
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
|
Create username scope, required for clients to get username in ID token
- For backwards compatibility with older Pinniped CLIs, the pinniped-cli
client does not need to request the username or groups scopes for them
to be granted. For dynamic clients, the usual OAuth2 rules apply:
the client must be allowed to request the scopes according to its
configuration, and the client must actually request the scopes in the
authorization request.
- If the username scope was not granted, then there will be no username
in the ID token, and the cluster-scoped token exchange will fail since
there would be no username in the resulting cluster-scoped ID token.
- The OIDC well-known discovery endpoint lists the username and groups
scopes in the scopes_supported list, and lists the username and groups
claims in the claims_supported list.
- Add username and groups scopes to the default list of scopes
put into kubeconfig files by "pinniped get kubeconfig" CLI command,
and the default list of scopes used by "pinniped login oidc" when
no list of scopes is specified in the kubeconfig file
- The warning header about group memberships changing during upstream
refresh will only be sent to the pinniped-cli client, since it is
only intended for kubectl and it could leak the username to the
client (which may not have the username scope granted) through the
warning message text.
- Add the user's username to the session storage as a new field, so that
during upstream refresh we can compare the original username from the
initial authorization to the refreshed username, even in the case when
the username scope was not granted (and therefore the username is not
stored in the ID token claims of the session storage)
- Bump the Supervisor session storage format version from 2 to 3
due to the username field being added to the session struct
- Extract commonly used string constants related to OIDC flows to api
package.
- Change some import names to make them consistent:
- Always import github.com/coreos/go-oidc/v3/oidc as "coreosoidc"
- Always import go.pinniped.dev/generated/latest/apis/supervisor/oidc
as "oidcapi"
- Always import go.pinniped.dev/internal/oidc as "oidc"
2022-08-08 23:29:22 +00:00
|
|
|
oidcapi "go.pinniped.dev/generated/latest/apis/supervisor/oidc"
|
2022-07-14 16:51:11 +00:00
|
|
|
supervisorclient "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned/typed/config/v1alpha1"
|
2023-06-22 22:12:33 +00:00
|
|
|
"go.pinniped.dev/internal/federationdomain/oidcclientvalidator"
|
2022-07-14 16:51:11 +00:00
|
|
|
"go.pinniped.dev/internal/oidcclientsecretstorage"
|
|
|
|
"go.pinniped.dev/internal/plog"
|
2021-06-15 16:27:30 +00:00
|
|
|
)
|
|
|
|
|
2022-07-14 16:51:11 +00:00
|
|
|
// Client represents a Pinniped OAuth/OIDC client. It can be the static pinniped-cli client
|
|
|
|
// or a dynamic client defined by an OIDCClient CR.
|
2021-06-15 16:27:30 +00:00
|
|
|
type Client struct {
|
|
|
|
fosite.DefaultOpenIDConnectClient
|
|
|
|
}
|
|
|
|
|
2022-07-14 16:51:11 +00:00
|
|
|
// Client implements the base, OIDC, and response_mode client interfaces of Fosite.
|
2021-06-15 16:27:30 +00:00
|
|
|
var (
|
|
|
|
_ fosite.Client = (*Client)(nil)
|
|
|
|
_ fosite.OpenIDConnectClient = (*Client)(nil)
|
2021-06-16 14:02:50 +00:00
|
|
|
_ fosite.ResponseModeClient = (*Client)(nil)
|
2021-06-15 16:27:30 +00:00
|
|
|
)
|
|
|
|
|
2022-07-22 22:19:19 +00:00
|
|
|
func (c *Client) GetResponseModes() []fosite.ResponseModeType {
|
Create username scope, required for clients to get username in ID token
- For backwards compatibility with older Pinniped CLIs, the pinniped-cli
client does not need to request the username or groups scopes for them
to be granted. For dynamic clients, the usual OAuth2 rules apply:
the client must be allowed to request the scopes according to its
configuration, and the client must actually request the scopes in the
authorization request.
- If the username scope was not granted, then there will be no username
in the ID token, and the cluster-scoped token exchange will fail since
there would be no username in the resulting cluster-scoped ID token.
- The OIDC well-known discovery endpoint lists the username and groups
scopes in the scopes_supported list, and lists the username and groups
claims in the claims_supported list.
- Add username and groups scopes to the default list of scopes
put into kubeconfig files by "pinniped get kubeconfig" CLI command,
and the default list of scopes used by "pinniped login oidc" when
no list of scopes is specified in the kubeconfig file
- The warning header about group memberships changing during upstream
refresh will only be sent to the pinniped-cli client, since it is
only intended for kubectl and it could leak the username to the
client (which may not have the username scope granted) through the
warning message text.
- Add the user's username to the session storage as a new field, so that
during upstream refresh we can compare the original username from the
initial authorization to the refreshed username, even in the case when
the username scope was not granted (and therefore the username is not
stored in the ID token claims of the session storage)
- Bump the Supervisor session storage format version from 2 to 3
due to the username field being added to the session struct
- Extract commonly used string constants related to OIDC flows to api
package.
- Change some import names to make them consistent:
- Always import github.com/coreos/go-oidc/v3/oidc as "coreosoidc"
- Always import go.pinniped.dev/generated/latest/apis/supervisor/oidc
as "oidcapi"
- Always import go.pinniped.dev/internal/oidc as "oidc"
2022-08-08 23:29:22 +00:00
|
|
|
if c.ID == oidcapi.ClientIDPinnipedCLI {
|
2022-07-14 16:51:11 +00:00
|
|
|
// The pinniped-cli client supports "" (unspecified), "query", and "form_post" response modes.
|
|
|
|
return []fosite.ResponseModeType{fosite.ResponseModeDefault, fosite.ResponseModeQuery, fosite.ResponseModeFormPost}
|
|
|
|
}
|
|
|
|
// For now, all other clients support only "" (unspecified) and "query" response modes.
|
|
|
|
return []fosite.ResponseModeType{fosite.ResponseModeDefault, fosite.ResponseModeQuery}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ClientManager is a fosite.ClientManager with a statically-defined client and with dynamically-defined clients.
|
|
|
|
type ClientManager struct {
|
|
|
|
oidcClientsClient supervisorclient.OIDCClientInterface
|
|
|
|
storage *oidcclientsecretstorage.OIDCClientSecretStorage
|
2022-07-20 20:55:56 +00:00
|
|
|
minBcryptCost int
|
2022-07-14 16:51:11 +00:00
|
|
|
}
|
2021-06-15 16:27:30 +00:00
|
|
|
|
2022-07-14 16:51:11 +00:00
|
|
|
var _ fosite.ClientManager = (*ClientManager)(nil)
|
2021-06-15 16:27:30 +00:00
|
|
|
|
2022-07-14 16:51:11 +00:00
|
|
|
func NewClientManager(
|
|
|
|
oidcClientsClient supervisorclient.OIDCClientInterface,
|
|
|
|
storage *oidcclientsecretstorage.OIDCClientSecretStorage,
|
2022-07-20 20:55:56 +00:00
|
|
|
minBcryptCost int,
|
2022-07-14 16:51:11 +00:00
|
|
|
) *ClientManager {
|
|
|
|
return &ClientManager{
|
|
|
|
oidcClientsClient: oidcClientsClient,
|
|
|
|
storage: storage,
|
2022-07-20 20:55:56 +00:00
|
|
|
minBcryptCost: minBcryptCost,
|
2022-07-14 16:51:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetClient returns the client specified by the given ID.
|
2021-06-15 16:27:30 +00:00
|
|
|
//
|
|
|
|
// It returns a fosite.ErrNotFound if an unknown client is specified.
|
2022-07-14 16:51:11 +00:00
|
|
|
// Other errors returned are plain errors, because fosite will wrap them into a new ErrInvalidClient error and
|
|
|
|
// use the plain error's text as that error's debug message (see client_authentication.go in fosite).
|
|
|
|
func (m *ClientManager) GetClient(ctx context.Context, id string) (fosite.Client, error) {
|
Create username scope, required for clients to get username in ID token
- For backwards compatibility with older Pinniped CLIs, the pinniped-cli
client does not need to request the username or groups scopes for them
to be granted. For dynamic clients, the usual OAuth2 rules apply:
the client must be allowed to request the scopes according to its
configuration, and the client must actually request the scopes in the
authorization request.
- If the username scope was not granted, then there will be no username
in the ID token, and the cluster-scoped token exchange will fail since
there would be no username in the resulting cluster-scoped ID token.
- The OIDC well-known discovery endpoint lists the username and groups
scopes in the scopes_supported list, and lists the username and groups
claims in the claims_supported list.
- Add username and groups scopes to the default list of scopes
put into kubeconfig files by "pinniped get kubeconfig" CLI command,
and the default list of scopes used by "pinniped login oidc" when
no list of scopes is specified in the kubeconfig file
- The warning header about group memberships changing during upstream
refresh will only be sent to the pinniped-cli client, since it is
only intended for kubectl and it could leak the username to the
client (which may not have the username scope granted) through the
warning message text.
- Add the user's username to the session storage as a new field, so that
during upstream refresh we can compare the original username from the
initial authorization to the refreshed username, even in the case when
the username scope was not granted (and therefore the username is not
stored in the ID token claims of the session storage)
- Bump the Supervisor session storage format version from 2 to 3
due to the username field being added to the session struct
- Extract commonly used string constants related to OIDC flows to api
package.
- Change some import names to make them consistent:
- Always import github.com/coreos/go-oidc/v3/oidc as "coreosoidc"
- Always import go.pinniped.dev/generated/latest/apis/supervisor/oidc
as "oidcapi"
- Always import go.pinniped.dev/internal/oidc as "oidc"
2022-08-08 23:29:22 +00:00
|
|
|
if id == oidcapi.ClientIDPinnipedCLI {
|
2022-07-14 16:51:11 +00:00
|
|
|
// Return the static client. No lookups needed.
|
2021-06-15 16:27:30 +00:00
|
|
|
return PinnipedCLI(), nil
|
2022-07-14 16:51:11 +00:00
|
|
|
}
|
|
|
|
|
Create username scope, required for clients to get username in ID token
- For backwards compatibility with older Pinniped CLIs, the pinniped-cli
client does not need to request the username or groups scopes for them
to be granted. For dynamic clients, the usual OAuth2 rules apply:
the client must be allowed to request the scopes according to its
configuration, and the client must actually request the scopes in the
authorization request.
- If the username scope was not granted, then there will be no username
in the ID token, and the cluster-scoped token exchange will fail since
there would be no username in the resulting cluster-scoped ID token.
- The OIDC well-known discovery endpoint lists the username and groups
scopes in the scopes_supported list, and lists the username and groups
claims in the claims_supported list.
- Add username and groups scopes to the default list of scopes
put into kubeconfig files by "pinniped get kubeconfig" CLI command,
and the default list of scopes used by "pinniped login oidc" when
no list of scopes is specified in the kubeconfig file
- The warning header about group memberships changing during upstream
refresh will only be sent to the pinniped-cli client, since it is
only intended for kubectl and it could leak the username to the
client (which may not have the username scope granted) through the
warning message text.
- Add the user's username to the session storage as a new field, so that
during upstream refresh we can compare the original username from the
initial authorization to the refreshed username, even in the case when
the username scope was not granted (and therefore the username is not
stored in the ID token claims of the session storage)
- Bump the Supervisor session storage format version from 2 to 3
due to the username field being added to the session struct
- Extract commonly used string constants related to OIDC flows to api
package.
- Change some import names to make them consistent:
- Always import github.com/coreos/go-oidc/v3/oidc as "coreosoidc"
- Always import go.pinniped.dev/generated/latest/apis/supervisor/oidc
as "oidcapi"
- Always import go.pinniped.dev/internal/oidc as "oidc"
2022-08-08 23:29:22 +00:00
|
|
|
if !strings.HasPrefix(id, oidcapi.ClientIDRequiredOIDCClientPrefix) {
|
2022-07-22 22:19:19 +00:00
|
|
|
// It shouldn't really be possible to find this OIDCClient because the OIDCClient CRD validates the name prefix
|
|
|
|
// upon create, but just in case, don't even try to lookup clients which lack the required name prefix.
|
|
|
|
return nil, fosite.ErrNotFound.WithDescription("no such client")
|
|
|
|
}
|
|
|
|
|
2022-07-14 16:51:11 +00:00
|
|
|
// Try to look up an OIDCClient with the given client ID (which will be the Name of the OIDCClient).
|
|
|
|
oidcClient, err := m.oidcClientsClient.Get(ctx, id, v1.GetOptions{})
|
|
|
|
if errors.IsNotFound(err) {
|
2021-06-15 16:27:30 +00:00
|
|
|
return nil, fosite.ErrNotFound.WithDescription("no such client")
|
|
|
|
}
|
2022-07-14 16:51:11 +00:00
|
|
|
if err != nil {
|
|
|
|
// Log the error so an admin can see why the lookup failed at the time of the request.
|
|
|
|
plog.Error("OIDC client lookup GetClient() failed to get OIDCClient", err, "clientID", id)
|
|
|
|
return nil, fmt.Errorf("failed to get client %q", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to find the corresponding client secret storage Secret.
|
|
|
|
storageSecret, err := m.storage.GetStorageSecret(ctx, oidcClient.UID)
|
|
|
|
if err != nil {
|
|
|
|
// Log the error so an admin can see why the lookup failed at the time of the request.
|
|
|
|
plog.Error("OIDC client lookup GetClient() failed to get storage secret for OIDCClient", err, "clientID", id)
|
|
|
|
return nil, fmt.Errorf("failed to get storage secret for client %q", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the OIDCClient and its corresponding Secret are valid.
|
2022-07-20 20:55:56 +00:00
|
|
|
valid, conditions, clientSecrets := oidcclientvalidator.Validate(oidcClient, storageSecret, m.minBcryptCost)
|
2022-07-14 16:51:11 +00:00
|
|
|
if !valid {
|
|
|
|
// Log the conditions so an admin can see exactly what was invalid at the time of the request.
|
|
|
|
plog.Debug("OIDC client lookup GetClient() found an invalid client", "clientID", id, "conditions", conditions)
|
|
|
|
return nil, fmt.Errorf("client %q exists but is invalid or not ready", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Everything is valid, so return the client. Note that it has at least one client secret to be considered valid.
|
|
|
|
return oidcClientCRToFositeClient(oidcClient, clientSecrets), nil
|
2021-06-15 16:27:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ClientAssertionJWTValid returns an error if the JTI is
|
|
|
|
// known or the DB check failed and nil if the JTI is not known.
|
|
|
|
//
|
2022-07-14 16:51:11 +00:00
|
|
|
// This functionality is not supported by the ClientManager.
|
|
|
|
func (*ClientManager) ClientAssertionJWTValid(ctx context.Context, jti string) error {
|
2021-06-15 16:27:30 +00:00
|
|
|
return fmt.Errorf("not implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetClientAssertionJWT marks a JTI as known for the given
|
|
|
|
// expiry time. Before inserting the new JTI, it will clean
|
|
|
|
// up any existing JTIs that have expired as those tokens can
|
|
|
|
// not be replayed due to the expiry.
|
|
|
|
//
|
2022-07-14 16:51:11 +00:00
|
|
|
// This functionality is not supported by the ClientManager.
|
|
|
|
func (*ClientManager) SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) error {
|
2021-06-15 16:27:30 +00:00
|
|
|
return fmt.Errorf("not implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
// PinnipedCLI returns the static Client corresponding to the Pinniped CLI.
|
|
|
|
func PinnipedCLI() *Client {
|
|
|
|
return &Client{
|
|
|
|
DefaultOpenIDConnectClient: fosite.DefaultOpenIDConnectClient{
|
|
|
|
DefaultClient: &fosite.DefaultClient{
|
Create username scope, required for clients to get username in ID token
- For backwards compatibility with older Pinniped CLIs, the pinniped-cli
client does not need to request the username or groups scopes for them
to be granted. For dynamic clients, the usual OAuth2 rules apply:
the client must be allowed to request the scopes according to its
configuration, and the client must actually request the scopes in the
authorization request.
- If the username scope was not granted, then there will be no username
in the ID token, and the cluster-scoped token exchange will fail since
there would be no username in the resulting cluster-scoped ID token.
- The OIDC well-known discovery endpoint lists the username and groups
scopes in the scopes_supported list, and lists the username and groups
claims in the claims_supported list.
- Add username and groups scopes to the default list of scopes
put into kubeconfig files by "pinniped get kubeconfig" CLI command,
and the default list of scopes used by "pinniped login oidc" when
no list of scopes is specified in the kubeconfig file
- The warning header about group memberships changing during upstream
refresh will only be sent to the pinniped-cli client, since it is
only intended for kubectl and it could leak the username to the
client (which may not have the username scope granted) through the
warning message text.
- Add the user's username to the session storage as a new field, so that
during upstream refresh we can compare the original username from the
initial authorization to the refreshed username, even in the case when
the username scope was not granted (and therefore the username is not
stored in the ID token claims of the session storage)
- Bump the Supervisor session storage format version from 2 to 3
due to the username field being added to the session struct
- Extract commonly used string constants related to OIDC flows to api
package.
- Change some import names to make them consistent:
- Always import github.com/coreos/go-oidc/v3/oidc as "coreosoidc"
- Always import go.pinniped.dev/generated/latest/apis/supervisor/oidc
as "oidcapi"
- Always import go.pinniped.dev/internal/oidc as "oidc"
2022-08-08 23:29:22 +00:00
|
|
|
ID: oidcapi.ClientIDPinnipedCLI,
|
2021-06-15 16:27:30 +00:00
|
|
|
Secret: nil,
|
|
|
|
RedirectURIs: []string{"http://127.0.0.1/callback"},
|
|
|
|
GrantTypes: fosite.Arguments{
|
Create username scope, required for clients to get username in ID token
- For backwards compatibility with older Pinniped CLIs, the pinniped-cli
client does not need to request the username or groups scopes for them
to be granted. For dynamic clients, the usual OAuth2 rules apply:
the client must be allowed to request the scopes according to its
configuration, and the client must actually request the scopes in the
authorization request.
- If the username scope was not granted, then there will be no username
in the ID token, and the cluster-scoped token exchange will fail since
there would be no username in the resulting cluster-scoped ID token.
- The OIDC well-known discovery endpoint lists the username and groups
scopes in the scopes_supported list, and lists the username and groups
claims in the claims_supported list.
- Add username and groups scopes to the default list of scopes
put into kubeconfig files by "pinniped get kubeconfig" CLI command,
and the default list of scopes used by "pinniped login oidc" when
no list of scopes is specified in the kubeconfig file
- The warning header about group memberships changing during upstream
refresh will only be sent to the pinniped-cli client, since it is
only intended for kubectl and it could leak the username to the
client (which may not have the username scope granted) through the
warning message text.
- Add the user's username to the session storage as a new field, so that
during upstream refresh we can compare the original username from the
initial authorization to the refreshed username, even in the case when
the username scope was not granted (and therefore the username is not
stored in the ID token claims of the session storage)
- Bump the Supervisor session storage format version from 2 to 3
due to the username field being added to the session struct
- Extract commonly used string constants related to OIDC flows to api
package.
- Change some import names to make them consistent:
- Always import github.com/coreos/go-oidc/v3/oidc as "coreosoidc"
- Always import go.pinniped.dev/generated/latest/apis/supervisor/oidc
as "oidcapi"
- Always import go.pinniped.dev/internal/oidc as "oidc"
2022-08-08 23:29:22 +00:00
|
|
|
oidcapi.GrantTypeAuthorizationCode,
|
|
|
|
oidcapi.GrantTypeRefreshToken,
|
|
|
|
oidcapi.GrantTypeTokenExchange,
|
2021-06-15 16:27:30 +00:00
|
|
|
},
|
|
|
|
ResponseTypes: []string{"code"},
|
|
|
|
Scopes: fosite.Arguments{
|
Create username scope, required for clients to get username in ID token
- For backwards compatibility with older Pinniped CLIs, the pinniped-cli
client does not need to request the username or groups scopes for them
to be granted. For dynamic clients, the usual OAuth2 rules apply:
the client must be allowed to request the scopes according to its
configuration, and the client must actually request the scopes in the
authorization request.
- If the username scope was not granted, then there will be no username
in the ID token, and the cluster-scoped token exchange will fail since
there would be no username in the resulting cluster-scoped ID token.
- The OIDC well-known discovery endpoint lists the username and groups
scopes in the scopes_supported list, and lists the username and groups
claims in the claims_supported list.
- Add username and groups scopes to the default list of scopes
put into kubeconfig files by "pinniped get kubeconfig" CLI command,
and the default list of scopes used by "pinniped login oidc" when
no list of scopes is specified in the kubeconfig file
- The warning header about group memberships changing during upstream
refresh will only be sent to the pinniped-cli client, since it is
only intended for kubectl and it could leak the username to the
client (which may not have the username scope granted) through the
warning message text.
- Add the user's username to the session storage as a new field, so that
during upstream refresh we can compare the original username from the
initial authorization to the refreshed username, even in the case when
the username scope was not granted (and therefore the username is not
stored in the ID token claims of the session storage)
- Bump the Supervisor session storage format version from 2 to 3
due to the username field being added to the session struct
- Extract commonly used string constants related to OIDC flows to api
package.
- Change some import names to make them consistent:
- Always import github.com/coreos/go-oidc/v3/oidc as "coreosoidc"
- Always import go.pinniped.dev/generated/latest/apis/supervisor/oidc
as "oidcapi"
- Always import go.pinniped.dev/internal/oidc as "oidc"
2022-08-08 23:29:22 +00:00
|
|
|
oidcapi.ScopeOpenID,
|
|
|
|
oidcapi.ScopeOfflineAccess,
|
|
|
|
oidcapi.ScopeProfile,
|
|
|
|
oidcapi.ScopeEmail,
|
|
|
|
oidcapi.ScopeRequestAudience,
|
|
|
|
oidcapi.ScopeUsername,
|
|
|
|
oidcapi.ScopeGroups,
|
2021-06-15 16:27:30 +00:00
|
|
|
},
|
|
|
|
Audience: nil,
|
|
|
|
Public: true,
|
|
|
|
},
|
|
|
|
RequestURIs: nil,
|
|
|
|
JSONWebKeys: nil,
|
|
|
|
JSONWebKeysURI: "",
|
|
|
|
RequestObjectSigningAlgorithm: "",
|
Create username scope, required for clients to get username in ID token
- For backwards compatibility with older Pinniped CLIs, the pinniped-cli
client does not need to request the username or groups scopes for them
to be granted. For dynamic clients, the usual OAuth2 rules apply:
the client must be allowed to request the scopes according to its
configuration, and the client must actually request the scopes in the
authorization request.
- If the username scope was not granted, then there will be no username
in the ID token, and the cluster-scoped token exchange will fail since
there would be no username in the resulting cluster-scoped ID token.
- The OIDC well-known discovery endpoint lists the username and groups
scopes in the scopes_supported list, and lists the username and groups
claims in the claims_supported list.
- Add username and groups scopes to the default list of scopes
put into kubeconfig files by "pinniped get kubeconfig" CLI command,
and the default list of scopes used by "pinniped login oidc" when
no list of scopes is specified in the kubeconfig file
- The warning header about group memberships changing during upstream
refresh will only be sent to the pinniped-cli client, since it is
only intended for kubectl and it could leak the username to the
client (which may not have the username scope granted) through the
warning message text.
- Add the user's username to the session storage as a new field, so that
during upstream refresh we can compare the original username from the
initial authorization to the refreshed username, even in the case when
the username scope was not granted (and therefore the username is not
stored in the ID token claims of the session storage)
- Bump the Supervisor session storage format version from 2 to 3
due to the username field being added to the session struct
- Extract commonly used string constants related to OIDC flows to api
package.
- Change some import names to make them consistent:
- Always import github.com/coreos/go-oidc/v3/oidc as "coreosoidc"
- Always import go.pinniped.dev/generated/latest/apis/supervisor/oidc
as "oidcapi"
- Always import go.pinniped.dev/internal/oidc as "oidc"
2022-08-08 23:29:22 +00:00
|
|
|
TokenEndpointAuthSigningAlgorithm: coreosoidc.RS256,
|
2021-06-15 16:27:30 +00:00
|
|
|
TokenEndpointAuthMethod: "none",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2022-07-14 16:51:11 +00:00
|
|
|
|
|
|
|
func oidcClientCRToFositeClient(oidcClient *configv1alpha1.OIDCClient, clientSecrets []string) *Client {
|
|
|
|
return &Client{
|
|
|
|
DefaultOpenIDConnectClient: fosite.DefaultOpenIDConnectClient{
|
|
|
|
DefaultClient: &fosite.DefaultClient{
|
|
|
|
ID: oidcClient.Name,
|
|
|
|
// We set RotatedSecrets, but we don't need to also set Secret because the client_authentication.go code
|
|
|
|
// will always call the hasher on the empty Secret first, and the bcrypt hasher will always fail very
|
|
|
|
// quickly (ErrHashTooShort error), and then client_authentication.go will move on to using the
|
|
|
|
// RotatedSecrets instead.
|
|
|
|
RotatedSecrets: stringSliceToByteSlices(clientSecrets),
|
|
|
|
RedirectURIs: redirectURIsToStrings(oidcClient.Spec.AllowedRedirectURIs),
|
|
|
|
GrantTypes: grantTypesToArguments(oidcClient.Spec.AllowedGrantTypes),
|
|
|
|
ResponseTypes: []string{"code"},
|
|
|
|
Scopes: scopesToArguments(oidcClient.Spec.AllowedScopes),
|
|
|
|
Audience: nil,
|
|
|
|
Public: false,
|
|
|
|
},
|
|
|
|
RequestURIs: nil,
|
|
|
|
JSONWebKeys: nil,
|
|
|
|
JSONWebKeysURI: "",
|
|
|
|
RequestObjectSigningAlgorithm: "",
|
Create username scope, required for clients to get username in ID token
- For backwards compatibility with older Pinniped CLIs, the pinniped-cli
client does not need to request the username or groups scopes for them
to be granted. For dynamic clients, the usual OAuth2 rules apply:
the client must be allowed to request the scopes according to its
configuration, and the client must actually request the scopes in the
authorization request.
- If the username scope was not granted, then there will be no username
in the ID token, and the cluster-scoped token exchange will fail since
there would be no username in the resulting cluster-scoped ID token.
- The OIDC well-known discovery endpoint lists the username and groups
scopes in the scopes_supported list, and lists the username and groups
claims in the claims_supported list.
- Add username and groups scopes to the default list of scopes
put into kubeconfig files by "pinniped get kubeconfig" CLI command,
and the default list of scopes used by "pinniped login oidc" when
no list of scopes is specified in the kubeconfig file
- The warning header about group memberships changing during upstream
refresh will only be sent to the pinniped-cli client, since it is
only intended for kubectl and it could leak the username to the
client (which may not have the username scope granted) through the
warning message text.
- Add the user's username to the session storage as a new field, so that
during upstream refresh we can compare the original username from the
initial authorization to the refreshed username, even in the case when
the username scope was not granted (and therefore the username is not
stored in the ID token claims of the session storage)
- Bump the Supervisor session storage format version from 2 to 3
due to the username field being added to the session struct
- Extract commonly used string constants related to OIDC flows to api
package.
- Change some import names to make them consistent:
- Always import github.com/coreos/go-oidc/v3/oidc as "coreosoidc"
- Always import go.pinniped.dev/generated/latest/apis/supervisor/oidc
as "oidcapi"
- Always import go.pinniped.dev/internal/oidc as "oidc"
2022-08-08 23:29:22 +00:00
|
|
|
TokenEndpointAuthSigningAlgorithm: coreosoidc.RS256,
|
2022-07-14 16:51:11 +00:00
|
|
|
TokenEndpointAuthMethod: "client_secret_basic",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func scopesToArguments(scopes []configv1alpha1.Scope) fosite.Arguments {
|
|
|
|
a := make(fosite.Arguments, len(scopes))
|
|
|
|
for i, scope := range scopes {
|
|
|
|
a[i] = string(scope)
|
|
|
|
}
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
|
|
|
func grantTypesToArguments(grantTypes []configv1alpha1.GrantType) fosite.Arguments {
|
|
|
|
a := make(fosite.Arguments, len(grantTypes))
|
|
|
|
for i, grantType := range grantTypes {
|
|
|
|
a[i] = string(grantType)
|
|
|
|
}
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
|
|
|
func redirectURIsToStrings(uris []configv1alpha1.RedirectURI) []string {
|
|
|
|
s := make([]string, len(uris))
|
|
|
|
for i, uri := range uris {
|
|
|
|
s[i] = string(uri)
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
func stringSliceToByteSlices(s []string) [][]byte {
|
|
|
|
b := make([][]byte, len(s))
|
|
|
|
for i, str := range s {
|
|
|
|
b[i] = []byte(str)
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|