diff --git a/apis/openidclient/v1alpha1/zz_clientclientpolicy_types.go b/apis/openidclient/v1alpha1/zz_clientclientpolicy_types.go index e56d38e5..3e982308 100755 --- a/apis/openidclient/v1alpha1/zz_clientclientpolicy_types.go +++ b/apis/openidclient/v1alpha1/zz_clientclientpolicy_types.go @@ -16,9 +16,19 @@ import ( type ClientClientPolicyInitParameters struct { // The clients allowed by this client policy. + // +crossplane:generate:reference:type=github.com/crossplane-contrib/provider-keycloak/apis/openidclient/v1alpha1.Client + // +crossplane:generate:reference:extractor=github.com/crossplane-contrib/provider-keycloak/config/common.UUIDExtractor() // +listType=set Clients []*string `json:"clients,omitempty" tf:"clients,omitempty"` + // References to Client in openidclient to populate clients. + // +kubebuilder:validation:Optional + ClientsRefs []v1.Reference `json:"clientsRefs,omitempty" tf:"-"` + + // Selector for a list of Client in openidclient to populate clients. + // +kubebuilder:validation:Optional + ClientsSelector *v1.Selector `json:"clientsSelector,omitempty" tf:"-"` + // (Computed) Dictates how the policies associated with a given permission are evaluated and how a final decision is obtained. Could be one of AFFIRMATIVE, CONSENSUS, or UNANIMOUS. Applies to permissions. DecisionStrategy *string `json:"decisionStrategy,omitempty" tf:"decision_strategy,omitempty"` @@ -87,10 +97,20 @@ type ClientClientPolicyObservation struct { type ClientClientPolicyParameters struct { // The clients allowed by this client policy. + // +crossplane:generate:reference:type=github.com/crossplane-contrib/provider-keycloak/apis/openidclient/v1alpha1.Client + // +crossplane:generate:reference:extractor=github.com/crossplane-contrib/provider-keycloak/config/common.UUIDExtractor() // +kubebuilder:validation:Optional // +listType=set Clients []*string `json:"clients,omitempty" tf:"clients,omitempty"` + // References to Client in openidclient to populate clients. + // +kubebuilder:validation:Optional + ClientsRefs []v1.Reference `json:"clientsRefs,omitempty" tf:"-"` + + // Selector for a list of Client in openidclient to populate clients. + // +kubebuilder:validation:Optional + ClientsSelector *v1.Selector `json:"clientsSelector,omitempty" tf:"-"` + // (Computed) Dictates how the policies associated with a given permission are evaluated and how a final decision is obtained. Could be one of AFFIRMATIVE, CONSENSUS, or UNANIMOUS. Applies to permissions. // +kubebuilder:validation:Optional DecisionStrategy *string `json:"decisionStrategy,omitempty" tf:"decision_strategy,omitempty"` @@ -171,7 +191,6 @@ type ClientClientPolicyStatus struct { type ClientClientPolicy struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // +kubebuilder:validation:XValidation:rule="!('*' in self.managementPolicies || 'Create' in self.managementPolicies || 'Update' in self.managementPolicies) || has(self.forProvider.clients) || (has(self.initProvider) && has(self.initProvider.clients))",message="spec.forProvider.clients is a required parameter" // +kubebuilder:validation:XValidation:rule="!('*' in self.managementPolicies || 'Create' in self.managementPolicies || 'Update' in self.managementPolicies) || has(self.forProvider.logic) || (has(self.initProvider) && has(self.initProvider.logic))",message="spec.forProvider.logic is a required parameter" // +kubebuilder:validation:XValidation:rule="!('*' in self.managementPolicies || 'Create' in self.managementPolicies || 'Update' in self.managementPolicies) || has(self.forProvider.name) || (has(self.initProvider) && has(self.initProvider.name))",message="spec.forProvider.name is a required parameter" Spec ClientClientPolicySpec `json:"spec"` diff --git a/apis/openidclient/v1alpha1/zz_clientgrouppolicy_types.go b/apis/openidclient/v1alpha1/zz_clientgrouppolicy_types.go index 8de1d0af..f6bf6ee5 100755 --- a/apis/openidclient/v1alpha1/zz_clientgrouppolicy_types.go +++ b/apis/openidclient/v1alpha1/zz_clientgrouppolicy_types.go @@ -119,8 +119,18 @@ type ClientGroupPolicyParameters struct { type GroupsInitParameters struct { ExtendChildren *bool `json:"extendChildren,omitempty" tf:"extend_children,omitempty"` + // +crossplane:generate:reference:type=github.com/crossplane-contrib/provider-keycloak/apis/group/v1alpha1.Group + // +crossplane:generate:reference:extractor=github.com/crossplane-contrib/provider-keycloak/config/common.UUIDExtractor() ID *string `json:"id,omitempty" tf:"id,omitempty"` + // Reference to a Group in group to populate id. + // +kubebuilder:validation:Optional + IDRef *v1.Reference `json:"idRef,omitempty" tf:"-"` + + // Selector for a Group in group to populate id. + // +kubebuilder:validation:Optional + IDSelector *v1.Selector `json:"idSelector,omitempty" tf:"-"` + Path *string `json:"path,omitempty" tf:"path,omitempty"` } @@ -137,8 +147,18 @@ type GroupsParameters struct { // +kubebuilder:validation:Optional ExtendChildren *bool `json:"extendChildren" tf:"extend_children,omitempty"` + // +crossplane:generate:reference:type=github.com/crossplane-contrib/provider-keycloak/apis/group/v1alpha1.Group + // +crossplane:generate:reference:extractor=github.com/crossplane-contrib/provider-keycloak/config/common.UUIDExtractor() + // +kubebuilder:validation:Optional + ID *string `json:"id,omitempty" tf:"id,omitempty"` + + // Reference to a Group in group to populate id. + // +kubebuilder:validation:Optional + IDRef *v1.Reference `json:"idRef,omitempty" tf:"-"` + + // Selector for a Group in group to populate id. // +kubebuilder:validation:Optional - ID *string `json:"id" tf:"id,omitempty"` + IDSelector *v1.Selector `json:"idSelector,omitempty" tf:"-"` // +kubebuilder:validation:Optional Path *string `json:"path" tf:"path,omitempty"` diff --git a/apis/openidclient/v1alpha1/zz_clientrolepolicy_types.go b/apis/openidclient/v1alpha1/zz_clientrolepolicy_types.go index 5225315d..4baed4c3 100755 --- a/apis/openidclient/v1alpha1/zz_clientrolepolicy_types.go +++ b/apis/openidclient/v1alpha1/zz_clientrolepolicy_types.go @@ -117,8 +117,19 @@ type ClientRolePolicyParameters struct { } type RoleInitParameters struct { + + // +crossplane:generate:reference:type=github.com/crossplane-contrib/provider-keycloak/apis/role/v1alpha1.Role + // +crossplane:generate:reference:extractor=github.com/crossplane-contrib/provider-keycloak/config/common.UUIDExtractor() ID *string `json:"id,omitempty" tf:"id,omitempty"` + // Reference to a Role in role to populate id. + // +kubebuilder:validation:Optional + IDRef *v1.Reference `json:"idRef,omitempty" tf:"-"` + + // Selector for a Role in role to populate id. + // +kubebuilder:validation:Optional + IDSelector *v1.Selector `json:"idSelector,omitempty" tf:"-"` + Required *bool `json:"required,omitempty" tf:"required,omitempty"` } @@ -130,8 +141,18 @@ type RoleObservation struct { type RoleParameters struct { + // +crossplane:generate:reference:type=github.com/crossplane-contrib/provider-keycloak/apis/role/v1alpha1.Role + // +crossplane:generate:reference:extractor=github.com/crossplane-contrib/provider-keycloak/config/common.UUIDExtractor() + // +kubebuilder:validation:Optional + ID *string `json:"id,omitempty" tf:"id,omitempty"` + + // Reference to a Role in role to populate id. + // +kubebuilder:validation:Optional + IDRef *v1.Reference `json:"idRef,omitempty" tf:"-"` + + // Selector for a Role in role to populate id. // +kubebuilder:validation:Optional - ID *string `json:"id" tf:"id,omitempty"` + IDSelector *v1.Selector `json:"idSelector,omitempty" tf:"-"` // +kubebuilder:validation:Optional Required *bool `json:"required" tf:"required,omitempty"` diff --git a/apis/openidclient/v1alpha1/zz_clientuserpolicy_types.go b/apis/openidclient/v1alpha1/zz_clientuserpolicy_types.go index 24271111..81250577 100755 --- a/apis/openidclient/v1alpha1/zz_clientuserpolicy_types.go +++ b/apis/openidclient/v1alpha1/zz_clientuserpolicy_types.go @@ -45,8 +45,18 @@ type ClientUserPolicyInitParameters struct { // +kubebuilder:validation:Optional ResourceServerIDSelector *v1.Selector `json:"resourceServerIdSelector,omitempty" tf:"-"` + // +crossplane:generate:reference:type=github.com/crossplane-contrib/provider-keycloak/apis/user/v1alpha1.User + // +crossplane:generate:reference:extractor=github.com/crossplane-contrib/provider-keycloak/config/common.UUIDExtractor() // +listType=set Users []*string `json:"users,omitempty" tf:"users,omitempty"` + + // References to User in user to populate users. + // +kubebuilder:validation:Optional + UsersRefs []v1.Reference `json:"usersRefs,omitempty" tf:"-"` + + // Selector for a list of User in user to populate users. + // +kubebuilder:validation:Optional + UsersSelector *v1.Selector `json:"usersSelector,omitempty" tf:"-"` } type ClientUserPolicyObservation struct { @@ -107,9 +117,19 @@ type ClientUserPolicyParameters struct { // +kubebuilder:validation:Optional ResourceServerIDSelector *v1.Selector `json:"resourceServerIdSelector,omitempty" tf:"-"` + // +crossplane:generate:reference:type=github.com/crossplane-contrib/provider-keycloak/apis/user/v1alpha1.User + // +crossplane:generate:reference:extractor=github.com/crossplane-contrib/provider-keycloak/config/common.UUIDExtractor() // +kubebuilder:validation:Optional // +listType=set Users []*string `json:"users,omitempty" tf:"users,omitempty"` + + // References to User in user to populate users. + // +kubebuilder:validation:Optional + UsersRefs []v1.Reference `json:"usersRefs,omitempty" tf:"-"` + + // Selector for a list of User in user to populate users. + // +kubebuilder:validation:Optional + UsersSelector *v1.Selector `json:"usersSelector,omitempty" tf:"-"` } // ClientUserPolicySpec defines the desired state of ClientUserPolicy @@ -151,7 +171,6 @@ type ClientUserPolicy struct { // +kubebuilder:validation:XValidation:rule="!('*' in self.managementPolicies || 'Create' in self.managementPolicies || 'Update' in self.managementPolicies) || has(self.forProvider.decisionStrategy) || (has(self.initProvider) && has(self.initProvider.decisionStrategy))",message="spec.forProvider.decisionStrategy is a required parameter" // +kubebuilder:validation:XValidation:rule="!('*' in self.managementPolicies || 'Create' in self.managementPolicies || 'Update' in self.managementPolicies) || has(self.forProvider.logic) || (has(self.initProvider) && has(self.initProvider.logic))",message="spec.forProvider.logic is a required parameter" // +kubebuilder:validation:XValidation:rule="!('*' in self.managementPolicies || 'Create' in self.managementPolicies || 'Update' in self.managementPolicies) || has(self.forProvider.name) || (has(self.initProvider) && has(self.initProvider.name))",message="spec.forProvider.name is a required parameter" - // +kubebuilder:validation:XValidation:rule="!('*' in self.managementPolicies || 'Create' in self.managementPolicies || 'Update' in self.managementPolicies) || has(self.forProvider.users) || (has(self.initProvider) && has(self.initProvider.users))",message="spec.forProvider.users is a required parameter" Spec ClientUserPolicySpec `json:"spec"` Status ClientUserPolicyStatus `json:"status,omitempty"` } diff --git a/apis/openidclient/v1alpha1/zz_generated.deepcopy.go b/apis/openidclient/v1alpha1/zz_generated.deepcopy.go index 92acfb9b..d8d84193 100644 --- a/apis/openidclient/v1alpha1/zz_generated.deepcopy.go +++ b/apis/openidclient/v1alpha1/zz_generated.deepcopy.go @@ -301,6 +301,18 @@ func (in *ClientClientPolicyInitParameters) DeepCopyInto(out *ClientClientPolicy } } } + if in.ClientsRefs != nil { + in, out := &in.ClientsRefs, &out.ClientsRefs + *out = make([]v1.Reference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ClientsSelector != nil { + in, out := &in.ClientsSelector, &out.ClientsSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } if in.DecisionStrategy != nil { in, out := &in.DecisionStrategy, &out.DecisionStrategy *out = new(string) @@ -470,6 +482,18 @@ func (in *ClientClientPolicyParameters) DeepCopyInto(out *ClientClientPolicyPara } } } + if in.ClientsRefs != nil { + in, out := &in.ClientsRefs, &out.ClientsRefs + *out = make([]v1.Reference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ClientsSelector != nil { + in, out := &in.ClientsSelector, &out.ClientsSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } if in.DecisionStrategy != nil { in, out := &in.DecisionStrategy, &out.DecisionStrategy *out = new(string) @@ -3831,6 +3855,18 @@ func (in *ClientUserPolicyInitParameters) DeepCopyInto(out *ClientUserPolicyInit } } } + if in.UsersRefs != nil { + in, out := &in.UsersRefs, &out.UsersRefs + *out = make([]v1.Reference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.UsersSelector != nil { + in, out := &in.UsersSelector, &out.UsersSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientUserPolicyInitParameters. @@ -4000,6 +4036,18 @@ func (in *ClientUserPolicyParameters) DeepCopyInto(out *ClientUserPolicyParamete } } } + if in.UsersRefs != nil { + in, out := &in.UsersRefs, &out.UsersRefs + *out = make([]v1.Reference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.UsersSelector != nil { + in, out := &in.UsersSelector, &out.UsersSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientUserPolicyParameters. @@ -4168,6 +4216,16 @@ func (in *GroupsInitParameters) DeepCopyInto(out *GroupsInitParameters) { *out = new(string) **out = **in } + if in.IDRef != nil { + in, out := &in.IDRef, &out.IDRef + *out = new(v1.Reference) + (*in).DeepCopyInto(*out) + } + if in.IDSelector != nil { + in, out := &in.IDSelector, &out.IDSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } if in.Path != nil { in, out := &in.Path, &out.Path *out = new(string) @@ -4228,6 +4286,16 @@ func (in *GroupsParameters) DeepCopyInto(out *GroupsParameters) { *out = new(string) **out = **in } + if in.IDRef != nil { + in, out := &in.IDRef, &out.IDRef + *out = new(v1.Reference) + (*in).DeepCopyInto(*out) + } + if in.IDSelector != nil { + in, out := &in.IDSelector, &out.IDSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } if in.Path != nil { in, out := &in.Path, &out.Path *out = new(string) @@ -4685,6 +4753,16 @@ func (in *RoleInitParameters) DeepCopyInto(out *RoleInitParameters) { *out = new(string) **out = **in } + if in.IDRef != nil { + in, out := &in.IDRef, &out.IDRef + *out = new(v1.Reference) + (*in).DeepCopyInto(*out) + } + if in.IDSelector != nil { + in, out := &in.IDSelector, &out.IDSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } if in.Required != nil { in, out := &in.Required, &out.Required *out = new(bool) @@ -4735,6 +4813,16 @@ func (in *RoleParameters) DeepCopyInto(out *RoleParameters) { *out = new(string) **out = **in } + if in.IDRef != nil { + in, out := &in.IDRef, &out.IDRef + *out = new(v1.Reference) + (*in).DeepCopyInto(*out) + } + if in.IDSelector != nil { + in, out := &in.IDSelector, &out.IDSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } if in.Required != nil { in, out := &in.Required, &out.Required *out = new(bool) diff --git a/apis/openidclient/v1alpha1/zz_generated.resolvers.go b/apis/openidclient/v1alpha1/zz_generated.resolvers.go index 55c93d90..1cc02ef6 100644 --- a/apis/openidclient/v1alpha1/zz_generated.resolvers.go +++ b/apis/openidclient/v1alpha1/zz_generated.resolvers.go @@ -198,7 +198,27 @@ func (mg *ClientClientPolicy) ResolveReferences(ctx context.Context, c client.Re r := reference.NewAPIResolver(c, mg) var rsp reference.ResolutionResponse + var mrsp reference.MultiResolutionResponse var err error + { + m, l, err = apisresolver.GetManagedResource("openidclient.keycloak.crossplane.io", "v1alpha1", "Client", "ClientList") + if err != nil { + return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution") + } + + mrsp, err = r.ResolveMultiple(ctx, reference.MultiResolutionRequest{ + CurrentValues: reference.FromPtrValues(mg.Spec.ForProvider.Clients), + Extract: common.UUIDExtractor(), + References: mg.Spec.ForProvider.ClientsRefs, + Selector: mg.Spec.ForProvider.ClientsSelector, + To: reference.To{List: l, Managed: m}, + }) + } + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Clients") + } + mg.Spec.ForProvider.Clients = reference.ToPtrValues(mrsp.ResolvedValues) + mg.Spec.ForProvider.ClientsRefs = mrsp.ResolvedReferences { m, l, err = apisresolver.GetManagedResource("realm.keycloak.crossplane.io", "v1alpha1", "Realm", "RealmList") if err != nil { @@ -237,6 +257,25 @@ func (mg *ClientClientPolicy) ResolveReferences(ctx context.Context, c client.Re } mg.Spec.ForProvider.ResourceServerID = reference.ToPtrValue(rsp.ResolvedValue) mg.Spec.ForProvider.ResourceServerIDRef = rsp.ResolvedReference + { + m, l, err = apisresolver.GetManagedResource("openidclient.keycloak.crossplane.io", "v1alpha1", "Client", "ClientList") + if err != nil { + return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution") + } + + mrsp, err = r.ResolveMultiple(ctx, reference.MultiResolutionRequest{ + CurrentValues: reference.FromPtrValues(mg.Spec.InitProvider.Clients), + Extract: common.UUIDExtractor(), + References: mg.Spec.InitProvider.ClientsRefs, + Selector: mg.Spec.InitProvider.ClientsSelector, + To: reference.To{List: l, Managed: m}, + }) + } + if err != nil { + return errors.Wrap(err, "mg.Spec.InitProvider.Clients") + } + mg.Spec.InitProvider.Clients = reference.ToPtrValues(mrsp.ResolvedValues) + mg.Spec.InitProvider.ClientsRefs = mrsp.ResolvedReferences { m, l, err = apisresolver.GetManagedResource("realm.keycloak.crossplane.io", "v1alpha1", "Realm", "RealmList") if err != nil { @@ -375,12 +414,33 @@ func (mg *ClientGroupPolicy) ResolveReferences(ctx context.Context, c client.Rea var rsp reference.ResolutionResponse var err error + + for i3 := 0; i3 < len(mg.Spec.ForProvider.Groups); i3++ { + { + m, l, err = apisresolver.GetManagedResource("group.keycloak.crossplane.io", "v1alpha1", "Group", "GroupList") + if err != nil { + return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution") + } + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Groups[i3].ID), + Extract: common.UUIDExtractor(), + Reference: mg.Spec.ForProvider.Groups[i3].IDRef, + Selector: mg.Spec.ForProvider.Groups[i3].IDSelector, + To: reference.To{List: l, Managed: m}, + }) + } + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Groups[i3].ID") + } + mg.Spec.ForProvider.Groups[i3].ID = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.Groups[i3].IDRef = rsp.ResolvedReference + + } { m, l, err = apisresolver.GetManagedResource("realm.keycloak.crossplane.io", "v1alpha1", "Realm", "RealmList") if err != nil { return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution") } - rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.RealmID), Extract: reference.ExternalName(), @@ -413,12 +473,33 @@ func (mg *ClientGroupPolicy) ResolveReferences(ctx context.Context, c client.Rea } mg.Spec.ForProvider.ResourceServerID = reference.ToPtrValue(rsp.ResolvedValue) mg.Spec.ForProvider.ResourceServerIDRef = rsp.ResolvedReference + + for i3 := 0; i3 < len(mg.Spec.InitProvider.Groups); i3++ { + { + m, l, err = apisresolver.GetManagedResource("group.keycloak.crossplane.io", "v1alpha1", "Group", "GroupList") + if err != nil { + return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution") + } + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.InitProvider.Groups[i3].ID), + Extract: common.UUIDExtractor(), + Reference: mg.Spec.InitProvider.Groups[i3].IDRef, + Selector: mg.Spec.InitProvider.Groups[i3].IDSelector, + To: reference.To{List: l, Managed: m}, + }) + } + if err != nil { + return errors.Wrap(err, "mg.Spec.InitProvider.Groups[i3].ID") + } + mg.Spec.InitProvider.Groups[i3].ID = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.InitProvider.Groups[i3].IDRef = rsp.ResolvedReference + + } { m, l, err = apisresolver.GetManagedResource("realm.keycloak.crossplane.io", "v1alpha1", "Realm", "RealmList") if err != nil { return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution") } - rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ CurrentValue: reference.FromPtrValue(mg.Spec.InitProvider.RealmID), Extract: reference.ExternalName(), @@ -677,12 +758,33 @@ func (mg *ClientRolePolicy) ResolveReferences(ctx context.Context, c client.Read } mg.Spec.ForProvider.ResourceServerID = reference.ToPtrValue(rsp.ResolvedValue) mg.Spec.ForProvider.ResourceServerIDRef = rsp.ResolvedReference + + for i3 := 0; i3 < len(mg.Spec.ForProvider.Role); i3++ { + { + m, l, err = apisresolver.GetManagedResource("role.keycloak.crossplane.io", "v1alpha1", "Role", "RoleList") + if err != nil { + return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution") + } + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Role[i3].ID), + Extract: common.UUIDExtractor(), + Reference: mg.Spec.ForProvider.Role[i3].IDRef, + Selector: mg.Spec.ForProvider.Role[i3].IDSelector, + To: reference.To{List: l, Managed: m}, + }) + } + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Role[i3].ID") + } + mg.Spec.ForProvider.Role[i3].ID = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.Role[i3].IDRef = rsp.ResolvedReference + + } { m, l, err = apisresolver.GetManagedResource("realm.keycloak.crossplane.io", "v1alpha1", "Realm", "RealmList") if err != nil { return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution") } - rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ CurrentValue: reference.FromPtrValue(mg.Spec.InitProvider.RealmID), Extract: reference.ExternalName(), @@ -716,6 +818,28 @@ func (mg *ClientRolePolicy) ResolveReferences(ctx context.Context, c client.Read mg.Spec.InitProvider.ResourceServerID = reference.ToPtrValue(rsp.ResolvedValue) mg.Spec.InitProvider.ResourceServerIDRef = rsp.ResolvedReference + for i3 := 0; i3 < len(mg.Spec.InitProvider.Role); i3++ { + { + m, l, err = apisresolver.GetManagedResource("role.keycloak.crossplane.io", "v1alpha1", "Role", "RoleList") + if err != nil { + return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution") + } + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.InitProvider.Role[i3].ID), + Extract: common.UUIDExtractor(), + Reference: mg.Spec.InitProvider.Role[i3].IDRef, + Selector: mg.Spec.InitProvider.Role[i3].IDSelector, + To: reference.To{List: l, Managed: m}, + }) + } + if err != nil { + return errors.Wrap(err, "mg.Spec.InitProvider.Role[i3].ID") + } + mg.Spec.InitProvider.Role[i3].ID = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.InitProvider.Role[i3].IDRef = rsp.ResolvedReference + + } + return nil } @@ -1028,6 +1152,7 @@ func (mg *ClientUserPolicy) ResolveReferences(ctx context.Context, c client.Read r := reference.NewAPIResolver(c, mg) var rsp reference.ResolutionResponse + var mrsp reference.MultiResolutionResponse var err error { m, l, err = apisresolver.GetManagedResource("realm.keycloak.crossplane.io", "v1alpha1", "Realm", "RealmList") @@ -1067,6 +1192,25 @@ func (mg *ClientUserPolicy) ResolveReferences(ctx context.Context, c client.Read } mg.Spec.ForProvider.ResourceServerID = reference.ToPtrValue(rsp.ResolvedValue) mg.Spec.ForProvider.ResourceServerIDRef = rsp.ResolvedReference + { + m, l, err = apisresolver.GetManagedResource("user.keycloak.crossplane.io", "v1alpha1", "User", "UserList") + if err != nil { + return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution") + } + + mrsp, err = r.ResolveMultiple(ctx, reference.MultiResolutionRequest{ + CurrentValues: reference.FromPtrValues(mg.Spec.ForProvider.Users), + Extract: common.UUIDExtractor(), + References: mg.Spec.ForProvider.UsersRefs, + Selector: mg.Spec.ForProvider.UsersSelector, + To: reference.To{List: l, Managed: m}, + }) + } + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.Users") + } + mg.Spec.ForProvider.Users = reference.ToPtrValues(mrsp.ResolvedValues) + mg.Spec.ForProvider.UsersRefs = mrsp.ResolvedReferences { m, l, err = apisresolver.GetManagedResource("realm.keycloak.crossplane.io", "v1alpha1", "Realm", "RealmList") if err != nil { @@ -1105,6 +1249,25 @@ func (mg *ClientUserPolicy) ResolveReferences(ctx context.Context, c client.Read } mg.Spec.InitProvider.ResourceServerID = reference.ToPtrValue(rsp.ResolvedValue) mg.Spec.InitProvider.ResourceServerIDRef = rsp.ResolvedReference + { + m, l, err = apisresolver.GetManagedResource("user.keycloak.crossplane.io", "v1alpha1", "User", "UserList") + if err != nil { + return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution") + } + + mrsp, err = r.ResolveMultiple(ctx, reference.MultiResolutionRequest{ + CurrentValues: reference.FromPtrValues(mg.Spec.InitProvider.Users), + Extract: common.UUIDExtractor(), + References: mg.Spec.InitProvider.UsersRefs, + Selector: mg.Spec.InitProvider.UsersSelector, + To: reference.To{List: l, Managed: m}, + }) + } + if err != nil { + return errors.Wrap(err, "mg.Spec.InitProvider.Users") + } + mg.Spec.InitProvider.Users = reference.ToPtrValues(mrsp.ResolvedValues) + mg.Spec.InitProvider.UsersRefs = mrsp.ResolvedReferences return nil } diff --git a/apis/samlclient/v1alpha1/zz_clientdefaultscopes_types.go b/apis/samlclient/v1alpha1/zz_clientdefaultscopes_types.go index 3083c06a..6fe3d758 100755 --- a/apis/samlclient/v1alpha1/zz_clientdefaultscopes_types.go +++ b/apis/samlclient/v1alpha1/zz_clientdefaultscopes_types.go @@ -16,15 +16,15 @@ import ( type ClientDefaultScopesInitParameters struct { // The ID of the client to attach default scopes to. Note that this is the unique ID of the client generated by Keycloak. - // +crossplane:generate:reference:type=github.com/crossplane-contrib/provider-keycloak/apis/openidclient/v1alpha1.Client + // +crossplane:generate:reference:type=github.com/crossplane-contrib/provider-keycloak/apis/samlclient/v1alpha1.Client // +crossplane:generate:reference:extractor=github.com/crossplane-contrib/provider-keycloak/config/common.UUIDExtractor() ClientID *string `json:"clientId,omitempty" tf:"client_id,omitempty"` - // Reference to a Client in openidclient to populate clientId. + // Reference to a Client in samlclient to populate clientId. // +kubebuilder:validation:Optional ClientIDRef *v1.Reference `json:"clientIdRef,omitempty" tf:"-"` - // Selector for a Client in openidclient to populate clientId. + // Selector for a Client in samlclient to populate clientId. // +kubebuilder:validation:Optional ClientIDSelector *v1.Selector `json:"clientIdSelector,omitempty" tf:"-"` @@ -63,16 +63,16 @@ type ClientDefaultScopesObservation struct { type ClientDefaultScopesParameters struct { // The ID of the client to attach default scopes to. Note that this is the unique ID of the client generated by Keycloak. - // +crossplane:generate:reference:type=github.com/crossplane-contrib/provider-keycloak/apis/openidclient/v1alpha1.Client + // +crossplane:generate:reference:type=github.com/crossplane-contrib/provider-keycloak/apis/samlclient/v1alpha1.Client // +crossplane:generate:reference:extractor=github.com/crossplane-contrib/provider-keycloak/config/common.UUIDExtractor() // +kubebuilder:validation:Optional ClientID *string `json:"clientId,omitempty" tf:"client_id,omitempty"` - // Reference to a Client in openidclient to populate clientId. + // Reference to a Client in samlclient to populate clientId. // +kubebuilder:validation:Optional ClientIDRef *v1.Reference `json:"clientIdRef,omitempty" tf:"-"` - // Selector for a Client in openidclient to populate clientId. + // Selector for a Client in samlclient to populate clientId. // +kubebuilder:validation:Optional ClientIDSelector *v1.Selector `json:"clientIdSelector,omitempty" tf:"-"` diff --git a/apis/samlclient/v1alpha1/zz_generated.resolvers.go b/apis/samlclient/v1alpha1/zz_generated.resolvers.go index d446ecc6..95e61096 100644 --- a/apis/samlclient/v1alpha1/zz_generated.resolvers.go +++ b/apis/samlclient/v1alpha1/zz_generated.resolvers.go @@ -115,7 +115,7 @@ func (mg *ClientDefaultScopes) ResolveReferences(ctx context.Context, c client.R var rsp reference.ResolutionResponse var err error { - m, l, err = apisresolver.GetManagedResource("openidclient.keycloak.crossplane.io", "v1alpha1", "Client", "ClientList") + m, l, err = apisresolver.GetManagedResource("samlclient.keycloak.crossplane.io", "v1alpha1", "Client", "ClientList") if err != nil { return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution") } @@ -153,7 +153,7 @@ func (mg *ClientDefaultScopes) ResolveReferences(ctx context.Context, c client.R mg.Spec.ForProvider.RealmID = reference.ToPtrValue(rsp.ResolvedValue) mg.Spec.ForProvider.RealmIDRef = rsp.ResolvedReference { - m, l, err = apisresolver.GetManagedResource("openidclient.keycloak.crossplane.io", "v1alpha1", "Client", "ClientList") + m, l, err = apisresolver.GetManagedResource("samlclient.keycloak.crossplane.io", "v1alpha1", "Client", "ClientList") if err != nil { return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution") } diff --git a/config/authentication/config.go b/config/authentication/config.go index 4218653f..26e121fe 100644 --- a/config/authentication/config.go +++ b/config/authentication/config.go @@ -1,8 +1,12 @@ package authentication import ( + "context" "github.com/crossplane-contrib/provider-keycloak/config/common" + "github.com/crossplane-contrib/provider-keycloak/config/lookup" "github.com/crossplane/upjet/pkg/config" + "github.com/keycloak/terraform-provider-keycloak/keycloak" + "strings" ) const ( @@ -79,3 +83,139 @@ func Configure(p *config.Provider) { } }) } + +var flowIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "alias"}, + GetIDByExternalName: getFlowIDByExternalName, + GetIDByIdentifyingProperties: getFlowIDByIdentifyingProperties, +} + +// FlowIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var FlowIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(flowIdentifyingPropertiesLookup) + +func getFlowIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetAuthenticationFlow(ctx, parameters["realm_id"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getFlowIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetAuthenticationFlowFromAlias(ctx, parameters["realm_id"].(string), parameters["alias"].(string)) + if err != nil { + if strings.Contains(err.Error(), "no authentication flow found for alias") { + return "", nil + } + + return "", err + } + + return found.Id, nil +} + +var subFlowIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "parent_flow_alias", "alias"}, + GetIDByExternalName: getSubFlowIDByExternalName, + GetIDByIdentifyingProperties: getSubFlowIDByIdentifyingProperties, +} + +// SubFlowIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var SubFlowIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(subFlowIdentifyingPropertiesLookup) + +func getSubFlowIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetAuthenticationSubFlow(ctx, parameters["realm_id"].(string), parameters["parent_flow_alias"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getSubFlowIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + executions, err := kcClient.ListAuthenticationExecutions(ctx, parameters["realm_id"].(string), parameters["parent_flow_alias"].(string)) + if err != nil { + return "", err + } + + filtered := lookup.Filter(executions, func(execution *keycloak.AuthenticationExecutionInfo) bool { + return execution.AuthenticationFlow && execution.Level == 0 + }) + + for _, flow := range filtered { + subFlow, err := kcClient.GetAuthenticationSubFlow(ctx, parameters["realm_id"].(string), parameters["parent_flow_alias"].(string), flow.FlowId) + if err != nil { + return "", err + } + if subFlow != nil && subFlow.Alias == parameters["alias"].(string) { + return subFlow.Id, nil + } + } + + return "", nil +} + +var executionIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "parent_flow_alias", "authenticator"}, + GetIDByExternalName: getExecutionIDByExternalName, + GetIDByIdentifyingProperties: getExecutionIDByIdentifyingProperties, +} + +// ExecutionIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var ExecutionIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(executionIdentifyingPropertiesLookup) + +func getExecutionIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetAuthenticationExecution(ctx, parameters["realm_id"].(string), parameters["parent_flow_alias"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getExecutionIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + executions, err := kcClient.ListAuthenticationExecutions(ctx, parameters["realm_id"].(string), parameters["parent_flow_alias"].(string)) + if err != nil { + return "", err + } + + // This limits the usage for authentication execution per flow to a single instance of the same ProviderId + // A workaround is to encapsulate a duplicated execution with same ProviderId into a subFlow + filtered := lookup.Filter(executions, func(execution *keycloak.AuthenticationExecutionInfo) bool { + // execution.Level == 0 means that this execution is directly assigned to the parent_flow_alias + // and not part of a nested subFlow + return !execution.AuthenticationFlow && execution.ProviderId == parameters["authenticator"].(string) && execution.Level == 0 + }) + + return lookup.SingleOrEmpty(filtered, func(execution *keycloak.AuthenticationExecutionInfo) string { + return execution.Id + }) +} + +var executionConfigIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "execution_id"}, + GetIDByExternalName: getExecutionConfigIDByExternalName, + GetIDByIdentifyingProperties: getExecutionConfigIDByIdentifyingProperties, +} + +// ExecutionConfigIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var ExecutionConfigIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(executionConfigIdentifyingPropertiesLookup) + +func getExecutionConfigIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + executionConfig := keycloak.AuthenticationExecutionConfig{ + Id: id, + RealmId: parameters["realm_id"].(string), + } + + err := kcClient.GetAuthenticationExecutionConfig(ctx, &executionConfig) + if err != nil { + return "", err + } + + return executionConfig.Id, nil +} + +func getExecutionConfigIDByIdentifyingProperties(_ context.Context, _ map[string]any, _ *keycloak.KeycloakClient) (string, error) { + // If External-Name is not matching anymore we can simply create a new config + // We do not need to try to find the existing one, because it´s a 1:1 relationship between Execution and ExecutionConfig + // We can simply create it + return "", nil +} diff --git a/config/external_name.go b/config/external_name.go index 7a017ea6..a76dc6b3 100644 --- a/config/external_name.go +++ b/config/external_name.go @@ -4,55 +4,69 @@ Copyright 2022 Upbound Inc. package config -import "github.com/crossplane/upjet/pkg/config" +import ( + "github.com/crossplane-contrib/provider-keycloak/config/authentication" + "github.com/crossplane-contrib/provider-keycloak/config/group" + "github.com/crossplane-contrib/provider-keycloak/config/identityprovider" + "github.com/crossplane-contrib/provider-keycloak/config/mapper" + "github.com/crossplane-contrib/provider-keycloak/config/oidc" + "github.com/crossplane-contrib/provider-keycloak/config/openidclient" + "github.com/crossplane-contrib/provider-keycloak/config/openidgroup" + "github.com/crossplane-contrib/provider-keycloak/config/realm" + "github.com/crossplane-contrib/provider-keycloak/config/role" + "github.com/crossplane-contrib/provider-keycloak/config/saml" + "github.com/crossplane-contrib/provider-keycloak/config/samlclient" + "github.com/crossplane-contrib/provider-keycloak/config/user" + "github.com/crossplane/upjet/pkg/config" +) // ExternalNameConfigs contains all external name configurations for this // provider. var ExternalNameConfigs = map[string]config.ExternalName{ // Import requires using a randomly generated ID from provider: nl-2e21sda - "keycloak_generic_protocol_mapper": config.IdentifierFromProvider, - "keycloak_generic_role_mapper": config.IdentifierFromProvider, - "keycloak_group_memberships": config.IdentifierFromProvider, - "keycloak_group_permissions": config.IdentifierFromProvider, - "keycloak_group_roles": config.IdentifierFromProvider, - "keycloak_group": config.IdentifierFromProvider, - "keycloak_openid_client_client_policy": config.IdentifierFromProvider, - "keycloak_openid_client_group_policy": config.IdentifierFromProvider, - "keycloak_openid_client_permissions": config.IdentifierFromProvider, - "keycloak_openid_client_role_policy": config.IdentifierFromProvider, - "keycloak_openid_client_user_policy": config.IdentifierFromProvider, - "keycloak_openid_client_default_scopes": config.IdentifierFromProvider, - "keycloak_openid_client_optional_scopes": config.IdentifierFromProvider, - "keycloak_openid_client_scope": config.IdentifierFromProvider, - "keycloak_openid_client": config.IdentifierFromProvider, - "keycloak_openid_group_membership_protocol_mapper": config.IdentifierFromProvider, - "keycloak_openid_client_service_account_realm_role": config.IdentifierFromProvider, - "keycloak_openid_client_service_account_role": config.IdentifierFromProvider, - "keycloak_realm": config.IdentifierFromProvider, - "keycloak_required_action": config.IdentifierFromProvider, - "keycloak_role": config.IdentifierFromProvider, - "keycloak_user_groups": config.IdentifierFromProvider, - "keycloak_user_roles": config.IdentifierFromProvider, - "keycloak_users_permissions": config.IdentifierFromProvider, - "keycloak_user": config.IdentifierFromProvider, - "keycloak_oidc_identity_provider": config.IdentifierFromProvider, - "keycloak_saml_identity_provider": config.IdentifierFromProvider, - "keycloak_custom_identity_provider_mapper": config.IdentifierFromProvider, - "keycloak_saml_client": config.IdentifierFromProvider, - "keycloak_saml_client_default_scopes": config.IdentifierFromProvider, - "keycloak_saml_client_scope": config.IdentifierFromProvider, - "keycloak_realm_keystore_rsa": config.IdentifierFromProvider, - "keycloak_realm_user_profile": config.IdentifierFromProvider, - "keycloak_realm_default_client_scopes": config.IdentifierFromProvider, - "keycloak_realm_optional_client_scopes": config.IdentifierFromProvider, - "keycloak_realm_events": config.IdentifierFromProvider, - "keycloak_authentication_flow": config.IdentifierFromProvider, - "keycloak_authentication_subflow": config.IdentifierFromProvider, - "keycloak_authentication_execution": config.IdentifierFromProvider, - "keycloak_authentication_execution_config": config.IdentifierFromProvider, - "keycloak_authentication_bindings": config.IdentifierFromProvider, - "keycloak_default_roles": config.IdentifierFromProvider, - "keycloak_default_groups": config.IdentifierFromProvider, + "keycloak_generic_protocol_mapper": mapper.ProtocolMapperIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_generic_role_mapper": config.IdentifierFromProvider, // {realm}/client|client-scope/{Client.UUid}/scope-mappings/{Client.UUid}/{Group.UUid} + "keycloak_group_memberships": config.IdentifierFromProvider, // {realm}/group-memberships/{Group.UUid} + "keycloak_group_permissions": config.IdentifierFromProvider, // {realm}/{Group.UUid} + "keycloak_group_roles": config.IdentifierFromProvider, // {realm}/{Group.UUid} + "keycloak_group": group.GroupIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_openid_client_client_policy": openidclient.AuthzClientPoliciesIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_openid_client_group_policy": openidclient.AuthzGroupPoliciesIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_openid_client_permissions": config.IdentifierFromProvider, // {realm}/{Client.UUid} + "keycloak_openid_client_role_policy": openidclient.AuthzRolePoliciesIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_openid_client_user_policy": openidclient.AuthzUserPoliciesIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_openid_client_default_scopes": config.IdentifierFromProvider, // {realm}/{Client.UUid} + "keycloak_openid_client_optional_scopes": config.IdentifierFromProvider, // {realm}/{Client.UUid} + "keycloak_openid_client_scope": openidclient.ClientScopeIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_openid_client": openidclient.ClientIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_openid_group_membership_protocol_mapper": openidgroup.IdentifierFromIdentifyingProperties, // {UUid} + "keycloak_openid_client_service_account_realm_role": config.IdentifierFromProvider, // {serviceAccountUserId.UUid}/{role.UUid} + "keycloak_openid_client_service_account_role": config.IdentifierFromProvider, // {serviceAccountUserId.UUid}/{role.UUid} + "keycloak_realm": realm.RealmIdentifierFromIdentifyingProperties, // {realm}} + "keycloak_required_action": config.IdentifierFromProvider, // {realm}/{alias} + "keycloak_role": role.IdentifierFromIdentifyingProperties, // {UUid} + "keycloak_user_groups": config.IdentifierFromProvider, // {realm}/{User.UUid} + "keycloak_user_roles": config.IdentifierFromProvider, // {realm}/{User.UUid} + "keycloak_users_permissions": config.IdentifierFromProvider, // {realm} + "keycloak_user": user.UserIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_oidc_identity_provider": oidc.IdentifierFromIdentifyingProperties, // {alias} + "keycloak_saml_identity_provider": saml.IdentifierFromIdentifyingProperties, // {alias} + "keycloak_custom_identity_provider_mapper": identityprovider.IdentifierFromIdentifyingProperties, // {UUid} + "keycloak_saml_client": samlclient.ClientIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_saml_client_default_scopes": config.IdentifierFromProvider, // {realm}/{Client.UUid} + "keycloak_saml_client_scope": samlclient.ClientScopeIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_realm_keystore_rsa": realm.KeystoreRsaIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_realm_user_profile": config.IdentifierFromProvider, // {realm} + "keycloak_realm_default_client_scopes": config.IdentifierFromProvider, // {realm} + "keycloak_realm_optional_client_scopes": config.IdentifierFromProvider, // {realm} + "keycloak_realm_events": config.IdentifierFromProvider, // {realm} + "keycloak_authentication_flow": authentication.FlowIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_authentication_subflow": authentication.SubFlowIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_authentication_execution": authentication.ExecutionIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_authentication_execution_config": authentication.ExecutionConfigIdentifierFromIdentifyingProperties, // {UUid} + "keycloak_authentication_bindings": config.IdentifierFromProvider, // {realm} + "keycloak_default_roles": config.IdentifierFromProvider, // {UUid} + "keycloak_default_groups": config.IdentifierFromProvider, // {realm}/default-groups // ldap "keycloak_ldap_user_federation": config.IdentifierFromProvider, "keycloak_ldap_user_attribute_mapper": config.IdentifierFromProvider, diff --git a/config/group/config.go b/config/group/config.go index 32bc659b..f7355203 100644 --- a/config/group/config.go +++ b/config/group/config.go @@ -1,6 +1,12 @@ package group -import "github.com/crossplane/upjet/pkg/config" +import ( + "context" + "github.com/crossplane-contrib/provider-keycloak/config/lookup" + "github.com/crossplane/upjet/pkg/config" + "github.com/keycloak/terraform-provider-keycloak/keycloak" + "strings" +) // Configure configures individual resources by adding custom ResourceConfigurators. func Configure(p *config.Provider) { @@ -35,3 +41,33 @@ func Configure(p *config.Provider) { } }) } + +var groupIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "name"}, + GetIDByExternalName: getGroupIDByExternalName, + GetIDByIdentifyingProperties: getGroupIDByIdentifyingProperties, +} + +// GroupIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var GroupIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(groupIdentifyingPropertiesLookup) + +func getGroupIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetGroup(ctx, parameters["realm_id"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getGroupIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetGroupByName(ctx, parameters["realm_id"].(string), parameters["name"].(string)) + if err != nil { + if strings.Contains(err.Error(), "no group with name") { + return "", nil + } + + return "", err + } + + return found.Id, nil +} diff --git a/config/identityprovider/config.go b/config/identityprovider/config.go index 2d1ccac7..41c7b258 100644 --- a/config/identityprovider/config.go +++ b/config/identityprovider/config.go @@ -1,6 +1,11 @@ package identityprovider -import "github.com/crossplane/upjet/pkg/config" +import ( + "context" + "github.com/crossplane-contrib/provider-keycloak/config/lookup" + "github.com/crossplane/upjet/pkg/config" + "github.com/keycloak/terraform-provider-keycloak/keycloak" +) const ( // Group is the short group for this provider. @@ -16,3 +21,35 @@ func Configure(p *config.Provider) { } }) } + +var identifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm", "identity_provider_alias", "name"}, + GetIDByExternalName: getIDByExternalName, + GetIDByIdentifyingProperties: getIDByIdentifyingProperties, +} + +// IdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var IdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(identifyingPropertiesLookup) + +func getIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetIdentityProviderMapper(ctx, parameters["realm"].(string), parameters["identity_provider_alias"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetIdentityProviderMappers(ctx, parameters["realm"].(string), parameters["identity_provider_alias"].(string)) + if err != nil { + return "", err + } + + filtered := lookup.Filter(found, func(mapper *keycloak.IdentityProviderMapper) bool { + return mapper.Name == parameters["name"].(string) + }) + + return lookup.SingleOrEmpty(filtered, func(mapper *keycloak.IdentityProviderMapper) string { + return mapper.Id + }) +} diff --git a/config/lookup/identifying_properties_lookup.go b/config/lookup/identifying_properties_lookup.go new file mode 100644 index 00000000..a51aa2fb --- /dev/null +++ b/config/lookup/identifying_properties_lookup.go @@ -0,0 +1,113 @@ +package lookup + +import ( + "context" + "errors" + "github.com/crossplane/upjet/pkg/config" + "github.com/keycloak/terraform-provider-keycloak/keycloak" + "strconv" +) + +type IdentifyingPropertiesLookupConfig struct { + GetIDByExternalName GetIDByExternalName + GetIDByIdentifyingProperties GetIDByIdentifyingProperties + RequiredParameters []string + OptionalParameters []string +} + +func BuildIdentifyingPropertiesLookupIDFn(lookupConfig IdentifyingPropertiesLookupConfig) config.GetIDFn { + return func(ctx context.Context, externalName string, parameters map[string]any, terraformProviderConfig map[string]any) (string, error) { + return GetIDFromIdentifyingProperties(ctx, externalName, parameters, terraformProviderConfig, lookupConfig) + } +} + +// BuildIdentifyingPropertiesLookup creates the ExternalName which contains all information that is necessary for naming operations +// It will set this specialized GetIDFn: GetIDFromIdentifyingProperties and return the ID as ExternalName +func BuildIdentifyingPropertiesLookup(lookupConfig IdentifyingPropertiesLookupConfig) config.ExternalName { + return config.ExternalName{ + SetIdentifierArgumentFn: config.NopSetIdentifierArgument, + GetExternalNameFn: config.IDAsExternalName, + GetIDFn: BuildIdentifyingPropertiesLookupIDFn(lookupConfig), + DisableNameInitializer: true, + } +} + +type GetIDByExternalName func(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) + +type GetIDByIdentifyingProperties func(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) + +// GetIDFromIdentifyingProperties is a specialized GetIDFn +// Check if external-name is set and try to resolve the resource by external-name (using GetIDByExternalName) +// If resource can NOT be resolved by external-name or external-name is NOT set +// then try to resolve resource by identifying properties like realmId, clientId, etc. (using GetIDByIdentifyingProperties) +func GetIDFromIdentifyingProperties(ctx context.Context, externalName string, parameters map[string]any, terraformProviderConfig map[string]any, lookupConfig IdentifyingPropertiesLookupConfig) (string, error) { + kcClient, err := newKeycloakClient(ctx, terraformProviderConfig) + if err != nil { + return "", err + } + + processedParameters := make(map[string]any) + + for _, reqParamName := range lookupConfig.RequiredParameters { + reqParam, reqParamExists := parameters[reqParamName] + if !reqParamExists { + return "", errors.New("required param '" + reqParamName + "' not set") + } + processedParameters[reqParamName] = reqParam + } + + for _, optParamName := range lookupConfig.OptionalParameters { + optParam, optParamExists := parameters[optParamName] + if !optParamExists { + optParam = "" + } + processedParameters[optParamName] = optParam + } + + if externalName != "" { + foundID, err := lookupConfig.GetIDByExternalName(ctx, externalName, processedParameters, kcClient) + if err != nil { + var apiErr *keycloak.ApiError + if !(errors.As(err, &apiErr) && apiErr.Code == 404) { + return "", err + } + } else { + return foundID, nil + } + } + + foundID, err := lookupConfig.GetIDByIdentifyingProperties(ctx, processedParameters, kcClient) + if err != nil { + var apiErr *keycloak.ApiError + if errors.As(err, &apiErr) && apiErr.Code == 404 { + return "", nil + } + + return "", err + } + + return foundID, nil +} + +func SingleOrEmpty[T any](list []*T, idFunc func(obj *T) string) (string, error) { + if len(list) == 0 { + return "", nil + } + + if len(list) > 1 { + return "", errors.New("Too many resources found, which match the identifying parameters. Expected 0 or 1, but was " + strconv.Itoa(len(list))) + } + + return idFunc(list[0]), nil +} + +func Filter[T any](list []*T, filterFunc func(obj *T) bool) []*T { + var filtered []*T + + for _, item := range list { + if filterFunc(item) { + filtered = append(filtered, item) + } + } + return filtered +} diff --git a/config/lookup/keycloak_client.go b/config/lookup/keycloak_client.go new file mode 100644 index 00000000..e29b57a0 --- /dev/null +++ b/config/lookup/keycloak_client.go @@ -0,0 +1,150 @@ +package lookup + +import ( + "context" + "errors" + "fmt" + "github.com/crossplane/upjet/pkg/terraform" + "github.com/keycloak/terraform-provider-keycloak/keycloak" +) + +// Component is a generic keycloak data model +// This needs to be removed in the future. See comments on GetComponents method +type Component struct { + Id string `json:"id,omitempty"` + Name string `json:"name"` + ProviderId string `json:"providerId"` + ProviderType string `json:"providerType"` + ParentId string `json:"parentId"` + Config map[string][]string `json:"config"` +} + +// newKeycloakClient creates a new keycloak client based on the settings in the provider configuration +// (This can be removed once this issue is resolved: https://github.com/crossplane/upjet/issues/464) +func newKeycloakClient(ctx context.Context, terraformProviderConfig map[string]any) (*keycloak.KeycloakClient, error) { + c := terraformProviderConfig["configuration"].(terraform.ProviderConfiguration) + + url := tryGetString(c, "url", "") + basePath := tryGetString(c, "base_path", "") + clientID := tryGetString(c, "client_id", "") + clientSecret := tryGetString(c, "client_secret", "") + username := tryGetString(c, "username", "") + password := tryGetString(c, "password", "") + realm := tryGetString(c, "realm", "master") + initialLogin := tryGetBool(c, "initial_login", true) + clientTimeout := tryGetInt(c, "client_timeout", 15) + tlsInsecureSkipVerify := tryGetBool(c, "tls_insecure_skip_verify", false) + rootCaCertificate := tryGetString(c, "root_ca_certificate", "") + redHatSSO := tryGetBool(c, "initial_login", false) + additionalHeaders := tryGetMap(c, "additional_headers") + userAgent := "Crossplane Keycloak Provider" + + keycloakClient, err := keycloak.NewKeycloakClient(ctx, url, basePath, clientID, clientSecret, realm, username, password, initialLogin, clientTimeout, rootCaCertificate, tlsInsecureSkipVerify, userAgent, redHatSSO, additionalHeaders) + if err != nil { + return nil, err + } + return keycloakClient, nil +} + +func tryGetString(m map[string]any, key string, defaultValue string) string { + value, ok := m[key] + if ok { + return value.(string) + } + return defaultValue +} + +func tryGetBool(m map[string]any, key string, defaultValue bool) bool { + value, ok := m[key] + if ok { + return value.(bool) + } + return defaultValue +} + +func tryGetInt(m map[string]any, key string, defaultValue int) int { + value, ok := m[key] + if ok { + return value.(int) + } + return defaultValue +} + +func tryGetMap(m map[string]any, key string) map[string]string { + value, ok := m[key] + result := make(map[string]string) + if ok { + for k, v := range value.(map[string]interface{}) { + result[k] = v.(string) + } + } + return result +} + +// GetComponents returns the components of the specified realm, type and name +// This needs to be removed in the future. +// We need to clarify with terraform-provider-keycloak maintainers if we could add a GetComponents method +// Currently we need this i.e. because there is no method to list all RealmKeystoreRsa +// or to get the RealmKeystoreRsa by name +func GetComponents(kcClient *keycloak.KeycloakClient, ctx context.Context, realmId string, typ string, name string) ([]*Component, error) { + params := make(map[string]string) + params["type"] = typ + params["name"] = name + + var components []*Component + + err := keycloakClientGet(kcClient, ctx, fmt.Sprintf("/realms/%s/components", realmId), &components, params) + if err != nil { + return nil, err + } + + return components, nil +} + +type GenericProtocolMappers struct { + ProtocolMappers []*keycloak.GenericProtocolMapper +} + +// GetGenericProtocolMappers returns the protocol mappers of the specified realm, clientId or clientScopeId +// We need to clarify with terraform-provider-keycloak maintainers if we could add a GetGenericProtocolMappers method +func GetGenericProtocolMappers(kcClient *keycloak.KeycloakClient, ctx context.Context, realmId string, clientId string, clientScopeId string) (*GenericProtocolMappers, error) { + var genericProtocolMappers GenericProtocolMappers + var typ string + var id string + + if clientId == "" && clientScopeId == "" { + return nil, errors.New("either clientId or clientScopeId must be present, but both are empty") + } + + if clientId != "" && clientScopeId != "" { + return nil, errors.New("either clientId or clientScopeId must be present, but both are not empty") + } + + if clientId != "" { + typ = "clients" + id = clientId + } + + if clientScopeId != "" { + typ = "client-scopes" + id = clientScopeId + } + + err := keycloakClientGet(kcClient, ctx, fmt.Sprintf("/realms/%s/%s/%s", realmId, typ, id), &genericProtocolMappers, nil) + if err != nil { + return nil, err + } + + for _, protocolMapper := range genericProtocolMappers.ProtocolMappers { + protocolMapper.RealmId = realmId + if clientId != "" { + protocolMapper.ClientId = clientId + } + if clientScopeId != "" { + protocolMapper.ClientId = clientScopeId + } + } + + return &genericProtocolMappers, nil + +} diff --git a/config/lookup/keycloak_client_links.go b/config/lookup/keycloak_client_links.go new file mode 100644 index 00000000..2e6bed81 --- /dev/null +++ b/config/lookup/keycloak_client_links.go @@ -0,0 +1,12 @@ +package lookup + +import ( + "context" + "github.com/keycloak/terraform-provider-keycloak/keycloak" + _ "unsafe" +) + +// This needs to be removed in the future. See comments on GetComponents method +// +//go:linkname keycloakClientGet github.com/keycloak/terraform-provider-keycloak/keycloak.(*KeycloakClient).get +func keycloakClientGet(*keycloak.KeycloakClient, context.Context, string, interface{}, map[string]string) error diff --git a/config/mapper/config.go b/config/mapper/config.go index 12d9da72..eb1401d5 100644 --- a/config/mapper/config.go +++ b/config/mapper/config.go @@ -1,6 +1,11 @@ package mapper -import "github.com/crossplane/upjet/pkg/config" +import ( + "context" + "github.com/crossplane-contrib/provider-keycloak/config/lookup" + "github.com/crossplane/upjet/pkg/config" + "github.com/keycloak/terraform-provider-keycloak/keycloak" +) // Configure configures individual resources by adding custom ResourceConfigurators. func Configure(p *config.Provider) { @@ -21,3 +26,36 @@ func Configure(p *config.Provider) { } }) } + +var protocolMapperIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "name"}, + OptionalParameters: []string{"client_id", "client_scope_id"}, + GetIDByExternalName: getProtocolMapperIDByExternalName, + GetIDByIdentifyingProperties: getProtocolMapperIDByIdentifyingProperties, +} + +// ProtocolMapperIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var ProtocolMapperIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(protocolMapperIdentifyingPropertiesLookup) + +func getProtocolMapperIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetGenericProtocolMapper(ctx, parameters["realm_id"].(string), parameters["client_id"].(string), parameters["client_scope_id"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getProtocolMapperIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := lookup.GetGenericProtocolMappers(kcClient, ctx, parameters["realm_id"].(string), parameters["client_id"].(string), parameters["client_scope_id"].(string)) + if err != nil { + return "", err + } + + filtered := lookup.Filter(found.ProtocolMappers, func(mapper *keycloak.GenericProtocolMapper) bool { + return mapper.Name == parameters["name"].(string) + }) + + return lookup.SingleOrEmpty(filtered, func(mapper *keycloak.GenericProtocolMapper) string { + return mapper.Id + }) +} diff --git a/config/oidc/config.go b/config/oidc/config.go index 41118bcb..54a70115 100644 --- a/config/oidc/config.go +++ b/config/oidc/config.go @@ -1,8 +1,11 @@ package oidc import ( + "context" "github.com/crossplane-contrib/provider-keycloak/config/common" + "github.com/crossplane-contrib/provider-keycloak/config/lookup" "github.com/crossplane/upjet/pkg/config" + "github.com/keycloak/terraform-provider-keycloak/keycloak" ) // Configure configures individual resources by adding custom ResourceConfigurators. @@ -19,3 +22,24 @@ func Configure(p *config.Provider) { } }) } + +var identifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm", "alias"}, + GetIDByExternalName: getIDByExternalName, + GetIDByIdentifyingProperties: getIDByIdentifyingProperties, +} + +// IdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var IdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(identifyingPropertiesLookup) + +func getIDByExternalName(ctx context.Context, _ string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + return getIDByIdentifyingProperties(ctx, parameters, kcClient) +} + +func getIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetIdentityProvider(ctx, parameters["realm"].(string), parameters["alias"].(string)) + if err != nil { + return "", err + } + return found.Alias, nil +} diff --git a/config/openidclient/config.go b/config/openidclient/config.go index 48339d45..7b80a874 100644 --- a/config/openidclient/config.go +++ b/config/openidclient/config.go @@ -1,8 +1,12 @@ package openidclient import ( + "context" "github.com/crossplane-contrib/provider-keycloak/config/common" + "github.com/crossplane-contrib/provider-keycloak/config/lookup" "github.com/crossplane/upjet/pkg/config" + "github.com/keycloak/terraform-provider-keycloak/keycloak" + "strings" ) const ( @@ -73,6 +77,11 @@ func Configure(p *config.Provider) { p.AddResourceConfigurator("keycloak_openid_client_client_policy", func(r *config.Resource) { r.ShortGroup = Group + r.References["clients"] = config.Reference{ + TerraformName: "keycloak_openid_client", + Extractor: common.PathUUIDExtractor, + } + if s, ok := r.TerraformResource.Schema["decisionStrategy"]; ok { s.Optional = false s.Computed = false @@ -86,6 +95,12 @@ func Configure(p *config.Provider) { p.AddResourceConfigurator("keycloak_openid_client_group_policy", func(r *config.Resource) { r.ShortGroup = Group + + r.References["groups.id"] = config.Reference{ + TerraformName: "keycloak_group", + Extractor: common.PathUUIDExtractor, + } + if s, ok := r.TerraformResource.Schema["decisionStrategy"]; ok { s.Optional = false s.Computed = false @@ -99,6 +114,12 @@ func Configure(p *config.Provider) { p.AddResourceConfigurator("keycloak_openid_client_role_policy", func(r *config.Resource) { r.ShortGroup = Group + + r.References["role.id"] = config.Reference{ + TerraformName: "keycloak_role", + Extractor: common.PathUUIDExtractor, + } + if s, ok := r.TerraformResource.Schema["decisionStrategy"]; ok { s.Optional = false s.Computed = false @@ -112,6 +133,12 @@ func Configure(p *config.Provider) { p.AddResourceConfigurator("keycloak_openid_client_user_policy", func(r *config.Resource) { r.ShortGroup = Group + + r.References["users"] = config.Reference{ + TerraformName: "keycloak_user", + Extractor: common.PathUUIDExtractor, + } + if s, ok := r.TerraformResource.Schema["decisionStrategy"]; ok { s.Optional = false s.Computed = false @@ -127,3 +154,159 @@ func Configure(p *config.Provider) { r.ShortGroup = Group }) } + +var clientIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "client_id"}, + GetIDByExternalName: getClientIDByExternalName, + GetIDByIdentifyingProperties: getClientIDByIdentifyingProperties, +} + +// ClientIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var ClientIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(clientIdentifyingPropertiesLookup) + +func getClientIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetGenericClient(ctx, parameters["realm_id"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getClientIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetGenericClientByClientId(ctx, parameters["realm_id"].(string), parameters["client_id"].(string)) + if err != nil { + if strings.Contains(err.Error(), "does not exist") { + return "", nil + } + + return "", err + } + return found.Id, nil +} + +var clientScopeIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "name"}, + GetIDByExternalName: getClientScopeIDByExternalName, + GetIDByIdentifyingProperties: getClientScopeIDByIdentifyingProperties, +} + +// ClientScopeIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var ClientScopeIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(clientScopeIdentifyingPropertiesLookup) + +func getClientScopeIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetOpenidClientScope(ctx, parameters["realm_id"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getClientScopeIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.ListOpenidClientScopesWithFilter(ctx, parameters["realm_id"].(string), func(scope *keycloak.OpenidClientScope) bool { + return scope.Name == parameters["name"].(string) + }) + + if err != nil { + return "", err + } + + return lookup.SingleOrEmpty(found, func(scope *keycloak.OpenidClientScope) string { + return scope.Id + }) +} + +func getAuthzPolicyIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetClientAuthorizationPolicyByName(ctx, parameters["realm_id"].(string), parameters["resource_server_id"].(string), parameters["name"].(string)) + if err != nil { + if strings.Contains(err.Error(), "unable to find client authorization policy with name") { + return "", nil + } + + return "", err + } + return found.Id, nil +} + +var authzClientPoliciesIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "resource_server_id", "name"}, + GetIDByExternalName: getAuthzClientPoliciesIDByExternalName, + GetIDByIdentifyingProperties: getAuthzClientPoliciesIDByIdentifyingProperties, +} + +// AuthzClientPoliciesIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var AuthzClientPoliciesIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(authzClientPoliciesIdentifyingPropertiesLookup) + +func getAuthzClientPoliciesIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetOpenidClientAuthorizationClientPolicy(ctx, parameters["realm_id"].(string), parameters["resource_server_id"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getAuthzClientPoliciesIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + return getAuthzPolicyIDByIdentifyingProperties(ctx, parameters, kcClient) +} + +var authzGroupPoliciesIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "resource_server_id", "name"}, + GetIDByExternalName: getAuthzGroupPoliciesIDByExternalName, + GetIDByIdentifyingProperties: getAuthzGroupPoliciesIDByIdentifyingProperties, +} + +// AuthzGroupPoliciesIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var AuthzGroupPoliciesIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(authzGroupPoliciesIdentifyingPropertiesLookup) + +func getAuthzGroupPoliciesIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetOpenidClientAuthorizationGroupPolicy(ctx, parameters["realm_id"].(string), parameters["resource_server_id"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getAuthzGroupPoliciesIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + return getAuthzPolicyIDByIdentifyingProperties(ctx, parameters, kcClient) +} + +var authzRolePoliciesIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "resource_server_id", "name"}, + GetIDByExternalName: getAuthzRolePoliciesIDByExternalName, + GetIDByIdentifyingProperties: getAuthzRolePoliciesIDByIdentifyingProperties, +} + +// AuthzRolePoliciesIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var AuthzRolePoliciesIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(authzRolePoliciesIdentifyingPropertiesLookup) + +func getAuthzRolePoliciesIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetOpenidClientAuthorizationRolePolicy(ctx, parameters["realm_id"].(string), parameters["resource_server_id"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getAuthzRolePoliciesIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + return getAuthzPolicyIDByIdentifyingProperties(ctx, parameters, kcClient) +} + +var authzUserPoliciesIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "resource_server_id", "name"}, + GetIDByExternalName: getAuthzUserPoliciesIDByExternalName, + GetIDByIdentifyingProperties: getAuthzUserPoliciesIDByIdentifyingProperties, +} + +// AuthzUserPoliciesIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var AuthzUserPoliciesIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(authzUserPoliciesIdentifyingPropertiesLookup) + +func getAuthzUserPoliciesIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetOpenidClientAuthorizationUserPolicy(ctx, parameters["realm_id"].(string), parameters["resource_server_id"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getAuthzUserPoliciesIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + return getAuthzPolicyIDByIdentifyingProperties(ctx, parameters, kcClient) +} diff --git a/config/openidgroup/config.go b/config/openidgroup/config.go index 24cd1156..3ac674ca 100644 --- a/config/openidgroup/config.go +++ b/config/openidgroup/config.go @@ -1,6 +1,11 @@ package openidgroup -import "github.com/crossplane/upjet/pkg/config" +import ( + "context" + "github.com/crossplane-contrib/provider-keycloak/config/lookup" + "github.com/crossplane/upjet/pkg/config" + "github.com/keycloak/terraform-provider-keycloak/keycloak" +) // Configure configures individual resources by adding custom ResourceConfigurators. func Configure(p *config.Provider) { @@ -13,3 +18,35 @@ func Configure(p *config.Provider) { } }) } + +var identifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "client_id", "name"}, + GetIDByExternalName: getIDByExternalName, + GetIDByIdentifyingProperties: getIDByIdentifyingProperties, +} + +// IdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var IdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(identifyingPropertiesLookup) + +func getIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetOpenIdGroupMembershipProtocolMapper(ctx, parameters["realm_id"].(string), parameters["client_id"].(string), "", id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetGenericProtocolMappers(ctx, parameters["realm_id"].(string), parameters["client_id"].(string)) + if err != nil { + return "", err + } + + filtered := lookup.Filter(found.ProtocolMappers, func(mapper *keycloak.GenericProtocolMapper) bool { + return mapper.Name == parameters["name"].(string) + }) + + return lookup.SingleOrEmpty(filtered, func(mapper *keycloak.GenericProtocolMapper) string { + return mapper.Id + }) +} diff --git a/config/realm/config.go b/config/realm/config.go index 2c1b9bc3..0593be3c 100644 --- a/config/realm/config.go +++ b/config/realm/config.go @@ -1,7 +1,10 @@ package realm import ( + "context" + "github.com/crossplane-contrib/provider-keycloak/config/lookup" "github.com/crossplane/upjet/pkg/config" + "github.com/keycloak/terraform-provider-keycloak/keycloak" ) // Group is the short group name for the resources in this package @@ -49,3 +52,54 @@ func Configure(p *config.Provider) { r.Kind = "RealmEvents" }) } + +var realmIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm"}, + GetIDByExternalName: getRealmIDByExternalName, + GetIDByIdentifyingProperties: getRealmIDByIdentifyingProperties, +} + +// RealmIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var RealmIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(realmIdentifyingPropertiesLookup) + +func getRealmIDByExternalName(ctx context.Context, _ string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + return getRealmIDByIdentifyingProperties(ctx, parameters, kcClient) +} + +func getRealmIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetRealm(ctx, parameters["realm"].(string)) + if err != nil { + return "", err + } + return found.Id, nil +} + +var keystoreRsaIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "name"}, + GetIDByExternalName: getKeystoreRsaIDByExternalName, + GetIDByIdentifyingProperties: getKeystoreRsaIDByIdentifyingProperties, +} + +// KeystoreRsaIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var KeystoreRsaIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(keystoreRsaIdentifyingPropertiesLookup) + +func getKeystoreRsaIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetRealmKeystoreRsa(ctx, parameters["realm_id"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getKeystoreRsaIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + components, err := lookup.GetComponents(kcClient, ctx, parameters["realm_id"].(string), "org.keycloak.keys.KeyProvider", parameters["name"].(string)) + if err != nil { + return "", err + } + + // Currently the Keycloak API allows to add multiple KeyProvider with the SAME name + // If this is the case an error would be thrown here + return lookup.SingleOrEmpty(components, func(scope *lookup.Component) string { + return scope.Id + }) +} diff --git a/config/role/config.go b/config/role/config.go index 8966cac1..197c3fc3 100644 --- a/config/role/config.go +++ b/config/role/config.go @@ -1,6 +1,11 @@ package role -import "github.com/crossplane/upjet/pkg/config" +import ( + "context" + "github.com/crossplane-contrib/provider-keycloak/config/lookup" + "github.com/crossplane/upjet/pkg/config" + "github.com/keycloak/terraform-provider-keycloak/keycloak" +) // Configure configures individual resources by adding custom ResourceConfigurators. func Configure(p *config.Provider) { @@ -13,3 +18,29 @@ func Configure(p *config.Provider) { } }) } + +var identifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "name"}, + OptionalParameters: []string{"client_id"}, + GetIDByExternalName: getIDByExternalName, + GetIDByIdentifyingProperties: getIDByIdentifyingProperties, +} + +// IdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var IdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(identifyingPropertiesLookup) + +func getIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetRole(ctx, parameters["realm_id"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetRoleByName(ctx, parameters["realm_id"].(string), parameters["client_id"].(string), parameters["name"].(string)) + if err != nil { + return "", err + } + return found.Id, nil +} diff --git a/config/saml/config.go b/config/saml/config.go index cf09c35e..815841fc 100644 --- a/config/saml/config.go +++ b/config/saml/config.go @@ -1,6 +1,11 @@ package saml -import "github.com/crossplane/upjet/pkg/config" +import ( + "context" + "github.com/crossplane-contrib/provider-keycloak/config/lookup" + "github.com/crossplane/upjet/pkg/config" + "github.com/keycloak/terraform-provider-keycloak/keycloak" +) // Group is the short group name for the resources in this package var Group = "saml" @@ -30,3 +35,24 @@ func Configure(p *config.Provider) { }) } + +var identifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm", "alias"}, + GetIDByExternalName: getIDByExternalName, + GetIDByIdentifyingProperties: getIDByIdentifyingProperties, +} + +// IdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var IdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(identifyingPropertiesLookup) + +func getIDByExternalName(ctx context.Context, _ string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + return getIDByIdentifyingProperties(ctx, parameters, kcClient) +} + +func getIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetIdentityProvider(ctx, parameters["realm"].(string), parameters["alias"].(string)) + if err != nil { + return "", err + } + return found.Alias, nil +} diff --git a/config/samlclient/config.go b/config/samlclient/config.go index aba65654..9beb59f1 100644 --- a/config/samlclient/config.go +++ b/config/samlclient/config.go @@ -1,6 +1,13 @@ package samlclient -import "github.com/crossplane/upjet/pkg/config" +import ( + "context" + "github.com/crossplane-contrib/provider-keycloak/config/common" + "github.com/crossplane-contrib/provider-keycloak/config/lookup" + "github.com/crossplane/upjet/pkg/config" + "github.com/keycloak/terraform-provider-keycloak/keycloak" + "strings" +) const ( // Group is the short group for this provider. @@ -17,6 +24,11 @@ func Configure(p *config.Provider) { p.AddResourceConfigurator("keycloak_saml_client_default_scopes", func(r *config.Resource) { // We need to override the default group that upjet generated for r.ShortGroup = Group + + r.References["client_id"] = config.Reference{ + TerraformName: "keycloak_saml_client", + Extractor: common.PathUUIDExtractor, + } }) p.AddResourceConfigurator("keycloak_saml_client_scope", func(r *config.Resource) { @@ -25,3 +37,63 @@ func Configure(p *config.Provider) { }) } + +var clientIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "client_id"}, + GetIDByExternalName: getClientIDByExternalName, + GetIDByIdentifyingProperties: getClientIDByIdentifyingProperties, +} + +// ClientIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var ClientIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(clientIdentifyingPropertiesLookup) + +func getClientIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetGenericClient(ctx, parameters["realm_id"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getClientIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetGenericClientByClientId(ctx, parameters["realm_id"].(string), parameters["client_id"].(string)) + if err != nil { + if strings.Contains(err.Error(), "does not exist") { + return "", nil + } + + return "", err + } + return found.Id, nil +} + +var clientScopeIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "name"}, + GetIDByExternalName: getClientScopeIDByExternalName, + GetIDByIdentifyingProperties: getClientScopeIDByIdentifyingProperties, +} + +// ClientScopeIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var ClientScopeIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(clientScopeIdentifyingPropertiesLookup) + +func getClientScopeIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetSamlClientScope(ctx, parameters["realm_id"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getClientScopeIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.ListSamlClientScopesWithFilter(ctx, parameters["realm_id"].(string), func(scope *keycloak.SamlClientScope) bool { + return scope.Name == parameters["name"].(string) + }) + + if err != nil { + return "", err + } + + return lookup.SingleOrEmpty(found, func(scope *keycloak.SamlClientScope) string { + return scope.Id + }) +} diff --git a/config/user/config.go b/config/user/config.go index d318c243..1536f700 100644 --- a/config/user/config.go +++ b/config/user/config.go @@ -1,6 +1,11 @@ package user -import "github.com/crossplane/upjet/pkg/config" +import ( + "context" + "github.com/crossplane-contrib/provider-keycloak/config/lookup" + "github.com/crossplane/upjet/pkg/config" + "github.com/keycloak/terraform-provider-keycloak/keycloak" +) // Configure configures individual resources by adding custom ResourceConfigurators. func Configure(p *config.Provider) { @@ -37,3 +42,31 @@ func Configure(p *config.Provider) { r.ShortGroup = "user" }) } + +var userIdentifyingPropertiesLookup = lookup.IdentifyingPropertiesLookupConfig{ + RequiredParameters: []string{"realm_id", "username"}, + GetIDByExternalName: getUserIDByExternalName, + GetIDByIdentifyingProperties: getUserIDByIdentifyingProperties, +} + +// UserIdentifierFromIdentifyingProperties is used to find the existing resource by it´s identifying properties +var UserIdentifierFromIdentifyingProperties = lookup.BuildIdentifyingPropertiesLookup(userIdentifyingPropertiesLookup) + +func getUserIDByExternalName(ctx context.Context, id string, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetUser(ctx, parameters["realm_id"].(string), id) + if err != nil { + return "", err + } + return found.Id, nil +} + +func getUserIDByIdentifyingProperties(ctx context.Context, parameters map[string]any, kcClient *keycloak.KeycloakClient) (string, error) { + found, err := kcClient.GetUserByUsername(ctx, parameters["realm_id"].(string), parameters["username"].(string)) + if err != nil { + return "", err + } + if found == nil { + return "", nil + } + return found.Id, nil +} diff --git a/dev/apps/keycloak.yaml b/dev/apps/keycloak.yaml index 0d4cec67..647da864 100644 --- a/dev/apps/keycloak.yaml +++ b/dev/apps/keycloak.yaml @@ -19,6 +19,7 @@ spec: - "--http-port=8080" - "--hostname-strict=false" - "--hostname-strict-https=false" + - "--features=declarative-user-profile,admin-fine-grained-authz" extraEnv: | - name: KEYCLOAK_ADMIN value: admin diff --git a/dev/demos/basic/namespace.yaml b/dev/demos/basic/001-namespace.yaml similarity index 100% rename from dev/demos/basic/namespace.yaml rename to dev/demos/basic/001-namespace.yaml diff --git a/dev/demos/basic/realm.yaml b/dev/demos/basic/002-realm.yaml similarity index 86% rename from dev/demos/basic/realm.yaml rename to dev/demos/basic/002-realm.yaml index 7ca271f3..23bb1106 100644 --- a/dev/demos/basic/realm.yaml +++ b/dev/demos/basic/002-realm.yaml @@ -6,7 +6,10 @@ metadata: name: dev # The name of the realm in Kubernetes namespace: dev # The namespace in which the realm will be created spec: + deletionPolicy: Orphan forProvider: realm: "dev" # The name of the realm in Keycloak + attributes: + userProfileEnabled: "true" providerConfigRef: name: "keycloak-provider-config" # Reference to the ProviderConfig resource diff --git a/dev/demos/basic/realm-scopes.yaml b/dev/demos/basic/003-realm-clientscopes.yaml similarity index 88% rename from dev/demos/basic/realm-scopes.yaml rename to dev/demos/basic/003-realm-clientscopes.yaml index 7438a2e4..be4a9d1f 100644 --- a/dev/demos/basic/realm-scopes.yaml +++ b/dev/demos/basic/003-realm-clientscopes.yaml @@ -4,6 +4,7 @@ metadata: name: dev-default-scopes namespace: dev spec: + deletionPolicy: Orphan forProvider: realmId: "dev" defaultScopes: @@ -18,9 +19,10 @@ spec: apiVersion: realm.keycloak.crossplane.io/v1alpha1 kind: OptionalClientScopes metadata: - name: dev-default-scopes + name: dev-optional-scopes namespace: dev spec: + deletionPolicy: Orphan forProvider: realmId: "dev" optionalScopes: diff --git a/dev/demos/basic/004-realm-required-action.yaml b/dev/demos/basic/004-realm-required-action.yaml new file mode 100644 index 00000000..20a05b76 --- /dev/null +++ b/dev/demos/basic/004-realm-required-action.yaml @@ -0,0 +1,14 @@ +apiVersion: realm.keycloak.crossplane.io/v1alpha1 +kind: RequiredAction +metadata: + name: required-action +spec: + deletionPolicy: Orphan + providerConfigRef: + name: "keycloak-provider-config" + forProvider: + alias: webauthn-register + enabled: true + name: Webauthn Register + realmIdRef: + name: "dev" \ No newline at end of file diff --git a/dev/demos/basic/005-realm-keystore-rsa.yaml b/dev/demos/basic/005-realm-keystore-rsa.yaml new file mode 100644 index 00000000..6f20ff68 --- /dev/null +++ b/dev/demos/basic/005-realm-keystore-rsa.yaml @@ -0,0 +1,121 @@ +apiVersion: v1 +kind: Secret +metadata: + namespace: dev + name: rsa-key +type: Opaque +stringData: + priv: | + -----BEGIN PRIVATE KEY----- + MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDTgHd4ifcPhPHw + 5rhfjXV3Hpm0SHH070Rhj+pa1ty5bVrw8ARP6rO3uTreUqsKcF7WnGuycNIfImjJ + a9u+E+NJUm3X3QF7AU1q3vSz0AxKbbKZFIqlX9mphIkNCQ/OSxTKh3dMcseKolyj + iNVPHKFGzGfU0Y82fD8YulycR+qAZBV+h3iMNIVflK0zLiA5Yifput+w0HYkiVss + QyfW1OHDMP3OcHnN8L5JdWTYy7V98rDXz/HH4DR+YyiHOXbjwIhve1PqjIvKu8qz + bouMgl7F75eSZruX+gwoUN4oVSxxczA+/H5qekdjZRbTjxk9U5HL3AzVppk5mDRp + e2WpEsk4B87xXLHZOIvrETjNiZQV8DiFwPeultuYM0pKUUwy47TetK47+5ZlY00i + lLPYIq2IlKLeAr86Bks0uYbVd3y0EsTl3HxN9KGY4ytf/t4AG233G62KSvDU4mlp + ZjgEXR6VzMMjN9b7sHVwcjJwTHyE7UK6v3IfniEkbXXh6NBTuddfebBbjSYdSWAg + H9xqOrKwjJtHuWq2afkdSz3MrXPBEx8rIA/Cy9npOIhpJmUFUVU02MmULIQNVr/F + Snl7NJwPqUh6SWZBTpS+678+QzEwh2mLAf5oHetBMh6n8g5iGIjN4zeIHdaLCfmC + LnDLowt1sv4EFAzOn0ga6gEjpESwPwIDAQABAoICACRdp+0pZ7oa5VDDEvuXApYH + RG1rsXBofYz65lRVMmGNrH/V/R/SnrEd38HfW/pxBNWI0rm9TVu+Gl9btM5TdBak + 3i8qGHmSaUHRcSjDwoE/qGBK2GYD+5R6ref4YWfaoAoMzdXuvGGZdi1GN0shhJd8 + SPQeTi4TofO6A3d5pBgMXWfCmlcdJrufAELXEQJc3rkBATpRNaclmPAc4eo9802R + DGrlCwR+evF9hmgCasPrqT2KSyvPYKdEnHDX0Bnr/mmQvoaLQPQMYxeED+mKdwo4 + noq1MFc6zsFV2kT8FCazpB2IY6IiQUkVVh3tMm1Jg72WKfzIZvswsP5cEchG6yLv + q+ClsVRtWOoXNGyFVXp5m7+kgiLZF/L0wcs57k0vtDkrhZt0/P7jCQ7rq/zLIBMr + Cy02sePyB5UbiwE9KBXDeTcZSTRpXmy7MTtxJwGWA7kprNaEnP7tHncrtP2JHggZ + JZB2ZW2RD6IVNZ5CaPZAiQIBd5OJ/NykuR8PycIT8byvNoHM6fWigasbynf9hDh1 + wIqd4+WmPVbnrg04iX4iyyyioZJXuneLMV7jCpp+d1eTEHyB/zjGP1o8O1tkQKh8 + Kth+ROhm84wNT2Jvgp69S6PwXQJRub/9qZflF6CefDnNBanXx9NCnh676cIVmfTn + rNMrAl0IssItCOFjz9ctAoIBAQDpCo4sU4/5JbGaIGdw9aP+MYGSc7YtB+8BKOlt + VihzkOn/ZaBPcT5SNPLqPmqU5hzizipJAuuEmUhVy0MQHVUc76ZRiOh2siYTRnzR + kk2S3npGuVnd1hnEtrZG2W/vf4bBw9I35FoJU7dPEZ/rXQrycpquZvg3S11rLVOK + 26M7XkE2sCFAQehY6+VtAM5r/y1puRjdbEvC3g7vnFtJCpWeIeKVWhd1h39WZxd/ + KbSbA1QWoJg8LTzSbiUpbLudbJjBXs9uJIrMFPClbwgyOcamwicMO5JJxWG3Y2nQ + Ye1BXfuzJWy2fCptWNDVUI0hMbnJEMGo/RCm+jXRzsBBk1zdAoIBAQDoVqyORkUw + PQ99mo6+mRxUddR6oGH6yOzqAXuFv4OyQKEN3e9uqM6xOZU70rt8bN7ic4q181xs + X5QDUGpEuE2aLUp7R5jScHxOPf8ZqDBqHHBLMNHUJ2JRv3XzKuPwf9PkX2lyCoJW + CNrX3p0mMsKzZHZ5zoHuqHaEM+kA2XEmrs9LXkIPd4WW8uRZBFjp/goyUgzolzrL + NIUYGg5ckXTgLZtJMnAu/VyijiMmrlYyccX8twFbhLL4+VH86hbH0AUaYpTUHTNt + 0MMRBJOlGWTzGpD/rcKO73U9YQcGOsUOTs/AVQPkQ+WVLcmZXKESxNsaVYROA/wD + DgC5e787FvHLAoIBAQCO8GCAI8kdYtdq2BFaofCyy57UO5E0UbdxmKyE2eH58Jr2 + glkwIR++wKd2sk4hgnuZSid5nQbk0DgkF4bM7ZD1LLFxNW6Qz3i3Pfb1fK2ENl39 + 31aV0OsBU6i+EESuPCRl2Typ97CaI/U91Guoq+s5Br6F1rbNA7cLNEQX2dxnX8rU + Rng+t65STG7uyx3R9DTgsh3kQra9kgLLSbZcONfIpLQcM6RL/I5IavdokiYrJoF/ + V4XI815pHBJDNGRnjOKtSQAmaNeRkg6BCneWhwWn51KYGDoEccMb/yg6Hrat732h + RoVF8Sb85wtArCXEhIGWKGcAxiheov478HrQPhkpAoIBACBYkMVt+wvwNEvkTKM0 + ZcFMdjxsP7fOaU1az3ubYhZa8REhKMhbMAt0XNZrTDbSYWVdeJ5EN0XPOKgNdf4D + OqqQS6JGIXyKtIOgFffYdoUPWILfVRcYNJP099LW0c0VvsY4klGSLPsVWIJdN7ut + rXWUMvpaSbSUN4vIPlQj8aDVDX8MrF3C4hJV1nyeIlLRT1IaIjnvc/v+X9kvJ3iz + t3XOY6QZJY0lMNRgvXwRIWb8d/fq8oFvRAttioCPhoRi7ZLyzo2JUzOisMWZwYRm + uKsvs6gk5MVJiST2cSfo3q5TQpEgRN5upXPTYBG9157pUSelaDoES/2Q4YDxPXpY + g2kCggEBAISLQW7Z0Djc0aruuBW9F7UP1lHXLRjumSXVDM8b5OFbqHPXAw+T8753 + q3/EINRo2GwSaTMJCKlbAL5qaU1m6WEE5BYzfWGXDVXXuDNREsATl89qm9u7D0Au + 2CukO+bslDYYgrHXM2o/ROIPTLuOymZBwtLyYaRvkHddU9Up6tcz7ndiWQt9TsXI + xNclTdJHbcWHsKzgVyzSR4WLIzeOLyP9P9zyd0nDHJoziyeegP5y1tsg97H+bUFR + SLQ+w3WGByzaDXiRM0N5m1MsdkVczO9j35PCdAlqNHkGNy2rMWFagqfGlfyC3g/X + hWxwnsSUC1vg3AIwHK7eGYuKHTBCPBg= + -----END PRIVATE KEY----- + cert: | + -----BEGIN CERTIFICATE----- + MIIF7zCCA9egAwIBAgIUb524clJkesN7U852IHtU91rygMkwDQYJKoZIhvcNAQEL + BQAwgYYxCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5hbWUxETAPBgNVBAcM + CENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkGA1UECwwSQ29tcGFu + eVNlY3Rpb25OYW1lMR0wGwYDVQQDDBRDb21tb25OYW1lT3JIb3N0bmFtZTAeFw0y + NTAyMDcyMjM4NTVaFw0zNTAyMDUyMjM4NTVaMIGGMQswCQYDVQQGEwJYWDESMBAG + A1UECAwJU3RhdGVOYW1lMREwDwYDVQQHDAhDaXR5TmFtZTEUMBIGA1UECgwLQ29t + cGFueU5hbWUxGzAZBgNVBAsMEkNvbXBhbnlTZWN0aW9uTmFtZTEdMBsGA1UEAwwU + Q29tbW9uTmFtZU9ySG9zdG5hbWUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK + AoICAQDTgHd4ifcPhPHw5rhfjXV3Hpm0SHH070Rhj+pa1ty5bVrw8ARP6rO3uTre + UqsKcF7WnGuycNIfImjJa9u+E+NJUm3X3QF7AU1q3vSz0AxKbbKZFIqlX9mphIkN + CQ/OSxTKh3dMcseKolyjiNVPHKFGzGfU0Y82fD8YulycR+qAZBV+h3iMNIVflK0z + LiA5Yifput+w0HYkiVssQyfW1OHDMP3OcHnN8L5JdWTYy7V98rDXz/HH4DR+YyiH + OXbjwIhve1PqjIvKu8qzbouMgl7F75eSZruX+gwoUN4oVSxxczA+/H5qekdjZRbT + jxk9U5HL3AzVppk5mDRpe2WpEsk4B87xXLHZOIvrETjNiZQV8DiFwPeultuYM0pK + UUwy47TetK47+5ZlY00ilLPYIq2IlKLeAr86Bks0uYbVd3y0EsTl3HxN9KGY4ytf + /t4AG233G62KSvDU4mlpZjgEXR6VzMMjN9b7sHVwcjJwTHyE7UK6v3IfniEkbXXh + 6NBTuddfebBbjSYdSWAgH9xqOrKwjJtHuWq2afkdSz3MrXPBEx8rIA/Cy9npOIhp + JmUFUVU02MmULIQNVr/FSnl7NJwPqUh6SWZBTpS+678+QzEwh2mLAf5oHetBMh6n + 8g5iGIjN4zeIHdaLCfmCLnDLowt1sv4EFAzOn0ga6gEjpESwPwIDAQABo1MwUTAd + BgNVHQ4EFgQUaTvuxF9+C6asaqA+aC6yhxxy8wswHwYDVR0jBBgwFoAUaTvuxF9+ + C6asaqA+aC6yhxxy8wswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC + AgEAkjbmhRv04JoMouSkuveVRz6VphOO7P0itMWKShrqsu7r6OBzTG7x2DWqWVVs + 5LDPXXRT1uwj45xUrYRSnbQeblOOAuI9zzqcrwF8re+SMsU2dDhEhwK618CzZna3 + jKCVa7IECthrF1ONGyORqOazcQ20wRXJp6PjaY50Oa3PKcMeqEkTtvMvO/yGZhEZ + 7zjLInFF1FWrGBzpeiYLDl+I5l3pLunOFoFAC8fXpR97JTLdtAJ1PegRRt3oEwAH + XqvNhg6ROYRLbVh21uYT7IB1iTB69pW4DVYQUZgP4rMxJ345BMPL9F+i/mZ7aLc2 + H0Ct4Cxtperl9w34EzyTh12xpfyyjuC27E2cncywWSoF3fJI04GtLde3oXDTNF4s + vQ5zrl99/fect/Ui3dydYvYYZcVVnns4uCUZFu3PWom9o8AJMtV2+9bnBeEc6iuR + +2aZJwFj6yq99RnEQ7jWyo616fwaQuVmLsRsJxA1xYqaiFIqW+BWnlWo3r2cKDnX + A4T3vnlUEHz5Ct+B9zSkHYi8zzH0f0mMDj/5y3Z82BFiLowCc3bpa/LVm9I0S88p + EpGf0SL0QBnijAkbavIzu2+5U2RqfFRX+57M9z8Adv0YfYHg0nCK+0suPLyQJRWx + /YtOGTlPQ0G0eZinOgXSA5eyZnWJnu/TefhBVS8qph5k0OQ= + -----END CERTIFICATE----- +--- +apiVersion: realm.keycloak.crossplane.io/v1alpha1 +kind: KeystoreRsa +metadata: + name: keystore-rsa +spec: + deletionPolicy: Orphan + forProvider: + active: true + algorithm: RS256 + certificateSecretRef: + key: cert + name: rsa-key + namespace: dev + enabled: true + name: my-rsa-key + priority: 100 + privateKeySecretRef: + key: priv + name: rsa-key + namespace: dev + providerId: rsa + realmIdRef: + name: "dev" + providerConfigRef: + name: "keycloak-provider-config" # Reference to the ProviderConfig resource diff --git a/dev/demos/basic/005-realm-user-profile.yaml b/dev/demos/basic/005-realm-user-profile.yaml new file mode 100644 index 00000000..b12028a2 --- /dev/null +++ b/dev/demos/basic/005-realm-user-profile.yaml @@ -0,0 +1,53 @@ +apiVersion: realm.keycloak.crossplane.io/v1alpha1 +kind: UserProfile +metadata: + name: userprofile +spec: + deletionPolicy: Orphan + forProvider: + attribute: + - annotations: + foo: bar + displayName: Field 1 + enabledWhenScope: + - offline_access + group: group1 + multiValued: false + name: field1 + permissions: + - edit: + - admin + - user + view: + - admin + - user + requiredForRoles: + - user + requiredForScopes: + - offline_access + validator: + - name: person-name-prohibited-characters + - config: + error-message: Nope + pattern: ^[a-z]+$ + name: pattern + - annotations: + foo: '{"key": "val" }' + name: field2 + validator: + - config: + options: '[ "opt1" ]' + name: options + group: + - annotations: + foo: bar + foo2: '{ "key": "val" }' + displayDescription: A first group + displayHeader: Group 1 + name: group1 + - name: group2 + realmIdRef: + name: "dev" + unmanagedAttributePolicy: ENABLED + providerConfigRef: + name: "keycloak-provider-config" diff --git a/dev/demos/basic/006-realm-events.yaml b/dev/demos/basic/006-realm-events.yaml new file mode 100644 index 00000000..6c3829b9 --- /dev/null +++ b/dev/demos/basic/006-realm-events.yaml @@ -0,0 +1,20 @@ +apiVersion: realm.keycloak.crossplane.io/v1alpha1 +kind: RealmEvents +metadata: + name: realm-events +spec: + deletionPolicy: Orphan + forProvider: + adminEventsDetailsEnabled: true + adminEventsEnabled: true + enabledEventTypes: + - LOGIN + - LOGOUT + eventsEnabled: true + eventsExpiration: 3600 + eventsListeners: + - jboss-logging + realmIdRef: + name: "dev" + providerConfigRef: + name: "keycloak-provider-config" diff --git a/dev/demos/basic/010-roles.yaml b/dev/demos/basic/010-roles.yaml new file mode 100644 index 00000000..2a73bae3 --- /dev/null +++ b/dev/demos/basic/010-roles.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: role.keycloak.crossplane.io/v1alpha1 +kind: Role +metadata: + name: test-client + namespace: upbound-system +spec: + deletionPolicy: Orphan + forProvider: + realmId: "dev" + name: "test-client" + clientIdRef: + name: "test" + description: "abc" + providerConfigRef: + name: "keycloak-provider-config" # Reference to the provider configuration +--- +apiVersion: role.keycloak.crossplane.io/v1alpha1 +kind: Role +metadata: + name: test + namespace: upbound-system +spec: + deletionPolicy: Orphan + forProvider: + realmId: "dev" + name: "test" + description: "abc" + providerConfigRef: + name: "keycloak-provider-config" # Reference to the provider configuration +--- +apiVersion: role.keycloak.crossplane.io/v1alpha1 +kind: Role +metadata: + name: offline-access + namespace: upbound-system +spec: + deletionPolicy: Orphan + forProvider: + realmId: "dev" + name: "offline_access" + providerConfigRef: + name: "keycloak-provider-config" # Reference to the provider configuration \ No newline at end of file diff --git a/dev/demos/basic/020-user.yaml b/dev/demos/basic/020-user.yaml new file mode 100644 index 00000000..04623a44 --- /dev/null +++ b/dev/demos/basic/020-user.yaml @@ -0,0 +1,30 @@ +--- +# Example 1: Basic User +# This is a basic user with the minimum required fields. +apiVersion: user.keycloak.crossplane.io/v1alpha1 +kind: User +metadata: + name: bree + namespace: dev +spec: + deletionPolicy: Orphan + forProvider: + realmId: "dev" # The realm to which this user belongs + username: "bree" # The username for this user + providerConfigRef: + name: "keycloak-provider-config" # Reference to the provider configuration +--- +# Example 1: Basic User +# This is a basic user with the minimum required fields. +apiVersion: user.keycloak.crossplane.io/v1alpha1 +kind: User +metadata: + name: tim-tester + namespace: dev +spec: + deletionPolicy: Orphan + forProvider: + realmId: "dev" # The realm to which this user belongs + username: "tim-tester" # The username for this user + providerConfigRef: + name: "keycloak-provider-config" # Reference to the provider configuration \ No newline at end of file diff --git a/dev/demos/basic/021-user-roles.yaml b/dev/demos/basic/021-user-roles.yaml new file mode 100644 index 00000000..35301b2a --- /dev/null +++ b/dev/demos/basic/021-user-roles.yaml @@ -0,0 +1,15 @@ +apiVersion: user.keycloak.crossplane.io/v1alpha1 +kind: Roles +metadata: + name: user-roles +spec: + deletionPolicy: Orphan + forProvider: + realmIdRef: + name: "dev" + roleIdsRefs: + - name: test + userIdRef: + name: "tim-tester" + providerConfigRef: + name: "keycloak-provider-config" \ No newline at end of file diff --git a/dev/demos/basic/022-users-groups.yaml b/dev/demos/basic/022-users-groups.yaml new file mode 100644 index 00000000..c9fd0836 --- /dev/null +++ b/dev/demos/basic/022-users-groups.yaml @@ -0,0 +1,15 @@ +apiVersion: user.keycloak.crossplane.io/v1alpha1 +kind: Groups +metadata: + name: user-groups +spec: + deletionPolicy: Orphan + forProvider: + realmIdRef: + name: "dev" + groupIdsRefs: + - name: test + userIdRef: + name: "tim-tester" + providerConfigRef: + name: "keycloak-provider-config" \ No newline at end of file diff --git a/dev/demos/basic/023-user-permissions.yaml b/dev/demos/basic/023-user-permissions.yaml new file mode 100644 index 00000000..ca258ccf --- /dev/null +++ b/dev/demos/basic/023-user-permissions.yaml @@ -0,0 +1,12 @@ +apiVersion: user.keycloak.crossplane.io/v1alpha1 +kind: Permissions +metadata: + name: my-user-permission + namespace: dev +spec: + deletionPolicy: Orphan + forProvider: + realmIdRef: + name: "dev" + providerConfigRef: + name: "keycloak-provider-config" diff --git a/dev/demos/basic/030-groups.yaml b/dev/demos/basic/030-groups.yaml new file mode 100644 index 00000000..8eb65ec2 --- /dev/null +++ b/dev/demos/basic/030-groups.yaml @@ -0,0 +1,11 @@ +apiVersion: group.keycloak.crossplane.io/v1alpha1 +kind: Group +metadata: + name: test +spec: + deletionPolicy: Orphan + forProvider: + name: test + realmId: dev + providerConfigRef: + name: "keycloak-provider-config" \ No newline at end of file diff --git a/dev/demos/basic/031-group-memberships.yaml b/dev/demos/basic/031-group-memberships.yaml new file mode 100644 index 00000000..d9ebf33f --- /dev/null +++ b/dev/demos/basic/031-group-memberships.yaml @@ -0,0 +1,14 @@ +apiVersion: group.keycloak.crossplane.io/v1alpha1 +kind: Memberships +metadata: + name: test-members +spec: + deletionPolicy: Orphan + forProvider: + groupIdRef: + name: test + members: + - bree + realmId: dev + providerConfigRef: + name: "keycloak-provider-config" \ No newline at end of file diff --git a/dev/demos/basic/032-groups-roles.yaml b/dev/demos/basic/032-groups-roles.yaml new file mode 100644 index 00000000..6bf2a5d0 --- /dev/null +++ b/dev/demos/basic/032-groups-roles.yaml @@ -0,0 +1,15 @@ +apiVersion: group.keycloak.crossplane.io/v1alpha1 +kind: Roles +metadata: + name: group-roles +spec: + deletionPolicy: Orphan + forProvider: + realmIdRef: + name: "dev" + groupIdRef: + name: test + roleIdsRefs: + - name: "test-client" + providerConfigRef: + name: "keycloak-provider-config" \ No newline at end of file diff --git a/dev/demos/basic/033-group-permissions.yaml b/dev/demos/basic/033-group-permissions.yaml new file mode 100644 index 00000000..eb23e658 --- /dev/null +++ b/dev/demos/basic/033-group-permissions.yaml @@ -0,0 +1,14 @@ +apiVersion: group.keycloak.crossplane.io/v1alpha1 +kind: Permissions +metadata: + name: my-group-permission + namespace: dev +spec: + deletionPolicy: Orphan + forProvider: + realmIdRef: + name: "dev" + groupIdRef: + name: "test" + providerConfigRef: + name: "keycloak-provider-config" diff --git a/dev/demos/basic/040-oidc-clients.yaml b/dev/demos/basic/040-oidc-clients.yaml new file mode 100644 index 00000000..63a9309e --- /dev/null +++ b/dev/demos/basic/040-oidc-clients.yaml @@ -0,0 +1,59 @@ +--- +apiVersion: openidclient.keycloak.crossplane.io/v1alpha1 +kind: Client +metadata: + name: test +spec: + deletionPolicy: Orphan + forProvider: + realmIdRef: + name: "dev" + accessType: "CONFIDENTIAL" + clientId: "test" + fullScopeAllowed: false + providerConfigRef: + name: "keycloak-provider-config" +--- +apiVersion: openidclient.keycloak.crossplane.io/v1alpha1 +kind: Client +metadata: + name: account +spec: + deletionPolicy: Orphan + forProvider: + realmIdRef: + name: "dev" + accessType: "CONFIDENTIAL" + clientId: "account" + providerConfigRef: + name: "keycloak-provider-config" +--- +apiVersion: openidclient.keycloak.crossplane.io/v1alpha1 +kind: Client +metadata: + name: service-acc-1 +spec: + deletionPolicy: Orphan + forProvider: + realmIdRef: + name: "dev" + accessType: "CONFIDENTIAL" + clientId: "service-acc-1" + serviceAccountsEnabled: true + providerConfigRef: + name: "keycloak-provider-config" +--- +apiVersion: role.keycloak.crossplane.io/v1alpha1 +kind: Role +metadata: + name: svc-role +spec: + deletionPolicy: Orphan + forProvider: + realmId: "dev" + name: "svc-role" + clientIdRef: + name: "test" + description: "Role that svc account uses" + providerConfigRef: + name: "keycloak-provider-config" # Reference to the provider configuration \ No newline at end of file diff --git a/dev/demos/basic/041-oidc-client-clientscopes.yaml b/dev/demos/basic/041-oidc-client-clientscopes.yaml new file mode 100644 index 00000000..96d68156 --- /dev/null +++ b/dev/demos/basic/041-oidc-client-clientscopes.yaml @@ -0,0 +1,55 @@ +apiVersion: openidclient.keycloak.crossplane.io/v1alpha1 +kind: ClientScope +metadata: + name: openid-client-scope +spec: + deletionPolicy: Orphan + providerConfigRef: + name: "keycloak-provider-config" + forProvider: + description: When requested, this scope will map a user's group memberships to + a claim + guiOrder: 1 + includeInTokenScope: true + name: my-groups + realmIdRef: + name: "dev" +--- +apiVersion: openidclient.keycloak.crossplane.io/v1alpha1 +kind: ClientDefaultScopes +metadata: + name: client-default-scopes +spec: + deletionPolicy: Orphan + forProvider: + clientIdRef: + name: "test" + defaultScopes: + - profile + - email + - roles + - web-origins + realmIdRef: + name: "dev" + providerConfigRef: + name: "keycloak-provider-config" +--- +apiVersion: openidclient.keycloak.crossplane.io/v1alpha1 +kind: ClientOptionalScopes +metadata: + name: client-optional-scopes +spec: + deletionPolicy: Orphan + forProvider: + clientIdRef: + name: "test" + optionalScopes: + - address + - phone + - offline_access + - microprofile-jwt + - my-groups + realmIdRef: + name: "dev" + providerConfigRef: + name: "keycloak-provider-config" diff --git a/dev/demos/basic/042-oidc-client-permissions.yaml b/dev/demos/basic/042-oidc-client-permissions.yaml new file mode 100644 index 00000000..7ca8bdee --- /dev/null +++ b/dev/demos/basic/042-oidc-client-permissions.yaml @@ -0,0 +1,13 @@ +apiVersion: openidclient.keycloak.crossplane.io/v1alpha1 +kind: ClientPermissions +metadata: + name: my-permission +spec: + providerConfigRef: + name: "keycloak-provider-config" + deletionPolicy: Orphan + forProvider: + clientIdRef: + name: "test" + realmIdRef: + name: "dev" \ No newline at end of file diff --git a/dev/demos/basic/044-oidc-group-membership-protocol-mapper.yaml b/dev/demos/basic/044-oidc-group-membership-protocol-mapper.yaml new file mode 100644 index 00000000..88c5c4cf --- /dev/null +++ b/dev/demos/basic/044-oidc-group-membership-protocol-mapper.yaml @@ -0,0 +1,15 @@ +apiVersion: openidgroup.keycloak.crossplane.io/v1alpha1 +kind: GroupMembershipProtocolMapper +metadata: + name: my-group-membership +spec: + providerConfigRef: + name: "keycloak-provider-config" + deletionPolicy: Orphan + forProvider: + name: "my-mapper" + clientIdRef: + name: "test" + realmIdRef: + name: "dev" + claimName: "test" \ No newline at end of file diff --git a/dev/demos/basic/045-oidc-service-account-roles.yaml b/dev/demos/basic/045-oidc-service-account-roles.yaml new file mode 100644 index 00000000..aca2aac5 --- /dev/null +++ b/dev/demos/basic/045-oidc-service-account-roles.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: role.keycloak.crossplane.io/v1alpha1 +kind: Role +metadata: + name: svc-realm-role +spec: + deletionPolicy: Orphan + forProvider: + realmId: "dev" + name: "svc-realm-role" + providerConfigRef: + name: "keycloak-provider-config" +--- +apiVersion: openidclient.keycloak.crossplane.io/v1alpha1 +kind: ClientServiceAccountRole +metadata: + name: service-account-role +spec: + providerConfigRef: + name: "keycloak-provider-config" + deletionPolicy: Orphan + forProvider: + clientIdRef: + name: "test" + realmIdRef: + name: "dev" + roleRef: + name: "svc-role" + serviceAccountUserClientIdRef: + name: "service-acc-1" +--- +apiVersion: openidclient.keycloak.crossplane.io/v1alpha1 +kind: ClientServiceAccountRealmRole +metadata: + name: service-account-realm-role +spec: + providerConfigRef: + name: "keycloak-provider-config" + deletionPolicy: Orphan + forProvider: + realmIdRef: + name: "dev" + role: "svc-realm-role" + serviceAccountUserClientIdRef: + name: "service-acc-1" diff --git a/dev/demos/basic/046-oidc-client-policies.yaml b/dev/demos/basic/046-oidc-client-policies.yaml new file mode 100644 index 00000000..b1ad6e89 --- /dev/null +++ b/dev/demos/basic/046-oidc-client-policies.yaml @@ -0,0 +1,81 @@ +apiVersion: openidclient.keycloak.crossplane.io/v1alpha1 +kind: ClientClientPolicy +metadata: + name: my-client-policy +spec: + providerConfigRef: + name: "keycloak-provider-config" + deletionPolicy: Orphan + forProvider: + name: my-client-policy + clientsRefs: + - name: "test" + decisionStrategy: UNANIMOUS + logic: POSITIVE + resourceServerIdRef: + name: "test" + realmIdRef: + name: "dev" +--- +apiVersion: openidclient.keycloak.crossplane.io/v1alpha1 +kind: ClientGroupPolicy +metadata: + name: my-group-policy +spec: + providerConfigRef: + name: "keycloak-provider-config" + deletionPolicy: Orphan + forProvider: + name: my-group-policy + groups: + - path: / + extendChildren: false + idRef: + name: "test" + decisionStrategy: UNANIMOUS + logic: POSITIVE + resourceServerIdRef: + name: "test" + realmIdRef: + name: "dev" +--- +apiVersion: openidclient.keycloak.crossplane.io/v1alpha1 +kind: ClientRolePolicy +metadata: + name: my-role-policy +spec: + providerConfigRef: + name: "keycloak-provider-config" + deletionPolicy: Orphan + forProvider: + name: my-role-policy + role: + - required: true + idRef: + name: "test" + decisionStrategy: UNANIMOUS + logic: POSITIVE + resourceServerIdRef: + name: "test" + realmIdRef: + name: "dev" + type: "role" +--- +apiVersion: openidclient.keycloak.crossplane.io/v1alpha1 +kind: ClientUserPolicy +metadata: + name: my-user-policy +spec: + providerConfigRef: + name: "keycloak-provider-config" + deletionPolicy: Orphan + forProvider: + name: my-user-policy + usersRefs: + - name: "tim-tester" + decisionStrategy: UNANIMOUS + logic: POSITIVE + resourceServerIdRef: + name: "test" + realmIdRef: + name: "dev" \ No newline at end of file diff --git a/dev/demos/basic/047-oidc-client-role-mapper.yaml b/dev/demos/basic/047-oidc-client-role-mapper.yaml new file mode 100644 index 00000000..6d6ea5cd --- /dev/null +++ b/dev/demos/basic/047-oidc-client-role-mapper.yaml @@ -0,0 +1,31 @@ +apiVersion: client.keycloak.crossplane.io/v1alpha1 +kind: RoleMapper +metadata: + name: openid-client-role-mapper +spec: + providerConfigRef: + name: "keycloak-provider-config" + deletionPolicy: Orphan + forProvider: + clientIdRef: + name: "test" + realmIdRef: + name: "dev" + roleIdRef: + name: "test-client" +--- +apiVersion: client.keycloak.crossplane.io/v1alpha1 +kind: RoleMapper +metadata: + name: openid-client-scope-role-mapper +spec: + providerConfigRef: + name: "keycloak-provider-config" + deletionPolicy: Orphan + forProvider: + clientScopeIdRef: + name: "openid-client-scope" + realmIdRef: + name: "dev" + roleIdRef: + name: "test-client" \ No newline at end of file diff --git a/dev/demos/basic/048-oidc-client-protocol-mapper.yaml b/dev/demos/basic/048-oidc-client-protocol-mapper.yaml new file mode 100644 index 00000000..3fd46abd --- /dev/null +++ b/dev/demos/basic/048-oidc-client-protocol-mapper.yaml @@ -0,0 +1,46 @@ +apiVersion: client.keycloak.crossplane.io/v1alpha1 +kind: ProtocolMapper +metadata: + name: openid-client-protocol-mapper +spec: + providerConfigRef: + name: "keycloak-provider-config" + deletionPolicy: Orphan + forProvider: + name: "picture" + protocol: "openid-connect" + clientIdRef: + name: "test" + realmIdRef: + name: "dev" + protocolMapper: "oidc-usermodel-attribute-mapper" + config: + userinfo.token.claim: "true" + user.attribute: "picture" + id.token.claim: "true" + access.token.claim: "true" + claim.name: "picture" + jsonType.label: "String" +--- +apiVersion: client.keycloak.crossplane.io/v1alpha1 +kind: ProtocolMapper +metadata: + name: openid-client-scope-protocol-mapper +spec: + providerConfigRef: + name: "keycloak-provider-config" + deletionPolicy: Orphan + forProvider: + name: "client roles" + protocol: "openid-connect" + clientScopeIdRef: + name: "openid-client-scope" + realmIdRef: + name: "dev" + protocolMapper: "oidc-usermodel-client-role-mapper" + config: + multivalued: "true" + user.attribute: "foo" + access.token.claim: "true" + claim.name: "resource_access.${client_id}.roles" + jsonType.label: "String" diff --git a/dev/demos/basic/050-oidc-identity-provider.yaml b/dev/demos/basic/050-oidc-identity-provider.yaml new file mode 100644 index 00000000..85c9cfe2 --- /dev/null +++ b/dev/demos/basic/050-oidc-identity-provider.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +kind: Secret +metadata: + namespace: dev + name: client-secret +type: Opaque +stringData: + client-secret: "xyz" +--- +apiVersion: oidc.keycloak.crossplane.io/v1alpha1 +kind: IdentityProvider +metadata: + name: oidc-identity-provider +spec: + deletionPolicy: Orphan + forProvider: + alias: my-idp + authorizationUrl: https://authorizationurl.com + clientId: "a" + clientSecretSecretRef: + key: client-secret + name: client-secret + namespace: dev + extraConfig: + clientAuthMethod: client_secret_post + realmRef: + name: "dev" + tokenUrl: https://tokenurl.com + providerConfigRef: + name: "keycloak-provider-config" \ No newline at end of file diff --git a/dev/demos/basic/051-saml-identity-provider.yaml b/dev/demos/basic/051-saml-identity-provider.yaml new file mode 100644 index 00000000..0651d23f --- /dev/null +++ b/dev/demos/basic/051-saml-identity-provider.yaml @@ -0,0 +1,22 @@ +apiVersion: saml.keycloak.crossplane.io/v1alpha1 +kind: IdentityProvider +metadata: + name: saml-identity-provider +spec: + deletionPolicy: Orphan + forProvider: + alias: my-saml-idp + backchannelSupported: true + entityId: https://domain.com/entity_id + forceAuthn: true + postBindingAuthnRequest: true + postBindingLogout: true + postBindingResponse: true + realmRef: + name: "dev" + singleLogoutServiceUrl: https://domain.com/adfs/ls/?wa=wsignout1.0 + singleSignOnServiceUrl: https://domain.com/adfs/ls/ + storeToken: false + trustEmail: true + providerConfigRef: + name: "keycloak-provider-config" \ No newline at end of file diff --git a/dev/demos/basic/052-identity-provider-mapper.yaml b/dev/demos/basic/052-identity-provider-mapper.yaml new file mode 100644 index 00000000..9d2182c7 --- /dev/null +++ b/dev/demos/basic/052-identity-provider-mapper.yaml @@ -0,0 +1,18 @@ +apiVersion: identityprovider.keycloak.crossplane.io/v1alpha1 +kind: IdentityProviderMapper +metadata: + name: oidc-identity-provider-mapper +spec: + deletionPolicy: Orphan + forProvider: + extraConfig: + Claim: my-email-claim + UserAttribute: email + syncMode: INHERIT + identityProviderAlias: my-idp + identityProviderMapper: '%s-user-attribute-idp-mapper' + name: email-attribute-importer + realmRef: + name: "dev" + providerConfigRef: + name: "keycloak-provider-config" \ No newline at end of file diff --git a/dev/demos/basic/060-saml-client.yaml b/dev/demos/basic/060-saml-client.yaml new file mode 100644 index 00000000..c8e7eb2f --- /dev/null +++ b/dev/demos/basic/060-saml-client.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: samlclient.keycloak.crossplane.io/v1alpha1 +kind: Client +metadata: + name: saml-client +spec: + deletionPolicy: Orphan + forProvider: + clientId: saml-client-id + includeAuthnStatement: true + name: saml-client + realmIdRef: + name: "dev" + signAssertions: true + signDocuments: false + signingCertificate: ${file("saml-cert.pem")} + signingPrivateKey: ${file("saml-key.pem")} + providerConfigRef: + name: "keycloak-provider-config" +--- \ No newline at end of file diff --git a/dev/demos/basic/061-saml-client-scopes.yaml b/dev/demos/basic/061-saml-client-scopes.yaml new file mode 100644 index 00000000..9a3286f3 --- /dev/null +++ b/dev/demos/basic/061-saml-client-scopes.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: samlclient.keycloak.crossplane.io/v1alpha1 +kind: ClientScope +metadata: + name: saml-client-scopes +spec: + deletionPolicy: Orphan + forProvider: + description: This scope will map a user's group memberships to SAML assertion + guiOrder: 1 + name: groups + realmIdRef: + name: "dev" + providerConfigRef: + name: "keycloak-provider-config" +--- \ No newline at end of file diff --git a/dev/demos/basic/062-saml-client-default-scopes.yaml b/dev/demos/basic/062-saml-client-default-scopes.yaml new file mode 100644 index 00000000..b1807f46 --- /dev/null +++ b/dev/demos/basic/062-saml-client-default-scopes.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: samlclient.keycloak.crossplane.io/v1alpha1 +kind: ClientDefaultScopes +metadata: + name: saml-client-default-scopes +spec: + deletionPolicy: Orphan + forProvider: + clientIdRef: + name: saml-client + defaultScopes: + - role_list + realmIdRef: + name: "dev" + providerConfigRef: + name: "keycloak-provider-config" +--- \ No newline at end of file diff --git a/dev/demos/basic/070-authentication-flows.yaml b/dev/demos/basic/070-authentication-flows.yaml new file mode 100644 index 00000000..433bd109 --- /dev/null +++ b/dev/demos/basic/070-authentication-flows.yaml @@ -0,0 +1,13 @@ +apiVersion: authenticationflow.keycloak.crossplane.io/v1alpha1 +kind: Flow +metadata: + name: flow +spec: + deletionPolicy: Orphan + forProvider: + alias: my-flow-alias + realmIdRef: + name: "dev" + providerConfigRef: + name: "keycloak-provider-config" +--- \ No newline at end of file diff --git a/dev/demos/basic/071-authentication-subflow.yaml b/dev/demos/basic/071-authentication-subflow.yaml new file mode 100644 index 00000000..b313bf8b --- /dev/null +++ b/dev/demos/basic/071-authentication-subflow.yaml @@ -0,0 +1,32 @@ +apiVersion: authenticationflow.keycloak.crossplane.io/v1alpha1 +kind: Subflow +metadata: + name: subflow +spec: + deletionPolicy: Orphan + forProvider: + alias: my-subflow-alias-1 + parentFlowAliasRef: + name: flow + providerId: basic-flow + realmIdRef: + name: "dev" + requirement: ALTERNATIVE + providerConfigRef: + name: "keycloak-provider-config" +--- +apiVersion: authenticationflow.keycloak.crossplane.io/v1alpha1 +kind: Subflow +metadata: + name: subflow-of-subflow +spec: + deletionPolicy: Orphan + forProvider: + alias: my-subflow-alias-99 + parentFlowAlias: my-subflow-alias-1 + providerId: basic-flow + realmIdRef: + name: "dev" + requirement: ALTERNATIVE + providerConfigRef: + name: "keycloak-provider-config" \ No newline at end of file diff --git a/dev/demos/basic/072-authentication-execution.yaml b/dev/demos/basic/072-authentication-execution.yaml new file mode 100644 index 00000000..24c124e2 --- /dev/null +++ b/dev/demos/basic/072-authentication-execution.yaml @@ -0,0 +1,62 @@ +apiVersion: authenticationflow.keycloak.crossplane.io/v1alpha1 +kind: Execution +metadata: + name: execution-one +spec: + deletionPolicy: Orphan + forProvider: + authenticator: auth-cookie + parentFlowAliasRef: + name: flow + realmIdRef: + name: "dev" + requirement: ALTERNATIVE + providerConfigRef: + name: "keycloak-provider-config" +--- +apiVersion: authenticationflow.keycloak.crossplane.io/v1alpha1 +kind: Execution +metadata: + name: execution-two +spec: + deletionPolicy: Orphan + forProvider: + authenticator: auth-cookie + parentFlowAlias: my-subflow-alias-1 + realmIdRef: + name: "dev" + requirement: ALTERNATIVE + providerConfigRef: + name: "keycloak-provider-config" +--- +apiVersion: authenticationflow.keycloak.crossplane.io/v1alpha1 +kind: Execution +metadata: + name: execution-three +spec: + deletionPolicy: Orphan + forProvider: + authenticator: auth-cookie + parentFlowAlias: my-subflow-alias-99 + realmIdRef: + name: "dev" + requirement: ALTERNATIVE + providerConfigRef: + name: "keycloak-provider-config" +--- +apiVersion: authenticationflow.keycloak.crossplane.io/v1alpha1 +kind: Execution +metadata: + name: execution-identity-redirect +spec: + deletionPolicy: Orphan + forProvider: + authenticator: identity-provider-redirector + parentFlowAliasRef: + name: flow + realmIdRef: + name: "dev" + requirement: ALTERNATIVE + providerConfigRef: + name: "keycloak-provider-config" +--- \ No newline at end of file diff --git a/dev/demos/basic/073-authentication-execution-config.yaml b/dev/demos/basic/073-authentication-execution-config.yaml new file mode 100644 index 00000000..732809a9 --- /dev/null +++ b/dev/demos/basic/073-authentication-execution-config.yaml @@ -0,0 +1,16 @@ +apiVersion: authenticationflow.keycloak.crossplane.io/v1alpha1 +kind: ExecutionConfig +metadata: + name: execution-identity-redirect-config +spec: + deletionPolicy: Orphan + forProvider: + alias: my-config-alias + config: + defaultProvider: my-config-default-idp + executionIdRef: + name: execution-identity-redirect + realmIdRef: + name: "dev" + providerConfigRef: + name: "keycloak-provider-config" diff --git a/dev/demos/basic/074-authentication-execution-binding.yaml b/dev/demos/basic/074-authentication-execution-binding.yaml new file mode 100644 index 00000000..e126b3ab --- /dev/null +++ b/dev/demos/basic/074-authentication-execution-binding.yaml @@ -0,0 +1,13 @@ +apiVersion: authenticationflow.keycloak.crossplane.io/v1alpha1 +kind: Bindings +metadata: + name: browser-authentication-binding +spec: + deletionPolicy: Orphan + forProvider: + dockerAuthenticationFlowRef: + name: "flow" + realmIdRef: + name: "dev" + providerConfigRef: + name: "keycloak-provider-config" diff --git a/dev/demos/basic/080-defaults-groups.yaml b/dev/demos/basic/080-defaults-groups.yaml new file mode 100644 index 00000000..014ead11 --- /dev/null +++ b/dev/demos/basic/080-defaults-groups.yaml @@ -0,0 +1,13 @@ +apiVersion: defaults.keycloak.crossplane.io/v1alpha1 +kind: DefaultGroups +metadata: + name: my-default-groups +spec: + deletionPolicy: Orphan + forProvider: + groupIdsRefs: + - name: test + realmIdRef: + name: "dev" + providerConfigRef: + name: "keycloak-provider-config" \ No newline at end of file diff --git a/dev/demos/basic/081-defaults-roles.yaml b/dev/demos/basic/081-defaults-roles.yaml new file mode 100644 index 00000000..6deeb967 --- /dev/null +++ b/dev/demos/basic/081-defaults-roles.yaml @@ -0,0 +1,13 @@ +apiVersion: defaults.keycloak.crossplane.io/v1alpha1 +kind: Roles +metadata: + name: default-roles +spec: + deletionPolicy: Orphan + forProvider: + defaultRolesRefs: + - name: test + realmIdRef: + name: "dev" + providerConfigRef: + name: "keycloak-provider-config" \ No newline at end of file diff --git a/dev/demos/basic/client.yaml b/dev/demos/basic/client.yaml deleted file mode 100644 index 279f142d..00000000 --- a/dev/demos/basic/client.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -apiVersion: openidclient.keycloak.crossplane.io/v1alpha1 -kind: Client -metadata: - name: test -spec: - forProvider: - realmIdRef: - name: "dev" - accessType: "CONFIDENTIAL" - clientId: "test" - serviceAccountsEnabled: true - providerConfigRef: - name: "keycloak-provider-config" \ No newline at end of file diff --git a/dev/demos/basic/users.yaml b/dev/demos/basic/users.yaml deleted file mode 100644 index 5400ed81..00000000 --- a/dev/demos/basic/users.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -# Example 1: Basic User -# This is a basic user with the minimum required fields. -apiVersion: user.keycloak.crossplane.io/v1alpha1 -kind: User -metadata: - name: bree - namespace: dev -spec: - forProvider: - realmId: "dev" # The realm to which this user belongs - username: "bree" # The username for this user - providerConfigRef: - name: "keycloak-provider-config" # Reference to the provider configuration \ No newline at end of file diff --git a/dev/setup_dev_environment.sh b/dev/setup_dev_environment.sh index d838000c..6aa6a721 100755 --- a/dev/setup_dev_environment.sh +++ b/dev/setup_dev_environment.sh @@ -4,14 +4,17 @@ set -eo pipefail # Default variable values CLUSTER_NAME="fenrir-1" skipmetallb=false - +runcloudproviderkind=false +uselocalprovider=false # Function to display script usage usage() { echo "Usage: $0 [OPTIONS]" echo "Options:" - echo " -h, --help Display this help message" - echo " -c, --cluster-name Name of the Cluster" - echo " -s, --skip-metal-lb Do not install MetalLB" + echo " -h, --help Display this help message" + echo " -c, --cluster-name Name of the Cluster" + echo " -s, --skip-metal-lb Do not install MetalLB" + echo " -p, --start-cloud-provider-kind Run 'cloud-provider-kind' with sudo as Background task due to rootless docker (metal lb wont work) + mounting user docker socket to root docker socket" + echo " -l, --use-local-provider Use local provider (Scales down 'provider-keycloak')" } has_argument() { @@ -33,6 +36,12 @@ handle_options() { -s | --skip-metal-lb) skipmetallb=true ;; + -p | --start-cloud-provider-kind) + runcloudproviderkind=true + ;; + -l | --use-local-provider) + uselocalprovider=true + ;; -c | --cluster-name*) if ! has_argument $@; then echo "Clustername not specified." >&2 @@ -130,7 +139,19 @@ while true; do done fi +if [[ "$runcloudproviderkind" == "true" ]]; then +echo "Starting cloud-provider-kind with sudo as BackgroundTask" +export CLOUD_PROVIDER_KIND_LOGS=$(mktemp) +echo "Cloud-Provider-Kind Logs are here: tail -f '$CLOUD_PROVIDER_KIND_LOGS'" +sudo echo -n "" +sudo ln -s /run/user/1000/docker.sock /var/run/docker.sock || true +sudo cloud-provider-kind -v 0 > $CLOUD_PROVIDER_KIND_LOGS 2>&1 & +fi + echo "########### Installing ArgoCD ###########" +if $kubectl_cmd diff -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml >/dev/null 2>&1; then + echo "Argo up-to-date." +else $kubectl_cmd create namespace argocd || true $kubectl_cmd apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml echo "* Exposing ArgoCD" @@ -146,7 +167,7 @@ export ARGOCD_IP=$($kubectl_cmd -n argocd get svc argocd-server -o json | jq -r echo "* Waiting for ArgoCD to be ready" $kubectl_cmd wait pod --all --for=condition=Ready --namespace argocd --timeout=300s - +fi echo "########### Installing Keycloak ###########" if $kubectl_cmd diff -f apps/keycloak.yaml >/dev/null 2>&1; then @@ -176,14 +197,13 @@ else sleep 10 $kubectl_cmd wait pod --all --for=condition=Ready --namespace crossplane-system --timeout=300s sleep 10 - fi echo "########### Installing Keycloak Provider ###########" +cat ./apps/keycloak-provider/keycloak-provider-secret.yaml | envsubst | $kubectl_cmd apply --namespace crossplane-system -f - if $kubectl_cmd diff -f apps/keycloak-provider/keycloak-provider-config.yaml >/dev/null 2>&1; then echo "Keycloak Provider up-to-date." else -cat ./apps/keycloak-provider/keycloak-provider-secret.yaml | envsubst | $kubectl_cmd apply --namespace crossplane-system -f - $kubectl_cmd apply -f ./apps/keycloak-provider/keycloak-provider.yaml sleep 10 $kubectl_cmd wait pod --all --for=condition=Ready --namespace crossplane-system --timeout=300s @@ -192,6 +212,12 @@ $kubectl_cmd wait --for condition=established --timeout=60s crd/providerconfigs. $kubectl_cmd apply -f ./apps/keycloak-provider/keycloak-provider-config.yaml fi +if [[ "$uselocalprovider" == "true" ]]; then +echo "Scaling down 'provider-keycloak' to use local provider" +$kubectl_cmd patch DeploymentRuntimeConfig enable-ess --type='merge' -p '{"spec":{"deploymentTemplate":{"spec":{"replicas":0}}}}' +$kubectl_cmd apply -f ../package/crds +fi + echo "#################################################" echo "You're ready to go!" echo "ArgoCD is ready at https://$ARGOCD_IP:443" @@ -200,3 +226,12 @@ echo "-------------------------------------------------" echo "Keycloak is ready at http://$KEYCLOAK_IP:$KEYCLOAK_PORT/auth" echo "Keycloak login: admin / admin" echo "#################################################" +if [[ "$runcloudproviderkind" == "true" ]]; then +echo "Cloud-Provider-Kind Logs are here: tail -f $CLOUD_PROVIDER_KIND_LOGS" + +# Do not finish script, so that Cloud-Provider-Kind keeps running! +tail -f /dev/null + +echo "Killing Cloud-Provider-Kind, which runs in background" +pkill -P $$ +fi diff --git a/examples-generated/openidclient/v1alpha1/clientclientpolicy.yaml b/examples-generated/openidclient/v1alpha1/clientclientpolicy.yaml index 87df7230..07a8d69c 100644 --- a/examples-generated/openidclient/v1alpha1/clientclientpolicy.yaml +++ b/examples-generated/openidclient/v1alpha1/clientclientpolicy.yaml @@ -8,8 +8,8 @@ metadata: name: token-exchange spec: forProvider: - clients: - - ${keycloak_openid_client.openid_client.id} + clientsRefs: + - name: openid_client decisionStrategy: UNANIMOUS logic: POSITIVE name: my-policy diff --git a/package/crds/openidclient.keycloak.crossplane.io_clientclientpolicies.yaml b/package/crds/openidclient.keycloak.crossplane.io_clientclientpolicies.yaml index 8b4b3322..7dee55b6 100644 --- a/package/crds/openidclient.keycloak.crossplane.io_clientclientpolicies.yaml +++ b/package/crds/openidclient.keycloak.crossplane.io_clientclientpolicies.yaml @@ -79,6 +79,85 @@ spec: type: string type: array x-kubernetes-list-type: set + clientsRefs: + description: References to Client in openidclient to populate + clients. + items: + description: A Reference to a named object. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + type: array + clientsSelector: + description: Selector for a list of Client in openidclient to + populate clients. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + policy: + description: Policies for selection. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + type: object decisionStrategy: description: (Computed) Dictates how the policies associated with a given permission are evaluated and how a final decision is @@ -272,6 +351,85 @@ spec: type: string type: array x-kubernetes-list-type: set + clientsRefs: + description: References to Client in openidclient to populate + clients. + items: + description: A Reference to a named object. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + type: array + clientsSelector: + description: Selector for a list of Client in openidclient to + populate clients. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + policy: + description: Policies for selection. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + type: object decisionStrategy: description: (Computed) Dictates how the policies associated with a given permission are evaluated and how a final decision is @@ -614,10 +772,6 @@ spec: - forProvider type: object x-kubernetes-validations: - - message: spec.forProvider.clients is a required parameter - rule: '!(''*'' in self.managementPolicies || ''Create'' in self.managementPolicies - || ''Update'' in self.managementPolicies) || has(self.forProvider.clients) - || (has(self.initProvider) && has(self.initProvider.clients))' - message: spec.forProvider.logic is a required parameter rule: '!(''*'' in self.managementPolicies || ''Create'' in self.managementPolicies || ''Update'' in self.managementPolicies) || has(self.forProvider.logic) diff --git a/package/crds/openidclient.keycloak.crossplane.io_clientgrouppolicies.yaml b/package/crds/openidclient.keycloak.crossplane.io_clientgrouppolicies.yaml index a145805b..d25fee34 100644 --- a/package/crds/openidclient.keycloak.crossplane.io_clientgrouppolicies.yaml +++ b/package/crds/openidclient.keycloak.crossplane.io_clientgrouppolicies.yaml @@ -84,6 +84,80 @@ spec: type: boolean id: type: string + idRef: + description: Reference to a Group in group to populate id. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + idSelector: + description: Selector for a Group in group to populate id. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching + labels is selected. + type: object + policy: + description: Policies for selection. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + type: object path: type: string type: object @@ -273,6 +347,80 @@ spec: type: boolean id: type: string + idRef: + description: Reference to a Group in group to populate id. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + idSelector: + description: Selector for a Group in group to populate id. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching + labels is selected. + type: object + policy: + description: Policies for selection. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + type: object path: type: string type: object diff --git a/package/crds/openidclient.keycloak.crossplane.io_clientrolepolicies.yaml b/package/crds/openidclient.keycloak.crossplane.io_clientrolepolicies.yaml index ba0b2984..91f23111 100644 --- a/package/crds/openidclient.keycloak.crossplane.io_clientrolepolicies.yaml +++ b/package/crds/openidclient.keycloak.crossplane.io_clientrolepolicies.yaml @@ -240,6 +240,80 @@ spec: properties: id: type: string + idRef: + description: Reference to a Role in role to populate id. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + idSelector: + description: Selector for a Role in role to populate id. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching + labels is selected. + type: object + policy: + description: Policies for selection. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + type: object required: type: boolean type: object @@ -427,6 +501,80 @@ spec: properties: id: type: string + idRef: + description: Reference to a Role in role to populate id. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + idSelector: + description: Selector for a Role in role to populate id. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching + labels is selected. + type: object + policy: + description: Policies for selection. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + type: object required: type: boolean type: object diff --git a/package/crds/openidclient.keycloak.crossplane.io_clientuserpolicies.yaml b/package/crds/openidclient.keycloak.crossplane.io_clientuserpolicies.yaml index 691b453b..e5138df0 100644 --- a/package/crds/openidclient.keycloak.crossplane.io_clientuserpolicies.yaml +++ b/package/crds/openidclient.keycloak.crossplane.io_clientuserpolicies.yaml @@ -240,6 +240,83 @@ spec: type: string type: array x-kubernetes-list-type: set + usersRefs: + description: References to User in user to populate users. + items: + description: A Reference to a named object. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + type: array + usersSelector: + description: Selector for a list of User in user to populate users. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + policy: + description: Policies for selection. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + type: object type: object initProvider: description: |- @@ -421,6 +498,83 @@ spec: type: string type: array x-kubernetes-list-type: set + usersRefs: + description: References to User in user to populate users. + items: + description: A Reference to a named object. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + type: array + usersSelector: + description: Selector for a list of User in user to populate users. + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + policy: + description: Policies for selection. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + type: object type: object managementPolicies: default: @@ -602,10 +756,6 @@ spec: rule: '!(''*'' in self.managementPolicies || ''Create'' in self.managementPolicies || ''Update'' in self.managementPolicies) || has(self.forProvider.name) || (has(self.initProvider) && has(self.initProvider.name))' - - message: spec.forProvider.users is a required parameter - rule: '!(''*'' in self.managementPolicies || ''Create'' in self.managementPolicies - || ''Update'' in self.managementPolicies) || has(self.forProvider.users) - || (has(self.initProvider) && has(self.initProvider.users))' status: description: ClientUserPolicyStatus defines the observed state of ClientUserPolicy. properties: diff --git a/package/crds/samlclient.keycloak.crossplane.io_clientdefaultscopes.yaml b/package/crds/samlclient.keycloak.crossplane.io_clientdefaultscopes.yaml index 567094c3..b57382c4 100644 --- a/package/crds/samlclient.keycloak.crossplane.io_clientdefaultscopes.yaml +++ b/package/crds/samlclient.keycloak.crossplane.io_clientdefaultscopes.yaml @@ -78,8 +78,7 @@ spec: Note that this is the unique ID of the client generated by Keycloak. type: string clientIdRef: - description: Reference to a Client in openidclient to populate - clientId. + description: Reference to a Client in samlclient to populate clientId. properties: name: description: Name of the referenced object. @@ -113,8 +112,7 @@ spec: - name type: object clientIdSelector: - description: Selector for a Client in openidclient to populate - clientId. + description: Selector for a Client in samlclient to populate clientId. properties: matchControllerRef: description: |- @@ -256,8 +254,7 @@ spec: Note that this is the unique ID of the client generated by Keycloak. type: string clientIdRef: - description: Reference to a Client in openidclient to populate - clientId. + description: Reference to a Client in samlclient to populate clientId. properties: name: description: Name of the referenced object. @@ -291,8 +288,7 @@ spec: - name type: object clientIdSelector: - description: Selector for a Client in openidclient to populate - clientId. + description: Selector for a Client in samlclient to populate clientId. properties: matchControllerRef: description: |-