@@ -3,6 +3,11 @@ package sar
33import (
44 "context"
55 "fmt"
6+ "sync"
7+
8+ "google.golang.org/grpc"
9+ pbv1 "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/options/grpc/protobuf/v1"
10+ "open-cluster-management.io/sdk-go/pkg/server/grpc/authz"
611
712 authv1 "k8s.io/api/authorization/v1"
813 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -16,41 +21,123 @@ import (
1621 "open-cluster-management.io/sdk-go/pkg/cloudevents/clients/lease"
1722 "open-cluster-management.io/sdk-go/pkg/cloudevents/clients/work/payload"
1823 "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types"
19- "open-cluster-management.io/sdk-go/pkg/cloudevents/server/grpc/authn"
20- "open-cluster-management.io/sdk-go/pkg/cloudevents/server/grpc/authz"
24+ "open-cluster-management.io/sdk-go/pkg/server/grpc/authn"
2125)
2226
2327type SARAuthorizer struct {
2428 kubeClient kubernetes.Interface
2529}
2630
27- func NewSARAuthorizer (kubeClient kubernetes.Interface ) authz.Authorizer {
31+ // validate SARAuthorizer implement StreamAuthorizer and UnaryAuthorizer
32+ var _ authz.StreamAuthorizer = (* SARAuthorizer )(nil )
33+ var _ authz.UnaryAuthorizer = (* SARAuthorizer )(nil )
34+
35+ // wrappedAuthorizedStream caches the subscription request that is already read.
36+ type wrappedAuthorizedStream struct {
37+ sync.Mutex
38+
39+ grpc.ServerStream
40+ authorizedReq * pbv1.SubscriptionRequest
41+ }
42+
43+ // RecvMsg set the msg from the cache.
44+ func (c * wrappedAuthorizedStream ) RecvMsg (m any ) error {
45+ c .Lock ()
46+ defer c .Unlock ()
47+
48+ msg , ok := m .(* pbv1.SubscriptionRequest )
49+ if ! ok {
50+ return fmt .Errorf ("unsupported request type %T" , m )
51+ }
52+
53+ msg .ClusterName = c .authorizedReq .ClusterName
54+ msg .Source = c .authorizedReq .Source
55+ msg .DataType = c .authorizedReq .DataType
56+ return nil
57+ }
58+
59+ func NewSARAuthorizer (kubeClient kubernetes.Interface ) * SARAuthorizer {
2860 return & SARAuthorizer {
2961 kubeClient : kubeClient ,
3062 }
3163}
3264
33- func (s * SARAuthorizer ) Authorize (ctx context.Context , cluster string , eventsType types.CloudEventsType ) error {
65+ func (s * SARAuthorizer ) AuthorizeRequest (ctx context.Context , req any ) (authz.Decision , error ) {
66+ pReq , ok := req .(* pbv1.PublishRequest )
67+ if ! ok {
68+ return authz .DecisionDeny , fmt .Errorf ("unsupported request type %T" , req )
69+ }
70+
71+ eventsType , err := types .ParseCloudEventsType (pReq .Event .Type )
72+ if err != nil {
73+ return authz .DecisionDeny , err
74+ }
75+
76+ // the event of grpc publish request is the original cloudevent data, we need a `ce-` prefix
77+ // to get the event attribute
78+ clusterAttr , ok := pReq .Event .Attributes [fmt .Sprintf ("ce-%s" , types .ExtensionClusterName )]
79+ if ! ok {
80+ return authz .DecisionDeny , fmt .Errorf ("missing ce-clustername in event attributes, %v" , pReq .Event .Attributes )
81+ }
82+
83+ decision , err := s .authorize (ctx , clusterAttr .GetCeString (), * eventsType )
84+ return decision , err
85+ }
86+
87+ func (s * SARAuthorizer ) AuthorizeStream (ctx context.Context , ss grpc.ServerStream , info * grpc.StreamServerInfo ) (authz.Decision , grpc.ServerStream , error ) {
88+ if info .IsClientStream {
89+ return authz .DecisionAllow , ss , nil
90+ }
91+
92+ if info .FullMethod != pbv1 .CloudEventService_Subscribe_FullMethodName {
93+ return authz .DecisionDeny , nil , fmt .Errorf ("unsupported service full method %s for SARAuthorizer" , info .FullMethod )
94+ }
95+
96+ var req pbv1.SubscriptionRequest
97+ if err := ss .RecvMsg (& req ); err != nil {
98+ return authz .DecisionDeny , nil , err
99+ }
100+
101+ eventDataType , err := types .ParseCloudEventsDataType (req .DataType )
102+ if err != nil {
103+ return authz .DecisionDeny , nil , err
104+ }
105+
106+ eventsType := types.CloudEventsType {
107+ CloudEventsDataType : * eventDataType ,
108+ SubResource : types .SubResourceSpec ,
109+ Action : types .WatchRequestAction ,
110+ }
111+
112+ decision , err := s .authorize (ss .Context (), req .ClusterName , eventsType )
113+ if err != nil {
114+ return decision , nil , err
115+ }
116+
117+ return decision , & wrappedAuthorizedStream {ServerStream : ss , authorizedReq : & req }, nil
118+ }
119+
120+ func (s * SARAuthorizer ) authorize (ctx context.Context , cluster string , eventsType types.CloudEventsType ) (authz.Decision , error ) {
34121 user , groups , err := userInfo (ctx )
35122 if err != nil {
36- return err
123+ return authz . DecisionDeny , err
37124 }
38125
39126 sar , err := toSubjectAccessReview (cluster , user , groups , eventsType )
40127 if err != nil {
41- return err
128+ return authz . DecisionDeny , err
42129 }
43130
44131 created , err := s .kubeClient .AuthorizationV1 ().SubjectAccessReviews ().Create (
45132 ctx , sar , metav1.CreateOptions {})
46133 if err != nil {
47- return err
134+ return authz . DecisionDeny , err
48135 }
49136 if ! created .Status .Allowed {
50- return fmt .Errorf ("the event %s is not allowed, (cluster=%s, sar=%v, reason=%v)" ,
137+ return authz . DecisionDeny , fmt .Errorf ("the event %s is not allowed, (cluster=%s, sar=%v, reason=%v)" ,
51138 eventsType , cluster , sar .Spec , created .Status )
52139 }
53- return nil
140+ return authz . DecisionAllow , nil
54141}
55142
56143func userInfo (ctx context.Context ) (user string , groups []string , err error ) {
0 commit comments