@@ -15,6 +15,7 @@ import (
1515 "github.com/prometheus/client_golang/prometheus"
1616 authorizationv1 "k8s.io/api/authorization/v1"
1717 "k8s.io/apimachinery/pkg/runtime/schema"
18+
1819 "k8s.io/apimachinery/pkg/types"
1920 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
2021 mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager"
@@ -28,6 +29,8 @@ import (
2829type AuthorizationHandler struct {
2930 fga openfgav1.OpenFGAServiceClient
3031 accountInfoName string
32+ orgStoreID string
33+ orgWorkspaceID string
3134 mgr mcmanager.Manager
3235 mps * mapperprovider.MapperProviders
3336}
@@ -40,12 +43,15 @@ var (
4043 })
4144)
4245
43- func NewAuthorizationHandler ( fga openfgav1. OpenFGAServiceClient , mgr mcmanager. Manager , accountInfoName string , mps * mapperprovider. MapperProviders ) ( * AuthorizationHandler , error ) {
46+ const rootOrgName = "tenancy_kcp_io_workspace:orgs"
4447
48+ func NewAuthorizationHandler (fga openfgav1.OpenFGAServiceClient , mgr mcmanager.Manager , accountInfoName string , orgStoreID , orgWorkspaceID string , mps * mapperprovider.MapperProviders ) (* AuthorizationHandler , error ) {
4549 return & AuthorizationHandler {
4650 fga : fga ,
4751 accountInfoName : accountInfoName ,
4852 mgr : mgr ,
53+ orgStoreID : orgStoreID ,
54+ orgWorkspaceID : orgWorkspaceID ,
4955 mps : mps ,
5056 }, nil
5157}
@@ -55,10 +61,12 @@ var ErrNoStoreID = errors.New("no store ID found")
5561func (a * AuthorizationHandler ) getAccountInfo (ctx context.Context , sar authorizationv1.SubjectAccessReview ) (* corev1alpha1.AccountInfo , error ) {
5662 log := logger .LoadLoggerFromContext (ctx )
5763 info := & corev1alpha1.AccountInfo {}
64+
5865 clusterNameAttr , ok := sar .Spec .Extra ["authorization.kubernetes.io/cluster-name" ]
5966 if ! ok || len (clusterNameAttr ) == 0 {
6067 return nil , errors .New ("no cluster name found in the request" )
6168 }
69+
6270 log .Debug ().Str ("cluster" , clusterNameAttr [0 ]).Str ("accountInfoName" , a .accountInfoName ).Msg ("Looking for AccountInfo" )
6371
6472 cluster , err := a .mgr .GetCluster (ctx , clusterNameAttr [0 ])
@@ -76,6 +84,7 @@ func (a *AuthorizationHandler) getAccountInfo(ctx context.Context, sar authoriza
7684 log .Error ().Msg ("AccountInfo found but Store.Id is empty" )
7785 return nil , ErrNoStoreID
7886 }
87+
7988 log .Debug ().Str ("storeId" , info .Spec .FGA .Store .Id ).Msg ("Retrieved Store ID" )
8089
8190 return info , nil
@@ -91,6 +100,7 @@ func (a *AuthorizationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
91100 http .Error (w , err .Error (), http .StatusBadRequest )
92101 return
93102 }
103+
94104 err = r .Body .Close ()
95105 if err != nil {
96106 log .Error ().Err (err ).Msg ("unable to close the request body" )
@@ -128,28 +138,14 @@ func (a *AuthorizationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
128138
129139 log .Debug ().Str ("sar" , fmt .Sprintf ("%+v" , sar )).Msg ("Received SubjectAccessReview" )
130140
131- // For resource attributes, we need to get the store ID
132- accountInfo , err := a .getAccountInfo (r .Context (), sar )
133- if err != nil {
134- log .Error ().Err (err ).Str ("user" , sar .Spec .User ).Msg ("error getting store ID from account info, responding with no opinion" )
135- noOpinion (w , sar )
136- return
137- }
138-
139- group := util .CapGroupToRelationLength (sar , 50 )
140- group = strings .ReplaceAll (group , "." , "_" )
141- relation := sar .Spec .ResourceAttributes .Verb
142-
143141 var clusterName string
144142 if sar .Spec .Extra != nil {
145143 if clusterNames , exists := sar .Spec .Extra ["authorization.kubernetes.io/cluster-name" ]; exists && len (clusterNames ) > 0 {
146144 clusterName = clusterNames [0 ]
147145 }
148146 }
149- log = log .ChildLogger ("clusterName" , clusterName )
150147
151- var namespaced bool
152- var gvk schema.GroupVersionKind
148+ log = log .ChildLogger ("clusterName" , clusterName )
153149
154150 restMapper , ok := a .mps .GetMapper (logicalcluster .Name (clusterName ))
155151 if ! ok {
@@ -158,6 +154,46 @@ func (a *AuthorizationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
158154 return
159155 }
160156
157+ group := util .CapGroupToRelationLength (sar , 50 )
158+ group = strings .ReplaceAll (group , "." , "_" )
159+ relation := sar .Spec .ResourceAttributes .Verb
160+
161+ user := fmt .Sprintf ("user:%s" , sar .Spec .User )
162+
163+ // request to orgs workspace
164+ if a .orgWorkspaceID == clusterName {
165+ relation = fmt .Sprintf ("%s_%s_%s" , sar .Spec .ResourceAttributes .Verb , group , sar .Spec .ResourceAttributes .Resource )
166+ object := rootOrgName
167+
168+ allowed , err := a .checkPermissions (r .Context (), object , relation , user , a .orgStoreID , nil )
169+ if err != nil {
170+ log .Error ().Err (err ).Str ("storeId" , a .orgStoreID ).Str ("object" , object ).Str ("relation" , relation ).Str ("user" , user ).Msg ("unable to call upstream openfga" )
171+ noOpinion (w , sar )
172+ return
173+ }
174+
175+ log .Info ().Str ("allowed" , fmt .Sprintf ("%t" , allowed )).Str ("user" , user ).Str ("object" , object ).Str ("relation" , relation ).Msg ("sar response" )
176+
177+ if ! allowed {
178+ noOpinion (w , sar )
179+ return
180+ }
181+
182+ writeResponse (w , sar , allowed )
183+ return
184+ }
185+
186+ // For resource attributes, we need to get the store ID
187+ accountInfo , err := a .getAccountInfo (r .Context (), sar )
188+ if err != nil {
189+ log .Error ().Err (err ).Str ("user" , sar .Spec .User ).Msg ("error getting store ID from account info" )
190+ noOpinion (w , sar )
191+ return
192+ }
193+
194+ var namespaced bool
195+ var gvk schema.GroupVersionKind
196+
161197 gvr := schema.GroupVersionResource {
162198 Group : sar .Spec .ResourceAttributes .Group ,
163199 Resource : sar .Spec .ResourceAttributes .Resource ,
@@ -240,46 +276,64 @@ func (a *AuthorizationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
240276 return
241277 }
242278
279+ allowed , err := a .checkPermissions (r .Context (), object , relation , user , accountInfo .Spec .FGA .Store .Id , contextualTuples )
280+ if err != nil {
281+ log .Error ().Err (err ).Str ("storeId" , a .orgStoreID ).Str ("object" , object ).Str ("relation" , relation ).Str ("user" , user ).Msg ("unable to call upstream openfga" )
282+ noOpinion (w , sar )
283+ return
284+ }
285+ log .Info ().Str ("allowed" , fmt .Sprintf ("%t" , allowed )).Str ("user" , user ).Str ("object" , object ).Str ("relation" , relation ).Msg ("sar response" )
286+
287+ if ! allowed {
288+ noOpinion (w , sar )
289+ return
290+ }
291+
292+ writeResponse (w , sar , allowed )
293+ }
294+
295+ func (a * AuthorizationHandler ) checkPermissions (ctx context.Context , object , relation , user , storeID string , contextualTuples []* openfgav1.TupleKey ) (bool , error ) {
243296 preReq := time .Now ()
244- res , err := a .fga .Check (r .Context (), & openfgav1.CheckRequest {
245- StoreId : accountInfo .Spec .FGA .Store .Id ,
297+
298+ checkReq := & openfgav1.CheckRequest {
299+ StoreId : storeID ,
246300 TupleKey : & openfgav1.CheckRequestTupleKey {
247301 Object : object ,
248302 Relation : relation ,
249- User : fmt . Sprintf ( " user:%s" , sar . Spec . User ) ,
303+ User : user ,
250304 },
251- ContextualTuples : & openfgav1.ContextualTupleKeys {
305+ }
306+
307+ if contextualTuples != nil {
308+ checkReq .ContextualTuples = & openfgav1.ContextualTupleKeys {
252309 TupleKeys : contextualTuples ,
253- },
254- })
310+ }
311+ }
255312
313+ res , err := a .fga .Check (ctx , checkReq )
256314 openfgaLatency .Observe (time .Since (preReq ).Seconds ())
257315 if err != nil {
258- log .Error ().Err (err ).Str ("storeId" , accountInfo .Spec .FGA .Store .Id ).Str ("object" , object ).Str ("relation" , relation ).Str ("user" , sar .Spec .User ).Msg ("unable to call upstream openfga" )
259- noOpinion (w , sar )
260- return
261- }
262- log .Info ().Str ("allowed" , fmt .Sprintf ("%t" , res .Allowed )).Str ("user" , sar .Spec .User ).Str ("object" , object ).Str ("relation" , relation ).Msg ("sar response" )
263- if ! res .Allowed {
264- noOpinion (w , sar )
265- return
316+ return false , err
266317 }
318+ return res .Allowed , nil
319+ }
267320
321+ func noOpinion (w http.ResponseWriter , sar authorizationv1.SubjectAccessReview ) {
268322 sar .Status = authorizationv1.SubjectAccessReviewStatus {
269- Allowed : res . Allowed ,
270- Denied : ! res . Allowed ,
323+ Allowed : false ,
324+ Reason : "NoOpinion" ,
271325 }
272-
273326 if err := json .NewEncoder (w ).Encode (& sar ); err != nil {
274327 http .Error (w , err .Error (), http .StatusInternalServerError )
275328 }
276329}
277330
278- func noOpinion (w http.ResponseWriter , sar authorizationv1.SubjectAccessReview ) {
331+ func writeResponse (w http.ResponseWriter , sar authorizationv1.SubjectAccessReview , allowed bool ) {
279332 sar .Status = authorizationv1.SubjectAccessReviewStatus {
280- Allowed : false ,
281- Reason : "NoOpinion" ,
333+ Allowed : allowed ,
334+ Denied : ! allowed ,
282335 }
336+
283337 if err := json .NewEncoder (w ).Encode (& sar ); err != nil {
284338 http .Error (w , err .Error (), http .StatusInternalServerError )
285339 }
0 commit comments