github.com/lusis/distribution@v2.0.1+incompatible/registry/auth/token/token_test.go (about) 1 package token 2 3 import ( 4 "crypto" 5 "crypto/rand" 6 "crypto/x509" 7 "encoding/base64" 8 "encoding/json" 9 "encoding/pem" 10 "fmt" 11 "io/ioutil" 12 "net/http" 13 "os" 14 "strings" 15 "testing" 16 "time" 17 18 "github.com/docker/distribution/registry/auth" 19 "github.com/docker/libtrust" 20 "golang.org/x/net/context" 21 ) 22 23 func makeRootKeys(numKeys int) ([]libtrust.PrivateKey, error) { 24 keys := make([]libtrust.PrivateKey, 0, numKeys) 25 26 for i := 0; i < numKeys; i++ { 27 key, err := libtrust.GenerateECP256PrivateKey() 28 if err != nil { 29 return nil, err 30 } 31 keys = append(keys, key) 32 } 33 34 return keys, nil 35 } 36 37 func makeSigningKeyWithChain(rootKey libtrust.PrivateKey, depth int) (libtrust.PrivateKey, error) { 38 if depth == 0 { 39 // Don't need to build a chain. 40 return rootKey, nil 41 } 42 43 var ( 44 x5c = make([]string, depth) 45 parentKey = rootKey 46 key libtrust.PrivateKey 47 cert *x509.Certificate 48 err error 49 ) 50 51 for depth > 0 { 52 if key, err = libtrust.GenerateECP256PrivateKey(); err != nil { 53 return nil, err 54 } 55 56 if cert, err = libtrust.GenerateCACert(parentKey, key); err != nil { 57 return nil, err 58 } 59 60 depth-- 61 x5c[depth] = base64.StdEncoding.EncodeToString(cert.Raw) 62 parentKey = key 63 } 64 65 key.AddExtendedField("x5c", x5c) 66 67 return key, nil 68 } 69 70 func makeRootCerts(rootKeys []libtrust.PrivateKey) ([]*x509.Certificate, error) { 71 certs := make([]*x509.Certificate, 0, len(rootKeys)) 72 73 for _, key := range rootKeys { 74 cert, err := libtrust.GenerateCACert(key, key) 75 if err != nil { 76 return nil, err 77 } 78 certs = append(certs, cert) 79 } 80 81 return certs, nil 82 } 83 84 func makeTrustedKeyMap(rootKeys []libtrust.PrivateKey) map[string]libtrust.PublicKey { 85 trustedKeys := make(map[string]libtrust.PublicKey, len(rootKeys)) 86 87 for _, key := range rootKeys { 88 trustedKeys[key.KeyID()] = key.PublicKey() 89 } 90 91 return trustedKeys 92 } 93 94 func makeTestToken(issuer, audience string, access []*ResourceActions, rootKey libtrust.PrivateKey, depth int) (*Token, error) { 95 signingKey, err := makeSigningKeyWithChain(rootKey, depth) 96 if err != nil { 97 return nil, fmt.Errorf("unable to amke signing key with chain: %s", err) 98 } 99 100 rawJWK, err := signingKey.PublicKey().MarshalJSON() 101 if err != nil { 102 return nil, fmt.Errorf("unable to marshal signing key to JSON: %s", err) 103 } 104 105 joseHeader := &Header{ 106 Type: "JWT", 107 SigningAlg: "ES256", 108 RawJWK: json.RawMessage(rawJWK), 109 } 110 111 now := time.Now() 112 113 randomBytes := make([]byte, 15) 114 if _, err = rand.Read(randomBytes); err != nil { 115 return nil, fmt.Errorf("unable to read random bytes for jwt id: %s", err) 116 } 117 118 claimSet := &ClaimSet{ 119 Issuer: issuer, 120 Subject: "foo", 121 Audience: audience, 122 Expiration: now.Add(5 * time.Minute).Unix(), 123 NotBefore: now.Unix(), 124 IssuedAt: now.Unix(), 125 JWTID: base64.URLEncoding.EncodeToString(randomBytes), 126 Access: access, 127 } 128 129 var joseHeaderBytes, claimSetBytes []byte 130 131 if joseHeaderBytes, err = json.Marshal(joseHeader); err != nil { 132 return nil, fmt.Errorf("unable to marshal jose header: %s", err) 133 } 134 if claimSetBytes, err = json.Marshal(claimSet); err != nil { 135 return nil, fmt.Errorf("unable to marshal claim set: %s", err) 136 } 137 138 encodedJoseHeader := joseBase64UrlEncode(joseHeaderBytes) 139 encodedClaimSet := joseBase64UrlEncode(claimSetBytes) 140 encodingToSign := fmt.Sprintf("%s.%s", encodedJoseHeader, encodedClaimSet) 141 142 var signatureBytes []byte 143 if signatureBytes, _, err = signingKey.Sign(strings.NewReader(encodingToSign), crypto.SHA256); err != nil { 144 return nil, fmt.Errorf("unable to sign jwt payload: %s", err) 145 } 146 147 signature := joseBase64UrlEncode(signatureBytes) 148 tokenString := fmt.Sprintf("%s.%s", encodingToSign, signature) 149 150 return NewToken(tokenString) 151 } 152 153 // This test makes 4 tokens with a varying number of intermediate 154 // certificates ranging from no intermediate chain to a length of 3 155 // intermediates. 156 func TestTokenVerify(t *testing.T) { 157 var ( 158 numTokens = 4 159 issuer = "test-issuer" 160 audience = "test-audience" 161 access = []*ResourceActions{ 162 { 163 Type: "repository", 164 Name: "foo/bar", 165 Actions: []string{"pull", "push"}, 166 }, 167 } 168 ) 169 170 rootKeys, err := makeRootKeys(numTokens) 171 if err != nil { 172 t.Fatal(err) 173 } 174 175 rootCerts, err := makeRootCerts(rootKeys) 176 if err != nil { 177 t.Fatal(err) 178 } 179 180 rootPool := x509.NewCertPool() 181 for _, rootCert := range rootCerts { 182 rootPool.AddCert(rootCert) 183 } 184 185 trustedKeys := makeTrustedKeyMap(rootKeys) 186 187 tokens := make([]*Token, 0, numTokens) 188 189 for i := 0; i < numTokens; i++ { 190 token, err := makeTestToken(issuer, audience, access, rootKeys[i], i) 191 if err != nil { 192 t.Fatal(err) 193 } 194 tokens = append(tokens, token) 195 } 196 197 verifyOps := VerifyOptions{ 198 TrustedIssuers: []string{issuer}, 199 AcceptedAudiences: []string{audience}, 200 Roots: rootPool, 201 TrustedKeys: trustedKeys, 202 } 203 204 for _, token := range tokens { 205 if err := token.Verify(verifyOps); err != nil { 206 t.Fatal(err) 207 } 208 } 209 } 210 211 func writeTempRootCerts(rootKeys []libtrust.PrivateKey) (filename string, err error) { 212 rootCerts, err := makeRootCerts(rootKeys) 213 if err != nil { 214 return "", err 215 } 216 217 tempFile, err := ioutil.TempFile("", "rootCertBundle") 218 if err != nil { 219 return "", err 220 } 221 defer tempFile.Close() 222 223 for _, cert := range rootCerts { 224 if err = pem.Encode(tempFile, &pem.Block{ 225 Type: "CERTIFICATE", 226 Bytes: cert.Raw, 227 }); err != nil { 228 os.Remove(tempFile.Name()) 229 return "", err 230 } 231 } 232 233 return tempFile.Name(), nil 234 } 235 236 // TestAccessController tests complete integration of the token auth package. 237 // It starts by mocking the options for a token auth accessController which 238 // it creates. It then tries a few mock requests: 239 // - don't supply a token; should error with challenge 240 // - supply an invalid token; should error with challenge 241 // - supply a token with insufficient access; should error with challenge 242 // - supply a valid token; should not error 243 func TestAccessController(t *testing.T) { 244 // Make 2 keys; only the first is to be a trusted root key. 245 rootKeys, err := makeRootKeys(2) 246 if err != nil { 247 t.Fatal(err) 248 } 249 250 rootCertBundleFilename, err := writeTempRootCerts(rootKeys[:1]) 251 if err != nil { 252 t.Fatal(err) 253 } 254 defer os.Remove(rootCertBundleFilename) 255 256 realm := "https://auth.example.com/token/" 257 issuer := "test-issuer.example.com" 258 service := "test-service.example.com" 259 260 options := map[string]interface{}{ 261 "realm": realm, 262 "issuer": issuer, 263 "service": service, 264 "rootcertbundle": rootCertBundleFilename, 265 } 266 267 accessController, err := newAccessController(options) 268 if err != nil { 269 t.Fatal(err) 270 } 271 272 // 1. Make a mock http.Request with no token. 273 req, err := http.NewRequest("GET", "http://example.com/foo", nil) 274 if err != nil { 275 t.Fatal(err) 276 } 277 278 testAccess := auth.Access{ 279 Resource: auth.Resource{ 280 Type: "foo", 281 Name: "bar", 282 }, 283 Action: "baz", 284 } 285 286 ctx := context.WithValue(nil, "http.request", req) 287 authCtx, err := accessController.Authorized(ctx, testAccess) 288 challenge, ok := err.(auth.Challenge) 289 if !ok { 290 t.Fatal("accessController did not return a challenge") 291 } 292 293 if challenge.Error() != ErrTokenRequired.Error() { 294 t.Fatalf("accessControler did not get expected error - got %s - expected %s", challenge, ErrTokenRequired) 295 } 296 297 if authCtx != nil { 298 t.Fatalf("expected nil auth context but got %s", authCtx) 299 } 300 301 // 2. Supply an invalid token. 302 token, err := makeTestToken( 303 issuer, service, 304 []*ResourceActions{{ 305 Type: testAccess.Type, 306 Name: testAccess.Name, 307 Actions: []string{testAccess.Action}, 308 }}, 309 rootKeys[1], 1, // Everything is valid except the key which signed it. 310 ) 311 if err != nil { 312 t.Fatal(err) 313 } 314 315 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.compactRaw())) 316 317 authCtx, err = accessController.Authorized(ctx, testAccess) 318 challenge, ok = err.(auth.Challenge) 319 if !ok { 320 t.Fatal("accessController did not return a challenge") 321 } 322 323 if challenge.Error() != ErrInvalidToken.Error() { 324 t.Fatalf("accessControler did not get expected error - got %s - expected %s", challenge, ErrTokenRequired) 325 } 326 327 if authCtx != nil { 328 t.Fatalf("expected nil auth context but got %s", authCtx) 329 } 330 331 // 3. Supply a token with insufficient access. 332 token, err = makeTestToken( 333 issuer, service, 334 []*ResourceActions{}, // No access specified. 335 rootKeys[0], 1, 336 ) 337 if err != nil { 338 t.Fatal(err) 339 } 340 341 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.compactRaw())) 342 343 authCtx, err = accessController.Authorized(ctx, testAccess) 344 challenge, ok = err.(auth.Challenge) 345 if !ok { 346 t.Fatal("accessController did not return a challenge") 347 } 348 349 if challenge.Error() != ErrInsufficientScope.Error() { 350 t.Fatalf("accessControler did not get expected error - got %s - expected %s", challenge, ErrInsufficientScope) 351 } 352 353 if authCtx != nil { 354 t.Fatalf("expected nil auth context but got %s", authCtx) 355 } 356 357 // 4. Supply the token we need, or deserve, or whatever. 358 token, err = makeTestToken( 359 issuer, service, 360 []*ResourceActions{{ 361 Type: testAccess.Type, 362 Name: testAccess.Name, 363 Actions: []string{testAccess.Action}, 364 }}, 365 rootKeys[0], 1, 366 ) 367 if err != nil { 368 t.Fatal(err) 369 } 370 371 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.compactRaw())) 372 373 authCtx, err = accessController.Authorized(ctx, testAccess) 374 if err != nil { 375 t.Fatalf("accessController returned unexpected error: %s", err) 376 } 377 378 userInfo, ok := authCtx.Value("auth.user").(auth.UserInfo) 379 if !ok { 380 t.Fatal("token accessController did not set auth.user context") 381 } 382 383 if userInfo.Name != "foo" { 384 t.Fatalf("expected user name %q, got %q", "foo", userInfo.Name) 385 } 386 }