mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
Merge pull request #13819 from endocrimes/dani/auth_test.go
migrate e2e/users tests to common framework
This commit is contained in:
commit
c4d055fe7b
366
tests/common/user_test.go
Normal file
366
tests/common/user_test.go
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
// 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 (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.etcd.io/etcd/tests/v3/framework/config"
|
||||||
|
"go.etcd.io/etcd/tests/v3/framework/testutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserAdd_Simple(t *testing.T) {
|
||||||
|
testRunner.BeforeTest(t)
|
||||||
|
tcs := []struct {
|
||||||
|
name string
|
||||||
|
config config.ClusterConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "NoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PeerTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.ManualTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PeerAutoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.AutoTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ClientTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.ManualTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ClientAutoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.AutoTLS},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
nestedCases := []struct {
|
||||||
|
name string
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
noPassword bool
|
||||||
|
expectedError string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty_username_not_allowed",
|
||||||
|
username: "",
|
||||||
|
password: "foobar",
|
||||||
|
// Very Vague error expectation because the CLI and the API return very
|
||||||
|
// different error structures.
|
||||||
|
expectedError: "user name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Can create a user with no password, restricted to CN auth
|
||||||
|
name: "no_password_with_noPassword_set",
|
||||||
|
username: "foo",
|
||||||
|
password: "",
|
||||||
|
noPassword: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Can create a user with no password, but not restricted to CN auth
|
||||||
|
name: "no_password_without_noPassword_set",
|
||||||
|
username: "foo",
|
||||||
|
password: "",
|
||||||
|
noPassword: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "regular_user_with_password",
|
||||||
|
username: "foo",
|
||||||
|
password: "bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, nc := range nestedCases {
|
||||||
|
t.Run(tc.name+"/"+nc.name, func(t *testing.T) {
|
||||||
|
clus := testRunner.NewCluster(t, tc.config)
|
||||||
|
defer clus.Close()
|
||||||
|
cc := clus.Client()
|
||||||
|
|
||||||
|
testutils.ExecuteWithTimeout(t, 10*time.Second, func() {
|
||||||
|
resp, err := cc.UserAdd(nc.username, nc.password, config.UserAddOptions{NoPassword: nc.noPassword})
|
||||||
|
if nc.expectedError != "" {
|
||||||
|
if err != nil {
|
||||||
|
assert.Contains(t, err.Error(), nc.expectedError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatalf("expected user creation to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatalf("unexpected nil response to successful user creation")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserAdd_DuplicateUserNotAllowed(t *testing.T) {
|
||||||
|
testRunner.BeforeTest(t)
|
||||||
|
tcs := []struct {
|
||||||
|
name string
|
||||||
|
config config.ClusterConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "NoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PeerTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.ManualTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PeerAutoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.AutoTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ClientTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.ManualTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ClientAutoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.AutoTLS},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
clus := testRunner.NewCluster(t, tc.config)
|
||||||
|
defer clus.Close()
|
||||||
|
cc := clus.Client()
|
||||||
|
|
||||||
|
testutils.ExecuteWithTimeout(t, 10*time.Second, func() {
|
||||||
|
user := "barb"
|
||||||
|
password := "rhubarb"
|
||||||
|
|
||||||
|
_, err := cc.UserAdd(user, password, config.UserAddOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("first user creation should succeed, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = cc.UserAdd(user, password, config.UserAddOptions{})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("duplicate user creation should fail")
|
||||||
|
}
|
||||||
|
assert.Contains(t, err.Error(), "etcdserver: user name already exists")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserList(t *testing.T) {
|
||||||
|
testRunner.BeforeTest(t)
|
||||||
|
tcs := []struct {
|
||||||
|
name string
|
||||||
|
config config.ClusterConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "NoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PeerTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.ManualTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PeerAutoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.AutoTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ClientTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.ManualTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ClientAutoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.AutoTLS},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
clus := testRunner.NewCluster(t, tc.config)
|
||||||
|
defer clus.Close()
|
||||||
|
cc := clus.Client()
|
||||||
|
|
||||||
|
testutils.ExecuteWithTimeout(t, 10*time.Second, func() {
|
||||||
|
// No Users Yet
|
||||||
|
resp, err := cc.UserList()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("user listing should succeed, err: %v", err)
|
||||||
|
}
|
||||||
|
if len(resp.Users) != 0 {
|
||||||
|
t.Fatalf("expected no pre-existing users, found: %q", resp.Users)
|
||||||
|
}
|
||||||
|
|
||||||
|
user := "barb"
|
||||||
|
password := "rhubarb"
|
||||||
|
|
||||||
|
_, err = cc.UserAdd(user, password, config.UserAddOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("user creation should succeed, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Users!
|
||||||
|
resp, err = cc.UserList()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("user listing should succeed, err: %v", err)
|
||||||
|
}
|
||||||
|
if len(resp.Users) != 1 {
|
||||||
|
t.Fatalf("expected one user, found: %q", resp.Users)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserDelete(t *testing.T) {
|
||||||
|
testRunner.BeforeTest(t)
|
||||||
|
tcs := []struct {
|
||||||
|
name string
|
||||||
|
config config.ClusterConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "NoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PeerTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.ManualTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PeerAutoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.AutoTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ClientTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.ManualTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ClientAutoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.AutoTLS},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
clus := testRunner.NewCluster(t, tc.config)
|
||||||
|
defer clus.Close()
|
||||||
|
cc := clus.Client()
|
||||||
|
|
||||||
|
testutils.ExecuteWithTimeout(t, 10*time.Second, func() {
|
||||||
|
user := "barb"
|
||||||
|
password := "rhubarb"
|
||||||
|
|
||||||
|
_, err := cc.UserAdd(user, password, config.UserAddOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("user creation should succeed, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := cc.UserList()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("user listing should succeed, err: %v", err)
|
||||||
|
}
|
||||||
|
if len(resp.Users) != 1 {
|
||||||
|
t.Fatalf("expected one user, found: %q", resp.Users)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete barb, sorry barb!
|
||||||
|
_, err = cc.UserDelete(user)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("user deletion should succeed at first, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = cc.UserList()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("user listing should succeed, err: %v", err)
|
||||||
|
}
|
||||||
|
if len(resp.Users) != 0 {
|
||||||
|
t.Fatalf("expected no users after deletion, found: %q", resp.Users)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to delete barb again
|
||||||
|
_, err = cc.UserDelete(user)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("deleting a non-existent user should fail")
|
||||||
|
}
|
||||||
|
assert.Contains(t, err.Error(), "user name not found")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserChangePassword(t *testing.T) {
|
||||||
|
testRunner.BeforeTest(t)
|
||||||
|
tcs := []struct {
|
||||||
|
name string
|
||||||
|
config config.ClusterConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "NoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PeerTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.ManualTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PeerAutoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.AutoTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ClientTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.ManualTLS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ClientAutoTLS",
|
||||||
|
config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.AutoTLS},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
clus := testRunner.NewCluster(t, tc.config)
|
||||||
|
defer clus.Close()
|
||||||
|
cc := clus.Client()
|
||||||
|
|
||||||
|
testutils.ExecuteWithTimeout(t, 10*time.Second, func() {
|
||||||
|
user := "barb"
|
||||||
|
password := "rhubarb"
|
||||||
|
newPassword := "potato"
|
||||||
|
|
||||||
|
_, err := cc.UserAdd(user, password, config.UserAddOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("user creation should succeed, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cc.UserChangePass(user, newPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("user password change should succeed, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cc.UserChangePass("non-existent-user", newPassword)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("user password change for non-existent user should fail")
|
||||||
|
}
|
||||||
|
assert.Contains(t, err.Error(), "user name not found")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1305,3 +1305,24 @@ func ctlV3EndpointHealth(cx ctlCtx) error {
|
|||||||
}
|
}
|
||||||
return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...)
|
return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ctlV3User(cx ctlCtx, args []string, expStr string, stdIn []string) error {
|
||||||
|
cmdArgs := append(cx.PrefixArgs(), "user")
|
||||||
|
cmdArgs = append(cmdArgs, args...)
|
||||||
|
|
||||||
|
proc, err := e2e.SpawnCmd(cmdArgs, cx.envMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer proc.Close()
|
||||||
|
|
||||||
|
// Send 'stdIn' strings as input.
|
||||||
|
for _, s := range stdIn {
|
||||||
|
if err = proc.Send(s + "\r"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = proc.Expect(expStr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -1,209 +0,0 @@
|
|||||||
// Copyright 2016 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 e2e
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"go.etcd.io/etcd/tests/v3/framework/e2e"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCtlV3UserAdd(t *testing.T) { testCtl(t, userAddTest) }
|
|
||||||
func TestCtlV3UserAddNoTLS(t *testing.T) { testCtl(t, userAddTest, withCfg(*e2e.NewConfigNoTLS())) }
|
|
||||||
func TestCtlV3UserAddClientTLS(t *testing.T) {
|
|
||||||
testCtl(t, userAddTest, withCfg(*e2e.NewConfigClientTLS()))
|
|
||||||
}
|
|
||||||
func TestCtlV3UserAddPeerTLS(t *testing.T) { testCtl(t, userAddTest, withCfg(*e2e.NewConfigPeerTLS())) }
|
|
||||||
func TestCtlV3UserAddTimeout(t *testing.T) { testCtl(t, userAddTest, withDialTimeout(0)) }
|
|
||||||
func TestCtlV3UserAddClientAutoTLS(t *testing.T) {
|
|
||||||
testCtl(t, userAddTest, withCfg(*e2e.NewConfigClientAutoTLS()))
|
|
||||||
}
|
|
||||||
func TestCtlV3UserList(t *testing.T) { testCtl(t, userListTest) }
|
|
||||||
func TestCtlV3UserListNoTLS(t *testing.T) { testCtl(t, userListTest, withCfg(*e2e.NewConfigNoTLS())) }
|
|
||||||
func TestCtlV3UserListClientTLS(t *testing.T) {
|
|
||||||
testCtl(t, userListTest, withCfg(*e2e.NewConfigClientTLS()))
|
|
||||||
}
|
|
||||||
func TestCtlV3UserListPeerTLS(t *testing.T) {
|
|
||||||
testCtl(t, userListTest, withCfg(*e2e.NewConfigPeerTLS()))
|
|
||||||
}
|
|
||||||
func TestCtlV3UserListClientAutoTLS(t *testing.T) {
|
|
||||||
testCtl(t, userListTest, withCfg(*e2e.NewConfigClientAutoTLS()))
|
|
||||||
}
|
|
||||||
func TestCtlV3UserDelete(t *testing.T) { testCtl(t, userDelTest) }
|
|
||||||
func TestCtlV3UserDeleteNoTLS(t *testing.T) { testCtl(t, userDelTest, withCfg(*e2e.NewConfigNoTLS())) }
|
|
||||||
func TestCtlV3UserDeleteClientTLS(t *testing.T) {
|
|
||||||
testCtl(t, userDelTest, withCfg(*e2e.NewConfigClientTLS()))
|
|
||||||
}
|
|
||||||
func TestCtlV3UserDeletePeerTLS(t *testing.T) {
|
|
||||||
testCtl(t, userDelTest, withCfg(*e2e.NewConfigPeerTLS()))
|
|
||||||
}
|
|
||||||
func TestCtlV3UserDeleteClientAutoTLS(t *testing.T) {
|
|
||||||
testCtl(t, userDelTest, withCfg(*e2e.NewConfigClientAutoTLS()))
|
|
||||||
}
|
|
||||||
func TestCtlV3UserPasswd(t *testing.T) { testCtl(t, userPasswdTest) }
|
|
||||||
func TestCtlV3UserPasswdNoTLS(t *testing.T) {
|
|
||||||
testCtl(t, userPasswdTest, withCfg(*e2e.NewConfigNoTLS()))
|
|
||||||
}
|
|
||||||
func TestCtlV3UserPasswdClientTLS(t *testing.T) {
|
|
||||||
testCtl(t, userPasswdTest, withCfg(*e2e.NewConfigClientTLS()))
|
|
||||||
}
|
|
||||||
func TestCtlV3UserPasswdPeerTLS(t *testing.T) {
|
|
||||||
testCtl(t, userPasswdTest, withCfg(*e2e.NewConfigPeerTLS()))
|
|
||||||
}
|
|
||||||
func TestCtlV3UserPasswdClientAutoTLS(t *testing.T) {
|
|
||||||
testCtl(t, userPasswdTest, withCfg(*e2e.NewConfigClientAutoTLS()))
|
|
||||||
}
|
|
||||||
|
|
||||||
type userCmdDesc struct {
|
|
||||||
args []string
|
|
||||||
expectedStr string
|
|
||||||
stdIn []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func userAddTest(cx ctlCtx) {
|
|
||||||
cmdSet := []userCmdDesc{
|
|
||||||
// Adds a user name.
|
|
||||||
{
|
|
||||||
args: []string{"add", "username", "--interactive=false"},
|
|
||||||
expectedStr: "User username created",
|
|
||||||
stdIn: []string{"password"},
|
|
||||||
},
|
|
||||||
// Adds a user name using the usertest:password syntax.
|
|
||||||
{
|
|
||||||
args: []string{"add", "usertest:password"},
|
|
||||||
expectedStr: "User usertest created",
|
|
||||||
stdIn: []string{},
|
|
||||||
},
|
|
||||||
// Tries to add a user with empty username.
|
|
||||||
{
|
|
||||||
args: []string{"add", ":password"},
|
|
||||||
expectedStr: "empty user name is not allowed",
|
|
||||||
stdIn: []string{},
|
|
||||||
},
|
|
||||||
// Tries to add a user name that already exists.
|
|
||||||
{
|
|
||||||
args: []string{"add", "username", "--interactive=false"},
|
|
||||||
expectedStr: "user name already exists",
|
|
||||||
stdIn: []string{"password"},
|
|
||||||
},
|
|
||||||
// Adds a user without password.
|
|
||||||
{
|
|
||||||
args: []string{"add", "userwopasswd", "--no-password"},
|
|
||||||
expectedStr: "User userwopasswd created",
|
|
||||||
stdIn: []string{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, cmd := range cmdSet {
|
|
||||||
if err := ctlV3User(cx, cmd.args, cmd.expectedStr, cmd.stdIn); err != nil {
|
|
||||||
if cx.dialTimeout > 0 && !isGRPCTimedout(err) {
|
|
||||||
cx.t.Fatalf("userAddTest #%d: ctlV3User error (%v)", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func userListTest(cx ctlCtx) {
|
|
||||||
cmdSet := []userCmdDesc{
|
|
||||||
// Adds a user name.
|
|
||||||
{
|
|
||||||
args: []string{"add", "username", "--interactive=false"},
|
|
||||||
expectedStr: "User username created",
|
|
||||||
stdIn: []string{"password"},
|
|
||||||
},
|
|
||||||
// List user name
|
|
||||||
{
|
|
||||||
args: []string{"list"},
|
|
||||||
expectedStr: "username",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, cmd := range cmdSet {
|
|
||||||
if err := ctlV3User(cx, cmd.args, cmd.expectedStr, cmd.stdIn); err != nil {
|
|
||||||
cx.t.Fatalf("userListTest #%d: ctlV3User error (%v)", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func userDelTest(cx ctlCtx) {
|
|
||||||
cmdSet := []userCmdDesc{
|
|
||||||
// Adds a user name.
|
|
||||||
{
|
|
||||||
args: []string{"add", "username", "--interactive=false"},
|
|
||||||
expectedStr: "User username created",
|
|
||||||
stdIn: []string{"password"},
|
|
||||||
},
|
|
||||||
// Deletes the user name just added.
|
|
||||||
{
|
|
||||||
args: []string{"delete", "username"},
|
|
||||||
expectedStr: "User username deleted",
|
|
||||||
},
|
|
||||||
// Deletes a user name that is not present.
|
|
||||||
{
|
|
||||||
args: []string{"delete", "username"},
|
|
||||||
expectedStr: "user name not found",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, cmd := range cmdSet {
|
|
||||||
if err := ctlV3User(cx, cmd.args, cmd.expectedStr, cmd.stdIn); err != nil {
|
|
||||||
cx.t.Fatalf("userDelTest #%d: ctlV3User error (%v)", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func userPasswdTest(cx ctlCtx) {
|
|
||||||
cmdSet := []userCmdDesc{
|
|
||||||
// Adds a user name.
|
|
||||||
{
|
|
||||||
args: []string{"add", "username", "--interactive=false"},
|
|
||||||
expectedStr: "User username created",
|
|
||||||
stdIn: []string{"password"},
|
|
||||||
},
|
|
||||||
// Changes the password.
|
|
||||||
{
|
|
||||||
args: []string{"passwd", "username", "--interactive=false"},
|
|
||||||
expectedStr: "Password updated",
|
|
||||||
stdIn: []string{"password1"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, cmd := range cmdSet {
|
|
||||||
if err := ctlV3User(cx, cmd.args, cmd.expectedStr, cmd.stdIn); err != nil {
|
|
||||||
cx.t.Fatalf("userPasswdTest #%d: ctlV3User error (%v)", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ctlV3User(cx ctlCtx, args []string, expStr string, stdIn []string) error {
|
|
||||||
cmdArgs := append(cx.PrefixArgs(), "user")
|
|
||||||
cmdArgs = append(cmdArgs, args...)
|
|
||||||
|
|
||||||
proc, err := e2e.SpawnCmd(cmdArgs, cx.envMap)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer proc.Close()
|
|
||||||
|
|
||||||
// Send 'stdIn' strings as input.
|
|
||||||
for _, s := range stdIn {
|
|
||||||
if err = proc.Send(s + "\r"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = proc.Expect(expStr)
|
|
||||||
return err
|
|
||||||
}
|
|
@ -55,3 +55,7 @@ type DefragOption struct {
|
|||||||
type LeaseOption struct {
|
type LeaseOption struct {
|
||||||
WithAttachedKeys bool
|
WithAttachedKeys bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserAddOptions struct {
|
||||||
|
NoPassword bool
|
||||||
|
}
|
||||||
|
@ -378,3 +378,90 @@ func (ctl *EtcdctlV3) AlarmDisarm(_ *clientv3.AlarmMember) (*clientv3.AlarmRespo
|
|||||||
err = json.Unmarshal([]byte(line), &resp)
|
err = json.Unmarshal([]byte(line), &resp)
|
||||||
return &resp, err
|
return &resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctl *EtcdctlV3) UserAdd(name, password string, opts config.UserAddOptions) (*clientv3.AuthUserAddResponse, error) {
|
||||||
|
args := ctl.cmdArgs()
|
||||||
|
args = append(args, "user", "add")
|
||||||
|
if password == "" {
|
||||||
|
args = append(args, name)
|
||||||
|
} else {
|
||||||
|
args = append(args, fmt.Sprintf("%s:%s", name, password))
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.NoPassword {
|
||||||
|
args = append(args, "--no-password")
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, "--interactive=false", "-w", "json")
|
||||||
|
|
||||||
|
cmd, err := SpawnCmd(args, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no password is provided, and NoPassword isn't set, the CLI will always
|
||||||
|
// wait for a password, send an enter in this case for an "empty" password.
|
||||||
|
if !opts.NoPassword && password == "" {
|
||||||
|
err := cmd.Send("\n")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp clientv3.AuthUserAddResponse
|
||||||
|
line, err := cmd.Expect("header")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal([]byte(line), &resp)
|
||||||
|
return &resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *EtcdctlV3) UserList() (*clientv3.AuthUserListResponse, error) {
|
||||||
|
args := ctl.cmdArgs()
|
||||||
|
args = append(args, "user", "list", "-w", "json")
|
||||||
|
cmd, err := SpawnCmd(args, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var resp clientv3.AuthUserListResponse
|
||||||
|
line, err := cmd.Expect("header")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal([]byte(line), &resp)
|
||||||
|
return &resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *EtcdctlV3) UserDelete(name string) (*clientv3.AuthUserDeleteResponse, error) {
|
||||||
|
args := ctl.cmdArgs()
|
||||||
|
args = append(args, "user", "delete", name, "-w", "json")
|
||||||
|
cmd, err := SpawnCmd(args, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var resp clientv3.AuthUserDeleteResponse
|
||||||
|
line, err := cmd.Expect("header")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal([]byte(line), &resp)
|
||||||
|
return &resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *EtcdctlV3) UserChangePass(user, newPass string) error {
|
||||||
|
args := ctl.cmdArgs()
|
||||||
|
args = append(args, "user", "passwd", user, "--interactive=false")
|
||||||
|
cmd, err := SpawnCmd(args, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Send(newPass + "\n")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = cmd.Expect("Password updated")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -289,3 +289,25 @@ func (c integrationClient) LeaseRevoke(id clientv3.LeaseID) (*clientv3.LeaseRevo
|
|||||||
|
|
||||||
return c.Client.Revoke(ctx, id)
|
return c.Client.Revoke(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c integrationClient) UserAdd(name, password string, opts config.UserAddOptions) (*clientv3.AuthUserAddResponse, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
return c.Client.UserAddWithOptions(ctx, name, password, &clientv3.UserAddOptions{
|
||||||
|
NoPassword: opts.NoPassword,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c integrationClient) UserList() (*clientv3.AuthUserListResponse, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
return c.Client.UserList(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c integrationClient) UserDelete(name string) (*clientv3.AuthUserDeleteResponse, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
return c.Client.UserDelete(ctx, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c integrationClient) UserChangePass(user, newPass string) error {
|
||||||
|
_, err := c.Client.UserChangePassword(context.Background(), user, newPass)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -55,4 +55,9 @@ type Client interface {
|
|||||||
LeaseList() (*clientv3.LeaseLeasesResponse, error)
|
LeaseList() (*clientv3.LeaseLeasesResponse, error)
|
||||||
LeaseKeepAliveOnce(id clientv3.LeaseID) (*clientv3.LeaseKeepAliveResponse, error)
|
LeaseKeepAliveOnce(id clientv3.LeaseID) (*clientv3.LeaseKeepAliveResponse, error)
|
||||||
LeaseRevoke(id clientv3.LeaseID) (*clientv3.LeaseRevokeResponse, error)
|
LeaseRevoke(id clientv3.LeaseID) (*clientv3.LeaseRevokeResponse, error)
|
||||||
|
|
||||||
|
UserAdd(name, password string, opts config.UserAddOptions) (*clientv3.AuthUserAddResponse, error)
|
||||||
|
UserList() (*clientv3.AuthUserListResponse, error)
|
||||||
|
UserDelete(name string) (*clientv3.AuthUserDeleteResponse, error)
|
||||||
|
UserChangePass(user, newPass string) error
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user