diff --git a/api/v1beta1/conversion.go b/api/v1beta1/conversion.go index 07a46af6..bd31aefd 100644 --- a/api/v1beta1/conversion.go +++ b/api/v1beta1/conversion.go @@ -142,3 +142,8 @@ func Convert_v1beta1_OCIManagedClusterStatus_To_v1beta2_OCIManagedClusterStatus( func Convert_v1beta2_OCIManagedClusterSpec_To_v1beta1_OCIManagedClusterSpec(in *v1beta2.OCIManagedClusterSpec, out *OCIManagedClusterSpec, s conversion.Scope) error { return autoConvert_v1beta2_OCIManagedClusterSpec_To_v1beta1_OCIManagedClusterSpec(in, out, s) } + +// Convert_v1beta2_ClusterOptions_To_v1beta1_ClusterOptions converts v1beta2 ClusterOptions to v1beta1 ClusterOptions +func Convert_v1beta2_ClusterOptions_To_v1beta1_ClusterOptions(in *v1beta2.ClusterOptions, out *ClusterOptions, s conversion.Scope) error { + return autoConvert_v1beta2_ClusterOptions_To_v1beta1_ClusterOptions(in, out, s) +} diff --git a/api/v1beta1/ocimanagedcontrolplane_conversion.go b/api/v1beta1/ocimanagedcontrolplane_conversion.go index ee01bfdf..9ffa0308 100644 --- a/api/v1beta1/ocimanagedcontrolplane_conversion.go +++ b/api/v1beta1/ocimanagedcontrolplane_conversion.go @@ -36,6 +36,26 @@ func (src *OCIManagedControlPlane) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.ClusterType = restored.Spec.ClusterType dst.Spec.Addons = restored.Spec.Addons dst.Status.AddonStatus = restored.Status.AddonStatus + + // Handle ClusterOption conversion + // Copy OpenIdConnectDiscovery if present + if restored.Spec.ClusterOption.OpenIdConnectDiscovery != nil { + if dst.Spec.ClusterOption.OpenIdConnectDiscovery == nil { + dst.Spec.ClusterOption.OpenIdConnectDiscovery = &v1beta2.OpenIDConnectDiscovery{} + } + dst.Spec.ClusterOption.OpenIdConnectDiscovery.IsOpenIdConnectDiscoveryEnabled = + restored.Spec.ClusterOption.OpenIdConnectDiscovery.IsOpenIdConnectDiscoveryEnabled + } + + // Copy OpenIdConnectTokenAuthenticationConfig if present + if restored.Spec.ClusterOption.OpenIdConnectTokenAuthenticationConfig != nil { + if dst.Spec.ClusterOption.OpenIdConnectTokenAuthenticationConfig == nil { + dst.Spec.ClusterOption.OpenIdConnectTokenAuthenticationConfig = &v1beta2.OpenIDConnectTokenAuthenticationConfig{} + } + *dst.Spec.ClusterOption.OpenIdConnectTokenAuthenticationConfig = + *restored.Spec.ClusterOption.OpenIdConnectTokenAuthenticationConfig + } + return nil } diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index 7c01366b..33b0218e 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -125,11 +125,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta2.ClusterOptions)(nil), (*ClusterOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta2_ClusterOptions_To_v1beta1_ClusterOptions(a.(*v1beta2.ClusterOptions), b.(*ClusterOptions), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*ClusterPodNetworkOptions)(nil), (*v1beta2.ClusterPodNetworkOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_ClusterPodNetworkOptions_To_v1beta2_ClusterPodNetworkOptions(a.(*ClusterPodNetworkOptions), b.(*v1beta2.ClusterPodNetworkOptions), scope) }); err != nil { @@ -910,6 +905,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta2.ClusterOptions)(nil), (*ClusterOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_ClusterOptions_To_v1beta1_ClusterOptions(a.(*v1beta2.ClusterOptions), b.(*ClusterOptions), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta2.LoadBalancer)(nil), (*LoadBalancer)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta2_LoadBalancer_To_v1beta1_LoadBalancer(a.(*v1beta2.LoadBalancer), b.(*LoadBalancer), scope) }); err != nil { @@ -1189,14 +1189,11 @@ func Convert_v1beta1_ClusterOptions_To_v1beta2_ClusterOptions(in *ClusterOptions func autoConvert_v1beta2_ClusterOptions_To_v1beta1_ClusterOptions(in *v1beta2.ClusterOptions, out *ClusterOptions, s conversion.Scope) error { out.AddOnOptions = (*AddOnOptions)(unsafe.Pointer(in.AddOnOptions)) out.AdmissionControllerOptions = (*AdmissionControllerOptions)(unsafe.Pointer(in.AdmissionControllerOptions)) + // WARNING: in.OpenIdConnectDiscovery requires manual conversion: does not exist in peer-type + // WARNING: in.OpenIdConnectTokenAuthenticationConfig requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta2_ClusterOptions_To_v1beta1_ClusterOptions is an autogenerated conversion function. -func Convert_v1beta2_ClusterOptions_To_v1beta1_ClusterOptions(in *v1beta2.ClusterOptions, out *ClusterOptions, s conversion.Scope) error { - return autoConvert_v1beta2_ClusterOptions_To_v1beta1_ClusterOptions(in, out, s) -} - func autoConvert_v1beta1_ClusterPodNetworkOptions_To_v1beta2_ClusterPodNetworkOptions(in *ClusterPodNetworkOptions, out *v1beta2.ClusterPodNetworkOptions, s conversion.Scope) error { out.CniType = v1beta2.CNIOptionEnum(in.CniType) return nil diff --git a/api/v1beta2/ocimanagedcontrolplane_types.go b/api/v1beta2/ocimanagedcontrolplane_types.go index c7714eff..eafc24db 100644 --- a/api/v1beta2/ocimanagedcontrolplane_types.go +++ b/api/v1beta2/ocimanagedcontrolplane_types.go @@ -17,6 +17,7 @@ limitations under the License. package v1beta2 import ( + "github.com/oracle/oci-go-sdk/v65/containerengine" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) @@ -122,8 +123,66 @@ type ClusterOptions struct { // AdmissionControllerOptions defines the properties that define supported admission controllers. // +optional AdmissionControllerOptions *AdmissionControllerOptions `json:"admissionControllerOptions,omitempty"` + + // OpenIDConnectDiscovery specifies OIDC discovery settings + // +optional + OpenIdConnectDiscovery *OpenIDConnectDiscovery `json:"openIdConnectDiscovery,omitempty"` + + //OpenIDConnectTokenAuthenticationConfig + // +optional + OpenIdConnectTokenAuthenticationConfig *OpenIDConnectTokenAuthenticationConfig `json:"openIdConnectTokenAuthenticationConfig,omitempty"` +} + +type OpenIDConnectDiscovery struct { + // IsOpenIDConnectDiscoveryEnabled defines whether or not to enable the OIDC discovery. + // +optional + IsOpenIdConnectDiscoveryEnabled *bool `json:"isOpenIdConnectDiscoveryEnabled,omitempty"` +} + +type OpenIDConnectTokenAuthenticationConfig struct { + // A Base64 encoded public RSA or ECDSA certificates used to sign your identity provider's web certificate. + // +optional + CaCertificate *string `json:"caCertificate,omitempty"` + + // A client id that all tokens must be issued for. + // +optional + ClientId *string `json:"clientId,omitempty"` + + // JWT claim to use as the user's group. If the claim is present it must be an array of strings. + // +optional + GroupsClaim *string `json:"groupsClaim,omitempty"` + + // Prefix prepended to group claims to prevent clashes with existing names (such as system:groups). + // +optional + GroupsPrefix *string `json:"groupsPrefix,omitempty"` + + // IsOpenIdConnectAuthEnabled defines whether or not to enable the OIDC authentication. + IsOpenIdConnectAuthEnabled bool `json:"isOpenIdConnectAuthEnabled"` + + // URL of the provider that allows the API server to discover public signing keys. Only URLs that use the https:// scheme are accepted. This is typically the provider's discovery URL, changed to have an empty path. + // +optional + IssuerUrl *string `json:"issuerUrl,omitempty"` + + // A key=value pair that describes a required claim in the ID Token. If set, the claim is verified to be present in the ID Token with a matching value. Repeat this flag to specify multiple claims. + // +optional + RequiredClaims []KeyValue `json:"requiredClaims,omitempty"` + + // The signing algorithms accepted. Default is ["RS256"]. + // +optional + SigningAlgorithms []string `json:"signingAlgorithms,omitempty"` + + // JWT claim to use as the user name. By default sub, which is expected to be a unique identifier of the end user. Admins can choose other claims, such as email or name, depending on their provider. However, claims other than email will be prefixed with the issuer URL to prevent naming clashes with other plugins. + // +optional + UsernameClaim *string `json:"usernameClaim,omitempty"` + + // Prefix prepended to username claims to prevent clashes with existing names (such as system:users). For example, the value oidc: will create usernames like oidc:jane.doe. If this flag isn't provided and --oidc-username-claim is a value other than email the prefix defaults to ( Issuer URL )# where ( Issuer URL ) is the value of --oidc-issuer-url. The value - can be used to disable all prefixing. + // +optional + UsernamePrefix *string `json:"usernamePrefix,omitempty"` } +// KeyValue defines the properties that define a key value pair. This is alias to containerengine.KeyValue, to support the sdk type +type KeyValue containerengine.KeyValue + // AddOnOptions defines the properties that define options for supported add-ons. type AddOnOptions struct { // IsKubernetesDashboardEnabled defines whether or not to enable the Kubernetes Dashboard add-on. diff --git a/api/v1beta2/ocimanagedcontrolplane_webhook.go b/api/v1beta2/ocimanagedcontrolplane_webhook.go index e66f6530..fb3ff702 100644 --- a/api/v1beta2/ocimanagedcontrolplane_webhook.go +++ b/api/v1beta2/ocimanagedcontrolplane_webhook.go @@ -56,6 +56,18 @@ func (c *OCIManagedControlPlane) ValidateCreate() (admission.Warnings, error) { if len(c.Name) > 31 { allErrs = append(allErrs, field.Invalid(field.NewPath("Name"), c.Name, "Name cannot be more than 31 characters")) } + + if c.Spec.ClusterOption.OpenIdConnectTokenAuthenticationConfig != nil && c.Spec.ClusterOption.OpenIdConnectTokenAuthenticationConfig.IsOpenIdConnectAuthEnabled { + if c.Spec.ClusterType != EnhancedClusterType { + allErrs = append(allErrs, field.Invalid(field.NewPath("ClusterType"), c.Spec.ClusterType, "ClusterType needs to be set to ENHANCED_CLUSTER for OpenIdConnectTokenAuthenticationConfig to be enabled.")) + } + if c.Spec.ClusterOption.OpenIdConnectTokenAuthenticationConfig.ClientId == nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("ClientId"), c.Spec.ClusterOption.OpenIdConnectTokenAuthenticationConfig.ClientId, "ClientId cannot be empty when OpenIdConnectAuth is enabled.")) + } + if c.Spec.ClusterOption.OpenIdConnectTokenAuthenticationConfig.IssuerUrl == nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("IssuerUrl "), c.Spec.ClusterOption.OpenIdConnectTokenAuthenticationConfig.IssuerUrl, "IssuerUrl cannot be empty when OpenIdConnectAuth is enabled.")) + } + } if len(allErrs) == 0 { return nil, nil } diff --git a/api/v1beta2/ocimanagedcontrolplane_webhook_test.go b/api/v1beta2/ocimanagedcontrolplane_webhook_test.go index bac388be..89aac63f 100644 --- a/api/v1beta2/ocimanagedcontrolplane_webhook_test.go +++ b/api/v1beta2/ocimanagedcontrolplane_webhook_test.go @@ -20,6 +20,8 @@ import ( "strings" "testing" + "github.com/oracle/oci-go-sdk/v65/common" + "github.com/onsi/gomega" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -79,6 +81,85 @@ func TestOCIManagedControlPlane_ValidateCreate(t *testing.T) { }, expectErr: false, }, + { + name: "OpenIdConnectAuthEnabledWithValidConfig", + c: &OCIManagedControlPlane{ + Spec: OCIManagedControlPlaneSpec{ + ClusterType: EnhancedClusterType, + ClusterOption: ClusterOptions{ + OpenIdConnectTokenAuthenticationConfig: &OpenIDConnectTokenAuthenticationConfig{ + IsOpenIdConnectAuthEnabled: *common.Bool(true), + ClientId: common.String("client-id"), + IssuerUrl: common.String("issuer-url"), + }, + }, + }, + }, + expectErr: false, + }, + { + name: "OpenIdConnectAuthEnabledWithInvalidClusterType", + c: &OCIManagedControlPlane{ + Spec: OCIManagedControlPlaneSpec{ + ClusterType: BasicClusterType, + ClusterOption: ClusterOptions{ + OpenIdConnectTokenAuthenticationConfig: &OpenIDConnectTokenAuthenticationConfig{ + IsOpenIdConnectAuthEnabled: *common.Bool(true), + ClientId: common.String("client-id"), + IssuerUrl: common.String("issuer-url"), + }, + }, + }, + }, + errorMgsShouldContain: "ClusterType needs to be set to ENHANCED_CLUSTER for OpenIdConnectTokenAuthenticationConfig to be enabled.", + expectErr: true, + }, + { + name: "OpenIdConnectAuthEnabledWithMissingClientId", + c: &OCIManagedControlPlane{ + Spec: OCIManagedControlPlaneSpec{ + ClusterType: EnhancedClusterType, + ClusterOption: ClusterOptions{ + OpenIdConnectTokenAuthenticationConfig: &OpenIDConnectTokenAuthenticationConfig{ + IsOpenIdConnectAuthEnabled: *common.Bool(true), + IssuerUrl: common.String("issuer-url"), + }, + }, + }, + }, + errorMgsShouldContain: "ClientId cannot be empty when OpenIdConnectAuth is enabled.", + expectErr: true, + }, + { + name: "OpenIdConnectAuthEnabledWithMissingIssuerUrl", + c: &OCIManagedControlPlane{ + Spec: OCIManagedControlPlaneSpec{ + ClusterType: EnhancedClusterType, + ClusterOption: ClusterOptions{ + OpenIdConnectTokenAuthenticationConfig: &OpenIDConnectTokenAuthenticationConfig{ + IsOpenIdConnectAuthEnabled: *common.Bool(true), + ClientId: common.String("client-id"), + }, + }, + }, + }, + errorMgsShouldContain: "IssuerUrl cannot be empty when OpenIdConnectAuth is enabled.", + expectErr: true, + }, + { + name: "OpenIdConnectAuthDisabled", + c: &OCIManagedControlPlane{ + Spec: OCIManagedControlPlaneSpec{ + ClusterType: BasicClusterType, + ClusterOption: ClusterOptions{ + OpenIdConnectTokenAuthenticationConfig: &OpenIDConnectTokenAuthenticationConfig{ + IsOpenIdConnectAuthEnabled: *common.Bool(false), + }, + }, + }, + }, + expectErr: false, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 8e8a3c35..44503ec8 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -529,6 +529,16 @@ func (in *ClusterOptions) DeepCopyInto(out *ClusterOptions) { *out = new(AdmissionControllerOptions) (*in).DeepCopyInto(*out) } + if in.OpenIdConnectDiscovery != nil { + in, out := &in.OpenIdConnectDiscovery, &out.OpenIdConnectDiscovery + *out = new(OpenIDConnectDiscovery) + (*in).DeepCopyInto(*out) + } + if in.OpenIdConnectTokenAuthenticationConfig != nil { + in, out := &in.OpenIdConnectTokenAuthenticationConfig, &out.OpenIdConnectTokenAuthenticationConfig + *out = new(OpenIDConnectTokenAuthenticationConfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterOptions. @@ -1025,6 +1035,31 @@ func (in *KeyDetails) DeepCopy() *KeyDetails { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KeyValue) DeepCopyInto(out *KeyValue) { + *out = *in + if in.Key != nil { + in, out := &in.Key, &out.Key + *out = new(string) + **out = **in + } + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeyValue. +func (in *KeyValue) DeepCopy() *KeyValue { + if in == nil { + return nil + } + out := new(KeyValue) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubernetesNetworkConfig) DeepCopyInto(out *KubernetesNetworkConfig) { *out = *in @@ -2607,6 +2642,88 @@ func (in *OCIManagedControlPlaneTemplateSpec) DeepCopy() *OCIManagedControlPlane return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenIDConnectDiscovery) DeepCopyInto(out *OpenIDConnectDiscovery) { + *out = *in + if in.IsOpenIdConnectDiscoveryEnabled != nil { + in, out := &in.IsOpenIdConnectDiscoveryEnabled, &out.IsOpenIdConnectDiscoveryEnabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenIDConnectDiscovery. +func (in *OpenIDConnectDiscovery) DeepCopy() *OpenIDConnectDiscovery { + if in == nil { + return nil + } + out := new(OpenIDConnectDiscovery) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenIDConnectTokenAuthenticationConfig) DeepCopyInto(out *OpenIDConnectTokenAuthenticationConfig) { + *out = *in + if in.CaCertificate != nil { + in, out := &in.CaCertificate, &out.CaCertificate + *out = new(string) + **out = **in + } + if in.ClientId != nil { + in, out := &in.ClientId, &out.ClientId + *out = new(string) + **out = **in + } + if in.GroupsClaim != nil { + in, out := &in.GroupsClaim, &out.GroupsClaim + *out = new(string) + **out = **in + } + if in.GroupsPrefix != nil { + in, out := &in.GroupsPrefix, &out.GroupsPrefix + *out = new(string) + **out = **in + } + if in.IssuerUrl != nil { + in, out := &in.IssuerUrl, &out.IssuerUrl + *out = new(string) + **out = **in + } + if in.RequiredClaims != nil { + in, out := &in.RequiredClaims, &out.RequiredClaims + *out = make([]KeyValue, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SigningAlgorithms != nil { + in, out := &in.SigningAlgorithms, &out.SigningAlgorithms + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.UsernameClaim != nil { + in, out := &in.UsernameClaim, &out.UsernameClaim + *out = new(string) + **out = **in + } + if in.UsernamePrefix != nil { + in, out := &in.UsernamePrefix, &out.UsernamePrefix + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenIDConnectTokenAuthenticationConfig. +func (in *OpenIDConnectTokenAuthenticationConfig) DeepCopy() *OpenIDConnectTokenAuthenticationConfig { + if in == nil { + return nil + } + out := new(OpenIDConnectTokenAuthenticationConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PeerRouteRule) DeepCopyInto(out *PeerRouteRule) { *out = *in diff --git a/cloud/scope/managed_control_plane.go b/cloud/scope/managed_control_plane.go index d1e665bc..c8e99087 100644 --- a/cloud/scope/managed_control_plane.go +++ b/cloud/scope/managed_control_plane.go @@ -19,13 +19,14 @@ package scope import ( "context" "encoding/base64" - "encoding/json" "fmt" "io" "reflect" "strings" "github.com/go-logr/logr" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" infrastructurev1beta2 "github.com/oracle/cluster-api-provider-oci/api/v1beta2" "github.com/oracle/cluster-api-provider-oci/cloud/ociutil" baseclient "github.com/oracle/cluster-api-provider-oci/cloud/services/base" @@ -160,6 +161,52 @@ func (s *ManagedControlPlaneScope) GetOrCreateControlPlane(ctx context.Context) createOptions.KubernetesNetworkConfig = &networkConfig } + if controlPlaneSpec.ClusterOption.OpenIdConnectDiscovery != nil { + createOptions.OpenIdConnectDiscovery = &oke.OpenIdConnectDiscovery{ + IsOpenIdConnectDiscoveryEnabled: controlPlaneSpec.ClusterOption.OpenIdConnectDiscovery.IsOpenIdConnectDiscoveryEnabled, + } + } + + if controlPlaneSpec.ClusterOption.OpenIdConnectTokenAuthenticationConfig != nil { + oidcConfig := controlPlaneSpec.ClusterOption.OpenIdConnectTokenAuthenticationConfig + createOptions.OpenIdConnectTokenAuthenticationConfig = &oke.OpenIdConnectTokenAuthenticationConfig{ + IsOpenIdConnectAuthEnabled: &oidcConfig.IsOpenIdConnectAuthEnabled, + } + + if oidcConfig.IssuerUrl != nil { + createOptions.OpenIdConnectTokenAuthenticationConfig.IssuerUrl = oidcConfig.IssuerUrl + } + if oidcConfig.ClientId != nil { + createOptions.OpenIdConnectTokenAuthenticationConfig.ClientId = oidcConfig.ClientId + } + if oidcConfig.UsernameClaim != nil { + createOptions.OpenIdConnectTokenAuthenticationConfig.UsernameClaim = oidcConfig.UsernameClaim + } + if oidcConfig.UsernamePrefix != nil { + createOptions.OpenIdConnectTokenAuthenticationConfig.UsernamePrefix = oidcConfig.UsernamePrefix + } + if oidcConfig.GroupsClaim != nil { + createOptions.OpenIdConnectTokenAuthenticationConfig.GroupsClaim = oidcConfig.GroupsClaim + } + if oidcConfig.GroupsPrefix != nil { + createOptions.OpenIdConnectTokenAuthenticationConfig.GroupsPrefix = oidcConfig.GroupsPrefix + } + if oidcConfig.RequiredClaims != nil { + // Convert []infrastructurev1beta2.KeyValue to []containerengine.KeyValue + requiredClaims := make([]oke.KeyValue, len(oidcConfig.RequiredClaims)) + for i, rc := range oidcConfig.RequiredClaims { + requiredClaims[i] = oke.KeyValue(rc) + } + createOptions.OpenIdConnectTokenAuthenticationConfig.RequiredClaims = requiredClaims + } + if oidcConfig.CaCertificate != nil { + createOptions.OpenIdConnectTokenAuthenticationConfig.CaCertificate = oidcConfig.CaCertificate + } + if oidcConfig.SigningAlgorithms != nil { + createOptions.OpenIdConnectTokenAuthenticationConfig.SigningAlgorithms = oidcConfig.SigningAlgorithms + } + } + if controlPlaneSpec.ClusterOption.AddOnOptions != nil { createOptions.AddOns = &oke.AddOnOptions{ IsKubernetesDashboardEnabled: controlPlaneSpec.ClusterOption.AddOnOptions.IsKubernetesDashboardEnabled, @@ -605,17 +652,8 @@ func (s *ManagedControlPlaneScope) UpdateControlPlane(ctx context.Context, okeCl setControlPlaneSpecDefaults(spec) actual := s.getSpecFromActual(okeCluster) - if !reflect.DeepEqual(spec, actual) { - // printing json specs will help debug problems when there are spurious/unwanted updates - jsonSpec, err := json.Marshal(*spec) - if err != nil { - return false, err - } - jsonActual, err := json.Marshal(*actual) - if err != nil { - return false, err - } - s.Logger.Info("Control plane", "spec", jsonSpec, "actual", jsonActual) + // Log the actual and desired specs + if !s.compareSpecs(spec, actual) { controlPlaneSpec := s.OCIManagedControlPlane.Spec updateOptions := oke.UpdateClusterOptionsDetails{} if controlPlaneSpec.ClusterOption.AdmissionControllerOptions != nil { @@ -623,6 +661,53 @@ func (s *ManagedControlPlaneScope) UpdateControlPlane(ctx context.Context, okeCl IsPodSecurityPolicyEnabled: controlPlaneSpec.ClusterOption.AdmissionControllerOptions.IsPodSecurityPolicyEnabled, } } + if controlPlaneSpec.ClusterOption.OpenIdConnectDiscovery != nil { + updateOptions.OpenIdConnectDiscovery = &oke.OpenIdConnectDiscovery{ + IsOpenIdConnectDiscoveryEnabled: controlPlaneSpec.ClusterOption.OpenIdConnectDiscovery.IsOpenIdConnectDiscoveryEnabled, + } + } + if controlPlaneSpec.ClusterOption.OpenIdConnectTokenAuthenticationConfig != nil { + s.Logger.Info("Updating OIDC Connect Token config") + oidcConfig := controlPlaneSpec.ClusterOption.OpenIdConnectTokenAuthenticationConfig + updateOptions.OpenIdConnectTokenAuthenticationConfig = &oke.OpenIdConnectTokenAuthenticationConfig{ + IsOpenIdConnectAuthEnabled: &oidcConfig.IsOpenIdConnectAuthEnabled, + } + + if oidcConfig.IssuerUrl != nil { + updateOptions.OpenIdConnectTokenAuthenticationConfig.IssuerUrl = oidcConfig.IssuerUrl + } + if oidcConfig.ClientId != nil { + updateOptions.OpenIdConnectTokenAuthenticationConfig.ClientId = oidcConfig.ClientId + } + if oidcConfig.UsernameClaim != nil { + updateOptions.OpenIdConnectTokenAuthenticationConfig.UsernameClaim = oidcConfig.UsernameClaim + } + if oidcConfig.UsernamePrefix != nil { + updateOptions.OpenIdConnectTokenAuthenticationConfig.UsernamePrefix = oidcConfig.UsernamePrefix + } + if oidcConfig.GroupsClaim != nil { + updateOptions.OpenIdConnectTokenAuthenticationConfig.GroupsClaim = oidcConfig.GroupsClaim + } + if oidcConfig.GroupsPrefix != nil { + updateOptions.OpenIdConnectTokenAuthenticationConfig.GroupsPrefix = oidcConfig.GroupsPrefix + } + if oidcConfig.RequiredClaims != nil { + // Convert []infrastructurev1beta2.KeyValue to []containerengine.KeyValue + requiredClaims := make([]oke.KeyValue, len(oidcConfig.RequiredClaims)) + for i, rc := range oidcConfig.RequiredClaims { + requiredClaims[i] = oke.KeyValue(rc) + } + updateOptions.OpenIdConnectTokenAuthenticationConfig.RequiredClaims = requiredClaims + } + if oidcConfig.CaCertificate != nil { + updateOptions.OpenIdConnectTokenAuthenticationConfig.CaCertificate = oidcConfig.CaCertificate + } + if oidcConfig.SigningAlgorithms != nil { + updateOptions.OpenIdConnectTokenAuthenticationConfig.SigningAlgorithms = oidcConfig.SigningAlgorithms + } + + s.Logger.Info("Updated OIDC Connect Token config", "config", updateOptions.OpenIdConnectTokenAuthenticationConfig) + } details := oke.UpdateClusterDetails{ Name: common.String(s.GetClusterName()), KubernetesVersion: controlPlaneSpec.Version, @@ -640,7 +725,7 @@ func (s *ManagedControlPlaneScope) UpdateControlPlane(ctx context.Context, okeCl ClusterId: okeCluster.Id, UpdateClusterDetails: details, } - _, err = s.ContainerEngineClient.UpdateCluster(ctx, updateClusterRequest) + _, err := s.ContainerEngineClient.UpdateCluster(ctx, updateClusterRequest) if err != nil { return false, errors.Wrapf(err, "failed to update cluster") } @@ -653,6 +738,21 @@ func (s *ManagedControlPlaneScope) UpdateControlPlane(ctx context.Context, okeCl return false, nil } +// compareSpecs compares two OCIManagedControlPlaneSpec objects for equality +func (s *ManagedControlPlaneScope) compareSpecs(spec1, spec2 *infrastructurev1beta2.OCIManagedControlPlaneSpec) bool { + if spec1 == nil || spec2 == nil { + return spec1 == spec2 + } + + // Use go-cmp to compare the specs + equal := cmp.Equal(spec1, spec2, cmpopts.EquateEmpty()) + if !equal { + diff := cmp.Diff(spec1, spec2, cmpopts.EquateEmpty()) + s.Logger.Info("Specs are different", "diff", diff) + } + return equal +} + // setControlPlaneSpecDefaults sets the defaults in the spec as returned by OKE API. We need to set defaults here rather than webhook as well as // there is a chance user will edit the cluster func setControlPlaneSpecDefaults(spec *infrastructurev1beta2.OCIManagedControlPlaneSpec) { @@ -727,6 +827,30 @@ func (s *ManagedControlPlaneScope) getSpecFromActual(cluster *oke.Cluster) *infr IsKubernetesDashboardEnabled: cluster.Options.AddOns.IsKubernetesDashboardEnabled, } } + if cluster.Options.OpenIdConnectDiscovery != nil { + spec.ClusterOption.OpenIdConnectDiscovery = &infrastructurev1beta2.OpenIDConnectDiscovery{ + IsOpenIdConnectDiscoveryEnabled: cluster.Options.OpenIdConnectDiscovery.IsOpenIdConnectDiscoveryEnabled, + } + } + if cluster.Options.OpenIdConnectTokenAuthenticationConfig != nil { + oidcConfig := cluster.Options.OpenIdConnectTokenAuthenticationConfig + requiredClaims := make([]infrastructurev1beta2.KeyValue, len(oidcConfig.RequiredClaims)) + for i, rc := range oidcConfig.RequiredClaims { + requiredClaims[i] = infrastructurev1beta2.KeyValue(rc) + } + spec.ClusterOption.OpenIdConnectTokenAuthenticationConfig = &infrastructurev1beta2.OpenIDConnectTokenAuthenticationConfig{ + IsOpenIdConnectAuthEnabled: *oidcConfig.IsOpenIdConnectAuthEnabled, + IssuerUrl: oidcConfig.IssuerUrl, + ClientId: oidcConfig.ClientId, + UsernameClaim: oidcConfig.UsernameClaim, + UsernamePrefix: oidcConfig.UsernamePrefix, + GroupsClaim: oidcConfig.GroupsClaim, + GroupsPrefix: oidcConfig.GroupsPrefix, + RequiredClaims: requiredClaims, + CaCertificate: oidcConfig.CaCertificate, + SigningAlgorithms: oidcConfig.SigningAlgorithms, + } + } } if cluster.Type != "" { switch cluster.Type { diff --git a/cloud/scope/managed_control_plane_test.go b/cloud/scope/managed_control_plane_test.go index 66291337..0f997cf7 100644 --- a/cloud/scope/managed_control_plane_test.go +++ b/cloud/scope/managed_control_plane_test.go @@ -1146,6 +1146,132 @@ func TestAddonReconcile(t *testing.T) { if tc.matchStatus != nil { g.Expect(cs.OCIManagedControlPlane.Status.AddonStatus).To(Equal(tc.matchStatus)) } + + }) + } +} + +func TestCompareSpecs(t *testing.T) { + var ( + cs *ManagedControlPlaneScope + mockCtrl *gomock.Controller + ) + + setup := func(t *testing.T, g *WithT) { + mockCtrl = gomock.NewController(t) + var err error + + ociClusterAccessor := OCIManagedCluster{ + &infrastructurev1beta2.OCIManagedCluster{ + ObjectMeta: metav1.ObjectMeta{ + UID: "cluster_uid", + }, + }, + } + + cs, err = NewManagedControlPlaneScope(ManagedControlPlaneScopeParams{ + OCIManagedControlPlane: &infrastructurev1beta2.OCIManagedControlPlane{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: infrastructurev1beta2.OCIManagedControlPlaneSpec{}, + }, + OCIClusterAccessor: ociClusterAccessor, + Cluster: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + }, + }) + g.Expect(err).To(BeNil()) + } + + teardown := func(t *testing.T, g *WithT) { + mockCtrl.Finish() + } + + specWithFields := func(version, kms string, enableDashboard bool) *infrastructurev1beta2.OCIManagedControlPlaneSpec { + return &infrastructurev1beta2.OCIManagedControlPlaneSpec{ + Version: common.String(version), + KmsKeyId: common.String(kms), + ClusterOption: infrastructurev1beta2.ClusterOptions{ + AddOnOptions: &infrastructurev1beta2.AddOnOptions{ + IsKubernetesDashboardEnabled: common.Bool(enableDashboard), + }, + }, + ImagePolicyConfig: &infrastructurev1beta2.ImagePolicyConfig{ + IsPolicyEnabled: common.Bool(true), + KeyDetails: []infrastructurev1beta2.KeyDetails{{ + KmsKeyId: common.String("image-kms-id"), + }}, + }, + ClusterPodNetworkOptions: []infrastructurev1beta2.ClusterPodNetworkOptions{ + { + CniType: infrastructurev1beta2.VCNNativeCNI, + }, + }, + } + } + + tests := []struct { + name string + spec1 *infrastructurev1beta2.OCIManagedControlPlaneSpec + spec2 *infrastructurev1beta2.OCIManagedControlPlaneSpec + expectedEqual bool + }{ + { + name: "both specs nil", + spec1: nil, + spec2: nil, + expectedEqual: true, + }, + { + name: "spec1 nil, spec2 not nil", + spec1: nil, + spec2: specWithFields("v1.27.2", "kms1", true), + expectedEqual: false, + }, + { + name: "spec2 nil, spec1 not nil", + spec1: specWithFields("v1.27.2", "kms1", true), + spec2: nil, + expectedEqual: false, + }, + { + name: "specs are equal", + spec1: specWithFields("v1.27.2", "kms1", true), + spec2: specWithFields("v1.27.2", "kms1", true), + expectedEqual: true, + }, + { + name: "specs differ in version", + spec1: specWithFields("v1.27.2", "kms1", true), + spec2: specWithFields("v1.26.0", "kms1", true), + expectedEqual: false, + }, + { + name: "specs differ in kmsKeyId", + spec1: specWithFields("v1.27.2", "kms1", true), + spec2: specWithFields("v1.27.2", "kms2", true), + expectedEqual: false, + }, + { + name: "specs differ in AddOnOptions", + spec1: specWithFields("v1.27.2", "kms1", true), + spec2: specWithFields("v1.27.2", "kms1", false), + expectedEqual: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + defer teardown(t, g) + setup(t, g) + + equal := cs.compareSpecs(tc.spec1, tc.spec2) + g.Expect(equal).To(Equal(tc.expectedEqual)) }) } } diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ociclusteridentities.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ociclusteridentities.yaml index 0941704c..3277353a 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ociclusteridentities.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ociclusteridentities.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ociclusteridentities.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ociclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ociclusters.yaml index 88453830..f8aec8a2 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ociclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ociclusters.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ociclusters.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ociclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ociclustertemplates.yaml index 104cbc1e..e1fe73fc 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ociclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ociclustertemplates.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ociclustertemplates.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachinepoolmachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachinepoolmachines.yaml index 4131abd3..5fffa4aa 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachinepoolmachines.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachinepoolmachines.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ocimachinepoolmachines.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachinepools.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachinepools.yaml index f88fe94d..55e61986 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachinepools.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachinepools.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ocimachinepools.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachines.yaml index 4fab7a6c..90983831 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachines.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachines.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ocimachines.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachinetemplates.yaml index 44a300a0..c859f2e1 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachinetemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimachinetemplates.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ocimachinetemplates.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedclusters.yaml index 90c5d6a3..0ef89aa8 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedclusters.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ocimanagedclusters.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedclustertemplates.yaml index eed29b39..a2746917 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedclustertemplates.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ocimanagedclustertemplates.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedcontrolplanes.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedcontrolplanes.yaml index 369727e1..75c39c93 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedcontrolplanes.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedcontrolplanes.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ocimanagedcontrolplanes.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io @@ -61,6 +61,87 @@ spec: not to enable the Pod Security Policy admission controller. type: boolean type: object + openIdConnectDiscovery: + description: OpenIDConnectDiscovery specifies OIDC discovery settings + properties: + isOpenIdConnectDiscoveryEnabled: + description: IsOpenIDConnectDiscoveryEnabled defines whether + or not to enable the OIDC discovery. + type: boolean + type: object + openIdConnectTokenAuthenticationConfig: + description: OpenIDConnectTokenAuthenticationConfig + properties: + caCertificate: + description: A Base64 encoded public RSA or ECDSA certificates + used to sign your identity provider's web certificate. + type: string + clientId: + description: A client id that all tokens must be issued for. + type: string + groupsClaim: + description: JWT claim to use as the user's group. If the + claim is present it must be an array of strings. + type: string + groupsPrefix: + description: Prefix prepended to group claims to prevent clashes + with existing names (such as system:groups). + type: string + isOpenIdConnectAuthEnabled: + description: IsOpenIdConnectAuthEnabled defines whether or + not to enable the OIDC authentication. + type: boolean + issuerUrl: + description: URL of the provider that allows the API server + to discover public signing keys. Only URLs that use the + https:// scheme are accepted. This is typically the provider's + discovery URL, changed to have an empty path. + type: string + requiredClaims: + description: A key=value pair that describes a required claim + in the ID Token. If set, the claim is verified to be present + in the ID Token with a matching value. Repeat this flag + to specify multiple claims. + items: + description: KeyValue The properties that define a key value + pair. + properties: + key: + description: The key of the pair. + type: string + value: + description: The value of the pair. + type: string + required: + - key + - value + type: object + type: array + signingAlgorithms: + description: The signing algorithms accepted. Default is ["RS256"]. + items: + type: string + type: array + usernameClaim: + description: JWT claim to use as the user name. By default + sub, which is expected to be a unique identifier of the + end user. Admins can choose other claims, such as email + or name, depending on their provider. However, claims other + than email will be prefixed with the issuer URL to prevent + naming clashes with other plugins. + type: string + usernamePrefix: + description: 'Prefix prepended to username claims to prevent + clashes with existing names (such as system:users). For + example, the value oidc: will create usernames like oidc:jane.doe. + If this flag isn''t provided and --oidc-username-claim is + a value other than email the prefix defaults to ( Issuer + URL )# where ( Issuer URL ) is the value of --oidc-issuer-url. + The value - can be used to disable all prefixing.' + type: string + required: + - isOpenIdConnectAuthEnabled + type: object type: object clusterPodNetworkOptions: description: ClusterPodNetworkOptions defines the available CNIs and diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedcontrolplanetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedcontrolplanetemplates.yaml index 456a6aeb..e9e679d1 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedcontrolplanetemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedcontrolplanetemplates.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ocimanagedcontrolplanetemplates.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io @@ -75,6 +75,94 @@ spec: controller. type: boolean type: object + openIdConnectDiscovery: + description: OpenIDConnectDiscovery specifies OIDC discovery + settings + properties: + isOpenIdConnectDiscoveryEnabled: + description: IsOpenIDConnectDiscoveryEnabled defines + whether or not to enable the OIDC discovery. + type: boolean + type: object + openIdConnectTokenAuthenticationConfig: + description: OpenIDConnectTokenAuthenticationConfig + properties: + caCertificate: + description: A Base64 encoded public RSA or ECDSA + certificates used to sign your identity provider's + web certificate. + type: string + clientId: + description: A client id that all tokens must be issued + for. + type: string + groupsClaim: + description: JWT claim to use as the user's group. + If the claim is present it must be an array of strings. + type: string + groupsPrefix: + description: Prefix prepended to group claims to prevent + clashes with existing names (such as system:groups). + type: string + isOpenIdConnectAuthEnabled: + description: IsOpenIdConnectAuthEnabled defines whether + or not to enable the OIDC authentication. + type: boolean + issuerUrl: + description: URL of the provider that allows the API + server to discover public signing keys. Only URLs + that use the https:// scheme are accepted. This + is typically the provider's discovery URL, changed + to have an empty path. + type: string + requiredClaims: + description: A key=value pair that describes a required + claim in the ID Token. If set, the claim is verified + to be present in the ID Token with a matching value. + Repeat this flag to specify multiple claims. + items: + description: KeyValue The properties that define + a key value pair. + properties: + key: + description: The key of the pair. + type: string + value: + description: The value of the pair. + type: string + required: + - key + - value + type: object + type: array + signingAlgorithms: + description: The signing algorithms accepted. Default + is ["RS256"]. + items: + type: string + type: array + usernameClaim: + description: JWT claim to use as the user name. By + default sub, which is expected to be a unique identifier + of the end user. Admins can choose other claims, + such as email or name, depending on their provider. + However, claims other than email will be prefixed + with the issuer URL to prevent naming clashes with + other plugins. + type: string + usernamePrefix: + description: 'Prefix prepended to username claims + to prevent clashes with existing names (such as + system:users). For example, the value oidc: will + create usernames like oidc:jane.doe. If this flag + isn''t provided and --oidc-username-claim is a value + other than email the prefix defaults to ( Issuer + URL )# where ( Issuer URL ) is the value of --oidc-issuer-url. + The value - can be used to disable all prefixing.' + type: string + required: + - isOpenIdConnectAuthEnabled + type: object type: object clusterPodNetworkOptions: description: ClusterPodNetworkOptions defines the available diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedmachinepools.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedmachinepools.yaml index 19a84998..5c32578e 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedmachinepools.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedmachinepools.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ocimanagedmachinepools.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedmachinepooltemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedmachinepooltemplates.yaml index 214732b3..52f39d59 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedmachinepooltemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocimanagedmachinepooltemplates.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ocimanagedmachinepooltemplates.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocivirtualmachinepools.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocivirtualmachinepools.yaml index d272ff29..b9367b16 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ocivirtualmachinepools.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ocivirtualmachinepools.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.14.0 name: ocivirtualmachinepools.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io diff --git a/docs/src/managed/oidc.md b/docs/src/managed/oidc.md new file mode 100644 index 00000000..c48e7251 --- /dev/null +++ b/docs/src/managed/oidc.md @@ -0,0 +1,60 @@ +## OpenID Connect (OIDC) with Cluster API Provider for Oracle Cloud Infrastructure (CAPOCI) + +### Overview + +Cluster API Provider for Oracle Cloud Infrastructure (CAPOCI) allows you to manage Kubernetes clusters on Oracle Cloud Infrastructure (OCI). Enabling OIDC in managed clusters using CAPOCI involves configuring the cluster to use OIDC for authentication and ensuring that the necessary components are set up correctly. + +### Prerequisites + +1. **OIDC Provider**: You need an OIDC provider (e.g., Auth0, Okta, Google Identity Platform, Oracle IDCS,etc.). +2. Ability to create Enhanced OKE clusters. + +#### Update CAPOCI Configuration + +The example below shows how to update the CAPOCI configuration to include the OIDC settings. This involves modifying the `OCIManagedControlPlane` resource to enable OIDC authentication. + +**Example `OCIManagedControlPlane` Configuration:** + +``` +kind: OCIManagedControlPlane +apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 +metadata: + name: "${CLUSTER_NAME}" + namespace: "${NAMESPACE}" +spec: + version: "${KUBERNETES_VERSION}" + clusterType: "ENHANCED_CLUSTER" + clusterOptions: + openIdConnectDiscovery: + isOpenIdConnectDiscoveryEnabled: true + openIdConnectTokenAuthenticationConfig: + isOpenIdConnectAuthEnabled: true + clientId: "" + issuerUrl: "" + groupsClaim: "" + groupsPrefix: "" + usernameClaim: "" + requiredClaims: + - "" + groupsPrefix: "" + usernamesPrefix: "" + signingAlgorithm: "" + caCertificate: "" +``` + +**Explanation of Fields:** + +- `clusterType`: Specifies the type of cluster. For OIDC, it should be set to `ENHANCED_CLUSTER`. This feature is not available for basic clusters. +- `openIdConnectDiscovery`: Enables OIDC discovery. `isOpenIdConnectDiscoveryEnabled` should be set to `true`. +- `isOpenIdConnectAuthEnabled`: Set to `true` to enable OIDC authentication. +- `clientId`: The client ID obtained from your OIDC provider. +- `issuerUrl`: The issuer URL of your OIDC provider. +- `groupsClaim`: The claim to use for group membership (optional). +- `usernameClaim`: The claim to use for the username (optional). +- `requiredClaims`: Additional claims that must be present in the token (optional). +- `groupsPrefix`: Prefix to add to group names (optional). +- `usernamesPrefix`: Prefix to add to usernames (optional). +- `signingAlgorithm`: The signing algorithm used by the OIDC provider (optional, default is [\"RS256\"]). +- `caCertificate`: The CA certificate used to verify the OIDC provider's TLS certificate (optional). + +**Note:** Ensure that the values for `clientId`, `issuerUrl`, and other fields are correctly configured according to your OIDC provider's settings. \ No newline at end of file