Skip to content

Commit 1e79396

Browse files
committed
Update.
Signed-off-by: xuezhaojun <zxue@redhat.com>
1 parent 6f76801 commit 1e79396

4 files changed

Lines changed: 91 additions & 35 deletions

File tree

pkg/cloudevents/server/grpc/authz/kube/sar.go

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -62,47 +62,45 @@ func NewSARAuthorizer(kubeClient kubernetes.Interface) *SARAuthorizer {
6262
}
6363
}
6464

65-
func (s *SARAuthorizer) AuthorizeRequest(ctx context.Context, req any) error {
65+
func (s *SARAuthorizer) AuthorizeRequest(ctx context.Context, req any) (authz.Decision, string, error) {
6666
pReq, ok := req.(*pbv1.PublishRequest)
6767
if !ok {
68-
return fmt.Errorf("unsupported request type %T", req)
68+
return authz.DecisionDeny, "unsupported request type", fmt.Errorf("unsupported request type %T", req)
6969
}
7070

7171
eventsType, err := types.ParseCloudEventsType(pReq.Event.Type)
7272
if err != nil {
73-
return err
73+
return authz.DecisionDeny, "invalid event type", err
7474
}
7575

7676
// the event of grpc publish request is the original cloudevent data, we need a `ce-` prefix
7777
// to get the event attribute
7878
clusterAttr, ok := pReq.Event.Attributes[fmt.Sprintf("ce-%s", types.ExtensionClusterName)]
7979
if !ok {
80-
return fmt.Errorf("missing ce-clustername in event attributes, %v", pReq.Event.Attributes)
80+
return authz.DecisionDeny, "missing cluster name", fmt.Errorf("missing ce-clustername in event attributes, %v", pReq.Event.Attributes)
8181
}
8282

83-
if err := s.authorize(ctx, clusterAttr.GetCeString(), *eventsType); err != nil {
84-
return err
85-
}
86-
return nil
83+
decision, reason, err := s.authorize(ctx, clusterAttr.GetCeString(), *eventsType)
84+
return decision, reason, err
8785
}
8886

89-
func (s *SARAuthorizer) AuthorizeStream(ctx context.Context, ss grpc.ServerStream, info *grpc.StreamServerInfo) (grpc.ServerStream, error) {
87+
func (s *SARAuthorizer) AuthorizeStream(ctx context.Context, ss grpc.ServerStream, info *grpc.StreamServerInfo) (authz.Decision, string, grpc.ServerStream, error) {
9088
if info.IsClientStream {
91-
return ss, nil
89+
return authz.DecisionAllow, "client stream allowed", ss, nil
9290
}
9391

9492
if info.FullMethod != pbv1.CloudEventService_Subscribe_FullMethodName {
95-
return nil, fmt.Errorf("unsupported service full method %s for SARAuthorizer", info.FullMethod)
93+
return authz.DecisionDeny, "unsupported method", nil, fmt.Errorf("unsupported service full method %s for SARAuthorizer", info.FullMethod)
9694
}
9795

9896
var req pbv1.SubscriptionRequest
9997
if err := ss.RecvMsg(&req); err != nil {
100-
return nil, err
98+
return authz.DecisionDeny, "failed to receive message", nil, err
10199
}
102100

103101
eventDataType, err := types.ParseCloudEventsDataType(req.DataType)
104102
if err != nil {
105-
return nil, err
103+
return authz.DecisionDeny, "invalid data type", nil, err
106104
}
107105

108106
eventsType := types.CloudEventsType{
@@ -111,34 +109,36 @@ func (s *SARAuthorizer) AuthorizeStream(ctx context.Context, ss grpc.ServerStrea
111109
Action: types.WatchRequestAction,
112110
}
113111

114-
if err := s.authorize(ss.Context(), req.ClusterName, eventsType); err != nil {
115-
return nil, err
112+
decision, reason, err := s.authorize(ss.Context(), req.ClusterName, eventsType)
113+
if err != nil {
114+
return decision, reason, nil, err
116115
}
117116

118-
return &wrappedAuthorizedStream{ServerStream: ss, authorizedReq: &req}, nil
117+
return decision, reason, &wrappedAuthorizedStream{ServerStream: ss, authorizedReq: &req}, nil
119118
}
120119

121-
func (s *SARAuthorizer) authorize(ctx context.Context, cluster string, eventsType types.CloudEventsType) error {
120+
func (s *SARAuthorizer) authorize(ctx context.Context, cluster string, eventsType types.CloudEventsType) (authz.Decision, string, error) {
122121
user, groups, err := userInfo(ctx)
123122
if err != nil {
124-
return err
123+
return authz.DecisionDeny, "failed to extract user info", err
125124
}
126125

127126
sar, err := toSubjectAccessReview(cluster, user, groups, eventsType)
128127
if err != nil {
129-
return err
128+
return authz.DecisionDeny, "failed to create SAR", err
130129
}
131130

132131
created, err := s.kubeClient.AuthorizationV1().SubjectAccessReviews().Create(
133132
ctx, sar, metav1.CreateOptions{})
134133
if err != nil {
135-
return err
134+
return authz.DecisionDeny, "SAR request failed", err
136135
}
137136
if !created.Status.Allowed {
138-
return fmt.Errorf("the event %s is not allowed, (cluster=%s, sar=%v, reason=%v)",
137+
reason := fmt.Sprintf("access denied for event %s (cluster=%s, reason=%s)", eventsType, cluster, created.Status.Reason)
138+
return authz.DecisionDeny, reason, fmt.Errorf("the event %s is not allowed, (cluster=%s, sar=%v, reason=%v)",
139139
eventsType, cluster, sar.Spec, created.Status)
140140
}
141-
return nil
141+
return authz.DecisionAllow, "access granted by SAR", nil
142142
}
143143

144144
func userInfo(ctx context.Context) (user string, groups []string, err error) {

pkg/cloudevents/server/grpc/authz/kube/sar_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"open-cluster-management.io/sdk-go/pkg/cloudevents/clients/work/payload"
1919
"open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types"
2020
"open-cluster-management.io/sdk-go/pkg/server/grpc/authn"
21+
"open-cluster-management.io/sdk-go/pkg/server/grpc/authz"
2122
)
2223

2324
func TestSARAuthorize(t *testing.T) {
@@ -197,13 +198,19 @@ func TestSARAuthorize(t *testing.T) {
197198

198199
auth := NewSARAuthorizer(client)
199200

200-
err := auth.authorize(tc.userCtx(), tc.cluster, tc.eventsType)
201+
decision, reason, err := auth.authorize(tc.userCtx(), tc.cluster, tc.eventsType)
201202
if tc.expectErr && err == nil {
202203
t.Errorf("expected error, got nil")
203204
}
204205
if !tc.expectErr && err != nil {
205206
t.Errorf("unexpected error: %v", err)
206207
}
208+
if !tc.expectErr && decision != authz.DecisionAllow {
209+
t.Errorf("expected DecisionAllow, got %v, reason: %s", decision, reason)
210+
}
211+
if tc.expectErr && decision != authz.DecisionDeny {
212+
t.Errorf("expected DecisionDeny, got %v, reason: %s", decision, reason)
213+
}
207214
})
208215
}
209216
}

pkg/server/grpc/authz/interface.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,38 @@ import (
66
"google.golang.org/grpc"
77
)
88

9+
// Decision represents the result of an authorization request.
10+
type Decision int
11+
12+
const (
13+
// DecisionDeny means the request is explicitly denied.
14+
DecisionDeny Decision = iota
15+
// DecisionAllow means the request is explicitly allowed.
16+
DecisionAllow
17+
// DecisionNoOpinion means the authorizer has no opinion on the request.
18+
// This allows the authorization chain to continue to the next authorizer.
19+
DecisionNoOpinion
20+
)
21+
922
// UnaryAuthorizer defines the interface for authorizing unary gRPC requests.
1023
// Implementations should validate whether the authenticated user has permission
1124
// to perform the requested operation based on the context and request.
1225
type UnaryAuthorizer interface {
1326
// AuthorizeRequest validates whether the user in the context is authorized
14-
// to perform the operation represented by the request. Returns an error
15-
// if authorization fails, or nil if the request is authorized.
16-
AuthorizeRequest(ctx context.Context, req any) error
27+
// to perform the operation represented by the request. Returns a Decision
28+
// indicating the authorization result, a reason string explaining the decision,
29+
// and an error if the authorization process itself fails.
30+
AuthorizeRequest(ctx context.Context, req any) (Decision, string, error)
1731
}
1832

1933
// StreamAuthorizer defines the interface for authorizing streaming gRPC requests.
2034
// Implementations should validate whether the authenticated user has permission
2135
// to establish and maintain the streaming connection.
2236
type StreamAuthorizer interface {
2337
// AuthorizeStream validates whether the user in the context is authorized
24-
// to establish the streaming connection. Returns a potentially wrapped ServerStream
25-
// and an error if authorization fails, or the original/wrapped stream and nil if authorized.
38+
// to establish the streaming connection. Returns a Decision indicating the
39+
// authorization result, a reason string explaining the decision, a potentially
40+
// wrapped ServerStream, and an error if the authorization process itself fails.
2641
// The returned ServerStream can be used to intercept and authorize individual messages.
27-
AuthorizeStream(ctx context.Context, ss grpc.ServerStream, info *grpc.StreamServerInfo) (grpc.ServerStream, error)
42+
AuthorizeStream(ctx context.Context, ss grpc.ServerStream, info *grpc.StreamServerInfo) (Decision, string, grpc.ServerStream, error)
2843
}

pkg/server/grpc/server.go

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,28 @@ func newAuthzUnaryInterceptor(authorizers ...authz.UnaryAuthorizer) grpc.UnarySe
173173
handler grpc.UnaryHandler,
174174
) (interface{}, error) {
175175
var errs []error
176+
var reasons []string
176177
for _, authorizer := range authorizers {
177-
if err := authorizer.AuthorizeRequest(ctx, req); err == nil {
178+
decision, reason, err := authorizer.AuthorizeRequest(ctx, req)
179+
switch decision {
180+
case authz.DecisionAllow:
178181
return handler(ctx, req)
179-
} else {
180-
errs = append(errs, err)
182+
case authz.DecisionDeny:
183+
if err != nil {
184+
errs = append(errs, err)
185+
}
186+
if reason != "" {
187+
reasons = append(reasons, reason)
188+
}
189+
return nil, fmt.Errorf("access denied: %s", reason)
190+
case authz.DecisionNoOpinion:
191+
if err != nil {
192+
errs = append(errs, err)
193+
}
194+
if reason != "" {
195+
reasons = append(reasons, reason)
196+
}
197+
// Continue to next authorizer
181198
}
182199
}
183200

@@ -245,12 +262,29 @@ func newAuthzStreamInterceptor(authorizers []authz.StreamAuthorizer) grpc.Stream
245262
handler grpc.StreamHandler,
246263
) error {
247264
var errs []error
265+
var reasons []string
248266

249267
for _, authorizer := range authorizers {
250-
if authorizedStream, err := authorizer.AuthorizeStream(ss.Context(), ss, info); err == nil {
268+
decision, reason, authorizedStream, err := authorizer.AuthorizeStream(ss.Context(), ss, info)
269+
switch decision {
270+
case authz.DecisionAllow:
251271
return handler(srv, authorizedStream)
252-
} else {
253-
errs = append(errs, err)
272+
case authz.DecisionDeny:
273+
if err != nil {
274+
errs = append(errs, err)
275+
}
276+
if reason != "" {
277+
reasons = append(reasons, reason)
278+
}
279+
return fmt.Errorf("access denied: %s", reason)
280+
case authz.DecisionNoOpinion:
281+
if err != nil {
282+
errs = append(errs, err)
283+
}
284+
if reason != "" {
285+
reasons = append(reasons, reason)
286+
}
287+
// Continue to next authorizer
254288
}
255289
}
256290

0 commit comments

Comments
 (0)