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	"go.etcd.io/etcd/auth/authpb"
26	epb "go.etcd.io/etcd/etcdserver/api/v3election/v3electionpb"
27	"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
28	pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
29	"go.etcd.io/etcd/pkg/testutil"
30
31	"github.com/grpc-ecosystem/grpc-gateway/runtime"
32)
33
34// TODO: remove /v3beta tests in 3.5 release
35var apiPrefix = []string{"/v3", "/v3beta"}
36
37func TestV3CurlPutGetNoTLS(t *testing.T) {
38	for _, p := range apiPrefix {
39		testCtl(t, testV3CurlPutGet, withApiPrefix(p), withCfg(configNoTLS))
40	}
41}
42func TestV3CurlPutGetAutoTLS(t *testing.T) {
43	for _, p := range apiPrefix {
44		testCtl(t, testV3CurlPutGet, withApiPrefix(p), withCfg(configAutoTLS))
45	}
46}
47func TestV3CurlPutGetAllTLS(t *testing.T) {
48	for _, p := range apiPrefix {
49		testCtl(t, testV3CurlPutGet, withApiPrefix(p), withCfg(configTLS))
50	}
51}
52func TestV3CurlPutGetPeerTLS(t *testing.T) {
53	for _, p := range apiPrefix {
54		testCtl(t, testV3CurlPutGet, withApiPrefix(p), withCfg(configPeerTLS))
55	}
56}
57func TestV3CurlPutGetClientTLS(t *testing.T) {
58	for _, p := range apiPrefix {
59		testCtl(t, testV3CurlPutGet, withApiPrefix(p), withCfg(configClientTLS))
60	}
61}
62func TestV3CurlWatch(t *testing.T) {
63	for _, p := range apiPrefix {
64		testCtl(t, testV3CurlWatch, withApiPrefix(p))
65	}
66}
67func TestV3CurlTxn(t *testing.T) {
68	for _, p := range apiPrefix {
69		testCtl(t, testV3CurlTxn, withApiPrefix(p))
70	}
71}
72func TestV3CurlAuth(t *testing.T) {
73	for _, p := range apiPrefix {
74		testCtl(t, testV3CurlAuth, withApiPrefix(p))
75	}
76}
77func TestV3CurlAuthClientTLSCertAuth(t *testing.T) {
78	for _, p := range apiPrefix {
79		testCtl(t, testV3CurlAuth, withApiPrefix(p), withCfg(configClientTLSCertAuthWithNoCN))
80	}
81}
82
83func testV3CurlPutGet(cx ctlCtx) {
84	var (
85		key   = []byte("foo")
86		value = []byte("bar") // this will be automatically base64-encoded by Go
87
88		expectPut = `"revision":"`
89		expectGet = `"value":"`
90	)
91	putData, err := json.Marshal(&pb.PutRequest{
92		Key:   key,
93		Value: value,
94	})
95	if err != nil {
96		cx.t.Fatal(err)
97	}
98	rangeData, err := json.Marshal(&pb.RangeRequest{
99		Key: key,
100	})
101	if err != nil {
102		cx.t.Fatal(err)
103	}
104
105	p := cx.apiPrefix
106
107	if err := cURLPost(cx.epc, cURLReq{endpoint: path.Join(p, "/kv/put"), value: string(putData), expected: expectPut}); err != nil {
108		cx.t.Fatalf("failed testV3CurlPutGet put with curl using prefix (%s) (%v)", p, err)
109	}
110	if err := cURLPost(cx.epc, cURLReq{endpoint: path.Join(p, "/kv/range"), value: string(rangeData), expected: expectGet}); err != nil {
111		cx.t.Fatalf("failed testV3CurlPutGet get with curl using prefix (%s) (%v)", p, err)
112	}
113	if cx.cfg.clientTLS == clientTLSAndNonTLS {
114		if err := cURLPost(cx.epc, cURLReq{endpoint: path.Join(p, "/kv/range"), value: string(rangeData), expected: expectGet, isTLS: true}); err != nil {
115			cx.t.Fatalf("failed testV3CurlPutGet get with curl using prefix (%s) (%v)", p, err)
116		}
117	}
118}
119
120func testV3CurlWatch(cx ctlCtx) {
121	// store "bar" into "foo"
122	putreq, err := json.Marshal(&pb.PutRequest{Key: []byte("foo"), Value: []byte("bar")})
123	if err != nil {
124		cx.t.Fatal(err)
125	}
126	// watch for first update to "foo"
127	wcr := &pb.WatchCreateRequest{Key: []byte("foo"), StartRevision: 1}
128	wreq, err := json.Marshal(wcr)
129	if err != nil {
130		cx.t.Fatal(err)
131	}
132	// marshaling the grpc to json gives:
133	// "{"RequestUnion":{"CreateRequest":{"key":"Zm9v","start_revision":1}}}"
134	// but the gprc-gateway expects a different format..
135	wstr := `{"create_request" : ` + string(wreq) + "}"
136	p := cx.apiPrefix
137
138	if err = cURLPost(cx.epc, cURLReq{endpoint: path.Join(p, "/kv/put"), value: string(putreq), expected: "revision"}); err != nil {
139		cx.t.Fatalf("failed testV3CurlWatch put with curl using prefix (%s) (%v)", p, err)
140	}
141	// expects "bar", timeout after 2 seconds since stream waits forever
142	if err = cURLPost(cx.epc, cURLReq{endpoint: path.Join(p, "/watch"), value: wstr, expected: `"YmFy"`, timeout: 2}); err != nil {
143		cx.t.Fatalf("failed testV3CurlWatch watch with curl using prefix (%s) (%v)", p, err)
144	}
145}
146
147func testV3CurlTxn(cx ctlCtx) {
148	txn := &pb.TxnRequest{
149		Compare: []*pb.Compare{
150			{
151				Key:         []byte("foo"),
152				Result:      pb.Compare_EQUAL,
153				Target:      pb.Compare_CREATE,
154				TargetUnion: &pb.Compare_CreateRevision{CreateRevision: 0},
155			},
156		},
157		Success: []*pb.RequestOp{
158			{
159				Request: &pb.RequestOp_RequestPut{
160					RequestPut: &pb.PutRequest{
161						Key:   []byte("foo"),
162						Value: []byte("bar"),
163					},
164				},
165			},
166		},
167	}
168	m := &runtime.JSONPb{}
169	jsonDat, jerr := m.Marshal(txn)
170	if jerr != nil {
171		cx.t.Fatal(jerr)
172	}
173	expected := `"succeeded":true,"responses":[{"response_put":{"header":{"revision":"2"}}}]`
174	p := cx.apiPrefix
175	if err := cURLPost(cx.epc, cURLReq{endpoint: path.Join(p, "/kv/txn"), value: string(jsonDat), expected: expected}); err != nil {
176		cx.t.Fatalf("failed testV3CurlTxn txn with curl using prefix (%s) (%v)", p, err)
177	}
178
179	// was crashing etcd server
180	malformed := `{"compare":[{"result":0,"target":1,"key":"Zm9v","TargetUnion":null}],"success":[{"Request":{"RequestPut":{"key":"Zm9v","value":"YmFy"}}}]}`
181	if err := cURLPost(cx.epc, cURLReq{endpoint: path.Join(p, "/kv/txn"), value: malformed, expected: "error"}); err != nil {
182		cx.t.Fatalf("failed testV3CurlTxn put with curl using prefix (%s) (%v)", p, err)
183	}
184
185}
186
187func testV3CurlAuth(cx ctlCtx) {
188	p := cx.apiPrefix
189	usernames := []string{"root", "nonroot", "nooption"}
190	pwds := []string{"toor", "pass", "pass"}
191	options := []*authpb.UserAddOptions{{NoPassword: false}, {NoPassword: false}, nil}
192
193	// create users
194	for i := 0; i < len(usernames); i++ {
195		user, err := json.Marshal(&pb.AuthUserAddRequest{Name: usernames[i], Password: pwds[i], Options: options[i]})
196		testutil.AssertNil(cx.t, err)
197
198		if err = cURLPost(cx.epc, cURLReq{endpoint: path.Join(p, "/auth/user/add"), value: string(user), expected: "revision"}); err != nil {
199			cx.t.Fatalf("failed testV3CurlAuth add user %v with curl (%v)", usernames[i], err)
200		}
201	}
202
203	// create root role
204	rolereq, err := json.Marshal(&pb.AuthRoleAddRequest{Name: string("root")})
205	testutil.AssertNil(cx.t, err)
206
207	if err = cURLPost(cx.epc, cURLReq{endpoint: path.Join(p, "/auth/role/add"), value: string(rolereq), expected: "revision"}); err != nil {
208		cx.t.Fatalf("failed testV3CurlAuth create role with curl using prefix (%s) (%v)", p, err)
209	}
210
211	//grant root role
212	for i := 0; i < len(usernames); i++ {
213		grantroleroot, err := json.Marshal(&pb.AuthUserGrantRoleRequest{User: usernames[i], Role: "root"})
214		testutil.AssertNil(cx.t, err)
215
216		if err = cURLPost(cx.epc, cURLReq{endpoint: path.Join(p, "/auth/user/grant"), value: string(grantroleroot), expected: "revision"}); err != nil {
217			cx.t.Fatalf("failed testV3CurlAuth grant role with curl using prefix (%s) (%v)", p, err)
218		}
219	}
220
221	// enable auth
222	if err = cURLPost(cx.epc, cURLReq{endpoint: path.Join(p, "/auth/enable"), value: string("{}"), expected: "revision"}); err != nil {
223		cx.t.Fatalf("failed testV3CurlAuth enable auth with curl using prefix (%s) (%v)", p, err)
224	}
225
226	for i := 0; i < len(usernames); i++ {
227		// put "bar[i]" into "foo[i]"
228		putreq, err := json.Marshal(&pb.PutRequest{Key: []byte(fmt.Sprintf("foo%d", i)), Value: []byte(fmt.Sprintf("bar%d", i))})
229		testutil.AssertNil(cx.t, err)
230
231		// fail put no auth
232		if err = cURLPost(cx.epc, cURLReq{endpoint: path.Join(p, "/kv/put"), value: string(putreq), expected: "error"}); err != nil {
233			cx.t.Fatalf("failed testV3CurlAuth no auth put with curl using prefix (%s) (%v)", p, err)
234		}
235
236		// auth request
237		authreq, err := json.Marshal(&pb.AuthenticateRequest{Name: usernames[i], Password: pwds[i]})
238		testutil.AssertNil(cx.t, err)
239
240		var (
241			authHeader string
242			cmdArgs    []string
243			lineFunc   = func(txt string) bool { return true }
244		)
245
246		cmdArgs = cURLPrefixArgs(cx.epc, "POST", cURLReq{endpoint: path.Join(p, "/auth/authenticate"), value: string(authreq)})
247		proc, err := spawnCmd(cmdArgs)
248		testutil.AssertNil(cx.t, err)
249
250		cURLRes, err := proc.ExpectFunc(lineFunc)
251		testutil.AssertNil(cx.t, err)
252
253		authRes := make(map[string]interface{})
254		testutil.AssertNil(cx.t, json.Unmarshal([]byte(cURLRes), &authRes))
255
256		token, ok := authRes[rpctypes.TokenFieldNameGRPC].(string)
257		if !ok {
258			cx.t.Fatalf("failed invalid token in authenticate response with curl using user (%v)", usernames[i])
259		}
260
261		authHeader = "Authorization: " + token
262
263		// put with auth
264		if err = cURLPost(cx.epc, cURLReq{endpoint: path.Join(p, "/kv/put"), value: string(putreq), header: authHeader, expected: "revision"}); err != nil {
265			cx.t.Fatalf("failed testV3CurlAuth auth put with curl using prefix (%s) and user (%v) (%v)", p, usernames[i], err)
266		}
267	}
268}
269
270func TestV3CurlCampaignNoTLS(t *testing.T) {
271	for _, p := range apiPrefix {
272		testCtl(t, testV3CurlCampaign, withApiPrefix(p), withCfg(configNoTLS))
273	}
274}
275
276func testV3CurlCampaign(cx ctlCtx) {
277	cdata, err := json.Marshal(&epb.CampaignRequest{
278		Name:  []byte("/election-prefix"),
279		Value: []byte("v1"),
280	})
281	if err != nil {
282		cx.t.Fatal(err)
283	}
284	cargs := cURLPrefixArgs(cx.epc, "POST", cURLReq{
285		endpoint: path.Join(cx.apiPrefix, "/election/campaign"),
286		value:    string(cdata),
287	})
288	lines, err := spawnWithExpectLines(cargs, `"leader":{"name":"`)
289	if err != nil {
290		cx.t.Fatalf("failed post campaign request (%s) (%v)", cx.apiPrefix, err)
291	}
292	if len(lines) != 1 {
293		cx.t.Fatalf("len(lines) expected 1, got %+v", lines)
294	}
295
296	var cresp campaignResponse
297	if err = json.Unmarshal([]byte(lines[0]), &cresp); err != nil {
298		cx.t.Fatalf("failed to unmarshal campaign response %v", err)
299	}
300	ndata, err := base64.StdEncoding.DecodeString(cresp.Leader.Name)
301	if err != nil {
302		cx.t.Fatalf("failed to decode leader key %v", err)
303	}
304	kdata, err := base64.StdEncoding.DecodeString(cresp.Leader.Key)
305	if err != nil {
306		cx.t.Fatalf("failed to decode leader key %v", err)
307	}
308
309	rev, _ := strconv.ParseInt(cresp.Leader.Rev, 10, 64)
310	lease, _ := strconv.ParseInt(cresp.Leader.Lease, 10, 64)
311	pdata, err := json.Marshal(&epb.ProclaimRequest{
312		Leader: &epb.LeaderKey{
313			Name:  ndata,
314			Key:   kdata,
315			Rev:   rev,
316			Lease: lease,
317		},
318		Value: []byte("v2"),
319	})
320	if err != nil {
321		cx.t.Fatal(err)
322	}
323	if err = cURLPost(cx.epc, cURLReq{
324		endpoint: path.Join(cx.apiPrefix, "/election/proclaim"),
325		value:    string(pdata),
326		expected: `"revision":`,
327	}); err != nil {
328		cx.t.Fatalf("failed post proclaim request (%s) (%v)", cx.apiPrefix, err)
329	}
330}
331
332func TestV3CurlProclaimMissiongLeaderKeyNoTLS(t *testing.T) {
333	for _, p := range apiPrefix {
334		testCtl(t, testV3CurlProclaimMissiongLeaderKey, withApiPrefix(p), withCfg(configNoTLS))
335	}
336}
337
338func testV3CurlProclaimMissiongLeaderKey(cx ctlCtx) {
339	pdata, err := json.Marshal(&epb.ProclaimRequest{Value: []byte("v2")})
340	if err != nil {
341		cx.t.Fatal(err)
342	}
343	if err = cURLPost(cx.epc, cURLReq{
344		endpoint: path.Join(cx.apiPrefix, "/election/proclaim"),
345		value:    string(pdata),
346		expected: `{"error":"\"leader\" field must be provided","message":"\"leader\" field must be provided","code":2}`,
347	}); err != nil {
348		cx.t.Fatalf("failed post proclaim request (%s) (%v)", cx.apiPrefix, err)
349	}
350}
351
352func TestV3CurlResignMissiongLeaderKeyNoTLS(t *testing.T) {
353	for _, p := range apiPrefix {
354		testCtl(t, testV3CurlResignMissiongLeaderKey, withApiPrefix(p), withCfg(configNoTLS))
355	}
356}
357
358func testV3CurlResignMissiongLeaderKey(cx ctlCtx) {
359	if err := cURLPost(cx.epc, cURLReq{
360		endpoint: path.Join(cx.apiPrefix, "/election/resign"),
361		value:    `{}`,
362		expected: `{"error":"\"leader\" field must be provided","message":"\"leader\" field must be provided","code":2}`,
363	}); err != nil {
364		cx.t.Fatalf("failed post resign request (%s) (%v)", cx.apiPrefix, err)
365	}
366}
367
368// to manually decode; JSON marshals integer fields with
369// string types, so can't unmarshal with epb.CampaignResponse
370type campaignResponse struct {
371	Leader struct {
372		Name  string `json:"name,omitempty"`
373		Key   string `json:"key,omitempty"`
374		Rev   string `json:"rev,omitempty"`
375		Lease string `json:"lease,omitempty"`
376	} `json:"leader,omitempty"`
377}
378
379func cURLWithExpected(cx ctlCtx, tests []v3cURLTest) error {
380	p := cx.apiPrefix
381	for _, t := range tests {
382		value := fmt.Sprintf("%v", t.value)
383		if err := cURLPost(cx.epc, cURLReq{endpoint: path.Join(p, t.endpoint), value: value, expected: t.expected}); err != nil {
384			return fmt.Errorf("prefix (%s) endpoint (%s): error (%v), wanted %v", p, t.endpoint, err, t.expected)
385		}
386	}
387	return nil
388}
389