Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Zazzscoot committed Dec 24, 2023
1 parent 57901b9 commit 17c775a
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 2 deletions.
10 changes: 10 additions & 0 deletions docs/eventing-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -6232,6 +6232,16 @@ state.
Source.</p>
</td>
</tr>
<tr>
<td>
<code>namespaces</code><br/>
<em>
[]string
</em>
</td>
<td>
</td>
</tr>
</tbody>
</table>
<h3 id="sources.knative.dev/v1.SinkBindingSpec">SinkBindingSpec
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/sources/v1/ping_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/sources/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

102 changes: 100 additions & 2 deletions pkg/reconciler/pingsource/pingsource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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", "")
Expand Down Expand Up @@ -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
}
14 changes: 14 additions & 0 deletions pkg/reconciler/pingsource/pingsource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
115 changes: 115 additions & 0 deletions pkg/reconciler/pingsource/resources/oidc_rolebinding.go
Original file line number Diff line number Diff line change
@@ -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

}

0 comments on commit 17c775a

Please sign in to comment.