@@ -21,6 +21,24 @@ import (
21
21
imageapi "github.com/openshift/origin/pkg/image/api"
22
22
)
23
23
24
+ // crossRepoMountAuthError
25
+ type crossRepoMountAuthError struct {
26
+ targetRepoName string
27
+ sourceRepoName string
28
+ err error
29
+ }
30
+
31
+ func (e crossRepoMountAuthError ) Error () string {
32
+ if e .targetRepoName != "" {
33
+ return fmt .Sprintf ("unauthorized to read from source repository %q during cross-repo mount to %q: %v" ,
34
+ e .sourceRepoName , e .targetRepoName , e .err )
35
+ }
36
+ return fmt .Sprintf ("unauthorized to read from source repository %q during cross-repo mount: %v" ,
37
+ e .sourceRepoName , e .targetRepoName , e .err )
38
+ }
39
+
40
+ type deferredErrors map [string ]error
41
+
24
42
// DefaultRegistryClient is exposed for testing the registry with fake client.
25
43
var DefaultRegistryClient = NewRegistryClient (clientcmd .NewConfig ().BindToFile ())
26
44
@@ -61,6 +79,27 @@ func UserClientFrom(ctx context.Context) (client.Interface, bool) {
61
79
return userClient , ok
62
80
}
63
81
82
+ const authPerformedKey = "openshift.auth.performed"
83
+
84
+ func WithAuthPerformed (parent context.Context ) context.Context {
85
+ return context .WithValue (parent , authPerformedKey , true )
86
+ }
87
+
88
+ func AuthPerformed (ctx context.Context ) bool {
89
+ authPerformed , ok := ctx .Value (authPerformedKey ).(bool )
90
+ return ok && authPerformed
91
+ }
92
+
93
+ const deferredErrorsKey = "openshift.auth.deferredErrors"
94
+
95
+ func WithDeferredErrors (parent context.Context , errs deferredErrors ) context.Context {
96
+ return context .WithValue (parent , deferredErrorsKey , errs )
97
+ }
98
+ func DeferredErrorsFrom (ctx context.Context ) (deferredErrors , bool ) {
99
+ errs , ok := ctx .Value (deferredErrorsKey ).(deferredErrors )
100
+ return errs , ok
101
+ }
102
+
64
103
type AccessController struct {
65
104
realm string
66
105
config restclient.Config
@@ -160,6 +199,11 @@ func (ac *AccessController) Authorized(ctx context.Context, accessRecords ...reg
160
199
}
161
200
}
162
201
202
+ // pushChecks remembers which ns/name pairs had push access checks done
203
+ pushChecks := map [string ]struct {}{}
204
+ // possibleCrossMountErrors holds errors which may be related to cross mount errors
205
+ possibleCrossMountErrors := deferredErrors {}
206
+
163
207
verifiedPrune := false
164
208
165
209
// Validate all requested accessRecords
@@ -178,6 +222,7 @@ func (ac *AccessController) Authorized(ctx context.Context, accessRecords ...reg
178
222
switch access .Action {
179
223
case "push" :
180
224
verb = "update"
225
+ pushChecks [access .Resource .Name ] = struct {}{}
181
226
case "pull" :
182
227
verb = "get"
183
228
case "*" :
@@ -197,7 +242,14 @@ func (ac *AccessController) Authorized(ctx context.Context, accessRecords ...reg
197
242
verifiedPrune = true
198
243
default :
199
244
if err := verifyImageStreamAccess (ctx , imageStreamNS , imageStreamName , verb , osClient ); err != nil {
200
- return nil , ac .wrapErr (err )
245
+ if verb == "get" {
246
+ possibleCrossMountErrors [access .Resource .Name ] = & crossRepoMountAuthError {
247
+ sourceRepoName : access .Resource .Name ,
248
+ err : err ,
249
+ }
250
+ } else {
251
+ return nil , ac .wrapErr (err )
252
+ }
201
253
}
202
254
}
203
255
@@ -219,6 +271,35 @@ func (ac *AccessController) Authorized(ctx context.Context, accessRecords ...reg
219
271
}
220
272
}
221
273
274
+ // deal with any possible cross-mount errors
275
+ for namespaceAndName , err := range possibleCrossMountErrors {
276
+ // If we have no push requests, this can't be a cross-mount request, so error
277
+ if len (pushChecks ) == 0 {
278
+ return nil , err
279
+ }
280
+ // If we also requested a push to this ns/name, this isn't a cross-mount request, so error
281
+ if _ , exists := pushChecks [namespaceAndName ]; exists {
282
+ return nil , err
283
+ }
284
+ if len (pushChecks ) > 1 {
285
+ context .GetLogger (ctx ).Warn ("cannot determine cross-repo mount target from multiple push checks" )
286
+ continue
287
+ }
288
+ for target := range pushChecks {
289
+ crmErr := err .(* crossRepoMountAuthError )
290
+ crmErr .targetRepoName = target
291
+ }
292
+ }
293
+
294
+ // Conditionally add auth errors we want to handle later to the context
295
+ if len (possibleCrossMountErrors ) != 0 {
296
+ context .GetLogger (ctx ).Debugf ("Origin auth: deferring errors: %#v" , possibleCrossMountErrors )
297
+ ctx = WithDeferredErrors (ctx , possibleCrossMountErrors )
298
+ }
299
+
300
+ // Always add a marker to the context so we know auth was run
301
+ ctx = WithAuthPerformed (ctx )
302
+
222
303
return WithUserClient (ctx , osClient ), nil
223
304
}
224
305
@@ -272,6 +353,9 @@ func verifyOpenShiftUser(ctx context.Context, client client.UsersInterface) erro
272
353
return nil
273
354
}
274
355
356
+ // verifyImageStreamAccess returns nil if the given client is granted access to an image stream identified by
357
+ // <namespace>/<imageRepo>. Otherwise the access is denied. The user embedded in given client must be able to
358
+ // <verb> (get/update) imagestreams/layers resource in the <namespace> to have the access granted.
275
359
func verifyImageStreamAccess (ctx context.Context , namespace , imageRepo , verb string , client client.LocalSubjectAccessReviewsNamespacer ) error {
276
360
sar := authorizationapi.LocalSubjectAccessReview {
277
361
Action : authorizationapi.AuthorizationAttributes {
@@ -299,6 +383,9 @@ func verifyImageStreamAccess(ctx context.Context, namespace, imageRepo, verb str
299
383
return nil
300
384
}
301
385
386
+ // verifyPruneAccess returns nil if the given client is granted access to images resource. Otherwise the
387
+ // access is denied. The user embedded in given client must be able to delete images resource in a cluster to
388
+ // have the access granted.
302
389
func verifyPruneAccess (ctx context.Context , client client.SubjectAccessReviews ) error {
303
390
sar := authorizationapi.SubjectAccessReview {
304
391
Action : authorizationapi.AuthorizationAttributes {
0 commit comments