// Copyright 2017 The etcd Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package auth import ( "context" "fmt" "testing" "go.uber.org/zap" ) const ( jwtRSAPubKey = "../integration/fixtures/server.crt" jwtRSAPrivKey = "../integration/fixtures/server.key.insecure" jwtECPubKey = "../integration/fixtures/server-ecdsa.crt" jwtECPrivKey = "../integration/fixtures/server-ecdsa.key.insecure" ) func TestJWTInfo(t *testing.T) { optsMap := map[string]map[string]string{ "RSA-priv": { "priv-key": jwtRSAPrivKey, "sign-method": "RS256", "ttl": "1h", }, "RSA": { "pub-key": jwtRSAPubKey, "priv-key": jwtRSAPrivKey, "sign-method": "RS256", }, "RSAPSS-priv": { "priv-key": jwtRSAPrivKey, "sign-method": "PS256", }, "RSAPSS": { "pub-key": jwtRSAPubKey, "priv-key": jwtRSAPrivKey, "sign-method": "PS256", }, "ECDSA-priv": { "priv-key": jwtECPrivKey, "sign-method": "ES256", }, "ECDSA": { "pub-key": jwtECPubKey, "priv-key": jwtECPrivKey, "sign-method": "ES256", }, "HMAC": { "priv-key": jwtECPrivKey, // any file, raw bytes used as shared secret "sign-method": "HS256", }, } for k, opts := range optsMap { t.Run(k, func(tt *testing.T) { testJWTInfo(tt, opts) }) } } func testJWTInfo(t *testing.T, opts map[string]string) { lg := zap.NewNop() jwt, err := newTokenProviderJWT(lg, opts) if err != nil { t.Fatal(err) } ctx := context.TODO() token, aerr := jwt.assign(ctx, "abc", 123) if aerr != nil { t.Fatalf("%#v", aerr) } ai, ok := jwt.info(ctx, token, 123) if !ok { t.Fatalf("failed to authenticate with token %s", token) } if ai.Revision != 123 { t.Fatalf("expected revision 123, got %d", ai.Revision) } ai, ok = jwt.info(ctx, "aaa", 120) if ok || ai != nil { t.Fatalf("expected aaa to fail to authenticate, got %+v", ai) } // test verify-only provider if opts["pub-key"] != "" && opts["priv-key"] != "" { t.Run("verify-only", func(t *testing.T) { newOpts := make(map[string]string, len(opts)) for k, v := range opts { newOpts[k] = v } delete(newOpts, "priv-key") verify, err := newTokenProviderJWT(lg, newOpts) if err != nil { t.Fatal(err) } ai, ok := verify.info(ctx, token, 123) if !ok { t.Fatalf("failed to authenticate with token %s", token) } if ai.Revision != 123 { t.Fatalf("expected revision 123, got %d", ai.Revision) } ai, ok = verify.info(ctx, "aaa", 120) if ok || ai != nil { t.Fatalf("expected aaa to fail to authenticate, got %+v", ai) } _, aerr := verify.assign(ctx, "abc", 123) if aerr != ErrVerifyOnly { t.Fatalf("unexpected error when attempting to sign with public key: %v", aerr) } }) } } func TestJWTBad(t *testing.T) { var badCases = map[string]map[string]string{ "no options": {}, "invalid method": { "sign-method": "invalid", }, "rsa no key": { "sign-method": "RS256", }, "invalid ttl": { "sign-method": "RS256", "ttl": "forever", }, "rsa invalid public key": { "sign-method": "RS256", "pub-key": jwtRSAPrivKey, "priv-key": jwtRSAPrivKey, }, "rsa invalid private key": { "sign-method": "RS256", "pub-key": jwtRSAPubKey, "priv-key": jwtRSAPubKey, }, "hmac no key": { "sign-method": "HS256", }, "hmac pub key": { "sign-method": "HS256", "pub-key": jwtRSAPubKey, }, "missing public key file": { "sign-method": "HS256", "pub-key": "missing-file", }, "missing private key file": { "sign-method": "HS256", "priv-key": "missing-file", }, "ecdsa no key": { "sign-method": "ES256", }, "ecdsa invalid public key": { "sign-method": "ES256", "pub-key": jwtECPrivKey, "priv-key": jwtECPrivKey, }, "ecdsa invalid private key": { "sign-method": "ES256", "pub-key": jwtECPubKey, "priv-key": jwtECPubKey, }, } lg := zap.NewNop() for k, v := range badCases { t.Run(k, func(t *testing.T) { _, err := newTokenProviderJWT(lg, v) if err == nil { t.Errorf("expected error for options %v", v) } }) } } // testJWTOpts is useful for passing to NewTokenProvider which requires a string. func testJWTOpts() string { return fmt.Sprintf("%s,pub-key=%s,priv-key=%s,sign-method=RS256", tokenTypeJWT, jwtRSAPubKey, jwtRSAPrivKey) }