mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 07:20:13 +00:00
Merge pull request #100868 from enj/enj/f/oidc_controller
oidc authenticator: make library usage easier
This commit is contained in:
commit
7f200cb75b
@ -151,10 +151,20 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
|
||||
// simply returns an error, the OpenID Connect plugin may query the provider to
|
||||
// update the keys, causing performance hits.
|
||||
if len(config.OIDCIssuerURL) > 0 && len(config.OIDCClientID) > 0 {
|
||||
// TODO(enj): wire up the Notifier and ControllerRunner bits when OIDC supports CA reload
|
||||
var oidcCAContent oidc.CAContentProvider
|
||||
if len(config.OIDCCAFile) != 0 {
|
||||
var oidcCAErr error
|
||||
oidcCAContent, oidcCAErr = dynamiccertificates.NewDynamicCAContentFromFile("oidc-authenticator", config.OIDCCAFile)
|
||||
if oidcCAErr != nil {
|
||||
return nil, nil, oidcCAErr
|
||||
}
|
||||
}
|
||||
|
||||
oidcAuth, err := newAuthenticatorFromOIDCIssuerURL(oidc.Options{
|
||||
IssuerURL: config.OIDCIssuerURL,
|
||||
ClientID: config.OIDCClientID,
|
||||
CAFile: config.OIDCCAFile,
|
||||
CAContentProvider: oidcCAContent,
|
||||
UsernameClaim: config.OIDCUsernameClaim,
|
||||
UsernamePrefix: config.OIDCUsernamePrefix,
|
||||
GroupsClaim: config.OIDCGroupsClaim,
|
||||
|
@ -70,6 +70,9 @@ type Options struct {
|
||||
// See: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
|
||||
IssuerURL string
|
||||
|
||||
// Optional KeySet to allow for synchronous initlization instead of fetching from the remote issuer.
|
||||
KeySet oidc.KeySet
|
||||
|
||||
// ClientID the JWT must be issued for, the "sub" field. This plugin only trusts a single
|
||||
// client to ensure the plugin can be used with public providers.
|
||||
//
|
||||
@ -78,8 +81,8 @@ type Options struct {
|
||||
// See: https://openid.net/specs/openid-connect-core-1_0.html#IDToken
|
||||
ClientID string
|
||||
|
||||
// Path to a PEM encoded root certificate of the provider.
|
||||
CAFile string
|
||||
// PEM encoded root certificate contents of the provider.
|
||||
CAContentProvider CAContentProvider
|
||||
|
||||
// UsernameClaim is the JWT field to use as the user's username.
|
||||
UsernameClaim string
|
||||
@ -116,6 +119,11 @@ type Options struct {
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
// Subset of dynamiccertificates.CAContentProvider that can be used to dynamically load root CAs.
|
||||
type CAContentProvider interface {
|
||||
CurrentCABundleContent() []byte
|
||||
}
|
||||
|
||||
// initVerifier creates a new ID token verifier for the given configuration and issuer URL. On success, calls setVerifier with the
|
||||
// resulting verifier.
|
||||
func initVerifier(ctx context.Context, config *oidc.Config, iss string) (*oidc.IDTokenVerifier, error) {
|
||||
@ -214,24 +222,6 @@ func (a *Authenticator) Close() {
|
||||
a.cancel()
|
||||
}
|
||||
|
||||
func New(opts Options) (*Authenticator, error) {
|
||||
return newAuthenticator(opts, func(ctx context.Context, a *Authenticator, config *oidc.Config) {
|
||||
// Asynchronously attempt to initialize the authenticator. This enables
|
||||
// self-hosted providers, providers that run on top of Kubernetes itself.
|
||||
go wait.PollImmediateUntil(time.Second*10, func() (done bool, err error) {
|
||||
provider, err := oidc.NewProvider(ctx, a.issuerURL)
|
||||
if err != nil {
|
||||
klog.Errorf("oidc authenticator: initializing plugin: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
verifier := provider.Verifier(config)
|
||||
a.setVerifier(verifier)
|
||||
return true, nil
|
||||
}, ctx.Done())
|
||||
})
|
||||
}
|
||||
|
||||
// whitelist of signing algorithms to ensure users don't mistakenly pass something
|
||||
// goofy.
|
||||
var allowedSigningAlgs = map[string]bool{
|
||||
@ -246,7 +236,7 @@ var allowedSigningAlgs = map[string]bool{
|
||||
oidc.PS512: true,
|
||||
}
|
||||
|
||||
func newAuthenticator(opts Options, initVerifier func(ctx context.Context, a *Authenticator, config *oidc.Config)) (*Authenticator, error) {
|
||||
func New(opts Options) (*Authenticator, error) {
|
||||
url, err := url.Parse(opts.IssuerURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -273,10 +263,11 @@ func newAuthenticator(opts Options, initVerifier func(ctx context.Context, a *Au
|
||||
}
|
||||
|
||||
var roots *x509.CertPool
|
||||
if opts.CAFile != "" {
|
||||
roots, err = certutil.NewPool(opts.CAFile)
|
||||
if opts.CAContentProvider != nil {
|
||||
// TODO(enj): make this reload CA data dynamically
|
||||
roots, err = certutil.NewPoolFromBytes(opts.CAContentProvider.CurrentCABundleContent())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to read the CA file: %v", err)
|
||||
return nil, fmt.Errorf("Failed to read the CA contents: %v", err)
|
||||
}
|
||||
} else {
|
||||
klog.Info("OIDC: No x509 certificates provided, will use host's root CA set")
|
||||
@ -321,7 +312,25 @@ func newAuthenticator(opts Options, initVerifier func(ctx context.Context, a *Au
|
||||
resolver: resolver,
|
||||
}
|
||||
|
||||
initVerifier(ctx, authenticator, verifierConfig)
|
||||
if opts.KeySet != nil {
|
||||
// We already have a key set, synchronously initialize the verifier.
|
||||
authenticator.setVerifier(oidc.NewVerifier(opts.IssuerURL, opts.KeySet, verifierConfig))
|
||||
} else {
|
||||
// Asynchronously attempt to initialize the authenticator. This enables
|
||||
// self-hosted providers, providers that run on top of Kubernetes itself.
|
||||
go wait.PollImmediateUntil(10*time.Second, func() (done bool, err error) {
|
||||
provider, err := oidc.NewProvider(ctx, opts.IssuerURL)
|
||||
if err != nil {
|
||||
klog.Errorf("oidc authenticator: initializing plugin: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
verifier := provider.Verifier(verifierConfig)
|
||||
authenticator.setVerifier(verifier)
|
||||
return true, nil
|
||||
}, ctx.Done())
|
||||
}
|
||||
|
||||
return authenticator, nil
|
||||
}
|
||||
|
||||
|
@ -35,9 +35,9 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
oidc "github.com/coreos/go-oidc"
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/server/dynamiccertificates"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
@ -254,7 +254,11 @@ func (c *claimsTest) run(t *testing.T) {
|
||||
// by writing its root CA certificate into a temporary file.
|
||||
tempFileName := writeTempCert(t, ts.TLS.Certificates[0].Certificate[0])
|
||||
defer os.Remove(tempFileName)
|
||||
c.options.CAFile = tempFileName
|
||||
caContent, err := dynamiccertificates.NewDynamicCAContentFromFile("oidc-authenticator", tempFileName)
|
||||
if err != nil {
|
||||
t.Fatalf("initialize ca: %v", err)
|
||||
}
|
||||
c.options.CAContentProvider = caContent
|
||||
|
||||
// Allow claims to refer to the serving URL of the test server. For this,
|
||||
// substitute all references to {{.URL}} in appropriate places.
|
||||
@ -266,15 +270,11 @@ func (c *claimsTest) run(t *testing.T) {
|
||||
c.claimToResponseMap[claim] = replace(response, &v)
|
||||
}
|
||||
|
||||
// Set the verifier to use the public key set instead of reading from a remote.
|
||||
c.options.KeySet = &staticKeySet{keys: c.pubKeys}
|
||||
|
||||
// Initialize the authenticator.
|
||||
a, err := newAuthenticator(c.options, func(ctx context.Context, a *Authenticator, config *oidc.Config) {
|
||||
// Set the verifier to use the public key set instead of reading from a remote.
|
||||
a.setVerifier(oidc.NewVerifier(
|
||||
c.options.IssuerURL,
|
||||
&staticKeySet{keys: c.pubKeys},
|
||||
config,
|
||||
))
|
||||
})
|
||||
a, err := New(c.options)
|
||||
if err != nil {
|
||||
if !c.wantInitErr {
|
||||
t.Fatalf("initialize authenticator: %v", err)
|
||||
|
Loading…
Reference in New Issue
Block a user