supervisor-generate-key: initial spike
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
This commit is contained in:
parent
aa705afc72
commit
6aed025c79
@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
import (
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid
|
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid
|
||||||
type OIDCProviderStatus string
|
type OIDCProviderStatus string
|
||||||
@ -39,11 +42,17 @@ type OIDCProviderConfigStatus struct {
|
|||||||
// +optional
|
// +optional
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
|
|
||||||
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
|
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
|
||||||
// around some undesirable behavior with respect to the empty metav1.Time value (see
|
// around some undesirable behavior with respect to the empty metav1.Time value (see
|
||||||
// https://github.com/kubernetes/kubernetes/issues/86811).
|
// https://github.com/kubernetes/kubernetes/issues/86811).
|
||||||
// +optional
|
// +optional
|
||||||
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
|
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
|
||||||
|
|
||||||
|
// JWKSSecret holds the name of the secret in which this OIDC Provider's signing/verification keys
|
||||||
|
// are stored. If it is empty, then the signing/verification keys are either unknown or they don't
|
||||||
|
// exist.
|
||||||
|
// +optional
|
||||||
|
JWKSSecret corev1.LocalObjectReference `json:"jwksSecret,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIDCProviderConfig describes the configuration of an OIDC provider.
|
// OIDCProviderConfig describes the configuration of an OIDC provider.
|
||||||
|
@ -13,6 +13,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/clock"
|
"k8s.io/apimachinery/pkg/util/clock"
|
||||||
|
kubeinformers "k8s.io/client-go/informers"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/pkg/version"
|
"k8s.io/client-go/pkg/version"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
@ -62,7 +64,9 @@ func waitForSignal() os.Signal {
|
|||||||
func startControllers(
|
func startControllers(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
issuerProvider *manager.Manager,
|
issuerProvider *manager.Manager,
|
||||||
|
kubeClient kubernetes.Interface,
|
||||||
pinnipedClient pinnipedclientset.Interface,
|
pinnipedClient pinnipedclientset.Interface,
|
||||||
|
kubeInformers kubeinformers.SharedInformerFactory,
|
||||||
pinnipedInformers pinnipedinformers.SharedInformerFactory,
|
pinnipedInformers pinnipedinformers.SharedInformerFactory,
|
||||||
) {
|
) {
|
||||||
// Create controller manager.
|
// Create controller manager.
|
||||||
@ -77,37 +81,60 @@ func startControllers(
|
|||||||
controllerlib.WithInformer,
|
controllerlib.WithInformer,
|
||||||
),
|
),
|
||||||
singletonWorker,
|
singletonWorker,
|
||||||
|
).
|
||||||
|
WithController(
|
||||||
|
supervisorconfig.NewJWKSController(
|
||||||
|
kubeClient,
|
||||||
|
pinnipedClient,
|
||||||
|
kubeInformers.Core().V1().Secrets(),
|
||||||
|
pinnipedInformers.Config().V1alpha1().OIDCProviderConfigs(),
|
||||||
|
controllerlib.WithInformer,
|
||||||
|
),
|
||||||
|
singletonWorker,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
kubeInformers.Start(ctx.Done())
|
||||||
pinnipedInformers.Start(ctx.Done())
|
pinnipedInformers.Start(ctx.Done())
|
||||||
|
|
||||||
go controllerManager.Start(ctx)
|
go controllerManager.Start(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPinnipedClient() (pinnipedclientset.Interface, error) {
|
func newClients() (kubernetes.Interface, pinnipedclientset.Interface, error) {
|
||||||
kubeConfig, err := restclient.InClusterConfig()
|
kubeConfig, err := restclient.InClusterConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not load in-cluster configuration: %w", err)
|
return nil, nil, fmt.Errorf("could not load in-cluster configuration: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to the core Kubernetes API.
|
// Connect to the core Kubernetes API.
|
||||||
pinnipedClient, err := pinnipedclientset.NewForConfig(kubeConfig)
|
kubeClient, err := kubernetes.NewForConfig(kubeConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not load in-cluster configuration: %w", err)
|
return nil, nil, fmt.Errorf("could not create kube client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pinnipedClient, nil
|
// Connect to the Pinniped API.
|
||||||
|
pinnipedClient, err := pinnipedclientset.NewForConfig(kubeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("could not create pinniped client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return kubeClient, pinnipedClient, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(serverInstallationNamespace string) error {
|
func run(serverInstallationNamespace string) error {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
pinnipedClient, err := newPinnipedClient()
|
kubeClient, pinnipedClient, err := newClients()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create k8s client: %w", err)
|
return fmt.Errorf("cannot create k8s client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kubeInformers := kubeinformers.NewSharedInformerFactoryWithOptions(
|
||||||
|
kubeClient,
|
||||||
|
defaultResyncInterval,
|
||||||
|
kubeinformers.WithNamespace(serverInstallationNamespace),
|
||||||
|
)
|
||||||
|
|
||||||
pinnipedInformers := pinnipedinformers.NewSharedInformerFactoryWithOptions(
|
pinnipedInformers := pinnipedinformers.NewSharedInformerFactoryWithOptions(
|
||||||
pinnipedClient,
|
pinnipedClient,
|
||||||
defaultResyncInterval,
|
defaultResyncInterval,
|
||||||
@ -115,7 +142,7 @@ func run(serverInstallationNamespace string) error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
oidProvidersManager := manager.NewManager(http.NotFoundHandler())
|
oidProvidersManager := manager.NewManager(http.NotFoundHandler())
|
||||||
startControllers(ctx, oidProvidersManager, pinnipedClient, pinnipedInformers)
|
startControllers(ctx, oidProvidersManager, kubeClient, pinnipedClient, kubeInformers, pinnipedInformers)
|
||||||
|
|
||||||
//nolint: gosec // Intentionally binding to all network interfaces.
|
//nolint: gosec // Intentionally binding to all network interfaces.
|
||||||
l, err := net.Listen("tcp", ":80")
|
l, err := net.Listen("tcp", ":80")
|
||||||
|
@ -55,6 +55,17 @@ spec:
|
|||||||
status:
|
status:
|
||||||
description: Status of the OIDC provider.
|
description: Status of the OIDC provider.
|
||||||
properties:
|
properties:
|
||||||
|
jwksSecret:
|
||||||
|
description: JWKSSecret holds the name of the secret in which this
|
||||||
|
OIDC Provider's signing/verification keys are stored. If it is empty,
|
||||||
|
then the signing/verification keys are either unknown or they don't
|
||||||
|
exist.
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||||
|
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
lastUpdateTime:
|
lastUpdateTime:
|
||||||
description: LastUpdateTime holds the time at which the Status was
|
description: LastUpdateTime holds the time at which the Status was
|
||||||
last updated. It is a pointer to get around some undesirable behavior
|
last updated. It is a pointer to get around some undesirable behavior
|
||||||
|
@ -13,6 +13,9 @@ metadata:
|
|||||||
labels:
|
labels:
|
||||||
app: #@ data.values.app_name
|
app: #@ data.values.app_name
|
||||||
rules:
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [secrets]
|
||||||
|
verbs: [create, get, list, patch, update, watch, delete]
|
||||||
- apiGroups: [config.pinniped.dev]
|
- apiGroups: [config.pinniped.dev]
|
||||||
resources: [oidcproviderconfigs]
|
resources: [oidcproviderconfigs]
|
||||||
verbs: [update, get, list, watch]
|
verbs: [update, get, list, watch]
|
||||||
|
1
generated/1.17/README.adoc
generated
1
generated/1.17/README.adoc
generated
@ -151,6 +151,7 @@ OIDCProviderConfigStatus is a struct that describes the actual state of an OIDC
|
|||||||
| *`status`* __OIDCProviderStatus__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
|
| *`status`* __OIDCProviderStatus__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
|
||||||
| *`message`* __string__ | Message provides human-readable details about the Status.
|
| *`message`* __string__ | Message provides human-readable details about the Status.
|
||||||
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#time-v1-meta[$$Time$$]__ | LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get around some undesirable behavior with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
|
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#time-v1-meta[$$Time$$]__ | LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get around some undesirable behavior with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
|
||||||
|
| *`jwksSecret`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#localobjectreference-v1-core[$$LocalObjectReference$$]__ | JWKSSecret holds the name of the secret in which this OIDC Provider's signing/verification keys are stored. If it is empty, then the signing/verification keys are either unknown or they don't exist.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
import (
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid
|
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid
|
||||||
type OIDCProviderStatus string
|
type OIDCProviderStatus string
|
||||||
@ -39,11 +42,17 @@ type OIDCProviderConfigStatus struct {
|
|||||||
// +optional
|
// +optional
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
|
|
||||||
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
|
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
|
||||||
// around some undesirable behavior with respect to the empty metav1.Time value (see
|
// around some undesirable behavior with respect to the empty metav1.Time value (see
|
||||||
// https://github.com/kubernetes/kubernetes/issues/86811).
|
// https://github.com/kubernetes/kubernetes/issues/86811).
|
||||||
// +optional
|
// +optional
|
||||||
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
|
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
|
||||||
|
|
||||||
|
// JWKSSecret holds the name of the secret in which this OIDC Provider's signing/verification keys
|
||||||
|
// are stored. If it is empty, then the signing/verification keys are either unknown or they don't
|
||||||
|
// exist.
|
||||||
|
// +optional
|
||||||
|
JWKSSecret corev1.LocalObjectReference `json:"jwksSecret,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIDCProviderConfig describes the configuration of an OIDC provider.
|
// OIDCProviderConfig describes the configuration of an OIDC provider.
|
||||||
|
@ -216,6 +216,7 @@ func (in *OIDCProviderConfigStatus) DeepCopyInto(out *OIDCProviderConfigStatus)
|
|||||||
in, out := &in.LastUpdateTime, &out.LastUpdateTime
|
in, out := &in.LastUpdateTime, &out.LastUpdateTime
|
||||||
*out = (*in).DeepCopy()
|
*out = (*in).DeepCopy()
|
||||||
}
|
}
|
||||||
|
out.JWKSSecret = in.JWKSSecret
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,11 +431,17 @@ func schema_117_apis_config_v1alpha1_OIDCProviderConfigStatus(ref common.Referen
|
|||||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
|
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"jwksSecret": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "JWKSSecret holds the name of the secret in which this OIDC Provider's signing/verification keys are stored. If it is empty, then the signing/verification keys are either unknown or they don't exist.",
|
||||||
|
Ref: ref("k8s.io/api/core/v1.LocalObjectReference"),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Dependencies: []string{
|
Dependencies: []string{
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
"k8s.io/api/core/v1.LocalObjectReference", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,17 @@ spec:
|
|||||||
status:
|
status:
|
||||||
description: Status of the OIDC provider.
|
description: Status of the OIDC provider.
|
||||||
properties:
|
properties:
|
||||||
|
jwksSecret:
|
||||||
|
description: JWKSSecret holds the name of the secret in which this
|
||||||
|
OIDC Provider's signing/verification keys are stored. If it is empty,
|
||||||
|
then the signing/verification keys are either unknown or they don't
|
||||||
|
exist.
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||||
|
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
lastUpdateTime:
|
lastUpdateTime:
|
||||||
description: LastUpdateTime holds the time at which the Status was
|
description: LastUpdateTime holds the time at which the Status was
|
||||||
last updated. It is a pointer to get around some undesirable behavior
|
last updated. It is a pointer to get around some undesirable behavior
|
||||||
|
1
generated/1.18/README.adoc
generated
1
generated/1.18/README.adoc
generated
@ -151,6 +151,7 @@ OIDCProviderConfigStatus is a struct that describes the actual state of an OIDC
|
|||||||
| *`status`* __OIDCProviderStatus__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
|
| *`status`* __OIDCProviderStatus__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
|
||||||
| *`message`* __string__ | Message provides human-readable details about the Status.
|
| *`message`* __string__ | Message provides human-readable details about the Status.
|
||||||
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#time-v1-meta[$$Time$$]__ | LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get around some undesirable behavior with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
|
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#time-v1-meta[$$Time$$]__ | LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get around some undesirable behavior with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
|
||||||
|
| *`jwksSecret`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#localobjectreference-v1-core[$$LocalObjectReference$$]__ | JWKSSecret holds the name of the secret in which this OIDC Provider's signing/verification keys are stored. If it is empty, then the signing/verification keys are either unknown or they don't exist.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
import (
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid
|
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid
|
||||||
type OIDCProviderStatus string
|
type OIDCProviderStatus string
|
||||||
@ -39,11 +42,17 @@ type OIDCProviderConfigStatus struct {
|
|||||||
// +optional
|
// +optional
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
|
|
||||||
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
|
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
|
||||||
// around some undesirable behavior with respect to the empty metav1.Time value (see
|
// around some undesirable behavior with respect to the empty metav1.Time value (see
|
||||||
// https://github.com/kubernetes/kubernetes/issues/86811).
|
// https://github.com/kubernetes/kubernetes/issues/86811).
|
||||||
// +optional
|
// +optional
|
||||||
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
|
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
|
||||||
|
|
||||||
|
// JWKSSecret holds the name of the secret in which this OIDC Provider's signing/verification keys
|
||||||
|
// are stored. If it is empty, then the signing/verification keys are either unknown or they don't
|
||||||
|
// exist.
|
||||||
|
// +optional
|
||||||
|
JWKSSecret corev1.LocalObjectReference `json:"jwksSecret,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIDCProviderConfig describes the configuration of an OIDC provider.
|
// OIDCProviderConfig describes the configuration of an OIDC provider.
|
||||||
|
@ -216,6 +216,7 @@ func (in *OIDCProviderConfigStatus) DeepCopyInto(out *OIDCProviderConfigStatus)
|
|||||||
in, out := &in.LastUpdateTime, &out.LastUpdateTime
|
in, out := &in.LastUpdateTime, &out.LastUpdateTime
|
||||||
*out = (*in).DeepCopy()
|
*out = (*in).DeepCopy()
|
||||||
}
|
}
|
||||||
|
out.JWKSSecret = in.JWKSSecret
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,11 +431,17 @@ func schema_118_apis_config_v1alpha1_OIDCProviderConfigStatus(ref common.Referen
|
|||||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
|
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"jwksSecret": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "JWKSSecret holds the name of the secret in which this OIDC Provider's signing/verification keys are stored. If it is empty, then the signing/verification keys are either unknown or they don't exist.",
|
||||||
|
Ref: ref("k8s.io/api/core/v1.LocalObjectReference"),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Dependencies: []string{
|
Dependencies: []string{
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
"k8s.io/api/core/v1.LocalObjectReference", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,17 @@ spec:
|
|||||||
status:
|
status:
|
||||||
description: Status of the OIDC provider.
|
description: Status of the OIDC provider.
|
||||||
properties:
|
properties:
|
||||||
|
jwksSecret:
|
||||||
|
description: JWKSSecret holds the name of the secret in which this
|
||||||
|
OIDC Provider's signing/verification keys are stored. If it is empty,
|
||||||
|
then the signing/verification keys are either unknown or they don't
|
||||||
|
exist.
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||||
|
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
lastUpdateTime:
|
lastUpdateTime:
|
||||||
description: LastUpdateTime holds the time at which the Status was
|
description: LastUpdateTime holds the time at which the Status was
|
||||||
last updated. It is a pointer to get around some undesirable behavior
|
last updated. It is a pointer to get around some undesirable behavior
|
||||||
|
1
generated/1.19/README.adoc
generated
1
generated/1.19/README.adoc
generated
@ -151,6 +151,7 @@ OIDCProviderConfigStatus is a struct that describes the actual state of an OIDC
|
|||||||
| *`status`* __OIDCProviderStatus__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
|
| *`status`* __OIDCProviderStatus__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
|
||||||
| *`message`* __string__ | Message provides human-readable details about the Status.
|
| *`message`* __string__ | Message provides human-readable details about the Status.
|
||||||
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#time-v1-meta[$$Time$$]__ | LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get around some undesirable behavior with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
|
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#time-v1-meta[$$Time$$]__ | LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get around some undesirable behavior with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
|
||||||
|
| *`jwksSecret`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#localobjectreference-v1-core[$$LocalObjectReference$$]__ | JWKSSecret holds the name of the secret in which this OIDC Provider's signing/verification keys are stored. If it is empty, then the signing/verification keys are either unknown or they don't exist.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
import (
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid
|
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid
|
||||||
type OIDCProviderStatus string
|
type OIDCProviderStatus string
|
||||||
@ -39,11 +42,17 @@ type OIDCProviderConfigStatus struct {
|
|||||||
// +optional
|
// +optional
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
|
|
||||||
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
|
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
|
||||||
// around some undesirable behavior with respect to the empty metav1.Time value (see
|
// around some undesirable behavior with respect to the empty metav1.Time value (see
|
||||||
// https://github.com/kubernetes/kubernetes/issues/86811).
|
// https://github.com/kubernetes/kubernetes/issues/86811).
|
||||||
// +optional
|
// +optional
|
||||||
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
|
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
|
||||||
|
|
||||||
|
// JWKSSecret holds the name of the secret in which this OIDC Provider's signing/verification keys
|
||||||
|
// are stored. If it is empty, then the signing/verification keys are either unknown or they don't
|
||||||
|
// exist.
|
||||||
|
// +optional
|
||||||
|
JWKSSecret corev1.LocalObjectReference `json:"jwksSecret,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIDCProviderConfig describes the configuration of an OIDC provider.
|
// OIDCProviderConfig describes the configuration of an OIDC provider.
|
||||||
|
@ -216,6 +216,7 @@ func (in *OIDCProviderConfigStatus) DeepCopyInto(out *OIDCProviderConfigStatus)
|
|||||||
in, out := &in.LastUpdateTime, &out.LastUpdateTime
|
in, out := &in.LastUpdateTime, &out.LastUpdateTime
|
||||||
*out = (*in).DeepCopy()
|
*out = (*in).DeepCopy()
|
||||||
}
|
}
|
||||||
|
out.JWKSSecret = in.JWKSSecret
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,11 +432,17 @@ func schema_119_apis_config_v1alpha1_OIDCProviderConfigStatus(ref common.Referen
|
|||||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
|
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"jwksSecret": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "JWKSSecret holds the name of the secret in which this OIDC Provider's signing/verification keys are stored. If it is empty, then the signing/verification keys are either unknown or they don't exist.",
|
||||||
|
Ref: ref("k8s.io/api/core/v1.LocalObjectReference"),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Dependencies: []string{
|
Dependencies: []string{
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
"k8s.io/api/core/v1.LocalObjectReference", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,17 @@ spec:
|
|||||||
status:
|
status:
|
||||||
description: Status of the OIDC provider.
|
description: Status of the OIDC provider.
|
||||||
properties:
|
properties:
|
||||||
|
jwksSecret:
|
||||||
|
description: JWKSSecret holds the name of the secret in which this
|
||||||
|
OIDC Provider's signing/verification keys are stored. If it is empty,
|
||||||
|
then the signing/verification keys are either unknown or they don't
|
||||||
|
exist.
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||||
|
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
lastUpdateTime:
|
lastUpdateTime:
|
||||||
description: LastUpdateTime holds the time at which the Status was
|
description: LastUpdateTime holds the time at which the Status was
|
||||||
last updated. It is a pointer to get around some undesirable behavior
|
last updated. It is a pointer to get around some undesirable behavior
|
||||||
|
1
go.mod
1
go.mod
@ -22,6 +22,7 @@ require (
|
|||||||
go.pinniped.dev/generated/1.19/client v0.0.0-00010101000000-000000000000
|
go.pinniped.dev/generated/1.19/client v0.0.0-00010101000000-000000000000
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6
|
||||||
|
gopkg.in/square/go-jose.v2 v2.5.1
|
||||||
k8s.io/api v0.19.2
|
k8s.io/api v0.19.2
|
||||||
k8s.io/apimachinery v0.19.2
|
k8s.io/apimachinery v0.19.2
|
||||||
k8s.io/apiserver v0.19.2
|
k8s.io/apiserver v0.19.2
|
||||||
|
2
go.sum
2
go.sum
@ -842,6 +842,8 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24
|
|||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA=
|
gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA=
|
||||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
|
332
internal/controller/supervisorconfig/jwks.go
Normal file
332
internal/controller/supervisorconfig/jwks.go
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package supervisorconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
corev1informers "k8s.io/client-go/informers/core/v1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/util/retry"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/config/v1alpha1"
|
||||||
|
pinnipedclientset "go.pinniped.dev/generated/1.19/client/clientset/versioned"
|
||||||
|
configinformers "go.pinniped.dev/generated/1.19/client/informers/externalversions/config/v1alpha1"
|
||||||
|
pinnipedcontroller "go.pinniped.dev/internal/controller"
|
||||||
|
"go.pinniped.dev/internal/controllerlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These constants are the keys in an OPC's Secret's Data map.
|
||||||
|
const (
|
||||||
|
// activeJWKKey points to the current private key used for signing tokens.
|
||||||
|
//
|
||||||
|
// Note! The value for this key will contain private key material!
|
||||||
|
activeJWKKey = "activeJWK"
|
||||||
|
// validJWKSKey points to the current JWKS used to verify tokens.
|
||||||
|
//
|
||||||
|
// Note! The value for this key will contain only public key material!
|
||||||
|
validJWKSKey = "validJWKS"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
opcKind = "OIDCProviderConfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
// jwkController holds the field necessary for the JWKS controller to communicate with OPC's and
|
||||||
|
// secrets, both via a cache and via the API.
|
||||||
|
type jwksController struct {
|
||||||
|
pinnipedClient pinnipedclientset.Interface
|
||||||
|
kubeClient kubernetes.Interface
|
||||||
|
opcInformer configinformers.OIDCProviderConfigInformer
|
||||||
|
secretInformer corev1informers.SecretInformer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJWKSController returns a controllerlib.Controller that ensures an OPC has a corresponding
|
||||||
|
// Secret that contains a valid active JWK and JWKS.
|
||||||
|
func NewJWKSController(
|
||||||
|
kubeClient kubernetes.Interface,
|
||||||
|
pinnipedClient pinnipedclientset.Interface,
|
||||||
|
secretInformer corev1informers.SecretInformer,
|
||||||
|
opcInformer configinformers.OIDCProviderConfigInformer,
|
||||||
|
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
||||||
|
) controllerlib.Controller {
|
||||||
|
return controllerlib.New(
|
||||||
|
controllerlib.Config{
|
||||||
|
Name: "JWKSController",
|
||||||
|
Syncer: &jwksController{
|
||||||
|
kubeClient: kubeClient,
|
||||||
|
pinnipedClient: pinnipedClient,
|
||||||
|
secretInformer: secretInformer,
|
||||||
|
opcInformer: opcInformer,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// We want to be notified when a OPC's secret gets updated or deleted. When this happens, we
|
||||||
|
// should get notified via the corresponding OPC key.
|
||||||
|
withInformer(
|
||||||
|
secretInformer,
|
||||||
|
controllerlib.FilterFuncs{
|
||||||
|
ParentFunc: func(obj metav1.Object) controllerlib.Key {
|
||||||
|
if isOPCControllee(obj) {
|
||||||
|
controller := metav1.GetControllerOf(obj)
|
||||||
|
return controllerlib.Key{
|
||||||
|
Name: controller.Name,
|
||||||
|
Namespace: obj.GetNamespace(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return controllerlib.Key{}
|
||||||
|
},
|
||||||
|
AddFunc: func(obj metav1.Object) bool {
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
UpdateFunc: func(oldObj, newObj metav1.Object) bool {
|
||||||
|
return isOPCControllee(oldObj) || isOPCControllee(newObj)
|
||||||
|
},
|
||||||
|
DeleteFunc: func(obj metav1.Object) bool {
|
||||||
|
return isOPCControllee(obj)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
controllerlib.InformerOption{},
|
||||||
|
),
|
||||||
|
// We want to be notified when anything happens to an OPC.
|
||||||
|
withInformer(
|
||||||
|
opcInformer,
|
||||||
|
pinnipedcontroller.NoOpFilter(),
|
||||||
|
controllerlib.InformerOption{},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync implements controllerlib.Syncer.
|
||||||
|
func (c *jwksController) Sync(ctx controllerlib.Context) error {
|
||||||
|
opc, err := c.opcInformer.Lister().OIDCProviderConfigs(ctx.Key.Namespace).Get(ctx.Key.Name)
|
||||||
|
notFound := k8serrors.IsNotFound(err)
|
||||||
|
if err != nil && !notFound {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"failed to get %s/%s OIDCProviderConfig: %w",
|
||||||
|
ctx.Key.Namespace,
|
||||||
|
ctx.Key.Name,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if notFound {
|
||||||
|
// The corresponding secret to this OPC should have been garbage collected since it should have
|
||||||
|
// had this OPC as its owner.
|
||||||
|
klog.InfoS(
|
||||||
|
"oidcproviderconfig deleted",
|
||||||
|
"oidcproviderconfig",
|
||||||
|
klog.KRef(ctx.Key.Namespace, ctx.Key.Name),
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var secret *corev1.Secret
|
||||||
|
if opc.Status.JWKSSecret.Name != "" {
|
||||||
|
// This OPC says it has a secret associated with it. Let's try to get it from the cache.
|
||||||
|
secret, err = c.secretInformer.Lister().Secrets(opc.Namespace).Get(opc.Status.JWKSSecret.Name)
|
||||||
|
if err != nil && !k8serrors.IsNotFound(err) {
|
||||||
|
return fmt.Errorf("cannot get secret: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if secret == nil {
|
||||||
|
// If the OPC does not have a secret associated with it, or that secret does not exist, we will
|
||||||
|
// generate a new secret (i.e., a JWKS).
|
||||||
|
secret, err = c.generateSecret(opc)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot wire secret: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the secret exists and looks like it should.
|
||||||
|
if err := c.createOrUpdateSecret(ctx.Context, secret); err != nil {
|
||||||
|
return fmt.Errorf("cannot create or update secret: %w", err)
|
||||||
|
}
|
||||||
|
klog.InfoS("created/updated secret", "secret", klog.KObj(secret))
|
||||||
|
|
||||||
|
// Ensure that the OPC points to the secret.
|
||||||
|
newOPC := opc.DeepCopy()
|
||||||
|
newOPC.Status.JWKSSecret.Name = secret.Name
|
||||||
|
if err := c.updateOPC(ctx.Context, newOPC); err != nil {
|
||||||
|
return fmt.Errorf("cannot update opc: %w", err)
|
||||||
|
}
|
||||||
|
klog.InfoS("updated oidcproviderconfig", "oidcproviderconfig", klog.KObj(newOPC))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *jwksController) generateSecret(opc *configv1alpha1.OIDCProviderConfig) (*corev1.Secret, error) {
|
||||||
|
// Note! This is where we could potentially add more handling of OPC spec fields which tell us how
|
||||||
|
// this OIDC provider should sign and verify ID tokens (e.g., hardcoded token secret, gRPC
|
||||||
|
// connection to KMS, etc).
|
||||||
|
//
|
||||||
|
// For now, we just generate an new RSA keypair and put that in the secret.
|
||||||
|
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot generate key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jwk := jose.JSONWebKey{
|
||||||
|
Key: privateKey,
|
||||||
|
KeyID: "some-key",
|
||||||
|
Algorithm: "RS256",
|
||||||
|
Use: "sig",
|
||||||
|
}
|
||||||
|
jwkData, err := json.Marshal(jwk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot marshal jwk: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jwks := jose.JSONWebKeySet{
|
||||||
|
Keys: []jose.JSONWebKey{jwk},
|
||||||
|
}
|
||||||
|
jwksData, err := json.Marshal(jwks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot marshal jwks: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: opc.Name + "-jwks",
|
||||||
|
Namespace: opc.Namespace,
|
||||||
|
OwnerReferences: []metav1.OwnerReference{
|
||||||
|
*metav1.NewControllerRef(opc, schema.GroupVersionKind{
|
||||||
|
Group: configv1alpha1.SchemeGroupVersion.Group,
|
||||||
|
Version: configv1alpha1.SchemeGroupVersion.Version,
|
||||||
|
Kind: opcKind,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
// TODO: custom labels.
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
activeJWKKey: jwkData,
|
||||||
|
validJWKSKey: jwksData,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *jwksController) createOrUpdateSecret(
|
||||||
|
ctx context.Context,
|
||||||
|
newSecret *corev1.Secret,
|
||||||
|
) error {
|
||||||
|
secretClient := c.kubeClient.CoreV1().Secrets(newSecret.Namespace)
|
||||||
|
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||||
|
oldSecret, err := secretClient.Get(ctx, newSecret.Name, metav1.GetOptions{})
|
||||||
|
notFound := k8serrors.IsNotFound(err)
|
||||||
|
if err != nil && !notFound {
|
||||||
|
return fmt.Errorf("cannot get secret: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if notFound {
|
||||||
|
// New secret doesn't exist, so create it.
|
||||||
|
_, err := secretClient.Create(ctx, newSecret, metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot create secret: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New secret already exists, so ensure it is up to date.
|
||||||
|
|
||||||
|
if isValid(oldSecret) {
|
||||||
|
// If the secret already has valid JWK's, then we are good to go and we don't need an update.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
oldSecret.Data = newSecret.Data
|
||||||
|
_, err = secretClient.Update(ctx, oldSecret, metav1.UpdateOptions{})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *jwksController) updateOPC(
|
||||||
|
ctx context.Context,
|
||||||
|
newOPC *configv1alpha1.OIDCProviderConfig,
|
||||||
|
) error {
|
||||||
|
opcClient := c.pinnipedClient.ConfigV1alpha1().OIDCProviderConfigs(newOPC.Namespace)
|
||||||
|
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||||
|
oldOPC, err := opcClient.Get(ctx, newOPC.Name, metav1.GetOptions{})
|
||||||
|
notFound := k8serrors.IsNotFound(err)
|
||||||
|
if err != nil && !notFound {
|
||||||
|
return fmt.Errorf("cannot get secret: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if newOPC.Status.JWKSSecret.Name == oldOPC.Status.JWKSSecret.Name {
|
||||||
|
// If the existing OPC is up to date, we don't need to update it.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
oldOPC.Status.JWKSSecret.Name = newOPC.Status.JWKSSecret.Name
|
||||||
|
_, err = opcClient.Update(ctx, oldOPC, metav1.UpdateOptions{})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// isOPCControlle returns whether the provided obj is controlled by an OPC.
|
||||||
|
func isOPCControllee(obj metav1.Object) bool {
|
||||||
|
controller := metav1.GetControllerOf(obj)
|
||||||
|
return controller != nil && controller.Kind == opcKind
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValid returns whether the provided secret contains a valid active JWK and verification JWKS.
|
||||||
|
func isValid(secret *corev1.Secret) bool {
|
||||||
|
jwkData, ok := secret.Data[activeJWKKey]
|
||||||
|
if !ok {
|
||||||
|
klog.InfoS("secret does not contain active jwk")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var activeJWK jose.JSONWebKey
|
||||||
|
if err := json.Unmarshal(jwkData, &activeJWK); err != nil {
|
||||||
|
klog.InfoS("cannot unmarshal active jwk", "err", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !activeJWK.Valid() {
|
||||||
|
klog.InfoS("active jwk is not valid", "keyid", activeJWK.KeyID)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
jwksData, ok := secret.Data[validJWKSKey]
|
||||||
|
if !ok {
|
||||||
|
klog.InfoS("secret does not contain valid jwks")
|
||||||
|
}
|
||||||
|
|
||||||
|
var validJWKS jose.JSONWebKeySet
|
||||||
|
if err := json.Unmarshal(jwksData, &validJWKS); err != nil {
|
||||||
|
klog.InfoS("cannot unmarshal valid jwks", "err", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
foundActiveJWK := false
|
||||||
|
for _, validJWK := range validJWKS.Keys {
|
||||||
|
if !validJWK.Valid() {
|
||||||
|
klog.InfoS("jwks key is not valid", "keyid", validJWK.KeyID)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if validJWK.KeyID == activeJWK.KeyID {
|
||||||
|
foundActiveJWK = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundActiveJWK {
|
||||||
|
klog.InfoS("did not find active jwk in valid jwks", "keyid", activeJWK.KeyID)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
9
internal/controller/supervisorconfig/jwks_test.go
Normal file
9
internal/controller/supervisorconfig/jwks_test.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package supervisorconfig
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestJWKSControllerSync(t *testing.T) {
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user