ctl_v3_auth_test.go (etcd-3.5.5) | : | ctl_v3_auth_test.go (etcd-3.5.6) | ||
---|---|---|---|---|
skipping to change at line 20 | skipping to change at line 20 | |||
// distributed under the License is distributed on an "AS IS" BASIS, | // distributed under the License is distributed on an "AS IS" BASIS, | |||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
// See the License for the specific language governing permissions and | // See the License for the specific language governing permissions and | |||
// limitations under the License. | // limitations under the License. | |||
package e2e | package e2e | |||
import ( | import ( | |||
"context" | "context" | |||
"fmt" | "fmt" | |||
"net/url" | ||||
"os" | "os" | |||
"path/filepath" | ||||
"syscall" | "syscall" | |||
"testing" | "testing" | |||
"time" | "time" | |||
"github.com/stretchr/testify/assert" | ||||
"github.com/stretchr/testify/require" | ||||
"go.etcd.io/etcd/client/v3" | "go.etcd.io/etcd/client/v3" | |||
) | ) | |||
func TestCtlV3AuthEnable(t *testing.T) { | func TestCtlV3AuthEnable(t *testing.T) { | |||
testCtl(t, authEnableTest) | testCtl(t, authEnableTest) | |||
} | } | |||
func TestCtlV3AuthDisable(t *testing.T) { testCtl(t, authDisableTest ) } | func TestCtlV3AuthDisable(t *testing.T) { testCtl(t, authDisableTest ) } | |||
func TestCtlV3AuthStatus(t *testing.T) { testCtl(t, authStatusTest) } | func TestCtlV3AuthStatus(t *testing.T) { testCtl(t, authStatusTest) } | |||
func TestCtlV3AuthWriteKey(t *testing.T) { testCtl(t, authCredWriteKe yTest) } | func TestCtlV3AuthWriteKey(t *testing.T) { testCtl(t, authCredWriteKe yTest) } | |||
func TestCtlV3AuthRoleUpdate(t *testing.T) { testCtl(t, authRoleUpdateT est) } | func TestCtlV3AuthRoleUpdate(t *testing.T) { testCtl(t, authRoleUpdateT est) } | |||
skipping to change at line 75 | skipping to change at line 80 | |||
func TestCtlV3AuthDefrag(t *testing.T) { testCtl(t, authTestDefrag) } | func TestCtlV3AuthDefrag(t *testing.T) { testCtl(t, authTestDefrag) } | |||
func TestCtlV3AuthEndpointHealth(t *testing.T) { | func TestCtlV3AuthEndpointHealth(t *testing.T) { | |||
testCtl(t, authTestEndpointHealth, withQuorum()) | testCtl(t, authTestEndpointHealth, withQuorum()) | |||
} | } | |||
func TestCtlV3AuthSnapshot(t *testing.T) { testCtl(t, authTestSnapsho t) } | func TestCtlV3AuthSnapshot(t *testing.T) { testCtl(t, authTestSnapsho t) } | |||
func TestCtlV3AuthSnapshotJWT(t *testing.T) { testCtl(t, authTestSnapsho t, withCfg(*newConfigJWT())) } | func TestCtlV3AuthSnapshotJWT(t *testing.T) { testCtl(t, authTestSnapsho t, withCfg(*newConfigJWT())) } | |||
func TestCtlV3AuthJWTExpire(t *testing.T) { testCtl(t, authTestJWTExpi re, withCfg(*newConfigJWT())) } | func TestCtlV3AuthJWTExpire(t *testing.T) { testCtl(t, authTestJWTExpi re, withCfg(*newConfigJWT())) } | |||
func TestCtlV3AuthRevisionConsistency(t *testing.T) { testCtl(t, authTestRevisio nConsistency) } | func TestCtlV3AuthRevisionConsistency(t *testing.T) { testCtl(t, authTestRevisio nConsistency) } | |||
func TestCtlV3AuthTestCacheReload(t *testing.T) { testCtl(t, authTestCacheRe load) } | func TestCtlV3AuthTestCacheReload(t *testing.T) { testCtl(t, authTestCacheRe load) } | |||
func TestCtlV3AuthRecoverFromSnapshot(t *testing.T) { | ||||
testCtl(t, authTestRecoverSnapshot, withCfg(*newConfigNoTLS()), withQuoru | ||||
m(), withSnapshotCount(5)) | ||||
} | ||||
func authEnableTest(cx ctlCtx) { | func authEnableTest(cx ctlCtx) { | |||
if err := authEnable(cx); err != nil { | if err := authEnable(cx); err != nil { | |||
cx.t.Fatal(err) | cx.t.Fatal(err) | |||
} | } | |||
} | } | |||
func authEnable(cx ctlCtx) error { | func authEnable(cx ctlCtx) error { | |||
// create root user with root role | // create root user with root role | |||
if err := ctlV3User(cx, []string{"add", "root", "--interactive=false"}, " User root created", []string{"root"}); err != nil { | if err := ctlV3User(cx, []string{"add", "root", "--interactive=false"}, " User root created", []string{"root"}); err != nil { | |||
return fmt.Errorf("failed to create root user %v", err) | return fmt.Errorf("failed to create root user %v", err) | |||
} | } | |||
if err := ctlV3User(cx, []string{"grant-role", "root", "root"}, "Role roo t is granted to user root", nil); err != nil { | if err := ctlV3User(cx, []string{"grant-role", "root", "root"}, "Role roo t is granted to user root", nil); err != nil { | |||
return fmt.Errorf("failed to grant root user root role %v", err) | return fmt.Errorf("failed to grant root user root role %v", err) | |||
} | } | |||
if err := ctlV3AuthEnable(cx); err != nil { | if err := ctlV3AuthEnable(cx); err != nil { | |||
return fmt.Errorf("authEnableTest ctlV3AuthEnable error (%v)", er r) | return fmt.Errorf("authEnableTest ctlV3AuthEnable error (%v)", er r) | |||
} | } | |||
return nil | return nil | |||
} | } | |||
func applyTLSWithRootCommonName() func() { | ||||
var ( | ||||
oldCertPath = certPath | ||||
oldPrivateKeyPath = privateKeyPath | ||||
oldCaPath = caPath | ||||
newCertPath = filepath.Join(fixturesDir, "CommonName-root.c | ||||
rt") | ||||
newPrivateKeyPath = filepath.Join(fixturesDir, "CommonName-root.k | ||||
ey") | ||||
newCaPath = filepath.Join(fixturesDir, "CommonName-root.c | ||||
rt") | ||||
) | ||||
certPath = newCertPath | ||||
privateKeyPath = newPrivateKeyPath | ||||
caPath = newCaPath | ||||
return func() { | ||||
certPath = oldCertPath | ||||
privateKeyPath = oldPrivateKeyPath | ||||
caPath = oldCaPath | ||||
} | ||||
} | ||||
func ctlV3AuthEnable(cx ctlCtx) error { | func ctlV3AuthEnable(cx ctlCtx) error { | |||
cmdArgs := append(cx.PrefixArgs(), "auth", "enable") | cmdArgs := append(cx.PrefixArgs(), "auth", "enable") | |||
return spawnWithExpectWithEnv(cmdArgs, cx.envMap, "Authentication Enabled ") | return spawnWithExpectWithEnv(cmdArgs, cx.envMap, "Authentication Enabled ") | |||
} | } | |||
func authDisableTest(cx ctlCtx) { | func authDisableTest(cx ctlCtx) { | |||
// a key that isn't granted to test-user | // a key that isn't granted to test-user | |||
if err := ctlV3Put(cx, "hoo", "a", ""); err != nil { | if err := ctlV3Put(cx, "hoo", "a", ""); err != nil { | |||
cx.t.Fatal(err) | cx.t.Fatal(err) | |||
} | } | |||
skipping to change at line 1292 | skipping to change at line 1323 | |||
node0.WithStopSignal(syscall.SIGINT) | node0.WithStopSignal(syscall.SIGINT) | |||
if err := node0.Restart(); err != nil { | if err := node0.Restart(); err != nil { | |||
cx.t.Fatal(err) | cx.t.Fatal(err) | |||
} | } | |||
// nothing has changed, but it fails without refreshing cache after resta rt | // nothing has changed, but it fails without refreshing cache after resta rt | |||
if _, err = c2.Put(context.TODO(), "foo", "bar2"); err != nil { | if _, err = c2.Put(context.TODO(), "foo", "bar2"); err != nil { | |||
cx.t.Fatal(err) | cx.t.Fatal(err) | |||
} | } | |||
} | } | |||
// Verify that etcd works after recovering from a snapshot. | ||||
// Refer to https://github.com/etcd-io/etcd/issues/14571. | ||||
func authTestRecoverSnapshot(cx ctlCtx) { | ||||
roles := []authRole{ | ||||
{ | ||||
role: "role0", | ||||
permission: clientv3.PermissionType(clientv3.PermReadWrit | ||||
e), | ||||
key: "foo", | ||||
}, | ||||
} | ||||
users := []authUser{ | ||||
{ | ||||
user: "root", | ||||
pass: "rootPass", | ||||
role: "root", | ||||
}, | ||||
{ | ||||
user: "user0", | ||||
pass: "user0Pass", | ||||
role: "role0", | ||||
}, | ||||
} | ||||
cx.t.Log("setup and enable auth") | ||||
setupAuth(cx, roles, users) | ||||
// create a client with root user | ||||
cx.t.Log("create a client with root user") | ||||
cliRoot, err := clientv3.New(clientv3.Config{Endpoints: cx.epc.EndpointsV | ||||
3(), Username: "root", Password: "rootPass", DialTimeout: 3 * time.Second}) | ||||
if err != nil { | ||||
cx.t.Fatal(err) | ||||
} | ||||
defer cliRoot.Close() | ||||
// write more than SnapshotCount keys, so that at least one snapshot is c | ||||
reated | ||||
cx.t.Log("Write enough key/value to trigger a snapshot") | ||||
for i := 0; i <= 6; i++ { | ||||
if _, err := cliRoot.Put(context.TODO(), fmt.Sprintf("key_%d", i) | ||||
, fmt.Sprintf("value_%d", i)); err != nil { | ||||
cx.t.Fatalf("failed to Put (%v)", err) | ||||
} | ||||
} | ||||
// add a new member into the cluster | ||||
// Refer to https://github.com/etcd-io/etcd/blob/17cb291f1515d0a4d712acdf | ||||
396a1f2874f172bf/tests/e2e/cluster_test.go#L238 | ||||
var ( | ||||
idx = 3 | ||||
name = fmt.Sprintf("test-%d", idx) | ||||
port = cx.cfg.basePort + 5*idx | ||||
curlHost = fmt.Sprintf("localhost:%d", port) | ||||
nodeClientURL = url.URL{Scheme: cx.cfg.clientScheme(), Host: cur | ||||
lHost} | ||||
nodePeerURL = url.URL{Scheme: cx.cfg.peerScheme(), Host: fmt.S | ||||
printf("localhost:%d", port+1)} | ||||
initialCluster = cx.epc.procs[0].Config().initialCluster + "," + | ||||
fmt.Sprintf("%s=%s", name, nodePeerURL.String()) | ||||
) | ||||
cx.t.Logf("Adding a new member: %s", nodePeerURL.String()) | ||||
// Must wait at least 5 seconds, otherwise it will always get an | ||||
// "etcdserver: unhealthy cluster" response, please refer to link below, | ||||
// https://github.com/etcd-io/etcd/blob/17cb291f1515d0a4d712acdf396a1f287 | ||||
4f172bf/server/etcdserver/server.go#L1611 | ||||
assert.Eventually(cx.t, func() bool { | ||||
if _, err := cliRoot.MemberAdd(context.TODO(), []string{nodePeerU | ||||
RL.String()}); err != nil { | ||||
cx.t.Logf("Failed to add member, peelURL: %s, error: %v", | ||||
nodePeerURL.String(), err) | ||||
return false | ||||
} | ||||
return true | ||||
}, 8*time.Second, 2*time.Second) | ||||
cx.t.Logf("Starting the new member: %s", nodePeerURL.String()) | ||||
newProc, err := runEtcdNode(name, cx.t.TempDir(), nodeClientURL.String(), | ||||
nodePeerURL.String(), "existing", initialCluster) | ||||
require.NoError(cx.t, err) | ||||
defer newProc.Stop() | ||||
// create a client with user "user0", and connects to the new member | ||||
cx.t.Log("create a client with user 'user0'") | ||||
cliUser, err := clientv3.New(clientv3.Config{Endpoints: []string{nodeClie | ||||
ntURL.String()}, Username: "user0", Password: "user0Pass", DialTimeout: 3 * time | ||||
.Second}) | ||||
if err != nil { | ||||
cx.t.Fatal(err) | ||||
} | ||||
defer cliUser.Close() | ||||
// write data using the cliUser, expect no error | ||||
cx.t.Log("Write a key/value using user 'user0'") | ||||
_, err = cliUser.Put(context.TODO(), "foo", "bar") | ||||
require.NoError(cx.t, err) | ||||
//verify all nodes have the same revision and hash | ||||
var endpoints []string | ||||
for _, proc := range cx.epc.procs { | ||||
endpoints = append(endpoints, proc.Config().acurl) | ||||
} | ||||
endpoints = append(endpoints, nodeClientURL.String()) | ||||
cx.t.Log("Verify all members have the same revision and hash") | ||||
assert.Eventually(cx.t, func() bool { | ||||
hashKvs, err := hashKVs(endpoints, cliRoot) | ||||
if err != nil { | ||||
cx.t.Logf("failed to get HashKV: %v", err) | ||||
return false | ||||
} | ||||
if len(hashKvs) != 4 { | ||||
cx.t.Logf("expected 4 hashkv responses, but got: %d", len | ||||
(hashKvs)) | ||||
return false | ||||
} | ||||
if !(hashKvs[0].Header.Revision == hashKvs[1].Header.Revision && | ||||
hashKvs[0].Header.Revision == hashKvs[2].Header.Revision | ||||
&& | ||||
hashKvs[0].Header.Revision == hashKvs[3].Header.Revision) | ||||
{ | ||||
cx.t.Logf("Got different revisions, [%d, %d, %d, %d]", | ||||
hashKvs[0].Header.Revision, | ||||
hashKvs[1].Header.Revision, | ||||
hashKvs[2].Header.Revision, | ||||
hashKvs[3].Header.Revision) | ||||
return false | ||||
} | ||||
assert.Equal(cx.t, hashKvs[0].Hash, hashKvs[1].Hash) | ||||
assert.Equal(cx.t, hashKvs[0].Hash, hashKvs[2].Hash) | ||||
assert.Equal(cx.t, hashKvs[0].Hash, hashKvs[3].Hash) | ||||
return true | ||||
}, 5*time.Second, 100*time.Millisecond) | ||||
} | ||||
type authRole struct { | ||||
role string | ||||
permission clientv3.PermissionType | ||||
key string | ||||
keyEnd string | ||||
} | ||||
type authUser struct { | ||||
user string | ||||
pass string | ||||
role string | ||||
} | ||||
func setupAuth(cx ctlCtx, roles []authRole, users []authUser) { | ||||
endpoint := cx.epc.procs[0].EndpointsV3()[0] | ||||
// create a client | ||||
c, err := clientv3.New(clientv3.Config{Endpoints: []string{endpoint}, Dia | ||||
lTimeout: 3 * time.Second}) | ||||
if err != nil { | ||||
cx.t.Fatal(err) | ||||
} | ||||
defer c.Close() | ||||
// create roles | ||||
for _, r := range roles { | ||||
// add role | ||||
if _, err = c.RoleAdd(context.TODO(), r.role); err != nil { | ||||
cx.t.Fatal(err) | ||||
} | ||||
// grant permission to role | ||||
if _, err = c.RoleGrantPermission(context.TODO(), r.role, r.key, | ||||
r.keyEnd, r.permission); err != nil { | ||||
cx.t.Fatal(err) | ||||
} | ||||
} | ||||
// create users | ||||
for _, u := range users { | ||||
// add user | ||||
if _, err = c.UserAdd(context.TODO(), u.user, u.pass); err != nil | ||||
{ | ||||
cx.t.Fatal(err) | ||||
} | ||||
// grant role to user | ||||
if _, err = c.UserGrantRole(context.TODO(), u.user, u.role); err | ||||
!= nil { | ||||
cx.t.Fatal(err) | ||||
} | ||||
} | ||||
// enable auth | ||||
if _, err = c.AuthEnable(context.TODO()); err != nil { | ||||
cx.t.Fatal(err) | ||||
} | ||||
} | ||||
func hashKVs(endpoints []string, cli *clientv3.Client) ([]*clientv3.HashKVRespon | ||||
se, error) { | ||||
var retHashKVs []*clientv3.HashKVResponse | ||||
for _, ep := range endpoints { | ||||
resp, err := cli.HashKV(context.TODO(), ep, 0) | ||||
if err != nil { | ||||
return nil, err | ||||
} | ||||
retHashKVs = append(retHashKVs, resp) | ||||
} | ||||
return retHashKVs, nil | ||||
} | ||||
End of changes. 6 change blocks. | ||||
0 lines changed or deleted | 35 lines changed or added |