1// Copyright 2016 The etcd Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package e2e 16 17import ( 18 "fmt" 19 "math/rand" 20 "strings" 21 "testing" 22 23 "github.com/coreos/etcd/pkg/testutil" 24) 25 26func TestV2CurlNoTLS(t *testing.T) { testCurlPutGet(t, &configNoTLS) } 27func TestV2CurlAutoTLS(t *testing.T) { testCurlPutGet(t, &configAutoTLS) } 28func TestV2CurlAllTLS(t *testing.T) { testCurlPutGet(t, &configTLS) } 29func TestV2CurlPeerTLS(t *testing.T) { testCurlPutGet(t, &configPeerTLS) } 30func TestV2CurlClientTLS(t *testing.T) { testCurlPutGet(t, &configClientTLS) } 31func TestV2CurlClientBoth(t *testing.T) { testCurlPutGet(t, &configClientBoth) } 32func testCurlPutGet(t *testing.T, cfg *etcdProcessClusterConfig) { 33 defer testutil.AfterTest(t) 34 35 // test doesn't use quorum gets, so ensure there are no followers to avoid 36 // stale reads that will break the test 37 cfg = configStandalone(*cfg) 38 39 epc, err := newEtcdProcessCluster(cfg) 40 if err != nil { 41 t.Fatalf("could not start etcd process cluster (%v)", err) 42 } 43 defer func() { 44 if err := epc.Close(); err != nil { 45 t.Fatalf("error closing etcd processes (%v)", err) 46 } 47 }() 48 49 var ( 50 expectPut = `{"action":"set","node":{"key":"/foo","value":"bar","` 51 expectGet = `{"action":"get","node":{"key":"/foo","value":"bar","` 52 ) 53 if err := cURLPut(epc, cURLReq{endpoint: "/v2/keys/foo", value: "bar", expected: expectPut}); err != nil { 54 t.Fatalf("failed put with curl (%v)", err) 55 } 56 if err := cURLGet(epc, cURLReq{endpoint: "/v2/keys/foo", expected: expectGet}); err != nil { 57 t.Fatalf("failed get with curl (%v)", err) 58 } 59 if cfg.clientTLS == clientTLSAndNonTLS { 60 if err := cURLGet(epc, cURLReq{endpoint: "/v2/keys/foo", expected: expectGet, isTLS: true}); err != nil { 61 t.Fatalf("failed get with curl (%v)", err) 62 } 63 } 64} 65 66func TestV2CurlIssue5182(t *testing.T) { 67 defer testutil.AfterTest(t) 68 69 epc := setupEtcdctlTest(t, &configNoTLS, false) 70 defer func() { 71 if err := epc.Close(); err != nil { 72 t.Fatalf("error closing etcd processes (%v)", err) 73 } 74 }() 75 76 expectPut := `{"action":"set","node":{"key":"/foo","value":"bar","` 77 if err := cURLPut(epc, cURLReq{endpoint: "/v2/keys/foo", value: "bar", expected: expectPut}); err != nil { 78 t.Fatal(err) 79 } 80 81 expectUserAdd := `{"user":"foo","roles":null}` 82 if err := cURLPut(epc, cURLReq{endpoint: "/v2/auth/users/foo", value: `{"user":"foo", "password":"pass"}`, expected: expectUserAdd}); err != nil { 83 t.Fatal(err) 84 } 85 expectRoleAdd := `{"role":"foo","permissions":{"kv":{"read":["/foo/*"],"write":null}}` 86 if err := cURLPut(epc, cURLReq{endpoint: "/v2/auth/roles/foo", value: `{"role":"foo", "permissions": {"kv": {"read": ["/foo/*"]}}}`, expected: expectRoleAdd}); err != nil { 87 t.Fatal(err) 88 } 89 expectUserUpdate := `{"user":"foo","roles":["foo"]}` 90 if err := cURLPut(epc, cURLReq{endpoint: "/v2/auth/users/foo", value: `{"user": "foo", "grant": ["foo"]}`, expected: expectUserUpdate}); err != nil { 91 t.Fatal(err) 92 } 93 94 if err := etcdctlUserAdd(epc, "root", "a"); err != nil { 95 t.Fatal(err) 96 } 97 if err := etcdctlAuthEnable(epc); err != nil { 98 t.Fatal(err) 99 } 100 101 if err := cURLGet(epc, cURLReq{endpoint: "/v2/keys/foo/", username: "root", password: "a", expected: "bar"}); err != nil { 102 t.Fatal(err) 103 } 104 if err := cURLGet(epc, cURLReq{endpoint: "/v2/keys/foo/", username: "foo", password: "pass", expected: "bar"}); err != nil { 105 t.Fatal(err) 106 } 107 if err := cURLGet(epc, cURLReq{endpoint: "/v2/keys/foo/", username: "foo", password: "", expected: "bar"}); err != nil { 108 if !strings.Contains(err.Error(), `The request requires user authentication`) { 109 t.Fatalf("expected 'The request requires user authentication' error, got %v", err) 110 } 111 } else { 112 t.Fatalf("expected 'The request requires user authentication' error") 113 } 114} 115 116type cURLReq struct { 117 username string 118 password string 119 120 isTLS bool 121 timeout int 122 123 endpoint string 124 125 value string 126 expected string 127 header string 128 129 metricsURLScheme string 130 131 ciphers string 132} 133 134// cURLPrefixArgs builds the beginning of a curl command for a given key 135// addressed to a random URL in the given cluster. 136func cURLPrefixArgs(clus *etcdProcessCluster, method string, req cURLReq) []string { 137 var ( 138 cmdArgs = []string{"curl"} 139 acurl = clus.procs[rand.Intn(clus.cfg.clusterSize)].Config().acurl 140 ) 141 if req.metricsURLScheme != "https" { 142 if req.isTLS { 143 if clus.cfg.clientTLS != clientTLSAndNonTLS { 144 panic("should not use cURLPrefixArgsUseTLS when serving only TLS or non-TLS") 145 } 146 cmdArgs = append(cmdArgs, "--cacert", caPath, "--cert", certPath, "--key", privateKeyPath) 147 acurl = toTLS(clus.procs[rand.Intn(clus.cfg.clusterSize)].Config().acurl) 148 } else if clus.cfg.clientTLS == clientTLS { 149 cmdArgs = append(cmdArgs, "--cacert", caPath, "--cert", certPath, "--key", privateKeyPath) 150 } 151 } 152 if req.metricsURLScheme != "" { 153 acurl = clus.procs[rand.Intn(clus.cfg.clusterSize)].EndpointsMetrics()[0] 154 } 155 ep := acurl + req.endpoint 156 157 if req.username != "" || req.password != "" { 158 cmdArgs = append(cmdArgs, "-L", "-u", fmt.Sprintf("%s:%s", req.username, req.password), ep) 159 } else { 160 cmdArgs = append(cmdArgs, "-L", ep) 161 } 162 if req.timeout != 0 { 163 cmdArgs = append(cmdArgs, "-m", fmt.Sprintf("%d", req.timeout)) 164 } 165 166 if req.header != "" { 167 cmdArgs = append(cmdArgs, "-H", req.header) 168 } 169 170 if req.ciphers != "" { 171 cmdArgs = append(cmdArgs, "--ciphers", req.ciphers) 172 } 173 174 switch method { 175 case "POST", "PUT": 176 dt := req.value 177 if !strings.HasPrefix(dt, "{") { // for non-JSON value 178 dt = "value=" + dt 179 } 180 cmdArgs = append(cmdArgs, "-X", method, "-d", dt) 181 } 182 return cmdArgs 183} 184 185func cURLPost(clus *etcdProcessCluster, req cURLReq) error { 186 return spawnWithExpect(cURLPrefixArgs(clus, "POST", req), req.expected) 187} 188 189func cURLPut(clus *etcdProcessCluster, req cURLReq) error { 190 return spawnWithExpect(cURLPrefixArgs(clus, "PUT", req), req.expected) 191} 192 193func cURLGet(clus *etcdProcessCluster, req cURLReq) error { 194 return spawnWithExpect(cURLPrefixArgs(clus, "GET", req), req.expected) 195} 196