From 4512fe6dc38a19c512c5a7b10891ec4cb8ddc836 Mon Sep 17 00:00:00 2001 From: Chao Chen Date: Wed, 14 Dec 2022 15:17:35 -0800 Subject: [PATCH] tests/common: migrate auth tests #1 Signed-off-by: Chao Chen --- tests/common/auth_test.go | 132 ++++++++++++++++++++++++++++++++++ tests/common/auth_util.go | 29 ++++++-- tests/e2e/ctl_v3_auth_test.go | 129 +-------------------------------- 3 files changed, 158 insertions(+), 132 deletions(-) create mode 100644 tests/common/auth_test.go diff --git a/tests/common/auth_test.go b/tests/common/auth_test.go new file mode 100644 index 000000000000..e9de1fecdd3f --- /dev/null +++ b/tests/common/auth_test.go @@ -0,0 +1,132 @@ +// Copyright 2022 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 common + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.etcd.io/etcd/tests/v3/framework/config" + "go.etcd.io/etcd/tests/v3/framework/testutils" +) + +func TestAuthEnable(t *testing.T) { + testRunner.BeforeTest(t) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) + defer clus.Close() + cc := testutils.MustClient(clus.Client()) + testutils.ExecuteUntil(ctx, t, func() { + require.NoErrorf(t, setupAuth(cc, []authRole{}, []authUser{rootUser}), "failed to enable auth") + }) +} + +func TestAuthDisable(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})) + defer clus.Close() + cc := testutils.MustClient(clus.Client()) + testutils.ExecuteUntil(ctx, t, func() { + require.NoError(t, cc.Put(ctx, "hoo", "a", config.PutOptions{})) + require.NoErrorf(t, setupAuth(cc, []authRole{testRole}, []authUser{rootUser, testUser}), "failed to enable auth") + + rootAuthClient := testutils.MustClient(clus.Client(WithAuth(rootUserName, rootPassword))) + testUserAuthClient := testutils.MustClient(clus.Client(WithAuth(testUserName, testPassword))) + + // test-user doesn't have the permission, it must fail + require.Error(t, testUserAuthClient.Put(ctx, "hoo", "bar", config.PutOptions{})) + require.NoErrorf(t, rootAuthClient.AuthDisable(ctx), "failed to disable auth") + // now ErrAuthNotEnabled of Authenticate() is simply ignored + require.NoError(t, testUserAuthClient.Put(ctx, "hoo", "bar", config.PutOptions{})) + // now the key can be accessed + require.NoError(t, cc.Put(ctx, "hoo", "bar", config.PutOptions{})) + // confirm put succeeded + resp, err := cc.Get(ctx, "hoo", config.GetOptions{}) + require.NoError(t, err) + if len(resp.Kvs) != 1 || string(resp.Kvs[0].Key) != "hoo" || string(resp.Kvs[0].Value) != "bar" { + t.Fatalf("want key value pair 'hoo', 'bar' but got %+v", resp.Kvs) + } + }) +} + +func TestAuthGracefulDisable(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})) + defer clus.Close() + cc := testutils.MustClient(clus.Client()) + testutils.ExecuteUntil(ctx, t, func() { + require.NoErrorf(t, setupAuth(cc, []authRole{}, []authUser{rootUser}), "failed to enable auth") + donec := make(chan struct{}) + rootAuthClient := testutils.MustClient(clus.Client(WithAuth(rootUserName, rootPassword))) + + go func() { + defer close(donec) + // sleep a bit to let the watcher connects while auth is still enabled + time.Sleep(time.Second) + // now disable auth... + if err := rootAuthClient.AuthDisable(ctx); err != nil { + t.Errorf("failed to auth disable %v", err) + return + } + // ...and restart the node + clus.Members()[0].Stop() + if err := clus.Members()[0].Start(ctx); err != nil { + t.Errorf("failed to restart member %v", err) + return + } + // the watcher should still work after reconnecting + require.NoErrorf(t, rootAuthClient.Put(ctx, "key", "value", config.PutOptions{}), "failed to put key value") + }() + + wCtx, wCancel := context.WithCancel(ctx) + defer wCancel() + + watchCh := rootAuthClient.Watch(wCtx, "key", config.WatchOptions{Revision: 1}) + wantedLen := 1 + watchTimeout := 10 * time.Second + wanted := []testutils.KV{{Key: "key", Val: "value"}} + kvs, err := testutils.KeyValuesFromWatchChan(watchCh, wantedLen, watchTimeout) + require.NoErrorf(t, err, "failed to get key-values from watch channel %s", err) + require.Equal(t, wanted, kvs) + <-donec + }) +} + +func TestAuthStatus(t *testing.T) { + testRunner.BeforeTest(t) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + clus := testRunner.NewCluster(ctx, t, config.WithClusterConfig(config.ClusterConfig{ClusterSize: 1})) + defer clus.Close() + cc := testutils.MustClient(clus.Client()) + testutils.ExecuteUntil(ctx, t, func() { + resp, err := cc.AuthStatus(ctx) + require.NoError(t, err) + require.Falsef(t, resp.Enabled, "want auth not enabled but enabled") + + require.NoErrorf(t, setupAuth(cc, []authRole{}, []authUser{rootUser}), "failed to enable auth") + rootAuthClient := testutils.MustClient(clus.Client(WithAuth(rootUserName, rootPassword))) + resp, err = rootAuthClient.AuthStatus(ctx) + require.NoError(t, err) + require.Truef(t, resp.Enabled, "want enabled but got not enabled") + }) +} diff --git a/tests/common/auth_util.go b/tests/common/auth_util.go index 1a78a19cf26b..c75f20cd312d 100644 --- a/tests/common/auth_util.go +++ b/tests/common/auth_util.go @@ -20,7 +20,28 @@ import ( clientv3 "go.etcd.io/etcd/client/v3" "go.etcd.io/etcd/tests/v3/framework/config" - intf "go.etcd.io/etcd/tests/v3/framework/interfaces" + "go.etcd.io/etcd/tests/v3/framework/interfaces" +) + +const ( + rootUserName = "root" + rootRoleName = "root" + rootPassword = "rootPassword" + testUserName = "test-user" + testRoleName = "test-role" + testPassword = "pass" +) + +var ( + rootUser = authUser{user: rootUserName, pass: rootPassword, role: rootRoleName} + testUser = authUser{user: testUserName, pass: testPassword, role: testRoleName} + + testRole = authRole{ + role: testRoleName, + permission: clientv3.PermissionType(clientv3.PermReadWrite), + key: "foo", + keyEnd: "", + } ) type authRole struct { @@ -36,7 +57,7 @@ type authUser struct { role string } -func createRoles(c intf.Client, roles []authRole) error { +func createRoles(c interfaces.Client, roles []authRole) error { for _, r := range roles { // add role if _, err := c.RoleAdd(context.TODO(), r.role); err != nil { @@ -52,7 +73,7 @@ func createRoles(c intf.Client, roles []authRole) error { return nil } -func createUsers(c intf.Client, users []authUser) error { +func createUsers(c interfaces.Client, users []authUser) error { for _, u := range users { // add user if _, err := c.UserAdd(context.TODO(), u.user, u.pass, config.UserAddOptions{}); err != nil { @@ -68,7 +89,7 @@ func createUsers(c intf.Client, users []authUser) error { return nil } -func setupAuth(c intf.Client, roles []authRole, users []authUser) error { +func setupAuth(c interfaces.Client, roles []authRole, users []authUser) error { // create roles if err := createRoles(c, roles); err != nil { return err diff --git a/tests/e2e/ctl_v3_auth_test.go b/tests/e2e/ctl_v3_auth_test.go index bcdc2254cedb..e2d7a4df2bea 100644 --- a/tests/e2e/ctl_v3_auth_test.go +++ b/tests/e2e/ctl_v3_auth_test.go @@ -26,12 +26,6 @@ import ( "go.etcd.io/etcd/tests/v3/framework/e2e" ) -func TestCtlV3AuthEnable(t *testing.T) { - testCtl(t, authEnableTest) -} -func TestCtlV3AuthDisable(t *testing.T) { testCtl(t, authDisableTest) } -func TestCtlV3AuthGracefulDisable(t *testing.T) { testCtl(t, authGracefulDisableTest) } -func TestCtlV3AuthStatus(t *testing.T) { testCtl(t, authStatusTest) } func TestCtlV3AuthWriteKey(t *testing.T) { testCtl(t, authCredWriteKeyTest) } func TestCtlV3AuthRoleUpdate(t *testing.T) { testCtl(t, authRoleUpdateTest) } func TestCtlV3AuthUserDeleteDuringOps(t *testing.T) { testCtl(t, authUserDeleteDuringOpsTest) } @@ -76,14 +70,7 @@ func TestCtlV3AuthJWTExpire(t *testing.T) { testCtl(t, authTestJWTExpire, withCfg(*e2e.NewConfigJWT())) } func TestCtlV3AuthRevisionConsistency(t *testing.T) { testCtl(t, authTestRevisionConsistency) } - -func TestCtlV3AuthTestCacheReload(t *testing.T) { testCtl(t, authTestCacheReload) } - -func authEnableTest(cx ctlCtx) { - if err := authEnable(cx); err != nil { - cx.t.Fatal(err) - } -} +func TestCtlV3AuthTestCacheReload(t *testing.T) { testCtl(t, authTestCacheReload) } func authEnable(cx ctlCtx) error { // create root user with root role @@ -104,120 +91,6 @@ func ctlV3AuthEnable(cx ctlCtx) error { return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, "Authentication Enabled") } -func authDisableTest(cx ctlCtx) { - // a key that isn't granted to test-user - if err := ctlV3Put(cx, "hoo", "a", ""); err != nil { - cx.t.Fatal(err) - } - - if err := authEnable(cx); err != nil { - cx.t.Fatal(err) - } - - cx.user, cx.pass = "root", "root" - authSetupTestUser(cx) - - // test-user doesn't have the permission, it must fail - cx.user, cx.pass = "test-user", "pass" - err := ctlV3PutFailPerm(cx, "hoo", "bar") - require.ErrorContains(cx.t, err, "permission denied") - - cx.user, cx.pass = "root", "root" - if err := ctlV3AuthDisable(cx); err != nil { - cx.t.Fatalf("authDisableTest ctlV3AuthDisable error (%v)", err) - } - - // now ErrAuthNotEnabled of Authenticate() is simply ignored - cx.user, cx.pass = "test-user", "pass" - if err := ctlV3Put(cx, "hoo", "bar", ""); err != nil { - cx.t.Fatal(err) - } - - // now the key can be accessed - cx.user, cx.pass = "", "" - if err := ctlV3Put(cx, "hoo", "bar", ""); err != nil { - cx.t.Fatal(err) - } - // confirm put succeeded - if err := ctlV3Get(cx, []string{"hoo"}, []kv{{"hoo", "bar"}}...); err != nil { - cx.t.Fatal(err) - } -} - -func authGracefulDisableTest(cx ctlCtx) { - if err := authEnable(cx); err != nil { - cx.t.Fatal(err) - } - - cx.user, cx.pass = "root", "root" - - donec := make(chan struct{}) - - go func() { - defer close(donec) - - // sleep a bit to let the watcher connects while auth is still enabled - time.Sleep(time.Second) - - // now disable auth... - if err := ctlV3AuthDisable(cx); err != nil { - cx.t.Fatalf("authGracefulDisableTest ctlV3AuthDisable error (%v)", err) - } - - // ...and restart the node - node0 := cx.epc.Procs[0] - if rerr := node0.Restart(context.TODO()); rerr != nil { - cx.t.Fatal(rerr) - } - - // the watcher should still work after reconnecting - if perr := ctlV3Put(cx, "key", "value", ""); perr != nil { - cx.t.Errorf("authGracefulDisableTest ctlV3Put error (%v)", perr) - } - }() - - err := ctlV3Watch(cx, []string{"key"}, kvExec{key: "key", val: "value"}) - - if err != nil { - if cx.dialTimeout > 0 && !isGRPCTimedout(err) { - cx.t.Errorf("authGracefulDisableTest ctlV3Watch error (%v)", err) - } - } - - <-donec -} - -func ctlV3AuthDisable(cx ctlCtx) error { - cmdArgs := append(cx.PrefixArgs(), "auth", "disable") - return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, "Authentication Disabled") -} - -func authStatusTest(cx ctlCtx) { - cmdArgs := append(cx.PrefixArgs(), "auth", "status") - if err := e2e.SpawnWithExpects(cmdArgs, cx.envMap, "Authentication Status: false", "AuthRevision:"); err != nil { - cx.t.Fatal(err) - } - - if err := authEnable(cx); err != nil { - cx.t.Fatal(err) - } - - cx.user, cx.pass = "root", "root" - cmdArgs = append(cx.PrefixArgs(), "auth", "status") - - if err := e2e.SpawnWithExpects(cmdArgs, cx.envMap, "Authentication Status: true", "AuthRevision:"); err != nil { - cx.t.Fatal(err) - } - - cmdArgs = append(cx.PrefixArgs(), "auth", "status", "--write-out", "json") - if err := e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, "enabled"); err != nil { - cx.t.Fatal(err) - } - if err := e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, "authRevision"); err != nil { - cx.t.Fatal(err) - } -} - func authCredWriteKeyTest(cx ctlCtx) { // baseline key to check for failed puts if err := ctlV3Put(cx, "foo", "a", ""); err != nil {