test: validate direct JWT passing and acceptance

Signed-off-by: Mike Crute <mike@crute.us>
This commit is contained in:
Mike Crute
2024-07-23 10:53:23 -07:00
parent 87d9a468d5
commit 4f46fb4465
8 changed files with 103 additions and 3 deletions

View File

@@ -29,8 +29,11 @@ import (
)
var tokenTTL = time.Second
var defaultKeyPath = mustAbsPath("../fixtures/server.key.insecure")
var defaultAuthToken = fmt.Sprintf("jwt,pub-key=%s,priv-key=%s,sign-method=RS256,ttl=%s",
mustAbsPath("../fixtures/server.crt"), mustAbsPath("../fixtures/server.key.insecure"), tokenTTL)
mustAbsPath("../fixtures/server.crt"), defaultKeyPath, tokenTTL)
var verifyJWTOnlyAuth = fmt.Sprintf("jwt,pub-key=%s,sign-method=RS256,ttl=%s",
mustAbsPath("../fixtures/server.crt"), tokenTTL)
const (
PermissionDenied = "etcdserver: permission denied"
@@ -758,6 +761,25 @@ func TestAuthJWTExpire(t *testing.T) {
})
}
func TestAuthJWTOnly(t *testing.T) {
testRunner.BeforeTest(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1, AuthToken: verifyJWTOnlyAuth}))
defer clus.Close()
cc := testutils.MustClient(clus.Client())
testutils.ExecuteUntil(ctx, t, func() {
authRev, err := setupAuthAndGetRevision(cc, []authRole{testRole}, []authUser{rootUser, testUser})
require.NoErrorf(t, err, "failed to enable auth")
token, err := createSignedJWT(defaultKeyPath, "RS256", testUserName, authRev)
require.NoErrorf(t, err, "failed to create test user JWT")
testUserAuthClient := testutils.MustClient(clus.Client(WithAuthToken(token)))
require.NoError(t, testUserAuthClient.Put(ctx, "foo", "bar", config.PutOptions{}))
})
}
// TestAuthRevisionConsistency ensures auth revision is the same after member restarts
func TestAuthRevisionConsistency(t *testing.T) {
testRunner.BeforeTest(t)

View File

@@ -17,8 +17,11 @@ package common
import (
"context"
"fmt"
"os"
"testing"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/stretchr/testify/require"
"go.etcd.io/etcd/api/v3/authpb"
@@ -93,6 +96,29 @@ func createUsers(c interfaces.Client, users []authUser) error {
return nil
}
func createSignedJWT(keyPath, alg, username string, authRevision uint64) (string, error) {
signMethod := jwt.GetSigningMethod(alg)
keyBytes, err := os.ReadFile(keyPath)
if err != nil {
return "", err
}
key, err := jwt.ParseRSAPrivateKeyFromPEM(keyBytes)
if err != nil {
return "", err
}
tk := jwt.NewWithClaims(signMethod,
jwt.MapClaims{
"username": username,
"revision": authRevision,
"exp": time.Now().Add(time.Minute).Unix(),
})
return tk.SignedString(key)
}
func setupAuth(c interfaces.Client, roles []authRole, users []authUser) error {
// create roles
if err := createRoles(c, roles); err != nil {
@@ -107,6 +133,29 @@ func setupAuth(c interfaces.Client, roles []authRole, users []authUser) error {
return c.AuthEnable(context.TODO())
}
func setupAuthAndGetRevision(c interfaces.Client, roles []authRole, users []authUser) (uint64, error) {
// create roles
if err := createRoles(c, roles); err != nil {
return 0, err
}
if err := createUsers(c, users); err != nil {
return 0, err
}
// This needs to happen before enabling auth for the TestAuthJWTOnly
// test case because once auth is enabled we can no longer mint a valid
// auth token without the revision, which we won't be able to obtain
// without a valid auth token.
authrev, err := c.AuthStatus(context.TODO())
if err != nil {
return 0, err
}
// enable auth
return authrev.AuthRevision, c.AuthEnable(context.TODO())
}
func requireRolePermissionEqual(t *testing.T, expectRole authRole, actual []*authpb.Permission) {
require.Equal(t, 1, len(actual))
require.Equal(t, expectRole.permission, clientv3.PermissionType(actual[0].PermType))

View File

@@ -78,6 +78,10 @@ func WithAuth(userName, password string) config.ClientOption {
return e2e.WithAuth(userName, password)
}
func WithAuthToken(token string) config.ClientOption {
return e2e.WithAuthToken(token)
}
func WithEndpoints(endpoints []string) config.ClientOption {
return e2e.WithEndpoints(endpoints)
}

View File

@@ -56,6 +56,10 @@ func WithAuth(userName, password string) config.ClientOption {
return integration.WithAuth(userName, password)
}
func WithAuthToken(token string) config.ClientOption {
return integration.WithAuthToken(token)
}
func WithEndpoints(endpoints []string) config.ClientOption {
return integration.WithEndpoints(endpoints)
}

View File

@@ -37,6 +37,10 @@ func WithAuth(userName, password string) config.ClientOption {
return func(any) {}
}
func WithAuthToken(token string) config.ClientOption {
return func(any) {}
}
func WithEndpoints(endpoints []string) config.ClientOption {
return func(any) {}
}

View File

@@ -55,6 +55,7 @@ func NewEtcdctl(cfg ClientConfig, endpoints []string, opts ...config.ClientOptio
DialOptions: []grpc.DialOption{grpc.WithBlock()},
Username: ctl.authConfig.Username,
Password: ctl.authConfig.Password,
Token: ctl.authConfig.Token,
})
if err != nil {
return nil, err
@@ -73,6 +74,13 @@ func WithAuth(userName, password string) config.ClientOption {
}
}
func WithAuthToken(token string) config.ClientOption {
return func(c any) {
ctl := c.(*EtcdctlV3)
ctl.authConfig.Token = token
}
}
func WithEndpoints(endpoints []string) config.ClientOption {
return func(c any) {
ctl := c.(*EtcdctlV3)
@@ -344,7 +352,9 @@ func (ctl *EtcdctlV3) flags() map[string]string {
}
}
fmap["endpoints"] = strings.Join(ctl.endpoints, ",")
if !ctl.authConfig.Empty() {
if ctl.authConfig.Token != "" {
fmap["auth-jwt-token"] = ctl.authConfig.Token
} else if !ctl.authConfig.Empty() {
fmap["user"] = ctl.authConfig.Username + ":" + ctl.authConfig.Password
}
return fmap

View File

@@ -1478,6 +1478,13 @@ func WithAuth(userName, password string) framecfg.ClientOption {
}
}
func WithAuthToken(token string) framecfg.ClientOption {
return func(c any) {
cfg := c.(*clientv3.Config)
cfg.Token = token
}
}
func WithEndpoints(endpoints []string) framecfg.ClientOption {
return func(c any) {
cfg := c.(*clientv3.Config)

View File

@@ -18,6 +18,7 @@ replace (
require (
github.com/anishathalye/porcupine v0.1.4
github.com/coreos/go-semver v0.3.1
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/protobuf v1.5.4
github.com/google/go-cmp v0.6.0
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
@@ -65,7 +66,6 @@ require (
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/uuid v1.6.0 // indirect