etcd/tests/e2e/v3_curl_auth_test.go
Ivan Valdes 9ac4f33be4
tests/e2e: address golangci var-naming issues
Signed-off-by: Ivan Valdes <ivan@vald.es>
2024-03-21 16:03:48 -07:00

459 lines
14 KiB
Go

// Copyright 2023 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package e2e
import (
"context"
"encoding/json"
"fmt"
"math/rand"
"testing"
"github.com/stretchr/testify/require"
"go.etcd.io/etcd/api/v3/authpb"
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
"go.etcd.io/etcd/client/pkg/v3/testutil"
"go.etcd.io/etcd/pkg/v3/expect"
"go.etcd.io/etcd/tests/v3/framework/e2e"
)
func TestCurlV3Auth(t *testing.T) {
testCtl(t, testCurlV3Auth)
}
func TestCurlV3AuthClientTLSCertAuth(t *testing.T) {
testCtl(t, testCurlV3Auth, withCfg(*e2e.NewConfigClientTLSCertAuthWithNoCN()))
}
func TestCurlV3AuthUserBasicOperations(t *testing.T) {
testCtl(t, testCurlV3AuthUserBasicOperations)
}
func TestCurlV3AuthUserGrantRevokeRoles(t *testing.T) {
testCtl(t, testCurlV3AuthUserGrantRevokeRoles)
}
func TestCurlV3AuthRoleBasicOperations(t *testing.T) {
testCtl(t, testCurlV3AuthRoleBasicOperations)
}
func TestCurlV3AuthRoleManagePermission(t *testing.T) {
testCtl(t, testCurlV3AuthRoleManagePermission)
}
func TestCurlV3AuthEnableDisableStatus(t *testing.T) {
testCtl(t, testCurlV3AuthEnableDisableStatus)
}
func testCurlV3Auth(cx ctlCtx) {
usernames := []string{"root", "nonroot", "nooption"}
pwds := []string{"toor", "pass", "pass"}
options := []*authpb.UserAddOptions{{NoPassword: false}, {NoPassword: false}, nil}
// create users
for i := 0; i < len(usernames); i++ {
user, err := json.Marshal(&pb.AuthUserAddRequest{Name: usernames[i], Password: pwds[i], Options: options[i]})
testutil.AssertNil(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/user/add",
Value: string(user),
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3Auth failed to add user %v (%v)", usernames[i], err)
}
}
// create root role
rolereq, err := json.Marshal(&pb.AuthRoleAddRequest{Name: "root"})
testutil.AssertNil(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/role/add",
Value: string(rolereq),
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3Auth failed to create role (%v)", err)
}
//grant root role
for i := 0; i < len(usernames); i++ {
grantroleroot, merr := json.Marshal(&pb.AuthUserGrantRoleRequest{User: usernames[i], Role: "root"})
testutil.AssertNil(cx.t, merr)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/user/grant",
Value: string(grantroleroot),
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3Auth failed to grant role (%v)", err)
}
}
// enable auth
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/enable",
Value: "{}",
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3Auth failed to enable auth (%v)", err)
}
for i := 0; i < len(usernames); i++ {
// put "bar[i]" into "foo[i]"
putreq, err := json.Marshal(&pb.PutRequest{Key: []byte(fmt.Sprintf("foo%d", i)), Value: []byte(fmt.Sprintf("bar%d", i))})
testutil.AssertNil(cx.t, err)
// fail put no auth
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/kv/put",
Value: string(putreq),
Expected: expect.ExpectedResponse{Value: "etcdserver: user name is empty"},
}); err != nil {
cx.t.Fatalf("testCurlV3Auth failed to put without token (%v)", err)
}
// auth request
authreq, err := json.Marshal(&pb.AuthenticateRequest{Name: usernames[i], Password: pwds[i]})
testutil.AssertNil(cx.t, err)
var (
authHeader string
cmdArgs []string
lineFunc = func(txt string) bool { return true }
)
cmdArgs = e2e.CURLPrefixArgsCluster(cx.epc.Cfg, cx.epc.Procs[rand.Intn(cx.epc.Cfg.ClusterSize)], "POST", e2e.CURLReq{
Endpoint: "/v3/auth/authenticate",
Value: string(authreq),
})
proc, err := e2e.SpawnCmd(cmdArgs, cx.envMap)
testutil.AssertNil(cx.t, err)
defer proc.Close()
cURLRes, err := proc.ExpectFunc(context.Background(), lineFunc)
testutil.AssertNil(cx.t, err)
authRes := make(map[string]any)
testutil.AssertNil(cx.t, json.Unmarshal([]byte(cURLRes), &authRes))
token, ok := authRes[rpctypes.TokenFieldNameGRPC].(string)
if !ok {
cx.t.Fatalf("failed invalid token in authenticate response using user (%v)", usernames[i])
}
authHeader = "Authorization: " + token
// put with auth
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/kv/put",
Value: string(putreq),
Header: authHeader,
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3Auth failed to auth put with user (%v) (%v)", usernames[i], err)
}
}
}
func testCurlV3AuthUserBasicOperations(cx ctlCtx) {
usernames := []string{"user1", "user2", "user3"}
// create users
for i := 0; i < len(usernames); i++ {
user, err := json.Marshal(&pb.AuthUserAddRequest{Name: usernames[i], Password: "123"})
require.NoError(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/user/add",
Value: string(user),
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthUserBasicOperations failed to add user %v (%v)", usernames[i], err)
}
}
// change password
user, err := json.Marshal(&pb.AuthUserChangePasswordRequest{Name: "user1", Password: "456"})
require.NoError(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/user/changepw",
Value: string(user),
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthUserBasicOperations failed to change user's password(%v)", err)
}
// get users
usernames = []string{"user1", "userX"}
expectedResponse := []string{"revision", "etcdserver: user name not found"}
for i := 0; i < len(usernames); i++ {
user, err = json.Marshal(&pb.AuthUserGetRequest{
Name: usernames[i],
})
require.NoError(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/user/get",
Value: string(user),
Expected: expect.ExpectedResponse{Value: expectedResponse[i]},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthUserBasicOperations failed to get user %v (%v)", usernames[i], err)
}
}
// delete users
usernames = []string{"user2", "userX"}
expectedResponse = []string{"revision", "etcdserver: user name not found"}
for i := 0; i < len(usernames); i++ {
user, err = json.Marshal(&pb.AuthUserDeleteRequest{
Name: usernames[i],
})
require.NoError(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/user/delete",
Value: string(user),
Expected: expect.ExpectedResponse{Value: expectedResponse[i]},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthUserBasicOperations failed to delete user %v (%v)", usernames[i], err)
}
}
// list users
clus := cx.epc
args := e2e.CURLPrefixArgsCluster(clus.Cfg, clus.Procs[rand.Intn(clus.Cfg.ClusterSize)], "POST", e2e.CURLReq{
Endpoint: "/v3/auth/user/list",
Value: "{}",
})
resp, err := runCommandAndReadJSONOutput(args)
require.NoError(cx.t, err)
users, ok := resp["users"]
require.True(cx.t, ok)
userSlice := users.([]any)
require.Equal(cx.t, 2, len(userSlice))
require.Equal(cx.t, "user1", userSlice[0])
require.Equal(cx.t, "user3", userSlice[1])
}
func testCurlV3AuthUserGrantRevokeRoles(cx ctlCtx) {
var (
username = "user1"
rolename = "role1"
)
// create user
user, err := json.Marshal(&pb.AuthUserAddRequest{Name: username, Password: "123"})
require.NoError(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/user/add",
Value: string(user),
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthUserGrantRevokeRoles failed to add user %v (%v)", username, err)
}
// create role
role, err := json.Marshal(&pb.AuthRoleAddRequest{Name: rolename})
require.NoError(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/role/add",
Value: string(role),
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthUserGrantRevokeRoles failed to add role %v (%v)", rolename, err)
}
// grant role to user
grantRoleReq, err := json.Marshal(&pb.AuthUserGrantRoleRequest{
User: username,
Role: rolename,
})
require.NoError(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/user/grant",
Value: string(grantRoleReq),
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthUserGrantRevokeRoles failed to grant role to user (%v)", err)
}
// revoke role from user
revokeRoleReq, err := json.Marshal(&pb.AuthUserRevokeRoleRequest{
Name: username,
Role: rolename,
})
require.NoError(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/user/revoke",
Value: string(revokeRoleReq),
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthUserGrantRevokeRoles failed to revoke role from user (%v)", err)
}
}
func testCurlV3AuthRoleBasicOperations(cx ctlCtx) {
rolenames := []string{"role1", "role2", "role3"}
// create roles
for i := 0; i < len(rolenames); i++ {
role, err := json.Marshal(&pb.AuthRoleAddRequest{Name: rolenames[i]})
require.NoError(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/role/add",
Value: string(role),
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthRoleBasicOperations failed to add role %v (%v)", rolenames[i], err)
}
}
// get roles
rolenames = []string{"role1", "roleX"}
expectedResponse := []string{"revision", "etcdserver: role name not found"}
for i := 0; i < len(rolenames); i++ {
role, err := json.Marshal(&pb.AuthRoleGetRequest{
Role: rolenames[i],
})
require.NoError(cx.t, err)
if err := e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/role/get",
Value: string(role),
Expected: expect.ExpectedResponse{Value: expectedResponse[i]},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthRoleBasicOperations failed to get role %v (%v)", rolenames[i], err)
}
}
// delete roles
rolenames = []string{"role2", "roleX"}
expectedResponse = []string{"revision", "etcdserver: role name not found"}
for i := 0; i < len(rolenames); i++ {
role, err := json.Marshal(&pb.AuthRoleDeleteRequest{
Role: rolenames[i],
})
require.NoError(cx.t, err)
if err := e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/role/delete",
Value: string(role),
Expected: expect.ExpectedResponse{Value: expectedResponse[i]},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthRoleBasicOperations failed to delete role %v (%v)", rolenames[i], err)
}
}
// list roles
clus := cx.epc
args := e2e.CURLPrefixArgsCluster(clus.Cfg, clus.Procs[rand.Intn(clus.Cfg.ClusterSize)], "POST", e2e.CURLReq{
Endpoint: "/v3/auth/role/list",
Value: "{}",
})
resp, err := runCommandAndReadJSONOutput(args)
require.NoError(cx.t, err)
roles, ok := resp["roles"]
require.True(cx.t, ok)
roleSlice := roles.([]any)
require.Equal(cx.t, 2, len(roleSlice))
require.Equal(cx.t, "role1", roleSlice[0])
require.Equal(cx.t, "role3", roleSlice[1])
}
func testCurlV3AuthRoleManagePermission(cx ctlCtx) {
var (
rolename = "role1"
)
// create a role
role, err := json.Marshal(&pb.AuthRoleAddRequest{Name: rolename})
require.NoError(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/role/add",
Value: string(role),
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthRoleManagePermission failed to add role %v (%v)", rolename, err)
}
// grant permission
grantPermissionReq, err := json.Marshal(&pb.AuthRoleGrantPermissionRequest{
Name: rolename,
Perm: &authpb.Permission{
PermType: authpb.READ,
Key: []byte("fakeKey"),
},
})
require.NoError(cx.t, err)
if err = e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/role/grant",
Value: string(grantPermissionReq),
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthRoleManagePermission failed to grant permission to role %v (%v)", rolename, err)
}
// revoke permission
revokePermissionReq, err := json.Marshal(&pb.AuthRoleRevokePermissionRequest{
Role: rolename,
Key: []byte("fakeKey"),
})
require.NoError(cx.t, err)
if err := e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/role/revoke",
Value: string(revokePermissionReq),
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthRoleManagePermission failed to revoke permission from role %v (%v)", rolename, err)
}
}
func testCurlV3AuthEnableDisableStatus(cx ctlCtx) {
// enable auth
if err := e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/enable",
Value: "{}",
Expected: expect.ExpectedResponse{Value: "etcdserver: root user does not exist"},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthEnableDisableStatus failed to enable auth (%v)", err)
}
// disable auth
if err := e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/disable",
Value: "{}",
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthEnableDisableStatus failed to disable auth (%v)", err)
}
// auth status
if err := e2e.CURLPost(cx.epc, e2e.CURLReq{
Endpoint: "/v3/auth/status",
Value: "{}",
Expected: expect.ExpectedResponse{Value: "revision"},
}); err != nil {
cx.t.Fatalf("testCurlV3AuthEnableDisableStatus failed to get auth status (%v)", err)
}
}