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