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  }