blob: 24d13ca017ace87dcf84a17ca6a4384ddfebe92b [file] [log] [blame]
// 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)
}