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