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 "encoding/base64" 19 "encoding/json" 20 "fmt" 21 "path" 22 "strconv" 23 "testing" 24 25 epb "github.com/coreos/etcd/etcdserver/api/v3election/v3electionpb" 26 pb "github.com/coreos/etcd/etcdserver/etcdserverpb" 27 "github.com/coreos/etcd/pkg/testutil" 28 "github.com/coreos/etcd/version" 29 30 "github.com/grpc-ecosystem/grpc-gateway/runtime" 31) 32 33// TODO: remove /v3alpha tests in 3.4 release 34 35func TestV3CurlPutGetNoTLSAlpha(t *testing.T) { testCurlPutGetGRPCGateway(t, &configNoTLS, "/v3alpha") } 36func TestV3CurlPutGetNoTLSBeta(t *testing.T) { testCurlPutGetGRPCGateway(t, &configNoTLS, "/v3beta") } 37func TestV3CurlPutGetAutoTLSAlpha(t *testing.T) { 38 testCurlPutGetGRPCGateway(t, &configAutoTLS, "/v3alpha") 39} 40func TestV3CurlPutGetAutoTLSBeta(t *testing.T) { 41 testCurlPutGetGRPCGateway(t, &configAutoTLS, "/v3beta") 42} 43func TestV3CurlPutGetAllTLSAlpha(t *testing.T) { testCurlPutGetGRPCGateway(t, &configTLS, "/v3alpha") } 44func TestV3CurlPutGetAllTLSBeta(t *testing.T) { testCurlPutGetGRPCGateway(t, &configTLS, "/v3beta") } 45func TestV3CurlPutGetPeerTLSAlpha(t *testing.T) { 46 testCurlPutGetGRPCGateway(t, &configPeerTLS, "/v3alpha") 47} 48func TestV3CurlPutGetPeerTLSBeta(t *testing.T) { 49 testCurlPutGetGRPCGateway(t, &configPeerTLS, "/v3beta") 50} 51func TestV3CurlPutGetClientTLSAlpha(t *testing.T) { 52 testCurlPutGetGRPCGateway(t, &configClientTLS, "/v3alpha") 53} 54func TestV3CurlPutGetClientTLSBeta(t *testing.T) { 55 testCurlPutGetGRPCGateway(t, &configClientTLS, "/v3beta") 56} 57func testCurlPutGetGRPCGateway(t *testing.T, cfg *etcdProcessClusterConfig, pathPrefix string) { 58 defer testutil.AfterTest(t) 59 60 epc, err := newEtcdProcessCluster(cfg) 61 if err != nil { 62 t.Fatalf("could not start etcd process cluster (%v)", err) 63 } 64 defer func() { 65 if cerr := epc.Close(); err != nil { 66 t.Fatalf("error closing etcd processes (%v)", cerr) 67 } 68 }() 69 70 var ( 71 key = []byte("foo") 72 value = []byte("bar") // this will be automatically base64-encoded by Go 73 74 expectPut = `"revision":"` 75 expectGet = `"value":"` 76 ) 77 putData, err := json.Marshal(&pb.PutRequest{ 78 Key: key, 79 Value: value, 80 }) 81 if err != nil { 82 t.Fatal(err) 83 } 84 rangeData, err := json.Marshal(&pb.RangeRequest{ 85 Key: key, 86 }) 87 if err != nil { 88 t.Fatal(err) 89 } 90 91 if err := cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/kv/put"), value: string(putData), expected: expectPut}); err != nil { 92 t.Fatalf("failed put with curl (%v)", err) 93 } 94 if err := cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/kv/range"), value: string(rangeData), expected: expectGet}); err != nil { 95 t.Fatalf("failed get with curl (%v)", err) 96 } 97 98 if cfg.clientTLS == clientTLSAndNonTLS { 99 if err := cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/kv/range"), value: string(rangeData), expected: expectGet, isTLS: true}); err != nil { 100 t.Fatalf("failed get with curl (%v)", err) 101 } 102 } 103} 104 105func TestV3CurlWatchAlpha(t *testing.T) { testV3CurlWatch(t, "/v3alpha") } 106func TestV3CurlWatchBeta(t *testing.T) { testV3CurlWatch(t, "/v3beta") } 107func testV3CurlWatch(t *testing.T, pathPrefix string) { 108 defer testutil.AfterTest(t) 109 110 epc, err := newEtcdProcessCluster(&configNoTLS) 111 if err != nil { 112 t.Fatalf("could not start etcd process cluster (%v)", err) 113 } 114 defer func() { 115 if cerr := epc.Close(); err != nil { 116 t.Fatalf("error closing etcd processes (%v)", cerr) 117 } 118 }() 119 120 // store "bar" into "foo" 121 putreq, err := json.Marshal(&pb.PutRequest{Key: []byte("foo"), Value: []byte("bar")}) 122 if err != nil { 123 t.Fatal(err) 124 } 125 if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/kv/put"), value: string(putreq), expected: "revision"}); err != nil { 126 t.Fatalf("failed put with curl (%v)", err) 127 } 128 // watch for first update to "foo" 129 wcr := &pb.WatchCreateRequest{Key: []byte("foo"), StartRevision: 1} 130 wreq, err := json.Marshal(wcr) 131 if err != nil { 132 t.Fatal(err) 133 } 134 // marshaling the grpc to json gives: 135 // "{"RequestUnion":{"CreateRequest":{"key":"Zm9v","start_revision":1}}}" 136 // but the gprc-gateway expects a different format.. 137 wstr := `{"create_request" : ` + string(wreq) + "}" 138 // expects "bar", timeout after 2 seconds since stream waits forever 139 if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/watch"), value: wstr, expected: `"YmFy"`, timeout: 2}); err != nil { 140 t.Fatal(err) 141 } 142} 143 144func TestV3CurlTxnAlpha(t *testing.T) { testV3CurlTxn(t, "/v3alpha") } 145func TestV3CurlTxnBeta(t *testing.T) { testV3CurlTxn(t, "/v3beta") } 146func testV3CurlTxn(t *testing.T, pathPrefix string) { 147 defer testutil.AfterTest(t) 148 epc, err := newEtcdProcessCluster(&configNoTLS) 149 if err != nil { 150 t.Fatalf("could not start etcd process cluster (%v)", err) 151 } 152 defer func() { 153 if cerr := epc.Close(); err != nil { 154 t.Fatalf("error closing etcd processes (%v)", cerr) 155 } 156 }() 157 158 txn := &pb.TxnRequest{ 159 Compare: []*pb.Compare{ 160 { 161 Key: []byte("foo"), 162 Result: pb.Compare_EQUAL, 163 Target: pb.Compare_CREATE, 164 TargetUnion: &pb.Compare_CreateRevision{0}, 165 }, 166 }, 167 Success: []*pb.RequestOp{ 168 { 169 Request: &pb.RequestOp_RequestPut{ 170 RequestPut: &pb.PutRequest{ 171 Key: []byte("foo"), 172 Value: []byte("bar"), 173 }, 174 }, 175 }, 176 }, 177 } 178 m := &runtime.JSONPb{} 179 jsonDat, jerr := m.Marshal(txn) 180 if jerr != nil { 181 t.Fatal(jerr) 182 } 183 expected := `"succeeded":true,"responses":[{"response_put":{"header":{"revision":"2"}}}]` 184 if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/kv/txn"), value: string(jsonDat), expected: expected}); err != nil { 185 t.Fatalf("failed txn with curl (%v)", err) 186 } 187 188 // was crashing etcd server 189 malformed := `{"compare":[{"result":0,"target":1,"key":"Zm9v","TargetUnion":null}],"success":[{"Request":{"RequestPut":{"key":"Zm9v","value":"YmFy"}}}]}` 190 if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/kv/txn"), value: malformed, expected: "error"}); err != nil { 191 t.Fatalf("failed put with curl (%v)", err) 192 } 193} 194 195func TestV3CurlAuthAlpha(t *testing.T) { testV3CurlAuth(t, "/v3alpha") } 196func TestV3CurlAuthBeta(t *testing.T) { testV3CurlAuth(t, "/v3beta") } 197func testV3CurlAuth(t *testing.T, pathPrefix string) { 198 defer testutil.AfterTest(t) 199 epc, err := newEtcdProcessCluster(&configNoTLS) 200 if err != nil { 201 t.Fatalf("could not start etcd process cluster (%v)", err) 202 } 203 defer func() { 204 if cerr := epc.Close(); err != nil { 205 t.Fatalf("error closing etcd processes (%v)", cerr) 206 } 207 }() 208 209 // create root user 210 userreq, err := json.Marshal(&pb.AuthUserAddRequest{Name: string("root"), Password: string("toor")}) 211 testutil.AssertNil(t, err) 212 213 if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/auth/user/add"), value: string(userreq), expected: "revision"}); err != nil { 214 t.Fatalf("failed add user with curl (%v)", err) 215 } 216 217 // create root role 218 rolereq, err := json.Marshal(&pb.AuthRoleAddRequest{Name: string("root")}) 219 testutil.AssertNil(t, err) 220 221 if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/auth/role/add"), value: string(rolereq), expected: "revision"}); err != nil { 222 t.Fatalf("failed create role with curl (%v)", err) 223 } 224 225 // grant root role 226 grantrolereq, err := json.Marshal(&pb.AuthUserGrantRoleRequest{User: string("root"), Role: string("root")}) 227 testutil.AssertNil(t, err) 228 229 if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/auth/user/grant"), value: string(grantrolereq), expected: "revision"}); err != nil { 230 t.Fatalf("failed grant role with curl (%v)", err) 231 } 232 233 // enable auth 234 if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/auth/enable"), value: string("{}"), expected: "revision"}); err != nil { 235 t.Fatalf("failed enable auth with curl (%v)", err) 236 } 237 238 // put "bar" into "foo" 239 putreq, err := json.Marshal(&pb.PutRequest{Key: []byte("foo"), Value: []byte("bar")}) 240 testutil.AssertNil(t, err) 241 242 // fail put no auth 243 if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/kv/put"), value: string(putreq), expected: "error"}); err != nil { 244 t.Fatalf("failed no auth put with curl (%v)", err) 245 } 246 247 // auth request 248 authreq, err := json.Marshal(&pb.AuthenticateRequest{Name: string("root"), Password: string("toor")}) 249 testutil.AssertNil(t, err) 250 251 var ( 252 authHeader string 253 cmdArgs []string 254 lineFunc = func(txt string) bool { return true } 255 ) 256 257 cmdArgs = cURLPrefixArgs(epc, "POST", cURLReq{endpoint: path.Join(pathPrefix, "/auth/authenticate"), value: string(authreq)}) 258 proc, err := spawnCmd(cmdArgs) 259 testutil.AssertNil(t, err) 260 261 cURLRes, err := proc.ExpectFunc(lineFunc) 262 testutil.AssertNil(t, err) 263 264 authRes := make(map[string]interface{}) 265 testutil.AssertNil(t, json.Unmarshal([]byte(cURLRes), &authRes)) 266 267 token, ok := authRes["token"].(string) 268 if !ok { 269 t.Fatalf("failed invalid token in authenticate response with curl") 270 } 271 272 authHeader = "Authorization : " + token 273 274 // put with auth 275 if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/kv/put"), value: string(putreq), header: authHeader, expected: "revision"}); err != nil { 276 t.Fatalf("failed auth put with curl (%v)", err) 277 } 278} 279 280func TestV3CurlCampaignAlpha(t *testing.T) { testV3CurlCampaign(t, "/v3alpha") } 281func TestV3CurlCampaignBeta(t *testing.T) { testV3CurlCampaign(t, "/v3beta") } 282func testV3CurlCampaign(t *testing.T, pathPrefix string) { 283 defer testutil.AfterTest(t) 284 285 epc, err := newEtcdProcessCluster(&configNoTLS) 286 if err != nil { 287 t.Fatalf("could not start etcd process cluster (%v)", err) 288 } 289 defer func() { 290 if cerr := epc.Close(); err != nil { 291 t.Fatalf("error closing etcd processes (%v)", cerr) 292 } 293 }() 294 295 cdata, err := json.Marshal(&epb.CampaignRequest{ 296 Name: []byte("/election-prefix"), 297 Value: []byte("v1"), 298 }) 299 if err != nil { 300 t.Fatal(err) 301 } 302 cargs := cURLPrefixArgs(epc, "POST", cURLReq{ 303 endpoint: path.Join(pathPrefix, "/election/campaign"), 304 value: string(cdata), 305 }) 306 lines, err := spawnWithExpectLines(cargs, `"leader":{"name":"`) 307 if err != nil { 308 t.Fatalf("failed post campaign request (%s) (%v)", pathPrefix, err) 309 } 310 if len(lines) != 1 { 311 t.Fatalf("len(lines) expected 1, got %+v", lines) 312 } 313 314 var cresp campaignResponse 315 if err = json.Unmarshal([]byte(lines[0]), &cresp); err != nil { 316 t.Fatalf("failed to unmarshal campaign response %v", err) 317 } 318 ndata, err := base64.StdEncoding.DecodeString(cresp.Leader.Name) 319 if err != nil { 320 t.Fatalf("failed to decode leader key %v", err) 321 } 322 kdata, err := base64.StdEncoding.DecodeString(cresp.Leader.Key) 323 if err != nil { 324 t.Fatalf("failed to decode leader key %v", err) 325 } 326 327 rev, _ := strconv.ParseInt(cresp.Leader.Rev, 10, 64) 328 lease, _ := strconv.ParseInt(cresp.Leader.Lease, 10, 64) 329 pdata, err := json.Marshal(&epb.ProclaimRequest{ 330 Leader: &epb.LeaderKey{ 331 Name: ndata, 332 Key: kdata, 333 Rev: rev, 334 Lease: lease, 335 }, 336 Value: []byte("v2"), 337 }) 338 if err != nil { 339 t.Fatal(err) 340 } 341 if err = cURLPost(epc, cURLReq{ 342 endpoint: path.Join(pathPrefix, "/election/proclaim"), 343 value: string(pdata), 344 expected: `"revision":`, 345 }); err != nil { 346 t.Fatalf("failed post proclaim request (%s) (%v)", pathPrefix, err) 347 } 348} 349 350func TestV3CurlProclaimMissiongLeaderKeyNoTLS(t *testing.T) { 351 testCtl(t, testV3CurlProclaimMissiongLeaderKey, withCfg(configNoTLS)) 352} 353 354func testV3CurlProclaimMissiongLeaderKey(cx ctlCtx) { 355 pdata, err := json.Marshal(&epb.ProclaimRequest{Value: []byte("v2")}) 356 if err != nil { 357 cx.t.Fatal(err) 358 } 359 if err != nil { 360 cx.t.Fatal(err) 361 } 362 if err = cURLPost(cx.epc, cURLReq{ 363 endpoint: path.Join("/v3beta", "/election/proclaim"), 364 value: string(pdata), 365 expected: `{"error":"\"leader\" field must be provided","code":2}`, 366 }); err != nil { 367 cx.t.Fatalf("failed post proclaim request (%s) (%v)", "/v3beta", err) 368 } 369} 370 371func TestV3CurlResignMissiongLeaderKeyNoTLS(t *testing.T) { 372 testCtl(t, testV3CurlResignMissiongLeaderKey, withCfg(configNoTLS)) 373} 374 375func testV3CurlResignMissiongLeaderKey(cx ctlCtx) { 376 if err := cURLPost(cx.epc, cURLReq{ 377 endpoint: path.Join("/v3beta", "/election/resign"), 378 value: `{}`, 379 expected: `{"error":"\"leader\" field must be provided","code":2}`, 380 }); err != nil { 381 cx.t.Fatalf("failed post resign request (%s) (%v)", "/v3beta", err) 382 } 383} 384 385// to manually decode; JSON marshals integer fields with 386// string types, so can't unmarshal with epb.CampaignResponse 387type campaignResponse struct { 388 Leader struct { 389 Name string `json:"name,omitempty"` 390 Key string `json:"key,omitempty"` 391 Rev string `json:"rev,omitempty"` 392 Lease string `json:"lease,omitempty"` 393 } `json:"leader,omitempty"` 394} 395 396func TestV3CurlCipherSuitesValid(t *testing.T) { testV3CurlCipherSuites(t, true) } 397func TestV3CurlCipherSuitesMismatch(t *testing.T) { testV3CurlCipherSuites(t, false) } 398func testV3CurlCipherSuites(t *testing.T, valid bool) { 399 cc := configClientTLS 400 cc.clusterSize = 1 401 cc.cipherSuites = []string{ 402 "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 403 "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 404 "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 405 "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 406 "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", 407 "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", 408 } 409 testFunc := cipherSuiteTestValid 410 if !valid { 411 testFunc = cipherSuiteTestMismatch 412 } 413 testCtl(t, testFunc, withCfg(cc)) 414} 415 416func cipherSuiteTestValid(cx ctlCtx) { 417 if err := cURLGet(cx.epc, cURLReq{ 418 endpoint: "/metrics", 419 expected: fmt.Sprintf(`etcd_server_version{server_version="%s"} 1`, version.Version), 420 metricsURLScheme: cx.cfg.metricsURLScheme, 421 ciphers: "ECDHE-RSA-AES128-GCM-SHA256", // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 422 }); err != nil { 423 cx.t.Fatalf("failed get with curl (%v)", err) 424 } 425} 426 427func cipherSuiteTestMismatch(cx ctlCtx) { 428 if err := cURLGet(cx.epc, cURLReq{ 429 endpoint: "/metrics", 430 expected: "alert handshake failure", 431 metricsURLScheme: cx.cfg.metricsURLScheme, 432 ciphers: "ECDHE-RSA-DES-CBC3-SHA", // TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA 433 }); err != nil { 434 cx.t.Fatalf("failed get with curl (%v)", err) 435 } 436} 437