diff --git a/e2e/ctl_v2_test.go b/e2e/ctl_v2_test.go index 84e93e733..e2356933e 100644 --- a/e2e/ctl_v2_test.go +++ b/e2e/ctl_v2_test.go @@ -158,7 +158,6 @@ func testCtlV2Watch(t *testing.T, cfg *etcdProcessClusterConfig, noSync bool) { func TestCtlV2GetRoleUser(t *testing.T) { testCtlV2GetRoleUser(t, &configNoTLS) } func TestCtlV2GetRoleUserWithProxy(t *testing.T) { testCtlV2GetRoleUser(t, &configWithProxy) } - func testCtlV2GetRoleUser(t *testing.T, cfg *etcdProcessClusterConfig) { defer testutil.AfterTest(t) @@ -181,6 +180,7 @@ func testCtlV2GetRoleUser(t *testing.T, cfg *etcdProcessClusterConfig) { if err := etcdctlUserGet(epc, "username"); err != nil { t.Fatalf("failed to get user (%v)", err) } + // ensure double grant gives an error; was crashing in 2.3.1 regrantArgs := etcdctlPrefixArgs(epc) regrantArgs = append(regrantArgs, "user", "grant", "--roles", "foo", "username") @@ -191,7 +191,6 @@ func testCtlV2GetRoleUser(t *testing.T, cfg *etcdProcessClusterConfig) { func TestCtlV2UserListUsername(t *testing.T) { testCtlV2UserList(t, "username") } func TestCtlV2UserListRoot(t *testing.T) { testCtlV2UserList(t, "root") } - func testCtlV2UserList(t *testing.T, username string) { defer testutil.AfterTest(t) @@ -325,6 +324,11 @@ func etcdctlUserList(clus *etcdProcessCluster, expectedUser string) error { return spawnWithExpect(cmdArgs, expectedUser) } +func etcdctlAuthEnable(clus *etcdProcessCluster) error { + cmdArgs := append(etcdctlPrefixArgs(clus), "auth", "enable") + return spawnWithExpect(cmdArgs, "Authentication Enabled") +} + func mustEtcdctl(t *testing.T) { if !fileutil.Exist("../bin/etcdctl") { t.Fatalf("could not find etcdctl binary") diff --git a/e2e/v2_curl_test.go b/e2e/v2_curl_test.go index d804e1ceb..ad1c8487c 100644 --- a/e2e/v2_curl_test.go +++ b/e2e/v2_curl_test.go @@ -15,7 +15,9 @@ package e2e import ( + "fmt" "math/rand" + "strings" "testing" "github.com/coreos/etcd/pkg/testutil" @@ -30,7 +32,6 @@ func TestV2CurlProxyNoTLS(t *testing.T) { testCurlPutGet(t, &configWithProxy) func TestV2CurlProxyTLS(t *testing.T) { testCurlPutGet(t, &configWithProxyTLS) } func TestV2CurlProxyPeerTLS(t *testing.T) { testCurlPutGet(t, &configWithProxyPeerTLS) } func TestV2CurlClientBoth(t *testing.T) { testCurlPutGet(t, &configClientBoth) } - func testCurlPutGet(t *testing.T, cfg *etcdProcessClusterConfig) { defer testutil.AfterTest(t) @@ -48,66 +49,124 @@ func testCurlPutGet(t *testing.T, cfg *etcdProcessClusterConfig) { } }() - expectPut := `{"action":"set","node":{"key":"/testKey","value":"foo","` - expectGet := `{"action":"get","node":{"key":"/testKey","value":"foo","` - + var ( + expectPut = `{"action":"set","node":{"key":"/foo","value":"bar","` + expectGet = `{"action":"get","node":{"key":"/foo","value":"bar","` + ) + if err := cURLPut(epc, cURLReq{endpoint: "/v2/keys/foo", value: "bar", expected: expectPut}); err != nil { + t.Fatalf("failed put with curl (%v)", err) + } + if err := cURLGet(epc, cURLReq{endpoint: "/v2/keys/foo", expected: expectGet}); err != nil { + t.Fatalf("failed get with curl (%v)", err) + } if cfg.clientTLS == clientTLSAndNonTLS { - if err := cURLPut(epc, "testKey", "foo", expectPut); err != nil { - t.Fatalf("failed put with curl (%v)", err) - } - - if err := cURLGet(epc, "testKey", expectGet); err != nil { - t.Fatalf("failed get with curl (%v)", err) - } - if err := cURLGetUseTLS(epc, "testKey", expectGet); err != nil { - t.Fatalf("failed get with curl (%v)", err) - } - } else { - if err := cURLPut(epc, "testKey", "foo", expectPut); err != nil { - t.Fatalf("failed put with curl (%v)", err) - } - - if err := cURLGet(epc, "testKey", expectGet); err != nil { + if err := cURLGet(epc, cURLReq{endpoint: "/v2/keys/foo", expected: expectGet, isTLS: true}); err != nil { t.Fatalf("failed get with curl (%v)", err) } } } +func TestV2CurlIssue5182(t *testing.T) { + defer testutil.AfterTest(t) + + epc := setupEtcdctlTest(t, &configNoTLS, false) + defer func() { + if err := epc.Close(); err != nil { + t.Fatalf("error closing etcd processes (%v)", err) + } + }() + + expectPut := `{"action":"set","node":{"key":"/foo","value":"bar","` + if err := cURLPut(epc, cURLReq{endpoint: "/v2/keys/foo", value: "bar", expected: expectPut}); err != nil { + t.Fatal(err) + } + + expectUserAdd := `{"user":"foo","roles":null}` + if err := cURLPut(epc, cURLReq{endpoint: "/v2/auth/users/foo", value: `{"user":"foo", "password":"pass"}`, expected: expectUserAdd}); err != nil { + t.Fatal(err) + } + expectRoleAdd := `{"role":"foo","permissions":{"kv":{"read":["/foo/*"],"write":null}}` + if err := cURLPut(epc, cURLReq{endpoint: "/v2/auth/roles/foo", value: `{"role":"foo", "permissions": {"kv": {"read": ["/foo/*"]}}}`, expected: expectRoleAdd}); err != nil { + t.Fatal(err) + } + expectUserUpdate := `{"user":"foo","roles":["foo"]}` + if err := cURLPut(epc, cURLReq{endpoint: "/v2/auth/users/foo", value: `{"user": "foo", "grant": ["foo"]}`, expected: expectUserUpdate}); err != nil { + t.Fatal(err) + } + + if err := etcdctlUserAdd(epc, "root", "a"); err != nil { + t.Fatal(err) + } + if err := etcdctlAuthEnable(epc); err != nil { + t.Fatal(err) + } + + if err := cURLGet(epc, cURLReq{endpoint: "/v2/keys/foo/", username: "root", password: "a", expected: "bar"}); err != nil { + t.Fatal(err) + } + if err := cURLGet(epc, cURLReq{endpoint: "/v2/keys/foo/", username: "foo", password: "pass", expected: "bar"}); err != nil { + t.Fatal(err) + } + if err := cURLGet(epc, cURLReq{endpoint: "/v2/keys/foo/", username: "foo", password: "", expected: "bar"}); err != nil { + if !strings.Contains(err.Error(), `The request requires user authentication`) { + t.Fatalf("expected 'The request requires user authentication' error, got %v", err) + } + } else { + t.Fatalf("expected 'The request requires user authentication' error") + } +} + +type cURLReq struct { + username string + password string + + isTLS bool + + endpoint string + + value string + expected string +} + // cURLPrefixArgs builds the beginning of a curl command for a given key // addressed to a random URL in the given cluster. -func cURLPrefixArgs(clus *etcdProcessCluster, key string) []string { - cmdArgs := []string{"curl"} - acurl := clus.procs[rand.Intn(clus.cfg.clusterSize)].cfg.acurl - - if clus.cfg.clientTLS == clientTLS { +func cURLPrefixArgs(clus *etcdProcessCluster, method string, req cURLReq) []string { + var ( + cmdArgs = []string{"curl"} + acurl = clus.procs[rand.Intn(clus.cfg.clusterSize)].cfg.acurl + ) + if req.isTLS { + if clus.cfg.clientTLS != clientTLSAndNonTLS { + panic("should not use cURLPrefixArgsUseTLS when serving only TLS or non-TLS") + } + cmdArgs = append(cmdArgs, "--cacert", caPath, "--cert", certPath, "--key", privateKeyPath) + acurl = clus.procs[rand.Intn(clus.cfg.clusterSize)].cfg.acurltls + } else if clus.cfg.clientTLS == clientTLS { cmdArgs = append(cmdArgs, "--cacert", caPath, "--cert", certPath, "--key", privateKeyPath) } - keyURL := acurl + "/v2/keys/testKey" - cmdArgs = append(cmdArgs, "-L", keyURL) - return cmdArgs -} + ep := acurl + req.endpoint -func cURLPrefixArgsUseTLS(clus *etcdProcessCluster, key string) []string { - cmdArgs := []string{"curl"} - if clus.cfg.clientTLS != clientTLSAndNonTLS { - panic("should not use cURLPrefixArgsUseTLS when serving only TLS or non-TLS") + if req.username != "" || req.password != "" { + cmdArgs = append(cmdArgs, "-L", "-u", fmt.Sprintf("%s:%s", req.username, req.password), ep) + } else { + cmdArgs = append(cmdArgs, "-L", ep) + } + + switch method { + case "PUT": + dt := req.value + if !strings.HasPrefix(dt, "{") { // for non-JSON value + dt = "value=" + dt + } + cmdArgs = append(cmdArgs, "-XPUT", "-d", dt) } - cmdArgs = append(cmdArgs, "--cacert", caPath, "--cert", certPath, "--key", privateKeyPath) - acurl := clus.procs[rand.Intn(clus.cfg.clusterSize)].cfg.acurltls - keyURL := acurl + "/v2/keys/testKey" - cmdArgs = append(cmdArgs, "-L", keyURL) return cmdArgs } -func cURLPut(clus *etcdProcessCluster, key, val, expected string) error { - args := append(cURLPrefixArgs(clus, key), "-XPUT", "-d", "value="+val) - return spawnWithExpect(args, expected) +func cURLPut(clus *etcdProcessCluster, req cURLReq) error { + return spawnWithExpect(cURLPrefixArgs(clus, "PUT", req), req.expected) } -func cURLGet(clus *etcdProcessCluster, key, expected string) error { - return spawnWithExpect(cURLPrefixArgs(clus, key), expected) -} - -func cURLGetUseTLS(clus *etcdProcessCluster, key, expected string) error { - return spawnWithExpect(cURLPrefixArgsUseTLS(clus, key), expected) +func cURLGet(clus *etcdProcessCluster, req cURLReq) error { + return spawnWithExpect(cURLPrefixArgs(clus, "GET", req), req.expected) }