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