1
1
package server
2
2
3
3
import (
4
- "encoding/base64"
5
4
"errors"
6
5
"fmt"
7
6
"net/http"
@@ -34,6 +33,13 @@ func (d deferredErrors) Empty() bool {
34
33
return len (d ) == 0
35
34
}
36
35
36
+ const (
37
+ OpenShiftAuth = "openshift"
38
+
39
+ RealmKey = "realm"
40
+ TokenRealmKey = "token-realm"
41
+ )
42
+
37
43
// DefaultRegistryClient is exposed for testing the registry with fake client.
38
44
var DefaultRegistryClient = NewRegistryClient (clientcmd .NewConfig ().BindToFile ())
39
45
@@ -58,7 +64,7 @@ func (r *RegistryClient) SafeClientConfig() restclient.Config {
58
64
}
59
65
60
66
func init () {
61
- registryauth .Register ("openshift" , registryauth .InitFunc (newAccessController ))
67
+ registryauth .Register (OpenShiftAuth , registryauth .InitFunc (newAccessController ))
62
68
}
63
69
64
70
type contextKey int
@@ -96,8 +102,9 @@ func DeferredErrorsFrom(ctx context.Context) (deferredErrors, bool) {
96
102
}
97
103
98
104
type AccessController struct {
99
- realm string
100
- config restclient.Config
105
+ realm string
106
+ tokenRealm string
107
+ config restclient.Config
101
108
}
102
109
103
110
var _ registryauth.AccessController = & AccessController {}
@@ -109,13 +116,20 @@ type authChallenge struct {
109
116
110
117
var _ registryauth.Challenge = & authChallenge {}
111
118
119
+ type tokenAuthChallenge struct {
120
+ realm string
121
+ service string
122
+ err error
123
+ }
124
+
125
+ var _ registryauth.Challenge = & tokenAuthChallenge {}
126
+
112
127
// Errors used and exported by this package.
113
128
var (
114
129
// Challenging errors
115
- ErrTokenRequired = errors .New ("authorization header with basic token required" )
116
- ErrTokenInvalid = errors .New ("failed to decode basic token" )
117
- ErrOpenShiftTokenRequired = errors .New ("expected bearer token as password for basic token to registry" )
118
- ErrOpenShiftAccessDenied = errors .New ("access denied" )
130
+ ErrTokenRequired = errors .New ("authorization header required" )
131
+ ErrTokenInvalid = errors .New ("failed to decode credentials" )
132
+ ErrOpenShiftAccessDenied = errors .New ("access denied" )
119
133
120
134
// Non-challenging errors
121
135
ErrNamespaceRequired = errors .New ("repository namespace required" )
@@ -125,12 +139,15 @@ var (
125
139
126
140
func newAccessController (options map [string ]interface {}) (registryauth.AccessController , error ) {
127
141
log .Info ("Using Origin Auth handler" )
128
- realm , ok := options ["realm" ].(string )
142
+ realm , ok := options [RealmKey ].(string )
129
143
if ! ok {
130
144
// Default to openshift if not present
131
145
realm = "origin"
132
146
}
133
- return & AccessController {realm : realm , config : DefaultRegistryClient .SafeClientConfig ()}, nil
147
+
148
+ tokenRealm , _ := options [TokenRealmKey ].(string )
149
+
150
+ return & AccessController {realm : realm , tokenRealm : tokenRealm , config : DefaultRegistryClient .SafeClientConfig ()}, nil
134
151
}
135
152
136
153
// Error returns the internal error string for this authChallenge.
@@ -149,10 +166,35 @@ func (ac *authChallenge) SetHeaders(w http.ResponseWriter) {
149
166
w .Header ().Set ("WWW-Authenticate" , str )
150
167
}
151
168
169
+ // Error returns the internal error string for this authChallenge.
170
+ func (ac * tokenAuthChallenge ) Error () string {
171
+ return ac .err .Error ()
172
+ }
173
+
174
+ // SetHeaders sets the bearer challenge header on the response.
175
+ func (ac * tokenAuthChallenge ) SetHeaders (w http.ResponseWriter ) {
176
+ // WWW-Authenticate response challenge header.
177
+ // See https://docs.docker.com/registry/spec/auth/token/#/how-to-authenticate and https://tools.ietf.org/html/rfc6750#section-3
178
+ str := fmt .Sprintf ("Bearer realm=%q" , ac .realm )
179
+ if ac .service != "" {
180
+ str += fmt .Sprintf (",service=%q" , ac .service )
181
+ }
182
+ w .Header ().Set ("WWW-Authenticate" , str )
183
+ }
184
+
152
185
// wrapErr wraps errors related to authorization in an authChallenge error that will present a WWW-Authenticate challenge response
153
186
func (ac * AccessController ) wrapErr (err error ) error {
154
187
switch err {
155
- case ErrTokenRequired , ErrTokenInvalid , ErrOpenShiftTokenRequired , ErrOpenShiftAccessDenied :
188
+ case ErrTokenRequired :
189
+ // Challenge for errors that involve missing tokens
190
+ if len (ac .tokenRealm ) > 0 {
191
+ // Direct to token auth if we've been given a place to direct to
192
+ return & tokenAuthChallenge {realm : ac .tokenRealm , err : err }
193
+ } else {
194
+ // Otherwise just send the basic challenge
195
+ return & authChallenge {realm : ac .realm , err : err }
196
+ }
197
+ case ErrTokenInvalid , ErrOpenShiftAccessDenied :
156
198
// Challenge for errors that involve tokens or access denied
157
199
return & authChallenge {realm : ac .realm , err : err }
158
200
case ErrNamespaceRequired , ErrUnsupportedAction , ErrUnsupportedResource :
@@ -175,7 +217,7 @@ func (ac *AccessController) Authorized(ctx context.Context, accessRecords ...reg
175
217
return nil , ac .wrapErr (err )
176
218
}
177
219
178
- bearerToken , err := getToken (ctx , req )
220
+ bearerToken , err := getOpenShiftAPIToken (ctx , req )
179
221
if err != nil {
180
222
return nil , ac .wrapErr (err )
181
223
}
@@ -301,26 +343,35 @@ func getNamespaceName(resourceName string) (string, string, error) {
301
343
return ns , name , nil
302
344
}
303
345
304
- func getToken (ctx context.Context , req * http.Request ) (string , error ) {
346
+ func getOpenShiftAPIToken (ctx context.Context , req * http.Request ) (string , error ) {
347
+ token := ""
348
+
305
349
authParts := strings .SplitN (req .Header .Get ("Authorization" ), " " , 2 )
306
- if len (authParts ) != 2 || strings . ToLower ( authParts [ 0 ]) != "basic" {
350
+ if len (authParts ) != 2 {
307
351
return "" , ErrTokenRequired
308
352
}
309
- basicToken := authParts [1 ]
310
353
311
- payload , err := base64 .StdEncoding .DecodeString (basicToken )
312
- if err != nil {
313
- context .GetLogger (ctx ).Errorf ("Basic token decode failed: %s" , err )
314
- return "" , ErrTokenInvalid
315
- }
354
+ switch strings .ToLower (authParts [0 ]) {
355
+ case "bearer" :
356
+ // This is either a direct API token, or a token issued by our docker token handler
357
+ token = authParts [1 ]
358
+ // Recognize the token issued to anonymous users by our docker token handler
359
+ if token == anonymousToken {
360
+ token = ""
361
+ }
362
+
363
+ case "basic" :
364
+ _ , password , ok := req .BasicAuth ()
365
+ if ! ok || len (password ) == 0 {
366
+ return "" , ErrTokenInvalid
367
+ }
368
+ token = password
316
369
317
- osAuthParts := strings .SplitN (string (payload ), ":" , 2 )
318
- if len (osAuthParts ) != 2 {
319
- return "" , ErrOpenShiftTokenRequired
370
+ default :
371
+ return "" , ErrTokenRequired
320
372
}
321
373
322
- bearerToken := osAuthParts [1 ]
323
- return bearerToken , nil
374
+ return token , nil
324
375
}
325
376
326
377
func verifyOpenShiftUser (ctx context.Context , client client.UsersInterface ) error {
0 commit comments