diff --git a/docs/eventing-api.md b/docs/eventing-api.md index b48faf9b2c3..be83e05f830 100644 --- a/docs/eventing-api.md +++ b/docs/eventing-api.md @@ -6232,6 +6232,16 @@ state. Source.

+ + +namespaces
+ +[]string + + + + +

SinkBindingSpec diff --git a/pkg/apis/sources/v1/ping_types.go b/pkg/apis/sources/v1/ping_types.go index 5390fc288ff..b752da20442 100644 --- a/pkg/apis/sources/v1/ping_types.go +++ b/pkg/apis/sources/v1/ping_types.go @@ -93,6 +93,8 @@ type PingSourceStatus struct { // * SinkURI - the current active sink URI that has been configured for the // Source. duckv1.SourceStatus `json:",inline"` + + Namespaces []string `json:"namespaces"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/sources/v1/zz_generated.deepcopy.go b/pkg/apis/sources/v1/zz_generated.deepcopy.go index 6d175e3c960..3bed1735e4a 100644 --- a/pkg/apis/sources/v1/zz_generated.deepcopy.go +++ b/pkg/apis/sources/v1/zz_generated.deepcopy.go @@ -358,6 +358,11 @@ func (in *PingSourceSpec) DeepCopy() *PingSourceSpec { func (in *PingSourceStatus) DeepCopyInto(out *PingSourceStatus) { *out = *in in.SourceStatus.DeepCopyInto(&out.SourceStatus) + if in.Namespaces != nil { + in, out := &in.Namespaces, &out.Namespaces + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/reconciler/pingsource/pingsource.go b/pkg/reconciler/pingsource/pingsource.go index cd88c938646..2297ff4ad5e 100644 --- a/pkg/reconciler/pingsource/pingsource.go +++ b/pkg/reconciler/pingsource/pingsource.go @@ -21,7 +21,7 @@ import ( "encoding/json" "fmt" - v1 "k8s.io/client-go/listers/core/v1" + clientv1 "k8s.io/client-go/listers/core/v1" "go.uber.org/zap" @@ -41,6 +41,7 @@ import ( "knative.dev/pkg/system" "knative.dev/pkg/tracker" + rbacv1listers "k8s.io/client-go/listers/rbac/v1" "knative.dev/eventing/pkg/adapter/mtping" "knative.dev/eventing/pkg/adapter/v2" "knative.dev/eventing/pkg/apis/feature" @@ -79,7 +80,10 @@ type Reconciler struct { // Leader election configuration for the mt receive adapter leConfig string - serviceAccountLister v1.ServiceAccountLister + serviceAccountLister clientv1.ServiceAccountLister + roleLister rbacv1listers.RoleLister + roleBindingLister rbacv1listers.RoleBindingLister + namespaceLister clientv1.NamespaceLister } // Check that our Reconciler implements ReconcileKind @@ -113,6 +117,23 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, source *sourcesv1.PingSo return err } + if featureFlags.IsOIDCAuthentication() { + // Create the role + err := r.createOIDCRole(ctx, source) + + if err != nil { + logging.FromContext(ctx).Errorw("Failed when creating the OIDC Role for PingSource", zap.Error(err)) + return err + } + + // Create the rolebinding + err = r.createOIDCRoleBinding(ctx, source) + if err != nil { + logging.FromContext(ctx).Errorw("Failed when creating the OIDC RoleBinding for PingSource", zap.Error(err)) + return err + } + } + sinkAddr, err := r.sinkResolver.AddressableFromDestinationV1(ctx, *dest, source) if err != nil { source.Status.MarkNoSink("NotFound", "") @@ -214,3 +235,80 @@ func findContainer(podSpec *corev1.PodSpec, name string) *corev1.Container { func zero(i *int32) bool { return i != nil && *i == 0 } + +func (r *Reconciler) createOIDCRole(ctx context.Context, source *sourcesv1.PingSource) error { + roleName := resources.GetOIDCTokenRoleName(source.Name) + + expected, err := resources.MakeOIDCRole(source) + + if err != nil { + return fmt.Errorf("Cannot create OIDC role for PingSource %s/%s: %w", source.GetName(), source.GetNamespace(), err) + } + // By querying roleLister to see whether the role exist or not + role, err := r.roleLister.Roles(source.GetNamespace()).Get(roleName) + + if apierrors.IsNotFound(err) { + // If the role does not exist, we will call kubeclient to create it + role = expected + _, err = r.kubeClientSet.RbacV1().Roles(source.GetNamespace()).Create(ctx, role, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("could not create OIDC service account role %s/%s for %s: %w", source.GetName(), source.GetNamespace(), "ApiServerSource", err) + } + } else { + // If the role does exist, we will check whether an update is needed + // By comparing the role's rule + if !equality.Semantic.DeepEqual(role.Rules, expected.Rules) { + // If the role's rules are not equal, we will update the role + role.Rules = expected.Rules + _, err = r.kubeClientSet.RbacV1().Roles(source.GetNamespace()).Update(ctx, role, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("could not update OIDC service account role %s/%s for %s: %w", source.GetName(), source.GetNamespace(), "ApiServerSource", err) + } + } else { + // If the role does exist and no update is needed, we will just return + return nil + } + } + + return nil + +} + +// createOIDCRoleBinding: this function will call resources package to get the rolebinding object +// and then pass to kubeclient to make the actual OIDC rolebinding +func (r *Reconciler) createOIDCRoleBinding(ctx context.Context, source *sourcesv1.PingSource) error { + roleBindingName := resources.GetOIDCTokenRoleBindingName(source.Name) + + expected, err := resources.MakeOIDCRoleBinding(source) + if err != nil { + return fmt.Errorf("Cannot create OIDC roleBinding for PingSource %s/%s: %w", source.GetName(), source.GetNamespace(), err) + } + + // By querying roleBindingLister to see whether the roleBinding exist or not + roleBinding, err := r.roleBindingLister.RoleBindings(source.GetNamespace()).Get(roleBindingName) + if apierrors.IsNotFound(err) { + // If the role does not exist, we will call kubeclient to create it + roleBinding = expected + _, err = r.kubeClientSet.RbacV1().RoleBindings(source.GetNamespace()).Create(ctx, roleBinding, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("could not create OIDC service account rolebinding %s/%s for %s: %w", source.GetName(), source.GetNamespace(), "apiserversource", err) + } + } else { + // If the role does exist, we will check whether an update is needed + // By comparing the role's rule + if !equality.Semantic.DeepEqual(roleBinding.RoleRef, expected.RoleRef) || !equality.Semantic.DeepEqual(roleBinding.Subjects, expected.Subjects) { + // If the role's rules are not equal, we will update the role + roleBinding.RoleRef = expected.RoleRef + roleBinding.Subjects = expected.Subjects + _, err = r.kubeClientSet.RbacV1().RoleBindings(source.GetNamespace()).Update(ctx, roleBinding, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("could not update OIDC service account rolebinding %s/%s for %s: %w", source.GetName(), source.GetNamespace(), "apiserversource", err) + } + } else { + // If the role does exist and no update is needed, we will just return + return nil + } + } + + return nil +} diff --git a/pkg/reconciler/pingsource/pingsource_test.go b/pkg/reconciler/pingsource/pingsource_test.go index 4e84a050926..036082c9c4a 100644 --- a/pkg/reconciler/pingsource/pingsource_test.go +++ b/pkg/reconciler/pingsource/pingsource_test.go @@ -87,6 +87,20 @@ var ( Name: &sinkURL.Scheme, URL: sinkURL, } + sinkAudience = "sink-oidc-audience" + sinkOIDCAddressable = &duckv1.Addressable{ + Name: &sinkURL.Scheme, + URL: sinkURL, + Audience: &sinkAudience, + } + sinkOIDCDest = duckv1.Destination{ + Ref: &duckv1.KReference{ + Name: sinkName, + Kind: "Channel", + APIVersion: "messaging.knative.dev/v1", + }, + Audience: &sinkAudience, + } ) const ( diff --git a/pkg/reconciler/pingsource/resources/oidc_rolebinding.go b/pkg/reconciler/pingsource/resources/oidc_rolebinding.go new file mode 100644 index 00000000000..91e51600515 --- /dev/null +++ b/pkg/reconciler/pingsource/resources/oidc_rolebinding.go @@ -0,0 +1,115 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// taken from #7452, with modifications for pingsource + +package resources + +import ( + "fmt" + + "knative.dev/eventing/pkg/apis/sources" + + "knative.dev/pkg/kmeta" + + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "knative.dev/eventing/pkg/apis/sources/v1" +) + +// GetOIDCTokenRoleName will return the name of the role for creating the JWT token +func GetOIDCTokenRoleName(sourceName string) string { + return kmeta.ChildName(sourceName, "-create-oidc-token") +} + +// GetOIDCTokenRoleBindingName will return the name of the rolebinding for creating the JWT token +func GetOIDCTokenRoleBindingName(sourceName string) string { + return kmeta.ChildName(sourceName, "-create-oidc-token") +} + +func MakeOIDCRole(source *v1.PingSource) (*rbacv1.Role, error) { + roleName := GetOIDCTokenRoleName(source.Name) + + if source.Status.Auth == nil || source.Status.Auth.ServiceAccountName == nil { + return nil, fmt.Errorf("Error when making OIDC Role for pingsource, as the OIDC service account does not exist") + } + + return &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleName, + Namespace: source.GetNamespace(), + Annotations: map[string]string{ + "description": fmt.Sprintf("Role for OIDC Authentication for PingSource %q", source.GetName()), + }, + Labels: map[string]string{ + sources.OIDCLabelKey: "", + }, + OwnerReferences: []metav1.OwnerReference{ + *kmeta.NewControllerRef(source), + }, + }, + Rules: []rbacv1.PolicyRule{ + rbacv1.PolicyRule{ + APIGroups: []string{""}, + // apiServerSource OIDC service account name, it is in the source.Status, NOT in source.Spec + ResourceNames: []string{*source.Status.Auth.ServiceAccountName}, + Resources: []string{"serviceaccounts/token"}, + Verbs: []string{"create"}, + }, + }, + }, nil + +} + +// MakeOIDCRoleBinding will return the rolebinding object for generating the JWT token +func MakeOIDCRoleBinding(source *v1.PingSource) (*rbacv1.RoleBinding, error) { + roleName := GetOIDCTokenRoleName(source.Name) + roleBindingName := GetOIDCTokenRoleBindingName(source.Name) + + if *source.Status.Auth.ServiceAccountName == "" { + return nil, fmt.Errorf("Error when making OIDC RoleBinding for pingserversource, as the Spec service account does not exist") + } + + return &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleBindingName, + Namespace: source.GetNamespace(), + Annotations: map[string]string{ + "description": fmt.Sprintf("Role Binding for OIDC Authentication for PingServerSource %q", source.GetName()), + }, + Labels: map[string]string{ + sources.OIDCLabelKey: "", + }, + OwnerReferences: []metav1.OwnerReference{ + *kmeta.NewControllerRef(source), + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: roleName, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Namespace: source.GetNamespace(), + //Note: apiServerSource service account name, it is in the source.Spec, NOT in source.Status.Auth + Name: *source.Status.Auth.ServiceAccountName, + }, + }, + }, nil + +}