mirror of https://github.com/status-im/consul.git
5740 lines
152 KiB
Go
5740 lines
152 KiB
Go
package consul
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/rpc"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/consul/acl"
|
|
"github.com/hashicorp/consul/agent/consul/authmethod/kubeauth"
|
|
"github.com/hashicorp/consul/agent/consul/authmethod/testauth"
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
tokenStore "github.com/hashicorp/consul/agent/token"
|
|
"github.com/hashicorp/consul/lib"
|
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
|
"github.com/hashicorp/consul/testrpc"
|
|
uuid "github.com/hashicorp/go-uuid"
|
|
msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestACLEndpoint_Bootstrap(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.Build = "0.8.0" // Too low for auto init of bootstrap.
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
// Expect an error initially since ACL bootstrap is not initialized.
|
|
arg := structs.DCSpecificRequest{
|
|
Datacenter: "dc1",
|
|
}
|
|
var out structs.ACL
|
|
// We can only do some high
|
|
// level checks on the ACL since we don't have control over the UUID or
|
|
// Raft indexes at this level.
|
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Bootstrap", &arg, &out); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if len(out.ID) != len("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") ||
|
|
!strings.HasPrefix(out.Name, "Bootstrap Token") ||
|
|
out.Type != structs.ACLTokenTypeManagement ||
|
|
out.CreateIndex == 0 || out.ModifyIndex == 0 {
|
|
t.Fatalf("bad: %#v", out)
|
|
}
|
|
|
|
// Finally, make sure that another attempt is rejected.
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.Bootstrap", &arg, &out)
|
|
if err.Error() != structs.ACLBootstrapNotAllowedErr.Error() {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_BootstrapTokens(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLsEnabled = true
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
// Expect an error initially since ACL bootstrap is not initialized.
|
|
arg := structs.DCSpecificRequest{
|
|
Datacenter: "dc1",
|
|
}
|
|
var out structs.ACLToken
|
|
// We can only do some high
|
|
// level checks on the ACL since we don't have control over the UUID or
|
|
// Raft indexes at this level.
|
|
require.NoError(t, msgpackrpc.CallWithCodec(codec, "ACL.BootstrapTokens", &arg, &out))
|
|
require.Equal(t, 36, len(out.AccessorID))
|
|
require.True(t, strings.HasPrefix(out.Description, "Bootstrap Token"))
|
|
require.Equal(t, out.Type, structs.ACLTokenTypeManagement)
|
|
require.True(t, out.CreateIndex > 0)
|
|
require.Equal(t, out.CreateIndex, out.ModifyIndex)
|
|
|
|
// Finally, make sure that another attempt is rejected.
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.BootstrapTokens", &arg, &out)
|
|
require.Error(t, err)
|
|
require.True(t, strings.HasPrefix(err.Error(), structs.ACLBootstrapNotAllowedErr.Error()))
|
|
|
|
_, resetIdx, err := s1.fsm.State().CanBootstrapACLToken()
|
|
|
|
resetPath := filepath.Join(dir1, "acl-bootstrap-reset")
|
|
require.NoError(t, ioutil.WriteFile(resetPath, []byte(fmt.Sprintf("%d", resetIdx)), 0600))
|
|
|
|
oldID := out.AccessorID
|
|
// Finally, make sure that another attempt is rejected.
|
|
require.NoError(t, msgpackrpc.CallWithCodec(codec, "ACL.BootstrapTokens", &arg, &out))
|
|
require.Equal(t, 36, len(out.AccessorID))
|
|
require.NotEqual(t, oldID, out.AccessorID)
|
|
require.True(t, strings.HasPrefix(out.Description, "Bootstrap Token"))
|
|
require.Equal(t, out.Type, structs.ACLTokenTypeManagement)
|
|
require.True(t, out.CreateIndex > 0)
|
|
require.Equal(t, out.CreateIndex, out.ModifyIndex)
|
|
}
|
|
|
|
func TestACLEndpoint_Apply(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
arg := structs.ACLRequest{
|
|
Datacenter: "dc1",
|
|
Op: structs.ACLSet,
|
|
ACL: structs.ACL{
|
|
Name: "User token",
|
|
Type: structs.ACLTokenTypeClient,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
var out string
|
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
id := out
|
|
|
|
// Verify
|
|
state := s1.fsm.State()
|
|
_, s, err := state.ACLTokenGetBySecret(nil, out, nil)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if s == nil {
|
|
t.Fatalf("should not be nil")
|
|
}
|
|
if s.SecretID != out {
|
|
t.Fatalf("bad: %v", s)
|
|
}
|
|
if s.Description != "User token" {
|
|
t.Fatalf("bad: %v", s)
|
|
}
|
|
|
|
// Do a delete
|
|
arg.Op = structs.ACLDelete
|
|
arg.ACL.ID = out
|
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Verify
|
|
_, s, err = state.ACLTokenGetBySecret(nil, id, nil)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if s != nil {
|
|
t.Fatalf("bad: %v", s)
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_Update_PurgeCache(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
arg := structs.ACLRequest{
|
|
Datacenter: "dc1",
|
|
Op: structs.ACLSet,
|
|
ACL: structs.ACL{
|
|
Name: "User token",
|
|
Type: structs.ACLTokenTypeClient,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
var out string
|
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
id := out
|
|
|
|
// Resolve
|
|
acl1, err := s1.ResolveToken(id)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if acl1 == nil {
|
|
t.Fatalf("should not be nil")
|
|
}
|
|
if acl1.KeyRead("foo", nil) != acl.Allow {
|
|
t.Fatalf("should be allowed")
|
|
}
|
|
|
|
// Do an update
|
|
arg.ACL.ID = out
|
|
arg.ACL.Rules = `{"key": {"": {"policy": "deny"}}}`
|
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Resolve again
|
|
acl2, err := s1.ResolveToken(id)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if acl2 == nil {
|
|
t.Fatalf("should not be nil")
|
|
}
|
|
if acl2 == acl1 {
|
|
t.Fatalf("should not be cached")
|
|
}
|
|
if acl2.KeyRead("foo", nil) == acl.Allow {
|
|
t.Fatalf("should not be allowed")
|
|
}
|
|
|
|
// Do a delete
|
|
arg.Op = structs.ACLDelete
|
|
arg.ACL.Rules = ""
|
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Resolve again
|
|
acl3, err := s1.ResolveToken(id)
|
|
if !acl.IsErrNotFound(err) {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if acl3 != nil {
|
|
t.Fatalf("should be nil")
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_Apply_CustomID(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
arg := structs.ACLRequest{
|
|
Datacenter: "dc1",
|
|
Op: structs.ACLSet,
|
|
ACL: structs.ACL{
|
|
ID: "foobarbaz", // Specify custom ID, does not exist
|
|
Name: "User token",
|
|
Type: structs.ACLTokenTypeClient,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
var out string
|
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if out != "foobarbaz" {
|
|
t.Fatalf("bad token ID: %s", out)
|
|
}
|
|
|
|
// Verify
|
|
state := s1.fsm.State()
|
|
_, s, err := state.ACLTokenGetBySecret(nil, out, nil)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if s == nil {
|
|
t.Fatalf("should not be nil")
|
|
}
|
|
if s.SecretID != out {
|
|
t.Fatalf("bad: %v", s)
|
|
}
|
|
if s.Description != "User token" {
|
|
t.Fatalf("bad: %v", s)
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_Apply_Denied(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
arg := structs.ACLRequest{
|
|
Datacenter: "dc1",
|
|
Op: structs.ACLSet,
|
|
ACL: structs.ACL{
|
|
Name: "User token",
|
|
Type: structs.ACLTokenTypeClient,
|
|
},
|
|
}
|
|
var out string
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out)
|
|
if !acl.IsErrPermissionDenied(err) {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_Apply_DeleteAnon(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
arg := structs.ACLRequest{
|
|
Datacenter: "dc1",
|
|
Op: structs.ACLDelete,
|
|
ACL: structs.ACL{
|
|
ID: anonymousToken,
|
|
Name: "User token",
|
|
Type: structs.ACLTokenTypeClient,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
var out string
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out)
|
|
if err == nil || !strings.Contains(err.Error(), "delete anonymous") {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_Apply_RootChange(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
arg := structs.ACLRequest{
|
|
Datacenter: "dc1",
|
|
Op: structs.ACLSet,
|
|
ACL: structs.ACL{
|
|
ID: "manage",
|
|
Name: "User token",
|
|
Type: structs.ACLTokenTypeClient,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
var out string
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out)
|
|
if err == nil || !strings.Contains(err.Error(), "root ACL") {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_Get(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
arg := structs.ACLRequest{
|
|
Datacenter: "dc1",
|
|
Op: structs.ACLSet,
|
|
ACL: structs.ACL{
|
|
Name: "User token",
|
|
Type: structs.ACLTokenTypeClient,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
var out string
|
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
getR := structs.ACLSpecificRequest{
|
|
Datacenter: "dc1",
|
|
ACL: out,
|
|
}
|
|
var acls structs.IndexedACLs
|
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Get", &getR, &acls); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if acls.Index == 0 {
|
|
t.Fatalf("Bad: %v", acls)
|
|
}
|
|
if len(acls.ACLs) != 1 {
|
|
t.Fatalf("Bad: %v", acls)
|
|
}
|
|
s := acls.ACLs[0]
|
|
if s.ID != out {
|
|
t.Fatalf("bad: %v", s)
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_GetPolicy(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
arg := structs.ACLRequest{
|
|
Datacenter: "dc1",
|
|
Op: structs.ACLSet,
|
|
ACL: structs.ACL{
|
|
Name: "User token",
|
|
Type: structs.ACLTokenTypeClient,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
var out string
|
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
getR := structs.ACLPolicyResolveLegacyRequest{
|
|
Datacenter: "dc1",
|
|
ACL: out,
|
|
}
|
|
|
|
var acls structs.ACLPolicyResolveLegacyResponse
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicy", &getR, &acls); err != nil {
|
|
r.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if acls.Policy == nil {
|
|
r.Fatalf("Bad: %v", acls)
|
|
}
|
|
if acls.TTL != 30*time.Second {
|
|
r.Fatalf("bad: %v", acls)
|
|
}
|
|
})
|
|
|
|
// Do a conditional lookup with etag
|
|
getR.ETag = acls.ETag
|
|
var out2 structs.ACLPolicyResolveLegacyResponse
|
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicy", &getR, &out2); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if out2.Policy != nil {
|
|
t.Fatalf("Bad: %v", out2)
|
|
}
|
|
if out2.TTL != 30*time.Second {
|
|
t.Fatalf("bad: %v", out2)
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_List(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
ids := []string{}
|
|
for i := 0; i < 5; i++ {
|
|
arg := structs.ACLRequest{
|
|
Datacenter: "dc1",
|
|
Op: structs.ACLSet,
|
|
ACL: structs.ACL{
|
|
Name: "User token",
|
|
Type: structs.ACLTokenTypeClient,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
var out string
|
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
ids = append(ids, out)
|
|
}
|
|
|
|
getR := structs.DCSpecificRequest{
|
|
Datacenter: "dc1",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
var acls structs.IndexedACLs
|
|
if err := msgpackrpc.CallWithCodec(codec, "ACL.List", &getR, &acls); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if acls.Index == 0 {
|
|
t.Fatalf("Bad: %v", acls)
|
|
}
|
|
|
|
// 5 + master
|
|
if len(acls.ACLs) != 6 {
|
|
t.Fatalf("Bad: %v", acls.ACLs)
|
|
}
|
|
for i := 0; i < len(acls.ACLs); i++ {
|
|
s := acls.ACLs[i]
|
|
if s.ID == anonymousToken || s.ID == "root" {
|
|
continue
|
|
}
|
|
if !lib.StrContains(ids, s.ID) {
|
|
t.Fatalf("bad: %v", s)
|
|
}
|
|
if s.Name != "User token" {
|
|
t.Fatalf("bad: %v", s)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_List_Denied(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
getR := structs.DCSpecificRequest{
|
|
Datacenter: "dc1",
|
|
}
|
|
var acls structs.IndexedACLs
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.List", &getR, &acls)
|
|
if !acl.IsErrPermissionDenied(err) {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_ReplicationStatus(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc2"
|
|
c.ACLsEnabled = true
|
|
c.ACLTokenReplication = true
|
|
c.ACLReplicationRate = 100
|
|
c.ACLReplicationBurst = 100
|
|
})
|
|
s1.tokens.UpdateReplicationToken("secret", tokenStore.TokenSourceConfig)
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
getR := structs.DCSpecificRequest{
|
|
Datacenter: "dc1",
|
|
}
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
var status structs.ACLReplicationStatus
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.ReplicationStatus", &getR, &status)
|
|
if err != nil {
|
|
r.Fatalf("err: %v", err)
|
|
}
|
|
if !status.Enabled || !status.Running || status.SourceDatacenter != "dc2" {
|
|
r.Fatalf("bad: %#v", status)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_TokenRead(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
|
|
c.ACLTokenMaxExpirationTTL = 5 * time.Second
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
t.Run("exists and matches what we created", func(t *testing.T) {
|
|
token, err := upsertTestToken(codec, "root", "dc1", nil)
|
|
require.NoError(t, err)
|
|
|
|
req := structs.ACLTokenGetRequest{
|
|
Datacenter: "dc1",
|
|
TokenID: token.AccessorID,
|
|
TokenIDType: structs.ACLTokenAccessor,
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLTokenResponse{}
|
|
|
|
err = acl.TokenRead(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
if !reflect.DeepEqual(resp.Token, token) {
|
|
t.Fatalf("tokens are not equal: %v != %v", resp.Token, token)
|
|
}
|
|
})
|
|
|
|
t.Run("expired tokens are filtered", func(t *testing.T) {
|
|
// insert a token that will expire
|
|
token, err := upsertTestToken(codec, "root", "dc1", func(t *structs.ACLToken) {
|
|
t.ExpirationTTL = 200 * time.Millisecond
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
t.Run("readable until expiration", func(t *testing.T) {
|
|
req := structs.ACLTokenGetRequest{
|
|
Datacenter: "dc1",
|
|
TokenID: token.AccessorID,
|
|
TokenIDType: structs.ACLTokenAccessor,
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLTokenResponse{}
|
|
|
|
require.NoError(t, acl.TokenRead(&req, &resp))
|
|
require.Equal(t, token, resp.Token)
|
|
})
|
|
|
|
t.Run("not returned when expired", func(t *testing.T) {
|
|
req := structs.ACLTokenGetRequest{
|
|
Datacenter: "dc1",
|
|
TokenID: token.AccessorID,
|
|
TokenIDType: structs.ACLTokenAccessor,
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLTokenResponse{}
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
require.NoError(r, acl.TokenRead(&req, &resp))
|
|
require.Nil(r, resp.Token)
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("nil when token does not exist", func(t *testing.T) {
|
|
fakeID, err := uuid.GenerateUUID()
|
|
require.NoError(t, err)
|
|
|
|
req := structs.ACLTokenGetRequest{
|
|
Datacenter: "dc1",
|
|
TokenID: fakeID,
|
|
TokenIDType: structs.ACLTokenAccessor,
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLTokenResponse{}
|
|
|
|
err = acl.TokenRead(&req, &resp)
|
|
require.Nil(t, resp.Token)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("validates ID format", func(t *testing.T) {
|
|
req := structs.ACLTokenGetRequest{
|
|
Datacenter: "dc1",
|
|
TokenID: "definitely-really-certainly-not-a-uuid",
|
|
TokenIDType: structs.ACLTokenAccessor,
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLTokenResponse{}
|
|
|
|
err := acl.TokenRead(&req, &resp)
|
|
require.Nil(t, resp.Token)
|
|
require.EqualError(t, err, "failed acl token lookup: index error: UUID must be 36 characters")
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_TokenClone(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
|
|
c.ACLTokenMaxExpirationTTL = 5 * time.Second
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
t1, err := upsertTestToken(codec, "root", "dc1", nil)
|
|
require.NoError(t, err)
|
|
|
|
endpoint := ACL{srv: s1}
|
|
|
|
t.Run("normal", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{AccessorID: t1.AccessorID},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
t2 := structs.ACLToken{}
|
|
|
|
err = endpoint.TokenClone(&req, &t2)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, t1.Description, t2.Description)
|
|
require.Equal(t, t1.Policies, t2.Policies)
|
|
require.Equal(t, t1.Rules, t2.Rules)
|
|
require.Equal(t, t1.Local, t2.Local)
|
|
require.NotEqual(t, t1.AccessorID, t2.AccessorID)
|
|
require.NotEqual(t, t1.SecretID, t2.SecretID)
|
|
})
|
|
|
|
t.Run("can't clone expired token", func(t *testing.T) {
|
|
// insert a token that will expire
|
|
t1, err := upsertTestToken(codec, "root", "dc1", func(t *structs.ACLToken) {
|
|
t.ExpirationTTL = 11 * time.Millisecond
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
time.Sleep(30 * time.Millisecond)
|
|
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{AccessorID: t1.AccessorID},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
t2 := structs.ACLToken{}
|
|
|
|
err = endpoint.TokenClone(&req, &t2)
|
|
require.Error(t, err)
|
|
require.Equal(t, acl.ErrNotFound, err)
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_TokenSet(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
|
|
c.ACLTokenMaxExpirationTTL = 5 * time.Second
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
var tokenID string
|
|
|
|
t.Run("Create it", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the token directly to validate that it exists
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID)
|
|
require.NoError(t, err)
|
|
token := tokenResp.Token
|
|
|
|
require.NotNil(t, token)
|
|
require.NotNil(t, token.AccessorID)
|
|
require.Equal(t, token.Description, "foobar")
|
|
require.Equal(t, token.AccessorID, resp.AccessorID)
|
|
|
|
tokenID = token.AccessorID
|
|
})
|
|
|
|
t.Run("Update it", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "new-description",
|
|
AccessorID: tokenID,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the token directly to validate that it exists
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID)
|
|
require.NoError(t, err)
|
|
token := tokenResp.Token
|
|
|
|
require.NotNil(t, token)
|
|
require.NotNil(t, token.AccessorID)
|
|
require.Equal(t, token.Description, "new-description")
|
|
require.Equal(t, token.AccessorID, resp.AccessorID)
|
|
})
|
|
|
|
t.Run("Create it using Policies linked by id and name", func(t *testing.T) {
|
|
policy1, err := upsertTestPolicy(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
policy2, err := upsertTestPolicy(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
Policies: []structs.ACLTokenPolicyLink{
|
|
structs.ACLTokenPolicyLink{
|
|
ID: policy1.ID,
|
|
},
|
|
structs.ACLTokenPolicyLink{
|
|
Name: policy2.Name,
|
|
},
|
|
},
|
|
Local: false,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err = acl.TokenSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Delete both policies to ensure that we skip resolving ID->Name
|
|
// in the returned data.
|
|
require.NoError(t, deleteTestPolicy(codec, "root", "dc1", policy1.ID))
|
|
require.NoError(t, deleteTestPolicy(codec, "root", "dc1", policy2.ID))
|
|
|
|
// Get the token directly to validate that it exists
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID)
|
|
require.NoError(t, err)
|
|
token := tokenResp.Token
|
|
|
|
require.NotNil(t, token)
|
|
require.NotNil(t, token.AccessorID)
|
|
require.Equal(t, token.Description, "foobar")
|
|
require.Equal(t, token.AccessorID, resp.AccessorID)
|
|
|
|
require.Len(t, token.Policies, 0)
|
|
})
|
|
|
|
t.Run("Create it using Roles linked by id and name", func(t *testing.T) {
|
|
role1, err := upsertTestRole(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
role2, err := upsertTestRole(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
Roles: []structs.ACLTokenRoleLink{
|
|
structs.ACLTokenRoleLink{
|
|
ID: role1.ID,
|
|
},
|
|
structs.ACLTokenRoleLink{
|
|
Name: role2.Name,
|
|
},
|
|
},
|
|
Local: false,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err = acl.TokenSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Delete both roles to ensure that we skip resolving ID->Name
|
|
// in the returned data.
|
|
require.NoError(t, deleteTestRole(codec, "root", "dc1", role1.ID))
|
|
require.NoError(t, deleteTestRole(codec, "root", "dc1", role2.ID))
|
|
|
|
// Get the token directly to validate that it exists
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID)
|
|
require.NoError(t, err)
|
|
token := tokenResp.Token
|
|
|
|
require.NotNil(t, token)
|
|
require.NotNil(t, token.AccessorID)
|
|
require.Equal(t, token.Description, "foobar")
|
|
require.Equal(t, token.AccessorID, resp.AccessorID)
|
|
|
|
require.Len(t, token.Roles, 0)
|
|
})
|
|
|
|
t.Run("Create it with AuthMethod set outside of login", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
AuthMethod: "fakemethod",
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
requireErrorContains(t, err, "AuthMethod field is disallowed outside of Login")
|
|
})
|
|
|
|
t.Run("Update auth method linked token and try to change auth method", func(t *testing.T) {
|
|
acl := ACL{srv: s1}
|
|
|
|
testSessionID := testauth.StartSession()
|
|
defer testauth.ResetSession(testSessionID)
|
|
testauth.InstallSessionToken(testSessionID, "fake-token", "default", "demo", "abc123")
|
|
|
|
method1, err := upsertTestAuthMethod(codec, "root", "dc1", testSessionID)
|
|
require.NoError(t, err)
|
|
|
|
_, err = upsertTestBindingRule(codec, "root", "dc1", method1.Name, "", structs.BindingRuleBindTypeService, "demo")
|
|
require.NoError(t, err)
|
|
|
|
// create a token in one method
|
|
methodToken := structs.ACLToken{}
|
|
require.NoError(t, acl.Login(&structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method1.Name,
|
|
BearerToken: "fake-token",
|
|
},
|
|
Datacenter: "dc1",
|
|
}, &methodToken))
|
|
|
|
method2, err := upsertTestAuthMethod(codec, "root", "dc1", "")
|
|
require.NoError(t, err)
|
|
|
|
// try to update the token and change the method
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
AccessorID: methodToken.AccessorID,
|
|
SecretID: methodToken.SecretID,
|
|
AuthMethod: method2.Name,
|
|
Description: "updated token",
|
|
Local: true,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err = acl.TokenSet(&req, &resp)
|
|
requireErrorContains(t, err, "Cannot change AuthMethod")
|
|
})
|
|
|
|
t.Run("Update auth method linked token and let the SecretID and AuthMethod be defaulted", func(t *testing.T) {
|
|
acl := ACL{srv: s1}
|
|
|
|
testSessionID := testauth.StartSession()
|
|
defer testauth.ResetSession(testSessionID)
|
|
testauth.InstallSessionToken(testSessionID, "fake-token", "default", "demo", "abc123")
|
|
|
|
method, err := upsertTestAuthMethod(codec, "root", "dc1", testSessionID)
|
|
require.NoError(t, err)
|
|
|
|
_, err = upsertTestBindingRule(codec, "root", "dc1", method.Name, "", structs.BindingRuleBindTypeService, "demo")
|
|
require.NoError(t, err)
|
|
|
|
methodToken := structs.ACLToken{}
|
|
require.NoError(t, acl.Login(&structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: "fake-token",
|
|
},
|
|
Datacenter: "dc1",
|
|
}, &methodToken))
|
|
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
AccessorID: methodToken.AccessorID,
|
|
// SecretID: methodToken.SecretID,
|
|
// AuthMethod: method.Name,
|
|
Description: "updated token",
|
|
Local: true,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
require.NoError(t, acl.TokenSet(&req, &resp))
|
|
|
|
// Get the token directly to validate that it exists
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID)
|
|
require.NoError(t, err)
|
|
token := tokenResp.Token
|
|
|
|
require.NotNil(t, token)
|
|
require.Len(t, token.Roles, 0)
|
|
require.Equal(t, "updated token", token.Description)
|
|
require.True(t, token.Local)
|
|
require.Equal(t, methodToken.SecretID, token.SecretID)
|
|
require.Equal(t, methodToken.AuthMethod, token.AuthMethod)
|
|
})
|
|
|
|
t.Run("Create it with invalid service identity (empty)", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
ServiceIdentities: []*structs.ACLServiceIdentity{
|
|
&structs.ACLServiceIdentity{ServiceName: ""},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
requireErrorContains(t, err, "Service identity is missing the service name field")
|
|
})
|
|
|
|
t.Run("Create it with invalid service identity (too large)", func(t *testing.T) {
|
|
long := strings.Repeat("x", serviceIdentityNameMaxLength+1)
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
ServiceIdentities: []*structs.ACLServiceIdentity{
|
|
&structs.ACLServiceIdentity{ServiceName: long},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.NotNil(t, err)
|
|
})
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
ok bool
|
|
}{
|
|
{"-abc", false},
|
|
{"abc-", false},
|
|
{"a-bc", true},
|
|
{"_abc", false},
|
|
{"abc_", false},
|
|
{"a_bc", true},
|
|
{":abc", false},
|
|
{"abc:", false},
|
|
{"a:bc", false},
|
|
{"Abc", false},
|
|
{"aBc", false},
|
|
{"abC", false},
|
|
{"0abc", true},
|
|
{"abc0", true},
|
|
{"a0bc", true},
|
|
} {
|
|
var testName string
|
|
if test.ok {
|
|
testName = "Create it with valid service identity (by regex): " + test.name
|
|
} else {
|
|
testName = "Create it with invalid service identity (by regex): " + test.name
|
|
}
|
|
t.Run(testName, func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
ServiceIdentities: []*structs.ACLServiceIdentity{
|
|
&structs.ACLServiceIdentity{ServiceName: test.name},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
if test.ok {
|
|
require.NoError(t, err)
|
|
|
|
// Get the token directly to validate that it exists
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID)
|
|
require.NoError(t, err)
|
|
token := tokenResp.Token
|
|
require.NotNil(t, token)
|
|
require.ElementsMatch(t, req.ACLToken.ServiceIdentities, token.ServiceIdentities)
|
|
} else {
|
|
require.NotNil(t, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("Create it with two of the same service identities", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
ServiceIdentities: []*structs.ACLServiceIdentity{
|
|
&structs.ACLServiceIdentity{ServiceName: "example"},
|
|
&structs.ACLServiceIdentity{ServiceName: "example"},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the token directly to validate that it exists
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID)
|
|
require.NoError(t, err)
|
|
token := tokenResp.Token
|
|
require.NotNil(t, token)
|
|
require.Len(t, token.ServiceIdentities, 1)
|
|
})
|
|
|
|
t.Run("Create it with two of the same service identities and different DCs", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
ServiceIdentities: []*structs.ACLServiceIdentity{
|
|
&structs.ACLServiceIdentity{
|
|
ServiceName: "example",
|
|
Datacenters: []string{"dc2", "dc3"},
|
|
},
|
|
&structs.ACLServiceIdentity{
|
|
ServiceName: "example",
|
|
Datacenters: []string{"dc1", "dc2"},
|
|
},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the token directly to validate that it exists
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID)
|
|
require.NoError(t, err)
|
|
token := tokenResp.Token
|
|
require.NotNil(t, token)
|
|
require.Len(t, token.ServiceIdentities, 1)
|
|
svcid := token.ServiceIdentities[0]
|
|
require.Equal(t, "example", svcid.ServiceName)
|
|
require.ElementsMatch(t, []string{"dc1", "dc2", "dc3"}, svcid.Datacenters)
|
|
})
|
|
|
|
t.Run("Create it with invalid service identity (datacenters set on local token)", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: true,
|
|
ServiceIdentities: []*structs.ACLServiceIdentity{
|
|
&structs.ACLServiceIdentity{ServiceName: "foo", Datacenters: []string{"dc2"}},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
requireErrorContains(t, err, "cannot specify a list of datacenters on a local token")
|
|
})
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
offset time.Duration
|
|
errString string
|
|
errStringTTL string
|
|
}{
|
|
{"before create time", -5 * time.Minute, "ExpirationTime cannot be before CreateTime", ""},
|
|
{"too soon", 1 * time.Millisecond, "ExpirationTime cannot be less than", "ExpirationTime cannot be less than"},
|
|
{"too distant", 25 * time.Hour, "ExpirationTime cannot be more than", "ExpirationTime cannot be more than"},
|
|
} {
|
|
t.Run("Create it with an expiration time that is "+test.name, func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
ExpirationTime: timePointer(time.Now().Add(test.offset)),
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
if test.errString != "" {
|
|
requireErrorContains(t, err, test.errString)
|
|
} else {
|
|
require.NotNil(t, err)
|
|
}
|
|
})
|
|
|
|
t.Run("Create it with an expiration TTL that is "+test.name, func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
ExpirationTTL: test.offset,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
if test.errString != "" {
|
|
requireErrorContains(t, err, test.errStringTTL)
|
|
} else {
|
|
require.NotNil(t, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("Create it with expiration time AND expiration TTL set (error)", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
ExpirationTime: timePointer(time.Now().Add(4 * time.Second)),
|
|
ExpirationTTL: 4 * time.Second,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
requireErrorContains(t, err, "Expiration TTL and Expiration Time cannot both be set")
|
|
})
|
|
|
|
t.Run("Create it with expiration time using TTLs", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
ExpirationTTL: 4 * time.Second,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the token directly to validate that it exists
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID)
|
|
require.NoError(t, err)
|
|
token := tokenResp.Token
|
|
|
|
expectExpTime := resp.CreateTime.Add(4 * time.Second)
|
|
|
|
require.NotNil(t, token)
|
|
require.NotNil(t, token.AccessorID)
|
|
require.Equal(t, token.Description, "foobar")
|
|
require.Equal(t, token.AccessorID, resp.AccessorID)
|
|
requireTimeEquals(t, &expectExpTime, resp.ExpirationTime)
|
|
|
|
tokenID = token.AccessorID
|
|
})
|
|
|
|
var expTime time.Time
|
|
t.Run("Create it with expiration time", func(t *testing.T) {
|
|
expTime = time.Now().Add(4 * time.Second)
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
ExpirationTime: &expTime,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the token directly to validate that it exists
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID)
|
|
require.NoError(t, err)
|
|
token := tokenResp.Token
|
|
|
|
require.NotNil(t, token)
|
|
require.NotNil(t, token.AccessorID)
|
|
require.Equal(t, token.Description, "foobar")
|
|
require.Equal(t, token.AccessorID, resp.AccessorID)
|
|
requireTimeEquals(t, &expTime, resp.ExpirationTime)
|
|
|
|
tokenID = token.AccessorID
|
|
})
|
|
|
|
// do not insert another test at this point: these tests need to be serial
|
|
|
|
t.Run("Update expiration time is not allowed", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "new-description",
|
|
AccessorID: tokenID,
|
|
ExpirationTime: timePointer(expTime.Add(-1 * time.Second)),
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
requireErrorContains(t, err, "Cannot change expiration time")
|
|
})
|
|
|
|
// do not insert another test at this point: these tests need to be serial
|
|
|
|
t.Run("Update anything except expiration time is ok - omit expiration time and let it default", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "new-description-1",
|
|
AccessorID: tokenID,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the token directly to validate that it exists
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID)
|
|
require.NoError(t, err)
|
|
token := tokenResp.Token
|
|
|
|
require.NotNil(t, token)
|
|
require.NotNil(t, token.AccessorID)
|
|
require.Equal(t, token.Description, "new-description-1")
|
|
require.Equal(t, token.AccessorID, resp.AccessorID)
|
|
requireTimeEquals(t, &expTime, resp.ExpirationTime)
|
|
})
|
|
|
|
t.Run("Update anything except expiration time is ok", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "new-description-2",
|
|
AccessorID: tokenID,
|
|
ExpirationTime: &expTime,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the token directly to validate that it exists
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID)
|
|
require.NoError(t, err)
|
|
token := tokenResp.Token
|
|
|
|
require.NotNil(t, token)
|
|
require.NotNil(t, token.AccessorID)
|
|
require.Equal(t, token.Description, "new-description-2")
|
|
require.Equal(t, token.AccessorID, resp.AccessorID)
|
|
requireTimeEquals(t, &expTime, resp.ExpirationTime)
|
|
})
|
|
|
|
t.Run("cannot update a token that is past its expiration time", func(t *testing.T) {
|
|
// create a token that will expire
|
|
expiringToken, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) {
|
|
token.ExpirationTTL = 11 * time.Millisecond
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
time.Sleep(20 * time.Millisecond) // now 'expiringToken' is expired
|
|
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "new-description",
|
|
AccessorID: expiringToken.AccessorID,
|
|
ExpirationTTL: 4 * time.Second,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err = acl.TokenSet(&req, &resp)
|
|
requireErrorContains(t, err, "Cannot find token")
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_TokenSet_CustomID(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
// No Create Arg
|
|
t.Run("no create arg", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
AccessorID: "5d62a983-bcab-4e0c-9bcd-5dabebe3e273",
|
|
SecretID: "10a8ad77-2bdf-4939-a9d7-1b7de79d6beb",
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
// Use the Create Arg
|
|
t.Run("create arg", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
AccessorID: "5d62a983-bcab-4e0c-9bcd-5dabebe3e273",
|
|
SecretID: "10a8ad77-2bdf-4939-a9d7-1b7de79d6beb",
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
},
|
|
Create: true,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the token directly to validate that it exists
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID)
|
|
require.NoError(t, err)
|
|
token := tokenResp.Token
|
|
|
|
require.NotNil(t, token)
|
|
require.Equal(t, req.ACLToken.AccessorID, token.AccessorID)
|
|
require.Equal(t, req.ACLToken.SecretID, token.SecretID)
|
|
require.Equal(t, token.Description, "foobar")
|
|
})
|
|
|
|
// Reserved AccessorID
|
|
t.Run("reserved AccessorID", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
AccessorID: "00000000-0000-0000-0000-000000000073",
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
},
|
|
Create: true,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
// Reserved SecretID
|
|
t.Run("reserved SecretID", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
SecretID: "00000000-0000-0000-0000-000000000073",
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
},
|
|
Create: true,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
// Accessor is dup
|
|
t.Run("accessor Dup", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
AccessorID: "5d62a983-bcab-4e0c-9bcd-5dabebe3e273",
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
},
|
|
Create: true,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
// Accessor is dup of secret
|
|
t.Run("accessor dup of secret", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
AccessorID: "10a8ad77-2bdf-4939-a9d7-1b7de79d6beb",
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
},
|
|
Create: true,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
// Secret is dup of Accessor
|
|
t.Run("secret dup of accessor", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
SecretID: "5d62a983-bcab-4e0c-9bcd-5dabebe3e273",
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
},
|
|
Create: true,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
// Secret is dup
|
|
t.Run("secret dup", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
SecretID: "10a8ad77-2bdf-4939-a9d7-1b7de79d6beb",
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
},
|
|
Create: true,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
// Update Accessor attempt
|
|
t.Run("update accessor", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
AccessorID: "75a0d6a9-6882-4f7a-a053-906db1d55a73",
|
|
SecretID: "10a8ad77-2bdf-4939-a9d7-1b7de79d6beb",
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
// Update Accessor attempt - with Create
|
|
t.Run("update accessor create", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
AccessorID: "75a0d6a9-6882-4f7a-a053-906db1d55a73",
|
|
SecretID: "10a8ad77-2bdf-4939-a9d7-1b7de79d6beb",
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
},
|
|
Create: true,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
// Update Secret attempt
|
|
t.Run("update secret", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
AccessorID: "5d62a983-bcab-4e0c-9bcd-5dabebe3e273",
|
|
SecretID: "f551f807-b3a7-4483-9ade-97230c974bf3",
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
// Update Secret attempt - with Create
|
|
t.Run("update secret create", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
AccessorID: "5d62a983-bcab-4e0c-9bcd-5dabebe3e273",
|
|
SecretID: "f551f807-b3a7-4483-9ade-97230c974bf3",
|
|
Description: "foobar",
|
|
Policies: nil,
|
|
Local: false,
|
|
},
|
|
Create: true,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
err := acl.TokenSet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_TokenSet_anon(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
policy, err := upsertTestPolicy(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
// Assign the policies to a token
|
|
tokenUpsertReq := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
AccessorID: structs.ACLTokenAnonymousID,
|
|
Policies: []structs.ACLTokenPolicyLink{
|
|
structs.ACLTokenPolicyLink{
|
|
ID: policy.ID,
|
|
},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
token := structs.ACLToken{}
|
|
err = acl.TokenSet(&tokenUpsertReq, &token)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, token.SecretID)
|
|
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", structs.ACLTokenAnonymousID)
|
|
require.Equal(t, len(tokenResp.Token.Policies), 1)
|
|
require.Equal(t, tokenResp.Token.Policies[0].ID, policy.ID)
|
|
|
|
}
|
|
|
|
func TestACLEndpoint_TokenDelete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
|
|
c.ACLTokenMaxExpirationTTL = 5 * time.Second
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.Datacenter = "dc2"
|
|
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
|
|
c.ACLTokenMaxExpirationTTL = 5 * time.Second
|
|
// token replication is required to test deleting non-local tokens in secondary dc
|
|
c.ACLTokenReplication = true
|
|
})
|
|
defer os.RemoveAll(dir2)
|
|
defer s2.Shutdown()
|
|
codec2 := rpcClient(t, s2)
|
|
defer codec2.Close()
|
|
|
|
s2.tokens.UpdateReplicationToken("root", tokenStore.TokenSourceConfig)
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
testrpc.WaitForLeader(t, s2.RPC, "dc2")
|
|
|
|
// Try to join
|
|
joinWAN(t, s2, s1)
|
|
|
|
waitForNewACLs(t, s1)
|
|
waitForNewACLs(t, s2)
|
|
|
|
// Ensure s2 is authoritative.
|
|
waitForNewACLReplication(t, s2, structs.ACLReplicateTokens, 1, 1, 0)
|
|
|
|
acl := ACL{srv: s1}
|
|
acl2 := ACL{srv: s2}
|
|
|
|
existingToken, err := upsertTestToken(codec, "root", "dc1", nil)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("deletes a token that has an expiration time in the future", func(t *testing.T) {
|
|
// create a token that will expire
|
|
testToken, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) {
|
|
token.ExpirationTTL = 4 * time.Second
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Make sure the token is listable
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", testToken.AccessorID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, tokenResp.Token)
|
|
|
|
// Now try to delete it (this should work).
|
|
req := structs.ACLTokenDeleteRequest{
|
|
Datacenter: "dc1",
|
|
TokenID: testToken.AccessorID,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var resp string
|
|
|
|
err = acl.TokenDelete(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Make sure the token is gone
|
|
tokenResp, err = retrieveTestToken(codec, "root", "dc1", testToken.AccessorID)
|
|
require.NoError(t, err)
|
|
require.Nil(t, tokenResp.Token)
|
|
})
|
|
|
|
t.Run("deletes a token that is past its expiration time", func(t *testing.T) {
|
|
// create a token that will expire
|
|
expiringToken, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) {
|
|
token.ExpirationTTL = 11 * time.Millisecond
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
time.Sleep(20 * time.Millisecond) // now 'expiringToken' is expired
|
|
|
|
// Make sure the token is not listable (filtered due to expiry)
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", expiringToken.AccessorID)
|
|
require.NoError(t, err)
|
|
require.Nil(t, tokenResp.Token)
|
|
|
|
// Now try to delete it (this should work).
|
|
req := structs.ACLTokenDeleteRequest{
|
|
Datacenter: "dc1",
|
|
TokenID: expiringToken.AccessorID,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var resp string
|
|
|
|
err = acl.TokenDelete(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Make sure the token is still gone (this time it's actually gone)
|
|
tokenResp, err = retrieveTestToken(codec, "root", "dc1", expiringToken.AccessorID)
|
|
require.NoError(t, err)
|
|
require.Nil(t, tokenResp.Token)
|
|
})
|
|
|
|
t.Run("deletes a token", func(t *testing.T) {
|
|
req := structs.ACLTokenDeleteRequest{
|
|
Datacenter: "dc1",
|
|
TokenID: existingToken.AccessorID,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var resp string
|
|
|
|
err = acl.TokenDelete(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Make sure the token is gone
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", existingToken.AccessorID)
|
|
require.Nil(t, tokenResp.Token)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("can't delete itself", func(t *testing.T) {
|
|
readReq := structs.ACLTokenGetRequest{
|
|
Datacenter: "dc1",
|
|
TokenID: "root",
|
|
TokenIDType: structs.ACLTokenSecret,
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
var out structs.ACLTokenResponse
|
|
|
|
err := acl.TokenRead(&readReq, &out)
|
|
|
|
require.NoError(t, err)
|
|
|
|
req := structs.ACLTokenDeleteRequest{
|
|
Datacenter: "dc1",
|
|
TokenID: out.Token.AccessorID,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var resp string
|
|
err = acl.TokenDelete(&req, &resp)
|
|
require.EqualError(t, err, "Deletion of the request's authorization token is not permitted")
|
|
})
|
|
|
|
t.Run("errors when token doesn't exist", func(t *testing.T) {
|
|
fakeID, err := uuid.GenerateUUID()
|
|
require.NoError(t, err)
|
|
|
|
req := structs.ACLTokenDeleteRequest{
|
|
Datacenter: "dc1",
|
|
TokenID: fakeID,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var resp string
|
|
|
|
err = acl.TokenDelete(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// token should be nil
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", existingToken.AccessorID)
|
|
require.Nil(t, tokenResp.Token)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("don't segfault when attempting to delete non existent token in secondary dc", func(t *testing.T) {
|
|
fakeID, err := uuid.GenerateUUID()
|
|
require.NoError(t, err)
|
|
|
|
req := structs.ACLTokenDeleteRequest{
|
|
Datacenter: "dc2",
|
|
TokenID: fakeID,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var resp string
|
|
|
|
err = acl2.TokenDelete(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// token should be nil
|
|
tokenResp, err := retrieveTestToken(codec2, "root", "dc1", existingToken.AccessorID)
|
|
require.Nil(t, tokenResp.Token)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_TokenDelete_anon(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
req := structs.ACLTokenDeleteRequest{
|
|
Datacenter: "dc1",
|
|
TokenID: structs.ACLTokenAnonymousID,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var resp string
|
|
|
|
err := acl.TokenDelete(&req, &resp)
|
|
require.EqualError(t, err, "Delete operation not permitted on the anonymous token")
|
|
|
|
// Make sure the token is still there
|
|
tokenResp, err := retrieveTestToken(codec, "root", "dc1", structs.ACLTokenAnonymousID)
|
|
require.NotNil(t, tokenResp.Token)
|
|
}
|
|
|
|
func TestACLEndpoint_TokenList(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
|
|
c.ACLTokenMaxExpirationTTL = 5 * time.Second
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
waitForLeaderEstablishment(t, s1)
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
t1, err := upsertTestToken(codec, "root", "dc1", nil)
|
|
require.NoError(t, err)
|
|
|
|
t2, err := upsertTestToken(codec, "root", "dc1", nil)
|
|
require.NoError(t, err)
|
|
|
|
masterTokenAccessorID, err := retrieveTestTokenAccessorForSecret(codec, "root", "dc1", "root")
|
|
require.NoError(t, err)
|
|
|
|
t.Run("normal", func(t *testing.T) {
|
|
// this will still be racey even with inserting the token + ttl inside the test function
|
|
// however previously inserting it outside of the subtest func resulted in this being
|
|
// extra flakey due to there being more code that needed to run to setup the subtest
|
|
// between when we inserted the token and when we performed the listing.
|
|
t3, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) {
|
|
token.ExpirationTTL = 50 * time.Millisecond
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
req := structs.ACLTokenListRequest{
|
|
Datacenter: "dc1",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLTokenListResponse{}
|
|
|
|
err = acl.TokenList(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
tokens := []string{
|
|
masterTokenAccessorID,
|
|
structs.ACLTokenAnonymousID,
|
|
t1.AccessorID,
|
|
t2.AccessorID,
|
|
t3.AccessorID,
|
|
}
|
|
require.ElementsMatch(t, gatherIDs(t, resp.Tokens), tokens)
|
|
})
|
|
|
|
time.Sleep(50 * time.Millisecond) // now 't3' is expired
|
|
|
|
t.Run("filter expired", func(t *testing.T) {
|
|
req := structs.ACLTokenListRequest{
|
|
Datacenter: "dc1",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLTokenListResponse{}
|
|
|
|
err = acl.TokenList(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
tokens := []string{
|
|
masterTokenAccessorID,
|
|
structs.ACLTokenAnonymousID,
|
|
t1.AccessorID,
|
|
t2.AccessorID,
|
|
}
|
|
require.ElementsMatch(t, gatherIDs(t, resp.Tokens), tokens)
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_TokenBatchRead(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
|
|
c.ACLTokenMaxExpirationTTL = 5 * time.Second
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
t1, err := upsertTestToken(codec, "root", "dc1", nil)
|
|
require.NoError(t, err)
|
|
|
|
t2, err := upsertTestToken(codec, "root", "dc1", nil)
|
|
require.NoError(t, err)
|
|
|
|
t3, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) {
|
|
token.ExpirationTTL = 4 * time.Second
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
t.Run("normal", func(t *testing.T) {
|
|
tokens := []string{t1.AccessorID, t2.AccessorID, t3.AccessorID}
|
|
|
|
req := structs.ACLTokenBatchGetRequest{
|
|
Datacenter: "dc1",
|
|
AccessorIDs: tokens,
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLTokenBatchResponse{}
|
|
|
|
err = acl.TokenBatchRead(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, gatherIDs(t, resp.Tokens), tokens)
|
|
})
|
|
|
|
time.Sleep(20 * time.Millisecond) // now 't3' is expired
|
|
|
|
t.Run("returns expired tokens", func(t *testing.T) {
|
|
tokens := []string{t1.AccessorID, t2.AccessorID, t3.AccessorID}
|
|
|
|
req := structs.ACLTokenBatchGetRequest{
|
|
Datacenter: "dc1",
|
|
AccessorIDs: tokens,
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLTokenBatchResponse{}
|
|
|
|
err = acl.TokenBatchRead(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, gatherIDs(t, resp.Tokens), tokens)
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_PolicyRead(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
policy, err := upsertTestPolicy(codec, "root", "dc1")
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
req := structs.ACLPolicyGetRequest{
|
|
Datacenter: "dc1",
|
|
PolicyID: policy.ID,
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLPolicyResponse{}
|
|
|
|
err = acl.PolicyRead(&req, &resp)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(resp.Policy, policy) {
|
|
t.Fatalf("tokens are not equal: %v != %v", resp.Policy, policy)
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_PolicyBatchRead(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
p1, err := upsertTestPolicy(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
p2, err := upsertTestPolicy(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
policies := []string{p1.ID, p2.ID}
|
|
|
|
req := structs.ACLPolicyBatchGetRequest{
|
|
Datacenter: "dc1",
|
|
PolicyIDs: policies,
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLPolicyBatchResponse{}
|
|
|
|
err = acl.PolicyBatchRead(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, gatherIDs(t, resp.Policies), []string{p1.ID, p2.ID})
|
|
}
|
|
|
|
func TestACLEndpoint_PolicySet(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
var policyID string
|
|
|
|
t.Run("Create it", func(t *testing.T) {
|
|
req := structs.ACLPolicySetRequest{
|
|
Datacenter: "dc1",
|
|
Policy: structs.ACLPolicy{
|
|
Description: "foobar",
|
|
Name: "baz",
|
|
Rules: "service \"\" { policy = \"read\" }",
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLPolicy{}
|
|
|
|
err := acl.PolicySet(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.ID)
|
|
|
|
// Get the policy directly to validate that it exists
|
|
policyResp, err := retrieveTestPolicy(codec, "root", "dc1", resp.ID)
|
|
require.NoError(t, err)
|
|
policy := policyResp.Policy
|
|
|
|
require.NotNil(t, policy.ID)
|
|
require.Equal(t, policy.Description, "foobar")
|
|
require.Equal(t, policy.Name, "baz")
|
|
require.Equal(t, policy.Rules, "service \"\" { policy = \"read\" }")
|
|
|
|
policyID = policy.ID
|
|
})
|
|
|
|
t.Run("Name Dup", func(t *testing.T) {
|
|
req := structs.ACLPolicySetRequest{
|
|
Datacenter: "dc1",
|
|
Policy: structs.ACLPolicy{
|
|
Description: "foobar",
|
|
Name: "baz",
|
|
Rules: "service \"\" { policy = \"read\" }",
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLPolicy{}
|
|
|
|
err := acl.PolicySet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("Update it", func(t *testing.T) {
|
|
req := structs.ACLPolicySetRequest{
|
|
Datacenter: "dc1",
|
|
Policy: structs.ACLPolicy{
|
|
ID: policyID,
|
|
Description: "bat",
|
|
Name: "bar",
|
|
Rules: "service \"\" { policy = \"write\" }",
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLPolicy{}
|
|
|
|
err := acl.PolicySet(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.ID)
|
|
|
|
// Get the policy directly to validate that it exists
|
|
policyResp, err := retrieveTestPolicy(codec, "root", "dc1", resp.ID)
|
|
require.NoError(t, err)
|
|
policy := policyResp.Policy
|
|
|
|
require.NotNil(t, policy.ID)
|
|
require.Equal(t, policy.Description, "bat")
|
|
require.Equal(t, policy.Name, "bar")
|
|
require.Equal(t, policy.Rules, "service \"\" { policy = \"write\" }")
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_PolicySet_CustomID(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
// Attempt to create policy with ID
|
|
req := structs.ACLPolicySetRequest{
|
|
Datacenter: "dc1",
|
|
Policy: structs.ACLPolicy{
|
|
ID: "7ee166a5-b4b7-453c-bdc0-bca8ce50823e",
|
|
Description: "foobar",
|
|
Name: "baz",
|
|
Rules: "service \"\" { policy = \"read\" }",
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLPolicy{}
|
|
|
|
err := acl.PolicySet(&req, &resp)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestACLEndpoint_PolicySet_globalManagement(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
// Can't change the rules
|
|
{
|
|
|
|
req := structs.ACLPolicySetRequest{
|
|
Datacenter: "dc1",
|
|
Policy: structs.ACLPolicy{
|
|
ID: structs.ACLPolicyGlobalManagementID,
|
|
Name: "foobar", // This is required to get past validation
|
|
Rules: "service \"\" { policy = \"write\" }",
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLPolicy{}
|
|
|
|
err := acl.PolicySet(&req, &resp)
|
|
require.EqualError(t, err, "Changing the Rules for the builtin global-management policy is not permitted")
|
|
}
|
|
|
|
// Can rename it
|
|
{
|
|
req := structs.ACLPolicySetRequest{
|
|
Datacenter: "dc1",
|
|
Policy: structs.ACLPolicy{
|
|
ID: structs.ACLPolicyGlobalManagementID,
|
|
Name: "foobar",
|
|
Rules: structs.ACLPolicyGlobalManagement,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLPolicy{}
|
|
|
|
err := acl.PolicySet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the policy again
|
|
policyResp, err := retrieveTestPolicy(codec, "root", "dc1", structs.ACLPolicyGlobalManagementID)
|
|
require.NoError(t, err)
|
|
policy := policyResp.Policy
|
|
|
|
require.Equal(t, policy.ID, structs.ACLPolicyGlobalManagementID)
|
|
require.Equal(t, policy.Name, "foobar")
|
|
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_PolicyDelete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
existingPolicy, err := upsertTestPolicy(codec, "root", "dc1")
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
req := structs.ACLPolicyDeleteRequest{
|
|
Datacenter: "dc1",
|
|
PolicyID: existingPolicy.ID,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var resp string
|
|
|
|
err = acl.PolicyDelete(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Make sure the policy is gone
|
|
tokenResp, err := retrieveTestPolicy(codec, "root", "dc1", existingPolicy.ID)
|
|
require.Nil(t, tokenResp.Policy)
|
|
}
|
|
|
|
func TestACLEndpoint_PolicyDelete_globalManagement(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
req := structs.ACLPolicyDeleteRequest{
|
|
Datacenter: "dc1",
|
|
PolicyID: structs.ACLPolicyGlobalManagementID,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
var resp string
|
|
|
|
err := acl.PolicyDelete(&req, &resp)
|
|
|
|
require.EqualError(t, err, "Delete operation not permitted on the builtin global-management policy")
|
|
}
|
|
|
|
func TestACLEndpoint_PolicyList(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
p1, err := upsertTestPolicy(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
p2, err := upsertTestPolicy(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
req := structs.ACLPolicyListRequest{
|
|
Datacenter: "dc1",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLPolicyListResponse{}
|
|
|
|
err = acl.PolicyList(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
policies := []string{
|
|
structs.ACLPolicyGlobalManagementID,
|
|
p1.ID,
|
|
p2.ID,
|
|
}
|
|
require.ElementsMatch(t, gatherIDs(t, resp.Policies), policies)
|
|
}
|
|
|
|
func TestACLEndpoint_PolicyResolve(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
p1, err := upsertTestPolicy(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
p2, err := upsertTestPolicy(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
policies := []string{p1.ID, p2.ID}
|
|
|
|
// Assign the policies to a token
|
|
tokenUpsertReq := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Policies: []structs.ACLTokenPolicyLink{
|
|
structs.ACLTokenPolicyLink{
|
|
ID: p1.ID,
|
|
},
|
|
structs.ACLTokenPolicyLink{
|
|
ID: p2.ID,
|
|
},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
token := structs.ACLToken{}
|
|
err = acl.TokenSet(&tokenUpsertReq, &token)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, token.SecretID)
|
|
|
|
resp := structs.ACLPolicyBatchResponse{}
|
|
req := structs.ACLPolicyBatchGetRequest{
|
|
Datacenter: "dc1",
|
|
PolicyIDs: []string{p1.ID, p2.ID},
|
|
QueryOptions: structs.QueryOptions{Token: token.SecretID},
|
|
}
|
|
err = acl.PolicyResolve(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, gatherIDs(t, resp.Policies), policies)
|
|
}
|
|
|
|
func TestACLEndpoint_RoleRead(t *testing.T) {
|
|
t.Parallel()
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
role, err := upsertTestRole(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
req := structs.ACLRoleGetRequest{
|
|
Datacenter: "dc1",
|
|
RoleID: role.ID,
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLRoleResponse{}
|
|
|
|
err = acl.RoleRead(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.Equal(t, role, resp.Role)
|
|
}
|
|
|
|
func TestACLEndpoint_RoleBatchRead(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
r1, err := upsertTestRole(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
r2, err := upsertTestRole(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
roles := []string{r1.ID, r2.ID}
|
|
|
|
req := structs.ACLRoleBatchGetRequest{
|
|
Datacenter: "dc1",
|
|
RoleIDs: roles,
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLRoleBatchResponse{}
|
|
|
|
err = acl.RoleBatchRead(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, gatherIDs(t, resp.Roles), roles)
|
|
}
|
|
|
|
func TestACLEndpoint_RoleSet(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
var roleID string
|
|
|
|
testPolicy1, err := upsertTestPolicy(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
testPolicy2, err := upsertTestPolicy(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
t.Run("Create it", func(t *testing.T) {
|
|
req := structs.ACLRoleSetRequest{
|
|
Datacenter: "dc1",
|
|
Role: structs.ACLRole{
|
|
Description: "foobar",
|
|
Name: "baz",
|
|
Policies: []structs.ACLRolePolicyLink{
|
|
structs.ACLRolePolicyLink{
|
|
ID: testPolicy1.ID,
|
|
},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLRole{}
|
|
|
|
err := acl.RoleSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.ID)
|
|
|
|
// Get the role directly to validate that it exists
|
|
roleResp, err := retrieveTestRole(codec, "root", "dc1", resp.ID)
|
|
require.NoError(t, err)
|
|
role := roleResp.Role
|
|
|
|
require.NotNil(t, role.ID)
|
|
require.Equal(t, role.Description, "foobar")
|
|
require.Equal(t, role.Name, "baz")
|
|
require.Len(t, role.Policies, 1)
|
|
require.Equal(t, testPolicy1.ID, role.Policies[0].ID)
|
|
|
|
roleID = role.ID
|
|
})
|
|
|
|
t.Run("Update it", func(t *testing.T) {
|
|
req := structs.ACLRoleSetRequest{
|
|
Datacenter: "dc1",
|
|
Role: structs.ACLRole{
|
|
ID: roleID,
|
|
Description: "bat",
|
|
Name: "bar",
|
|
Policies: []structs.ACLRolePolicyLink{
|
|
structs.ACLRolePolicyLink{
|
|
ID: testPolicy2.ID,
|
|
},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLRole{}
|
|
|
|
err := acl.RoleSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.ID)
|
|
|
|
// Get the role directly to validate that it exists
|
|
roleResp, err := retrieveTestRole(codec, "root", "dc1", resp.ID)
|
|
require.NoError(t, err)
|
|
role := roleResp.Role
|
|
|
|
require.NotNil(t, role.ID)
|
|
require.Equal(t, role.Description, "bat")
|
|
require.Equal(t, role.Name, "bar")
|
|
require.Len(t, role.Policies, 1)
|
|
require.Equal(t, testPolicy2.ID, role.Policies[0].ID)
|
|
})
|
|
|
|
t.Run("Create it using Policies linked by id and name", func(t *testing.T) {
|
|
policy1, err := upsertTestPolicy(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
policy2, err := upsertTestPolicy(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
req := structs.ACLRoleSetRequest{
|
|
Datacenter: "dc1",
|
|
Role: structs.ACLRole{
|
|
Description: "foobar",
|
|
Name: "baz",
|
|
Policies: []structs.ACLRolePolicyLink{
|
|
structs.ACLRolePolicyLink{
|
|
ID: policy1.ID,
|
|
},
|
|
structs.ACLRolePolicyLink{
|
|
Name: policy2.Name,
|
|
},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLRole{}
|
|
|
|
err = acl.RoleSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.ID)
|
|
|
|
// Delete both policies to ensure that we skip resolving ID->Name
|
|
// in the returned data.
|
|
require.NoError(t, deleteTestPolicy(codec, "root", "dc1", policy1.ID))
|
|
require.NoError(t, deleteTestPolicy(codec, "root", "dc1", policy2.ID))
|
|
|
|
// Get the role directly to validate that it exists
|
|
roleResp, err := retrieveTestRole(codec, "root", "dc1", resp.ID)
|
|
require.NoError(t, err)
|
|
role := roleResp.Role
|
|
|
|
require.NotNil(t, role.ID)
|
|
require.Equal(t, role.Description, "foobar")
|
|
require.Equal(t, role.Name, "baz")
|
|
|
|
require.Len(t, role.Policies, 0)
|
|
})
|
|
|
|
roleNameGen := func(t *testing.T) string {
|
|
t.Helper()
|
|
name, err := uuid.GenerateUUID()
|
|
require.NoError(t, err)
|
|
return name
|
|
}
|
|
|
|
t.Run("Create it with invalid service identity (empty)", func(t *testing.T) {
|
|
req := structs.ACLRoleSetRequest{
|
|
Datacenter: "dc1",
|
|
Role: structs.ACLRole{
|
|
Description: "foobar",
|
|
Name: roleNameGen(t),
|
|
ServiceIdentities: []*structs.ACLServiceIdentity{
|
|
&structs.ACLServiceIdentity{ServiceName: ""},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLRole{}
|
|
|
|
err := acl.RoleSet(&req, &resp)
|
|
requireErrorContains(t, err, "Service identity is missing the service name field")
|
|
})
|
|
|
|
t.Run("Create it with invalid service identity (too large)", func(t *testing.T) {
|
|
long := strings.Repeat("x", serviceIdentityNameMaxLength+1)
|
|
req := structs.ACLRoleSetRequest{
|
|
Datacenter: "dc1",
|
|
Role: structs.ACLRole{
|
|
Description: "foobar",
|
|
Name: roleNameGen(t),
|
|
ServiceIdentities: []*structs.ACLServiceIdentity{
|
|
&structs.ACLServiceIdentity{ServiceName: long},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLRole{}
|
|
|
|
err := acl.RoleSet(&req, &resp)
|
|
require.NotNil(t, err)
|
|
})
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
ok bool
|
|
}{
|
|
{"-abc", false},
|
|
{"abc-", false},
|
|
{"a-bc", true},
|
|
{"_abc", false},
|
|
{"abc_", false},
|
|
{"a_bc", true},
|
|
{":abc", false},
|
|
{"abc:", false},
|
|
{"a:bc", false},
|
|
{"Abc", false},
|
|
{"aBc", false},
|
|
{"abC", false},
|
|
{"0abc", true},
|
|
{"abc0", true},
|
|
{"a0bc", true},
|
|
} {
|
|
var testName string
|
|
if test.ok {
|
|
testName = "Create it with valid service identity (by regex): " + test.name
|
|
} else {
|
|
testName = "Create it with invalid service identity (by regex): " + test.name
|
|
}
|
|
t.Run(testName, func(t *testing.T) {
|
|
req := structs.ACLRoleSetRequest{
|
|
Datacenter: "dc1",
|
|
Role: structs.ACLRole{
|
|
Description: "foobar",
|
|
Name: roleNameGen(t),
|
|
ServiceIdentities: []*structs.ACLServiceIdentity{
|
|
&structs.ACLServiceIdentity{ServiceName: test.name},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLRole{}
|
|
|
|
err := acl.RoleSet(&req, &resp)
|
|
if test.ok {
|
|
require.NoError(t, err)
|
|
|
|
// Get the token directly to validate that it exists
|
|
roleResp, err := retrieveTestRole(codec, "root", "dc1", resp.ID)
|
|
require.NoError(t, err)
|
|
role := roleResp.Role
|
|
require.ElementsMatch(t, req.Role.ServiceIdentities, role.ServiceIdentities)
|
|
} else {
|
|
require.NotNil(t, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("Create it with two of the same service identities", func(t *testing.T) {
|
|
req := structs.ACLRoleSetRequest{
|
|
Datacenter: "dc1",
|
|
Role: structs.ACLRole{
|
|
Description: "foobar",
|
|
Name: roleNameGen(t),
|
|
ServiceIdentities: []*structs.ACLServiceIdentity{
|
|
&structs.ACLServiceIdentity{ServiceName: "example"},
|
|
&structs.ACLServiceIdentity{ServiceName: "example"},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLRole{}
|
|
|
|
err := acl.RoleSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the role directly to validate that it exists
|
|
roleResp, err := retrieveTestRole(codec, "root", "dc1", resp.ID)
|
|
require.NoError(t, err)
|
|
role := roleResp.Role
|
|
require.Len(t, role.ServiceIdentities, 1)
|
|
})
|
|
|
|
t.Run("Create it with two of the same service identities and different DCs", func(t *testing.T) {
|
|
req := structs.ACLRoleSetRequest{
|
|
Datacenter: "dc1",
|
|
Role: structs.ACLRole{
|
|
Description: "foobar",
|
|
Name: roleNameGen(t),
|
|
ServiceIdentities: []*structs.ACLServiceIdentity{
|
|
&structs.ACLServiceIdentity{
|
|
ServiceName: "example",
|
|
Datacenters: []string{"dc2", "dc3"},
|
|
},
|
|
&structs.ACLServiceIdentity{
|
|
ServiceName: "example",
|
|
Datacenters: []string{"dc1", "dc2"},
|
|
},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLRole{}
|
|
|
|
err := acl.RoleSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the role directly to validate that it exists
|
|
roleResp, err := retrieveTestRole(codec, "root", "dc1", resp.ID)
|
|
require.NoError(t, err)
|
|
role := roleResp.Role
|
|
require.Len(t, role.ServiceIdentities, 1)
|
|
svcid := role.ServiceIdentities[0]
|
|
require.Equal(t, "example", svcid.ServiceName)
|
|
require.ElementsMatch(t, []string{"dc1", "dc2", "dc3"}, svcid.Datacenters)
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_RoleSet_names(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
testPolicy1, err := upsertTestPolicy(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
ok bool
|
|
}{
|
|
{"", false},
|
|
{"-bad", true},
|
|
{"bad-", true},
|
|
{"bad?bad", false},
|
|
{strings.Repeat("x", 257), false},
|
|
{strings.Repeat("x", 256), true},
|
|
{"-abc", true},
|
|
{"abc-", true},
|
|
{"a-bc", true},
|
|
{"_abc", true},
|
|
{"abc_", true},
|
|
{"a_bc", true},
|
|
{":abc", false},
|
|
{"abc:", false},
|
|
{"a:bc", false},
|
|
{"Abc", true},
|
|
{"aBc", true},
|
|
{"abC", true},
|
|
{"0abc", true},
|
|
{"abc0", true},
|
|
{"a0bc", true},
|
|
} {
|
|
var testName string
|
|
if test.ok {
|
|
testName = "create with valid name: " + test.name
|
|
} else {
|
|
testName = "create with invalid name: " + test.name
|
|
}
|
|
|
|
t.Run(testName, func(t *testing.T) {
|
|
// cleanup from a prior insertion that may have succeeded
|
|
require.NoError(t, deleteTestRoleByName(codec, "root", "dc1", test.name))
|
|
|
|
req := structs.ACLRoleSetRequest{
|
|
Datacenter: "dc1",
|
|
Role: structs.ACLRole{
|
|
Name: test.name,
|
|
Description: "foobar",
|
|
Policies: []structs.ACLRolePolicyLink{
|
|
structs.ACLRolePolicyLink{
|
|
ID: testPolicy1.ID,
|
|
},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLRole{}
|
|
|
|
err := acl.RoleSet(&req, &resp)
|
|
if test.ok {
|
|
require.NoError(t, err)
|
|
|
|
roleResp, err := retrieveTestRole(codec, "root", "dc1", resp.ID)
|
|
require.NoError(t, err)
|
|
role := roleResp.Role
|
|
require.Equal(t, test.name, role.Name)
|
|
} else {
|
|
require.Error(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_RoleDelete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
existingRole, err := upsertTestRole(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
req := structs.ACLRoleDeleteRequest{
|
|
Datacenter: "dc1",
|
|
RoleID: existingRole.ID,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var resp string
|
|
|
|
err = acl.RoleDelete(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Make sure the role is gone
|
|
roleResp, err := retrieveTestRole(codec, "root", "dc1", existingRole.ID)
|
|
require.Nil(t, roleResp.Role)
|
|
}
|
|
|
|
func TestACLEndpoint_RoleList(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
r1, err := upsertTestRole(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
r2, err := upsertTestRole(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
req := structs.ACLRoleListRequest{
|
|
Datacenter: "dc1",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLRoleListResponse{}
|
|
|
|
err = acl.RoleList(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, gatherIDs(t, resp.Roles), []string{r1.ID, r2.ID})
|
|
}
|
|
|
|
func TestACLEndpoint_RoleResolve(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
t.Run("Normal", func(t *testing.T) {
|
|
r1, err := upsertTestRole(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
r2, err := upsertTestRole(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
// Assign the roles to a token
|
|
tokenUpsertReq := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Roles: []structs.ACLTokenRoleLink{
|
|
structs.ACLTokenRoleLink{
|
|
ID: r1.ID,
|
|
},
|
|
structs.ACLTokenRoleLink{
|
|
ID: r2.ID,
|
|
},
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
token := structs.ACLToken{}
|
|
err = acl.TokenSet(&tokenUpsertReq, &token)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, token.SecretID)
|
|
|
|
resp := structs.ACLRoleBatchResponse{}
|
|
req := structs.ACLRoleBatchGetRequest{
|
|
Datacenter: "dc1",
|
|
RoleIDs: []string{r1.ID, r2.ID},
|
|
QueryOptions: structs.QueryOptions{Token: token.SecretID},
|
|
}
|
|
err = acl.RoleResolve(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, gatherIDs(t, resp.Roles), []string{r1.ID, r2.ID})
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_AuthMethodSet(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tempDir, err := ioutil.TempDir("", "consul")
|
|
require.NoError(t, err)
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
newAuthMethod := func(name string) structs.ACLAuthMethod {
|
|
return structs.ACLAuthMethod{
|
|
Name: name,
|
|
Description: "test",
|
|
Type: "testing",
|
|
}
|
|
}
|
|
|
|
t.Run("Create", func(t *testing.T) {
|
|
reqMethod := newAuthMethod("test")
|
|
|
|
req := structs.ACLAuthMethodSetRequest{
|
|
Datacenter: "dc1",
|
|
AuthMethod: reqMethod,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLAuthMethod{}
|
|
|
|
err := acl.AuthMethodSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the method directly to validate that it exists
|
|
methodResp, err := retrieveTestAuthMethod(codec, "root", "dc1", resp.Name)
|
|
require.NoError(t, err)
|
|
method := methodResp.AuthMethod
|
|
|
|
require.Equal(t, method.Name, "test")
|
|
require.Equal(t, method.Description, "test")
|
|
require.Equal(t, method.Type, "testing")
|
|
})
|
|
|
|
t.Run("Update fails; not allowed to change types", func(t *testing.T) {
|
|
reqMethod := newAuthMethod("test")
|
|
reqMethod.Type = "invalid"
|
|
|
|
req := structs.ACLAuthMethodSetRequest{
|
|
Datacenter: "dc1",
|
|
AuthMethod: reqMethod,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLAuthMethod{}
|
|
|
|
err := acl.AuthMethodSet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("Update - allow type to default", func(t *testing.T) {
|
|
reqMethod := newAuthMethod("test")
|
|
reqMethod.Description = "test modified 1"
|
|
reqMethod.Type = "" // unset
|
|
|
|
req := structs.ACLAuthMethodSetRequest{
|
|
Datacenter: "dc1",
|
|
AuthMethod: reqMethod,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLAuthMethod{}
|
|
|
|
err := acl.AuthMethodSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the method directly to validate that it exists
|
|
methodResp, err := retrieveTestAuthMethod(codec, "root", "dc1", resp.Name)
|
|
require.NoError(t, err)
|
|
method := methodResp.AuthMethod
|
|
|
|
require.Equal(t, method.Name, "test")
|
|
require.Equal(t, method.Description, "test modified 1")
|
|
require.Equal(t, method.Type, "testing")
|
|
})
|
|
|
|
t.Run("Update - specify type", func(t *testing.T) {
|
|
reqMethod := newAuthMethod("test")
|
|
reqMethod.Description = "test modified 2"
|
|
|
|
req := structs.ACLAuthMethodSetRequest{
|
|
Datacenter: "dc1",
|
|
AuthMethod: reqMethod,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLAuthMethod{}
|
|
|
|
err := acl.AuthMethodSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
// Get the method directly to validate that it exists
|
|
methodResp, err := retrieveTestAuthMethod(codec, "root", "dc1", resp.Name)
|
|
require.NoError(t, err)
|
|
method := methodResp.AuthMethod
|
|
|
|
require.Equal(t, method.Name, "test")
|
|
require.Equal(t, method.Description, "test modified 2")
|
|
require.Equal(t, method.Type, "testing")
|
|
})
|
|
|
|
t.Run("Create with no name", func(t *testing.T) {
|
|
req := structs.ACLAuthMethodSetRequest{
|
|
Datacenter: "dc1",
|
|
AuthMethod: newAuthMethod(""),
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLAuthMethod{}
|
|
|
|
err := acl.AuthMethodSet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("Create with invalid type", func(t *testing.T) {
|
|
req := structs.ACLAuthMethodSetRequest{
|
|
Datacenter: "dc1",
|
|
AuthMethod: structs.ACLAuthMethod{
|
|
Name: "invalid",
|
|
Description: "invalid test",
|
|
Type: "invalid",
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLAuthMethod{}
|
|
|
|
err := acl.AuthMethodSet(&req, &resp)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
ok bool
|
|
}{
|
|
{strings.Repeat("x", 129), false},
|
|
{strings.Repeat("x", 128), true},
|
|
{"-abc", true},
|
|
{"abc-", true},
|
|
{"a-bc", true},
|
|
{"_abc", true},
|
|
{"abc_", true},
|
|
{"a_bc", true},
|
|
{":abc", false},
|
|
{"abc:", false},
|
|
{"a:bc", false},
|
|
{"Abc", true},
|
|
{"aBc", true},
|
|
{"abC", true},
|
|
{"0abc", true},
|
|
{"abc0", true},
|
|
{"a0bc", true},
|
|
} {
|
|
var testName string
|
|
if test.ok {
|
|
testName = "Create with valid name (by regex): " + test.name
|
|
} else {
|
|
testName = "Create with invalid name (by regex): " + test.name
|
|
}
|
|
t.Run(testName, func(t *testing.T) {
|
|
req := structs.ACLAuthMethodSetRequest{
|
|
Datacenter: "dc1",
|
|
AuthMethod: newAuthMethod(test.name),
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLAuthMethod{}
|
|
|
|
err := acl.AuthMethodSet(&req, &resp)
|
|
|
|
if test.ok {
|
|
require.NoError(t, err)
|
|
|
|
// Get the method directly to validate that it exists
|
|
methodResp, err := retrieveTestAuthMethod(codec, "root", "dc1", resp.Name)
|
|
require.NoError(t, err)
|
|
method := methodResp.AuthMethod
|
|
|
|
require.Equal(t, method.Name, test.name)
|
|
require.Equal(t, method.Type, "testing")
|
|
} else {
|
|
require.Error(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_AuthMethodDelete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
testSessionID := testauth.StartSession()
|
|
defer testauth.ResetSession(testSessionID)
|
|
|
|
existingMethod, err := upsertTestAuthMethod(codec, "root", "dc1", testSessionID)
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
t.Run("normal", func(t *testing.T) {
|
|
req := structs.ACLAuthMethodDeleteRequest{
|
|
Datacenter: "dc1",
|
|
AuthMethodName: existingMethod.Name,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var ignored bool
|
|
err = acl.AuthMethodDelete(&req, &ignored)
|
|
require.NoError(t, err)
|
|
|
|
// Make sure the method is gone
|
|
methodResp, err := retrieveTestAuthMethod(codec, "root", "dc1", existingMethod.Name)
|
|
require.NoError(t, err)
|
|
require.Nil(t, methodResp.AuthMethod)
|
|
})
|
|
|
|
t.Run("delete something that doesn't exist", func(t *testing.T) {
|
|
req := structs.ACLAuthMethodDeleteRequest{
|
|
Datacenter: "dc1",
|
|
AuthMethodName: "missing",
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var ignored bool
|
|
err = acl.AuthMethodDelete(&req, &ignored)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
// Deleting an auth method atomically deletes all rules and tokens as well.
|
|
func TestACLEndpoint_AuthMethodDelete_RuleAndTokenCascade(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
testSessionID1 := testauth.StartSession()
|
|
defer testauth.ResetSession(testSessionID1)
|
|
testauth.InstallSessionToken(testSessionID1, "fake-token1", "default", "abc", "abc123")
|
|
|
|
testSessionID2 := testauth.StartSession()
|
|
defer testauth.ResetSession(testSessionID2)
|
|
testauth.InstallSessionToken(testSessionID2, "fake-token2", "default", "abc", "abc123")
|
|
|
|
createToken := func(methodName, bearerToken string) *structs.ACLToken {
|
|
acl := ACL{srv: s1}
|
|
|
|
resp := structs.ACLToken{}
|
|
|
|
require.NoError(t, acl.Login(&structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: methodName,
|
|
BearerToken: bearerToken,
|
|
},
|
|
Datacenter: "dc1",
|
|
}, &resp))
|
|
|
|
return &resp
|
|
}
|
|
|
|
method1, err := upsertTestAuthMethod(codec, "root", "dc1", testSessionID1)
|
|
require.NoError(t, err)
|
|
i1_r1, err := upsertTestBindingRule(
|
|
codec, "root", "dc1",
|
|
method1.Name,
|
|
"serviceaccount.name==abc",
|
|
structs.BindingRuleBindTypeService,
|
|
"abc",
|
|
)
|
|
require.NoError(t, err)
|
|
i1_r2, err := upsertTestBindingRule(
|
|
codec, "root", "dc1",
|
|
method1.Name,
|
|
"serviceaccount.name==def",
|
|
structs.BindingRuleBindTypeService,
|
|
"def",
|
|
)
|
|
require.NoError(t, err)
|
|
i1_t1 := createToken(method1.Name, "fake-token1")
|
|
i1_t2 := createToken(method1.Name, "fake-token1")
|
|
|
|
method2, err := upsertTestAuthMethod(codec, "root", "dc1", testSessionID2)
|
|
require.NoError(t, err)
|
|
i2_r1, err := upsertTestBindingRule(
|
|
codec, "root", "dc1",
|
|
method2.Name,
|
|
"serviceaccount.name==abc",
|
|
structs.BindingRuleBindTypeService,
|
|
"abc",
|
|
)
|
|
require.NoError(t, err)
|
|
i2_r2, err := upsertTestBindingRule(
|
|
codec, "root", "dc1",
|
|
method2.Name,
|
|
"serviceaccount.name==def",
|
|
structs.BindingRuleBindTypeService,
|
|
"def",
|
|
)
|
|
require.NoError(t, err)
|
|
i2_t1 := createToken(method2.Name, "fake-token2")
|
|
i2_t2 := createToken(method2.Name, "fake-token2")
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
req := structs.ACLAuthMethodDeleteRequest{
|
|
Datacenter: "dc1",
|
|
AuthMethodName: method1.Name,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var ignored bool
|
|
err = acl.AuthMethodDelete(&req, &ignored)
|
|
require.NoError(t, err)
|
|
|
|
// Make sure the method is gone.
|
|
methodResp, err := retrieveTestAuthMethod(codec, "root", "dc1", method1.Name)
|
|
require.NoError(t, err)
|
|
require.Nil(t, methodResp.AuthMethod)
|
|
|
|
// Make sure the rules and tokens are gone.
|
|
for _, id := range []string{i1_r1.ID, i1_r2.ID} {
|
|
ruleResp, err := retrieveTestBindingRule(codec, "root", "dc1", id)
|
|
require.NoError(t, err)
|
|
require.Nil(t, ruleResp.BindingRule)
|
|
}
|
|
for _, id := range []string{i1_t1.AccessorID, i1_t2.AccessorID} {
|
|
tokResp, err := retrieveTestToken(codec, "root", "dc1", id)
|
|
require.NoError(t, err)
|
|
require.Nil(t, tokResp.Token)
|
|
}
|
|
|
|
// Make sure the rules and tokens for the untouched auth method are still there.
|
|
for _, id := range []string{i2_r1.ID, i2_r2.ID} {
|
|
ruleResp, err := retrieveTestBindingRule(codec, "root", "dc1", id)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, ruleResp.BindingRule)
|
|
}
|
|
for _, id := range []string{i2_t1.AccessorID, i2_t2.AccessorID} {
|
|
tokResp, err := retrieveTestToken(codec, "root", "dc1", id)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, tokResp.Token)
|
|
}
|
|
}
|
|
|
|
func TestACLEndpoint_AuthMethodList(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
i1, err := upsertTestAuthMethod(codec, "root", "dc1", "")
|
|
require.NoError(t, err)
|
|
|
|
i2, err := upsertTestAuthMethod(codec, "root", "dc1", "")
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
req := structs.ACLAuthMethodListRequest{
|
|
Datacenter: "dc1",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLAuthMethodListResponse{}
|
|
|
|
err = acl.AuthMethodList(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, gatherIDs(t, resp.AuthMethods), []string{i1.Name, i2.Name})
|
|
}
|
|
|
|
func TestACLEndpoint_BindingRuleSet(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
var ruleID string
|
|
|
|
testAuthMethod, err := upsertTestAuthMethod(codec, "root", "dc1", "")
|
|
require.NoError(t, err)
|
|
|
|
otherTestAuthMethod, err := upsertTestAuthMethod(codec, "root", "dc1", "")
|
|
require.NoError(t, err)
|
|
|
|
newRule := func() structs.ACLBindingRule {
|
|
return structs.ACLBindingRule{
|
|
Description: "foobar",
|
|
AuthMethod: testAuthMethod.Name,
|
|
Selector: "serviceaccount.name==abc",
|
|
BindType: structs.BindingRuleBindTypeService,
|
|
BindName: "abc",
|
|
}
|
|
}
|
|
|
|
requireSetErrors := func(t *testing.T, reqRule structs.ACLBindingRule) {
|
|
req := structs.ACLBindingRuleSetRequest{
|
|
Datacenter: "dc1",
|
|
BindingRule: reqRule,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLBindingRule{}
|
|
|
|
err := acl.BindingRuleSet(&req, &resp)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
requireOK := func(t *testing.T, reqRule structs.ACLBindingRule) *structs.ACLBindingRule {
|
|
req := structs.ACLBindingRuleSetRequest{
|
|
Datacenter: "dc1",
|
|
BindingRule: reqRule,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLBindingRule{}
|
|
|
|
err := acl.BindingRuleSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, resp.ID)
|
|
return &resp
|
|
}
|
|
|
|
t.Run("Create it", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
|
|
req := structs.ACLBindingRuleSetRequest{
|
|
Datacenter: "dc1",
|
|
BindingRule: reqRule,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLBindingRule{}
|
|
|
|
err := acl.BindingRuleSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.ID)
|
|
|
|
// Get the rule directly to validate that it exists
|
|
ruleResp, err := retrieveTestBindingRule(codec, "root", "dc1", resp.ID)
|
|
require.NoError(t, err)
|
|
rule := ruleResp.BindingRule
|
|
|
|
require.NotEmpty(t, rule.ID)
|
|
require.Equal(t, rule.Description, "foobar")
|
|
require.Equal(t, rule.AuthMethod, testAuthMethod.Name)
|
|
require.Equal(t, "serviceaccount.name==abc", rule.Selector)
|
|
require.Equal(t, structs.BindingRuleBindTypeService, rule.BindType)
|
|
require.Equal(t, "abc", rule.BindName)
|
|
|
|
ruleID = rule.ID
|
|
})
|
|
|
|
t.Run("Update fails; cannot change method name", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.ID = ruleID
|
|
reqRule.AuthMethod = otherTestAuthMethod.Name
|
|
requireSetErrors(t, reqRule)
|
|
})
|
|
|
|
t.Run("Update it - omit method name", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.ID = ruleID
|
|
reqRule.Description = "foobar modified 1"
|
|
reqRule.Selector = "serviceaccount.namespace==def"
|
|
reqRule.BindType = structs.BindingRuleBindTypeRole
|
|
reqRule.BindName = "def"
|
|
reqRule.AuthMethod = "" // clear
|
|
|
|
req := structs.ACLBindingRuleSetRequest{
|
|
Datacenter: "dc1",
|
|
BindingRule: reqRule,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLBindingRule{}
|
|
|
|
err := acl.BindingRuleSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.ID)
|
|
|
|
// Get the rule directly to validate that it exists
|
|
ruleResp, err := retrieveTestBindingRule(codec, "root", "dc1", resp.ID)
|
|
require.NoError(t, err)
|
|
rule := ruleResp.BindingRule
|
|
|
|
require.NotEmpty(t, rule.ID)
|
|
require.Equal(t, rule.Description, "foobar modified 1")
|
|
require.Equal(t, rule.AuthMethod, testAuthMethod.Name)
|
|
require.Equal(t, "serviceaccount.namespace==def", rule.Selector)
|
|
require.Equal(t, structs.BindingRuleBindTypeRole, rule.BindType)
|
|
require.Equal(t, "def", rule.BindName)
|
|
})
|
|
|
|
t.Run("Update it - specify method name", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.ID = ruleID
|
|
reqRule.Description = "foobar modified 2"
|
|
reqRule.Selector = "serviceaccount.namespace==def"
|
|
reqRule.BindType = structs.BindingRuleBindTypeRole
|
|
reqRule.BindName = "def"
|
|
|
|
req := structs.ACLBindingRuleSetRequest{
|
|
Datacenter: "dc1",
|
|
BindingRule: reqRule,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLBindingRule{}
|
|
|
|
err := acl.BindingRuleSet(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp.ID)
|
|
|
|
// Get the rule directly to validate that it exists
|
|
ruleResp, err := retrieveTestBindingRule(codec, "root", "dc1", resp.ID)
|
|
require.NoError(t, err)
|
|
rule := ruleResp.BindingRule
|
|
|
|
require.NotEmpty(t, rule.ID)
|
|
require.Equal(t, rule.Description, "foobar modified 2")
|
|
require.Equal(t, rule.AuthMethod, testAuthMethod.Name)
|
|
require.Equal(t, "serviceaccount.namespace==def", rule.Selector)
|
|
require.Equal(t, structs.BindingRuleBindTypeRole, rule.BindType)
|
|
require.Equal(t, "def", rule.BindName)
|
|
})
|
|
|
|
t.Run("Create fails; empty method name", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.AuthMethod = ""
|
|
requireSetErrors(t, reqRule)
|
|
})
|
|
|
|
t.Run("Create fails; unknown method name", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.AuthMethod = "unknown"
|
|
requireSetErrors(t, reqRule)
|
|
})
|
|
|
|
t.Run("Create with no explicit selector", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.Selector = ""
|
|
|
|
rule := requireOK(t, reqRule)
|
|
require.Empty(t, rule.Selector, 0)
|
|
})
|
|
|
|
t.Run("Create fails; match selector with unknown vars", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.Selector = "serviceaccount.name==a and serviceaccount.bizarroname==b"
|
|
requireSetErrors(t, reqRule)
|
|
})
|
|
|
|
t.Run("Create fails; match selector invalid", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.Selector = "serviceaccount.name"
|
|
requireSetErrors(t, reqRule)
|
|
})
|
|
|
|
t.Run("Create fails; empty bind type", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.BindType = ""
|
|
requireSetErrors(t, reqRule)
|
|
})
|
|
|
|
t.Run("Create fails; empty bind name", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.BindName = ""
|
|
requireSetErrors(t, reqRule)
|
|
})
|
|
|
|
t.Run("Create fails; invalid bind type", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.BindType = "invalid"
|
|
requireSetErrors(t, reqRule)
|
|
})
|
|
|
|
t.Run("Create fails; bind name with unknown vars", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.BindName = "method-${serviceaccount.bizarroname}"
|
|
requireSetErrors(t, reqRule)
|
|
})
|
|
|
|
t.Run("Create fails; invalid bind name no template", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.BindName = "-abc:"
|
|
requireSetErrors(t, reqRule)
|
|
})
|
|
|
|
t.Run("Create fails; invalid bind name with template", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.BindName = "method-${serviceaccount.name"
|
|
requireSetErrors(t, reqRule)
|
|
})
|
|
t.Run("Create fails; invalid bind name after template computed", func(t *testing.T) {
|
|
reqRule := newRule()
|
|
reqRule.BindName = "method-${serviceaccount.name}:blah-"
|
|
requireSetErrors(t, reqRule)
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_BindingRuleDelete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
testAuthMethod, err := upsertTestAuthMethod(codec, "root", "dc1", "")
|
|
require.NoError(t, err)
|
|
|
|
existingRule, err := upsertTestBindingRule(
|
|
codec, "root", "dc1",
|
|
testAuthMethod.Name,
|
|
"serviceaccount.name==abc",
|
|
structs.BindingRuleBindTypeService,
|
|
"abc",
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
t.Run("normal", func(t *testing.T) {
|
|
req := structs.ACLBindingRuleDeleteRequest{
|
|
Datacenter: "dc1",
|
|
BindingRuleID: existingRule.ID,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var ignored bool
|
|
err = acl.BindingRuleDelete(&req, &ignored)
|
|
require.NoError(t, err)
|
|
|
|
// Make sure the rule is gone
|
|
ruleResp, err := retrieveTestBindingRule(codec, "root", "dc1", existingRule.ID)
|
|
require.NoError(t, err)
|
|
require.Nil(t, ruleResp.BindingRule)
|
|
})
|
|
|
|
t.Run("delete something that doesn't exist", func(t *testing.T) {
|
|
fakeID, err := uuid.GenerateUUID()
|
|
require.NoError(t, err)
|
|
|
|
req := structs.ACLBindingRuleDeleteRequest{
|
|
Datacenter: "dc1",
|
|
BindingRuleID: fakeID,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var ignored bool
|
|
err = acl.BindingRuleDelete(&req, &ignored)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_BindingRuleList(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
testAuthMethod, err := upsertTestAuthMethod(codec, "root", "dc1", "")
|
|
require.NoError(t, err)
|
|
|
|
r1, err := upsertTestBindingRule(
|
|
codec, "root", "dc1",
|
|
testAuthMethod.Name,
|
|
"serviceaccount.name==abc",
|
|
structs.BindingRuleBindTypeService,
|
|
"abc",
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
r2, err := upsertTestBindingRule(
|
|
codec, "root", "dc1",
|
|
testAuthMethod.Name,
|
|
"serviceaccount.name==def",
|
|
structs.BindingRuleBindTypeService,
|
|
"def",
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
req := structs.ACLBindingRuleListRequest{
|
|
Datacenter: "dc1",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLBindingRuleListResponse{}
|
|
|
|
err = acl.BindingRuleList(&req, &resp)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, gatherIDs(t, resp.BindingRules), []string{r1.ID, r2.ID})
|
|
}
|
|
|
|
func TestACLEndpoint_SecureIntroEndpoints_LocalTokensDisabled(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
|
|
c.ACLTokenMaxExpirationTTL = 5 * time.Second
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.Datacenter = "dc2"
|
|
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
|
|
c.ACLTokenMaxExpirationTTL = 5 * time.Second
|
|
// disable local tokens
|
|
c.ACLTokenReplication = false
|
|
})
|
|
defer os.RemoveAll(dir2)
|
|
defer s2.Shutdown()
|
|
codec2 := rpcClient(t, s2)
|
|
defer codec2.Close()
|
|
|
|
s2.tokens.UpdateReplicationToken("root", tokenStore.TokenSourceConfig)
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
testrpc.WaitForLeader(t, s2.RPC, "dc2")
|
|
|
|
// Try to join
|
|
joinWAN(t, s2, s1)
|
|
|
|
waitForNewACLs(t, s1)
|
|
waitForNewACLs(t, s2)
|
|
|
|
acl2 := ACL{srv: s2}
|
|
var ignored bool
|
|
|
|
errString := errAuthMethodsRequireTokenReplication.Error()
|
|
|
|
t.Run("AuthMethodRead", func(t *testing.T) {
|
|
requireErrorContains(t,
|
|
acl2.AuthMethodRead(&structs.ACLAuthMethodGetRequest{Datacenter: "dc2"},
|
|
&structs.ACLAuthMethodResponse{}),
|
|
errString,
|
|
)
|
|
})
|
|
t.Run("AuthMethodSet", func(t *testing.T) {
|
|
requireErrorContains(t,
|
|
acl2.AuthMethodSet(&structs.ACLAuthMethodSetRequest{Datacenter: "dc2"},
|
|
&structs.ACLAuthMethod{}),
|
|
errString,
|
|
)
|
|
})
|
|
t.Run("AuthMethodDelete", func(t *testing.T) {
|
|
requireErrorContains(t,
|
|
acl2.AuthMethodDelete(&structs.ACLAuthMethodDeleteRequest{Datacenter: "dc2"}, &ignored),
|
|
errString,
|
|
)
|
|
})
|
|
t.Run("AuthMethodList", func(t *testing.T) {
|
|
requireErrorContains(t,
|
|
acl2.AuthMethodList(&structs.ACLAuthMethodListRequest{Datacenter: "dc2"},
|
|
&structs.ACLAuthMethodListResponse{}),
|
|
errString,
|
|
)
|
|
})
|
|
|
|
t.Run("BindingRuleRead", func(t *testing.T) {
|
|
requireErrorContains(t,
|
|
acl2.BindingRuleRead(&structs.ACLBindingRuleGetRequest{Datacenter: "dc2"},
|
|
&structs.ACLBindingRuleResponse{}),
|
|
errString,
|
|
)
|
|
})
|
|
t.Run("BindingRuleSet", func(t *testing.T) {
|
|
requireErrorContains(t,
|
|
acl2.BindingRuleSet(&structs.ACLBindingRuleSetRequest{Datacenter: "dc2"},
|
|
&structs.ACLBindingRule{}),
|
|
errString,
|
|
)
|
|
})
|
|
t.Run("BindingRuleDelete", func(t *testing.T) {
|
|
requireErrorContains(t,
|
|
acl2.BindingRuleDelete(&structs.ACLBindingRuleDeleteRequest{Datacenter: "dc2"}, &ignored),
|
|
errString,
|
|
)
|
|
})
|
|
t.Run("BindingRuleList", func(t *testing.T) {
|
|
requireErrorContains(t,
|
|
acl2.BindingRuleList(&structs.ACLBindingRuleListRequest{Datacenter: "dc2"},
|
|
&structs.ACLBindingRuleListResponse{}),
|
|
errString,
|
|
)
|
|
})
|
|
|
|
t.Run("Login", func(t *testing.T) {
|
|
requireErrorContains(t,
|
|
acl2.Login(&structs.ACLLoginRequest{Datacenter: "dc2"},
|
|
&structs.ACLToken{}),
|
|
errString,
|
|
)
|
|
})
|
|
t.Run("Logout", func(t *testing.T) {
|
|
requireErrorContains(t,
|
|
acl2.Logout(&structs.ACLLogoutRequest{Datacenter: "dc2"}, &ignored),
|
|
errString,
|
|
)
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
|
|
c.ACLTokenMaxExpirationTTL = 5 * time.Second
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec1 := rpcClient(t, s1)
|
|
defer codec1.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.Datacenter = "dc2"
|
|
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
|
|
c.ACLTokenMaxExpirationTTL = 5 * time.Second
|
|
// enable token replication so secure intro works
|
|
c.ACLTokenReplication = true
|
|
})
|
|
defer os.RemoveAll(dir2)
|
|
defer s2.Shutdown()
|
|
codec2 := rpcClient(t, s2)
|
|
defer codec2.Close()
|
|
|
|
s2.tokens.UpdateReplicationToken("root", tokenStore.TokenSourceConfig)
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
testrpc.WaitForLeader(t, s2.RPC, "dc2")
|
|
|
|
// Try to join
|
|
joinWAN(t, s2, s1)
|
|
|
|
waitForNewACLs(t, s1)
|
|
waitForNewACLs(t, s2)
|
|
|
|
// Ensure s2 is authoritative.
|
|
waitForNewACLReplication(t, s2, structs.ACLReplicateTokens, 1, 1, 0)
|
|
|
|
acl := ACL{srv: s1}
|
|
acl2 := ACL{srv: s2}
|
|
|
|
//
|
|
// this order is specific so that we can do it in one pass
|
|
//
|
|
|
|
testSessionID_1 := testauth.StartSession()
|
|
defer testauth.ResetSession(testSessionID_1)
|
|
|
|
testSessionID_2 := testauth.StartSession()
|
|
defer testauth.ResetSession(testSessionID_2)
|
|
|
|
testauth.InstallSessionToken(
|
|
testSessionID_1,
|
|
"fake-web1-token",
|
|
"default", "web1", "abc123",
|
|
)
|
|
testauth.InstallSessionToken(
|
|
testSessionID_2,
|
|
"fake-web2-token",
|
|
"default", "web2", "def456",
|
|
)
|
|
|
|
t.Run("create auth method", func(t *testing.T) {
|
|
req := structs.ACLAuthMethodSetRequest{
|
|
Datacenter: "dc2",
|
|
AuthMethod: structs.ACLAuthMethod{
|
|
Name: "testmethod",
|
|
Description: "test original",
|
|
Type: "testing",
|
|
Config: map[string]interface{}{
|
|
"SessionID": testSessionID_2,
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLAuthMethod{}
|
|
|
|
require.NoError(t, acl2.AuthMethodSet(&req, &resp))
|
|
|
|
// present in dc2
|
|
resp2, err := retrieveTestAuthMethod(codec2, "root", "dc2", "testmethod")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp2.AuthMethod)
|
|
require.Equal(t, "test original", resp2.AuthMethod.Description)
|
|
// absent in dc1
|
|
resp2, err = retrieveTestAuthMethod(codec1, "root", "dc1", "testmethod")
|
|
require.NoError(t, err)
|
|
require.Nil(t, resp2.AuthMethod)
|
|
})
|
|
|
|
t.Run("update auth method", func(t *testing.T) {
|
|
req := structs.ACLAuthMethodSetRequest{
|
|
Datacenter: "dc2",
|
|
AuthMethod: structs.ACLAuthMethod{
|
|
Name: "testmethod",
|
|
Description: "test updated",
|
|
Config: map[string]interface{}{
|
|
"SessionID": testSessionID_2,
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
resp := structs.ACLAuthMethod{}
|
|
|
|
require.NoError(t, acl2.AuthMethodSet(&req, &resp))
|
|
|
|
// present in dc2
|
|
resp2, err := retrieveTestAuthMethod(codec2, "root", "dc2", "testmethod")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp2.AuthMethod)
|
|
require.Equal(t, "test updated", resp2.AuthMethod.Description)
|
|
// absent in dc1
|
|
resp2, err = retrieveTestAuthMethod(codec1, "root", "dc1", "testmethod")
|
|
require.NoError(t, err)
|
|
require.Nil(t, resp2.AuthMethod)
|
|
})
|
|
|
|
t.Run("read auth method", func(t *testing.T) {
|
|
// present in dc2
|
|
req := structs.ACLAuthMethodGetRequest{
|
|
Datacenter: "dc2",
|
|
AuthMethodName: "testmethod",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
resp := structs.ACLAuthMethodResponse{}
|
|
require.NoError(t, acl2.AuthMethodRead(&req, &resp))
|
|
require.NotNil(t, resp.AuthMethod)
|
|
require.Equal(t, "test updated", resp.AuthMethod.Description)
|
|
|
|
// absent in dc1
|
|
req = structs.ACLAuthMethodGetRequest{
|
|
Datacenter: "dc1",
|
|
AuthMethodName: "testmethod",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
resp = structs.ACLAuthMethodResponse{}
|
|
require.NoError(t, acl.AuthMethodRead(&req, &resp))
|
|
require.Nil(t, resp.AuthMethod)
|
|
})
|
|
|
|
t.Run("list auth method", func(t *testing.T) {
|
|
// present in dc2
|
|
req := structs.ACLAuthMethodListRequest{
|
|
Datacenter: "dc2",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
resp := structs.ACLAuthMethodListResponse{}
|
|
require.NoError(t, acl2.AuthMethodList(&req, &resp))
|
|
require.Len(t, resp.AuthMethods, 1)
|
|
|
|
// absent in dc1
|
|
req = structs.ACLAuthMethodListRequest{
|
|
Datacenter: "dc1",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
resp = structs.ACLAuthMethodListResponse{}
|
|
require.NoError(t, acl.AuthMethodList(&req, &resp))
|
|
require.Len(t, resp.AuthMethods, 0)
|
|
})
|
|
|
|
var ruleID string
|
|
t.Run("create binding rule", func(t *testing.T) {
|
|
req := structs.ACLBindingRuleSetRequest{
|
|
Datacenter: "dc2",
|
|
BindingRule: structs.ACLBindingRule{
|
|
Description: "test original",
|
|
AuthMethod: "testmethod",
|
|
BindType: structs.BindingRuleBindTypeService,
|
|
BindName: "${serviceaccount.name}",
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLBindingRule{}
|
|
|
|
require.NoError(t, acl2.BindingRuleSet(&req, &resp))
|
|
ruleID = resp.ID
|
|
|
|
// present in dc2
|
|
resp2, err := retrieveTestBindingRule(codec2, "root", "dc2", ruleID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp2.BindingRule)
|
|
require.Equal(t, "test original", resp2.BindingRule.Description)
|
|
// absent in dc1
|
|
resp2, err = retrieveTestBindingRule(codec1, "root", "dc1", ruleID)
|
|
require.NoError(t, err)
|
|
require.Nil(t, resp2.BindingRule)
|
|
})
|
|
|
|
t.Run("update binding rule", func(t *testing.T) {
|
|
req := structs.ACLBindingRuleSetRequest{
|
|
Datacenter: "dc2",
|
|
BindingRule: structs.ACLBindingRule{
|
|
ID: ruleID,
|
|
Description: "test updated",
|
|
AuthMethod: "testmethod",
|
|
BindType: structs.BindingRuleBindTypeService,
|
|
BindName: "${serviceaccount.name}",
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLBindingRule{}
|
|
|
|
require.NoError(t, acl2.BindingRuleSet(&req, &resp))
|
|
ruleID = resp.ID
|
|
|
|
// present in dc2
|
|
resp2, err := retrieveTestBindingRule(codec2, "root", "dc2", ruleID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp2.BindingRule)
|
|
require.Equal(t, "test updated", resp2.BindingRule.Description)
|
|
// absent in dc1
|
|
resp2, err = retrieveTestBindingRule(codec1, "root", "dc1", ruleID)
|
|
require.NoError(t, err)
|
|
require.Nil(t, resp2.BindingRule)
|
|
})
|
|
|
|
t.Run("read binding rule", func(t *testing.T) {
|
|
// present in dc2
|
|
req := structs.ACLBindingRuleGetRequest{
|
|
Datacenter: "dc2",
|
|
BindingRuleID: ruleID,
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
resp := structs.ACLBindingRuleResponse{}
|
|
require.NoError(t, acl2.BindingRuleRead(&req, &resp))
|
|
require.NotNil(t, resp.BindingRule)
|
|
require.Equal(t, "test updated", resp.BindingRule.Description)
|
|
|
|
// absent in dc1
|
|
req = structs.ACLBindingRuleGetRequest{
|
|
Datacenter: "dc1",
|
|
BindingRuleID: ruleID,
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
resp = structs.ACLBindingRuleResponse{}
|
|
require.NoError(t, acl.BindingRuleRead(&req, &resp))
|
|
require.Nil(t, resp.BindingRule)
|
|
})
|
|
|
|
t.Run("list binding rule", func(t *testing.T) {
|
|
// present in dc2
|
|
req := structs.ACLBindingRuleListRequest{
|
|
Datacenter: "dc2",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
resp := structs.ACLBindingRuleListResponse{}
|
|
require.NoError(t, acl2.BindingRuleList(&req, &resp))
|
|
require.Len(t, resp.BindingRules, 1)
|
|
|
|
// absent in dc1
|
|
req = structs.ACLBindingRuleListRequest{
|
|
Datacenter: "dc1",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
resp = structs.ACLBindingRuleListResponse{}
|
|
require.NoError(t, acl.BindingRuleList(&req, &resp))
|
|
require.Len(t, resp.BindingRules, 0)
|
|
})
|
|
|
|
var remoteToken *structs.ACLToken
|
|
t.Run("login in remote", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Datacenter: "dc2",
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: "testmethod",
|
|
BearerToken: "fake-web2-token",
|
|
},
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
require.NoError(t, acl2.Login(&req, &resp))
|
|
remoteToken = &resp
|
|
|
|
// present in dc2
|
|
resp2, err := retrieveTestToken(codec2, "root", "dc2", remoteToken.AccessorID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp2.Token)
|
|
require.Len(t, resp2.Token.ServiceIdentities, 1)
|
|
require.Equal(t, "web2", resp2.Token.ServiceIdentities[0].ServiceName)
|
|
// absent in dc1
|
|
resp2, err = retrieveTestToken(codec1, "root", "dc1", remoteToken.AccessorID)
|
|
require.NoError(t, err)
|
|
require.Nil(t, resp2.Token)
|
|
})
|
|
|
|
// We delay until now to setup an auth method and binding rule in the
|
|
// primary so our earlier listing tests were sane. We need to be able to
|
|
// use auth methods in both datacenters in order to verify Logout is
|
|
// properly scoped.
|
|
t.Run("initialize primary so we can test logout", func(t *testing.T) {
|
|
reqAM := structs.ACLAuthMethodSetRequest{
|
|
Datacenter: "dc1",
|
|
AuthMethod: structs.ACLAuthMethod{
|
|
Name: "primarymethod",
|
|
Type: "testing",
|
|
Config: map[string]interface{}{
|
|
"SessionID": testSessionID_1,
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
respAM := structs.ACLAuthMethod{}
|
|
require.NoError(t, acl.AuthMethodSet(&reqAM, &respAM))
|
|
|
|
reqBR := structs.ACLBindingRuleSetRequest{
|
|
Datacenter: "dc1",
|
|
BindingRule: structs.ACLBindingRule{
|
|
AuthMethod: "primarymethod",
|
|
BindType: structs.BindingRuleBindTypeService,
|
|
BindName: "${serviceaccount.name}",
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
respBR := structs.ACLBindingRule{}
|
|
require.NoError(t, acl.BindingRuleSet(&reqBR, &respBR))
|
|
})
|
|
|
|
var primaryToken *structs.ACLToken
|
|
t.Run("login in primary", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Datacenter: "dc1",
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: "primarymethod",
|
|
BearerToken: "fake-web1-token",
|
|
},
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
require.NoError(t, acl.Login(&req, &resp))
|
|
primaryToken = &resp
|
|
|
|
// present in dc1
|
|
resp2, err := retrieveTestToken(codec1, "root", "dc1", primaryToken.AccessorID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp2.Token)
|
|
require.Len(t, resp2.Token.ServiceIdentities, 1)
|
|
require.Equal(t, "web1", resp2.Token.ServiceIdentities[0].ServiceName)
|
|
// absent in dc2
|
|
resp2, err = retrieveTestToken(codec2, "root", "dc2", primaryToken.AccessorID)
|
|
require.NoError(t, err)
|
|
require.Nil(t, resp2.Token)
|
|
})
|
|
|
|
t.Run("logout of remote token in remote dc", func(t *testing.T) {
|
|
// if the other test fails this one will to but will now not segfault
|
|
require.NotNil(t, remoteToken)
|
|
|
|
req := structs.ACLLogoutRequest{
|
|
Datacenter: "dc2",
|
|
WriteRequest: structs.WriteRequest{Token: remoteToken.SecretID},
|
|
}
|
|
|
|
var ignored bool
|
|
require.NoError(t, acl.Logout(&req, &ignored))
|
|
|
|
// absent in dc2
|
|
resp2, err := retrieveTestToken(codec2, "root", "dc2", remoteToken.AccessorID)
|
|
require.NoError(t, err)
|
|
require.Nil(t, resp2.Token)
|
|
// absent in dc1
|
|
resp2, err = retrieveTestToken(codec1, "root", "dc1", remoteToken.AccessorID)
|
|
require.NoError(t, err)
|
|
require.Nil(t, resp2.Token)
|
|
})
|
|
|
|
t.Run("logout of primary token in remote dc should not work", func(t *testing.T) {
|
|
req := structs.ACLLogoutRequest{
|
|
Datacenter: "dc2",
|
|
WriteRequest: structs.WriteRequest{Token: primaryToken.SecretID},
|
|
}
|
|
|
|
var ignored bool
|
|
requireErrorContains(t, acl.Logout(&req, &ignored), "ACL not found")
|
|
|
|
// present in dc1
|
|
resp2, err := retrieveTestToken(codec1, "root", "dc1", primaryToken.AccessorID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp2.Token)
|
|
require.Len(t, resp2.Token.ServiceIdentities, 1)
|
|
require.Equal(t, "web1", resp2.Token.ServiceIdentities[0].ServiceName)
|
|
// absent in dc2
|
|
resp2, err = retrieveTestToken(codec2, "root", "dc2", primaryToken.AccessorID)
|
|
require.NoError(t, err)
|
|
require.Nil(t, resp2.Token)
|
|
})
|
|
|
|
// Don't trigger the auth method delete cascade so we know the individual
|
|
// endpoints follow the rules.
|
|
|
|
t.Run("delete binding rule", func(t *testing.T) {
|
|
req := structs.ACLBindingRuleDeleteRequest{
|
|
Datacenter: "dc2",
|
|
BindingRuleID: ruleID,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var ignored bool
|
|
require.NoError(t, acl2.BindingRuleDelete(&req, &ignored))
|
|
|
|
// absent in dc2
|
|
resp2, err := retrieveTestBindingRule(codec2, "root", "dc2", ruleID)
|
|
require.NoError(t, err)
|
|
require.Nil(t, resp2.BindingRule)
|
|
// absent in dc1
|
|
resp2, err = retrieveTestBindingRule(codec1, "root", "dc1", ruleID)
|
|
require.NoError(t, err)
|
|
require.Nil(t, resp2.BindingRule)
|
|
})
|
|
|
|
t.Run("delete auth method", func(t *testing.T) {
|
|
req := structs.ACLAuthMethodDeleteRequest{
|
|
Datacenter: "dc2",
|
|
AuthMethodName: "testmethod",
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var ignored bool
|
|
require.NoError(t, acl2.AuthMethodDelete(&req, &ignored))
|
|
|
|
// absent in dc2
|
|
resp2, err := retrieveTestAuthMethod(codec2, "root", "dc2", "testmethod")
|
|
require.NoError(t, err)
|
|
require.Nil(t, resp2.AuthMethod)
|
|
// absent in dc1
|
|
resp2, err = retrieveTestAuthMethod(codec1, "root", "dc1", "testmethod")
|
|
require.NoError(t, err)
|
|
require.Nil(t, resp2.AuthMethod)
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_Login(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
testSessionID := testauth.StartSession()
|
|
defer testauth.ResetSession(testSessionID)
|
|
|
|
testauth.InstallSessionToken(
|
|
testSessionID,
|
|
"fake-web", // no rules
|
|
"default", "web", "abc123",
|
|
)
|
|
testauth.InstallSessionToken(
|
|
testSessionID,
|
|
"fake-db", // 1 rule (service)
|
|
"default", "db", "def456",
|
|
)
|
|
testauth.InstallSessionToken(
|
|
testSessionID,
|
|
"fake-vault", // 1 rule (role)
|
|
"default", "vault", "jkl012",
|
|
)
|
|
testauth.InstallSessionToken(
|
|
testSessionID,
|
|
"fake-monolith", // 2 rules (one of each)
|
|
"default", "monolith", "ghi789",
|
|
)
|
|
|
|
method, err := upsertTestAuthMethod(codec, "root", "dc1", testSessionID)
|
|
require.NoError(t, err)
|
|
|
|
// 'fake-db' rules
|
|
ruleDB, err := upsertTestBindingRule(
|
|
codec, "root", "dc1", method.Name,
|
|
"serviceaccount.namespace==default and serviceaccount.name==db",
|
|
structs.BindingRuleBindTypeService,
|
|
"method-${serviceaccount.name}",
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// 'fake-vault' rules
|
|
_, err = upsertTestBindingRule(
|
|
codec, "root", "dc1", method.Name,
|
|
"serviceaccount.namespace==default and serviceaccount.name==vault",
|
|
structs.BindingRuleBindTypeRole,
|
|
"method-${serviceaccount.name}",
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// 'fake-monolith' rules
|
|
_, err = upsertTestBindingRule(
|
|
codec, "root", "dc1", method.Name,
|
|
"serviceaccount.namespace==default and serviceaccount.name==monolith",
|
|
structs.BindingRuleBindTypeService,
|
|
"method-${serviceaccount.name}",
|
|
)
|
|
require.NoError(t, err)
|
|
_, err = upsertTestBindingRule(
|
|
codec, "root", "dc1", method.Name,
|
|
"serviceaccount.namespace==default and serviceaccount.name==monolith",
|
|
structs.BindingRuleBindTypeRole,
|
|
"method-${serviceaccount.name}",
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("do not provide a token", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: "fake-web",
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
req.Token = "nope"
|
|
resp := structs.ACLToken{}
|
|
|
|
requireErrorContains(t, acl.Login(&req, &resp), "do not provide a token")
|
|
})
|
|
|
|
t.Run("unknown method", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name + "-notexist",
|
|
BearerToken: "fake-web",
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
requireErrorContains(t, acl.Login(&req, &resp), "ACL not found")
|
|
})
|
|
|
|
t.Run("invalid method token", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: "invalid",
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
require.Error(t, acl.Login(&req, &resp))
|
|
})
|
|
|
|
t.Run("valid method token no bindings", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: "fake-web",
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
requireErrorContains(t, acl.Login(&req, &resp), "Permission denied")
|
|
})
|
|
|
|
t.Run("valid method token 1 role binding and role does not exist", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: "fake-vault",
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
requireErrorContains(t, acl.Login(&req, &resp), "Permission denied")
|
|
})
|
|
|
|
// create the role so that the bindtype=role login works
|
|
var vaultRoleID string
|
|
{
|
|
arg := structs.ACLRoleSetRequest{
|
|
Datacenter: "dc1",
|
|
Role: structs.ACLRole{
|
|
Name: "method-vault",
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var out structs.ACLRole
|
|
require.NoError(t, acl.RoleSet(&arg, &out))
|
|
|
|
vaultRoleID = out.ID
|
|
}
|
|
|
|
t.Run("valid method token 1 role binding and role now exists", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: "fake-vault",
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
require.NoError(t, acl.Login(&req, &resp))
|
|
|
|
require.Equal(t, method.Name, resp.AuthMethod)
|
|
require.Equal(t, `token created via login: {"pod":"pod1"}`, resp.Description)
|
|
require.True(t, resp.Local)
|
|
require.Len(t, resp.ServiceIdentities, 0)
|
|
require.Len(t, resp.Roles, 1)
|
|
role := resp.Roles[0]
|
|
require.Equal(t, vaultRoleID, role.ID)
|
|
require.Equal(t, "method-vault", role.Name)
|
|
})
|
|
|
|
t.Run("valid method token 1 service binding 1 role binding and role does not exist", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: "fake-monolith",
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
require.NoError(t, acl.Login(&req, &resp))
|
|
|
|
require.Equal(t, method.Name, resp.AuthMethod)
|
|
require.Equal(t, `token created via login: {"pod":"pod1"}`, resp.Description)
|
|
require.True(t, resp.Local)
|
|
require.Len(t, resp.ServiceIdentities, 1)
|
|
require.Len(t, resp.Roles, 0)
|
|
svcid := resp.ServiceIdentities[0]
|
|
require.Len(t, svcid.Datacenters, 0)
|
|
require.Equal(t, "method-monolith", svcid.ServiceName)
|
|
})
|
|
|
|
// create the role so that the bindtype=role login works
|
|
var monolithRoleID string
|
|
{
|
|
arg := structs.ACLRoleSetRequest{
|
|
Datacenter: "dc1",
|
|
Role: structs.ACLRole{
|
|
Name: "method-monolith",
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var out structs.ACLRole
|
|
require.NoError(t, acl.RoleSet(&arg, &out))
|
|
|
|
monolithRoleID = out.ID
|
|
}
|
|
|
|
t.Run("valid method token 1 service binding 1 role binding and role now exists", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: "fake-monolith",
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
require.NoError(t, acl.Login(&req, &resp))
|
|
|
|
require.Equal(t, method.Name, resp.AuthMethod)
|
|
require.Equal(t, `token created via login: {"pod":"pod1"}`, resp.Description)
|
|
require.True(t, resp.Local)
|
|
require.Len(t, resp.ServiceIdentities, 1)
|
|
require.Len(t, resp.Roles, 1)
|
|
role := resp.Roles[0]
|
|
require.Equal(t, monolithRoleID, role.ID)
|
|
require.Equal(t, "method-monolith", role.Name)
|
|
svcid := resp.ServiceIdentities[0]
|
|
require.Len(t, svcid.Datacenters, 0)
|
|
require.Equal(t, "method-monolith", svcid.ServiceName)
|
|
})
|
|
|
|
t.Run("valid bearer token 1 service binding", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: "fake-db",
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
require.NoError(t, acl.Login(&req, &resp))
|
|
|
|
require.Equal(t, method.Name, resp.AuthMethod)
|
|
require.Equal(t, `token created via login: {"pod":"pod1"}`, resp.Description)
|
|
require.True(t, resp.Local)
|
|
require.Len(t, resp.Roles, 0)
|
|
require.Len(t, resp.ServiceIdentities, 1)
|
|
svcid := resp.ServiceIdentities[0]
|
|
require.Len(t, svcid.Datacenters, 0)
|
|
require.Equal(t, "method-db", svcid.ServiceName)
|
|
})
|
|
|
|
{
|
|
req := structs.ACLBindingRuleSetRequest{
|
|
Datacenter: "dc1",
|
|
BindingRule: structs.ACLBindingRule{
|
|
AuthMethod: ruleDB.AuthMethod,
|
|
BindType: structs.BindingRuleBindTypeService,
|
|
BindName: ruleDB.BindName,
|
|
Selector: "",
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var out structs.ACLBindingRule
|
|
require.NoError(t, acl.BindingRuleSet(&req, &out))
|
|
}
|
|
|
|
t.Run("valid bearer token 1 binding (no selectors this time)", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: "fake-db",
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
require.NoError(t, acl.Login(&req, &resp))
|
|
|
|
require.Equal(t, method.Name, resp.AuthMethod)
|
|
require.Equal(t, `token created via login: {"pod":"pod1"}`, resp.Description)
|
|
require.True(t, resp.Local)
|
|
require.Len(t, resp.Roles, 0)
|
|
require.Len(t, resp.ServiceIdentities, 1)
|
|
svcid := resp.ServiceIdentities[0]
|
|
require.Len(t, svcid.Datacenters, 0)
|
|
require.Equal(t, "method-db", svcid.ServiceName)
|
|
})
|
|
|
|
testSessionID_2 := testauth.StartSession()
|
|
defer testauth.ResetSession(testSessionID_2)
|
|
{
|
|
// Update the method to force the cache to invalidate for the next
|
|
// subtest.
|
|
updated := *method
|
|
updated.Description = "updated for the test"
|
|
updated.Config = map[string]interface{}{
|
|
"SessionID": testSessionID_2,
|
|
}
|
|
|
|
req := structs.ACLAuthMethodSetRequest{
|
|
Datacenter: "dc1",
|
|
AuthMethod: updated,
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
var ignored structs.ACLAuthMethod
|
|
require.NoError(t, acl.AuthMethodSet(&req, &ignored))
|
|
}
|
|
|
|
t.Run("updating the method invalidates the cache", func(t *testing.T) {
|
|
// We'll try to login with the 'fake-db' cred which DOES exist in the
|
|
// old fake validator, but no longer exists in the new fake validator.
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: "fake-db",
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
requireErrorContains(t, acl.Login(&req, &resp), "ACL not found")
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_Login_k8s(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
// spin up a fake api server
|
|
testSrv := kubeauth.StartTestAPIServer(t)
|
|
defer testSrv.Stop()
|
|
|
|
testSrv.AuthorizeJWT(goodJWT_A)
|
|
testSrv.SetAllowedServiceAccount(
|
|
"default",
|
|
"demo",
|
|
"76091af4-4b56-11e9-ac4b-708b11801cbe",
|
|
"",
|
|
goodJWT_B,
|
|
)
|
|
|
|
method, err := upsertTestKubernetesAuthMethod(
|
|
codec, "root", "dc1",
|
|
testSrv.CACert(),
|
|
testSrv.Addr(),
|
|
goodJWT_A,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("invalid bearer token", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: "invalid",
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
require.Error(t, acl.Login(&req, &resp))
|
|
})
|
|
|
|
t.Run("valid bearer token no bindings", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: goodJWT_B,
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
requireErrorContains(t, acl.Login(&req, &resp), "Permission denied")
|
|
})
|
|
|
|
_, err = upsertTestBindingRule(
|
|
codec, "root", "dc1", method.Name,
|
|
"serviceaccount.namespace==default",
|
|
structs.BindingRuleBindTypeService,
|
|
"${serviceaccount.name}",
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("valid bearer token 1 service binding", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: goodJWT_B,
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
require.NoError(t, acl.Login(&req, &resp))
|
|
|
|
require.Equal(t, method.Name, resp.AuthMethod)
|
|
require.Equal(t, `token created via login: {"pod":"pod1"}`, resp.Description)
|
|
require.True(t, resp.Local)
|
|
require.Len(t, resp.Roles, 0)
|
|
require.Len(t, resp.ServiceIdentities, 1)
|
|
svcid := resp.ServiceIdentities[0]
|
|
require.Len(t, svcid.Datacenters, 0)
|
|
require.Equal(t, "demo", svcid.ServiceName)
|
|
})
|
|
|
|
// annotate the account
|
|
testSrv.SetAllowedServiceAccount(
|
|
"default",
|
|
"demo",
|
|
"76091af4-4b56-11e9-ac4b-708b11801cbe",
|
|
"alternate-name",
|
|
goodJWT_B,
|
|
)
|
|
|
|
t.Run("valid bearer token 1 service binding - with annotation", func(t *testing.T) {
|
|
req := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: goodJWT_B,
|
|
Meta: map[string]string{"pod": "pod1"},
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
resp := structs.ACLToken{}
|
|
|
|
require.NoError(t, acl.Login(&req, &resp))
|
|
|
|
require.Equal(t, method.Name, resp.AuthMethod)
|
|
require.Equal(t, `token created via login: {"pod":"pod1"}`, resp.Description)
|
|
require.True(t, resp.Local)
|
|
require.Len(t, resp.Roles, 0)
|
|
require.Len(t, resp.ServiceIdentities, 1)
|
|
svcid := resp.ServiceIdentities[0]
|
|
require.Len(t, svcid.Datacenters, 0)
|
|
require.Equal(t, "alternate-name", svcid.ServiceName)
|
|
})
|
|
}
|
|
|
|
func TestACLEndpoint_Logout(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
c.ACLDatacenter = "dc1"
|
|
c.ACLsEnabled = true
|
|
c.ACLMasterToken = "root"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
testSessionID := testauth.StartSession()
|
|
defer testauth.ResetSession(testSessionID)
|
|
testauth.InstallSessionToken(
|
|
testSessionID,
|
|
"fake-db", // 1 rule
|
|
"default", "db", "def456",
|
|
)
|
|
|
|
method, err := upsertTestAuthMethod(codec, "root", "dc1", testSessionID)
|
|
require.NoError(t, err)
|
|
|
|
_, err = upsertTestBindingRule(
|
|
codec, "root", "dc1", method.Name,
|
|
"",
|
|
structs.BindingRuleBindTypeService,
|
|
"method-${serviceaccount.name}",
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("you must provide a token", func(t *testing.T) {
|
|
req := structs.ACLLogoutRequest{
|
|
Datacenter: "dc1",
|
|
// WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
req.Token = ""
|
|
var ignored bool
|
|
|
|
requireErrorContains(t, acl.Logout(&req, &ignored), "ACL not found")
|
|
})
|
|
|
|
t.Run("logout from deleted token", func(t *testing.T) {
|
|
req := structs.ACLLogoutRequest{
|
|
Datacenter: "dc1",
|
|
WriteRequest: structs.WriteRequest{Token: "not-found"},
|
|
}
|
|
var ignored bool
|
|
requireErrorContains(t, acl.Logout(&req, &ignored), "ACL not found")
|
|
})
|
|
|
|
t.Run("logout from non-auth method-linked token should fail", func(t *testing.T) {
|
|
req := structs.ACLLogoutRequest{
|
|
Datacenter: "dc1",
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
var ignored bool
|
|
requireErrorContains(t, acl.Logout(&req, &ignored), "Permission denied")
|
|
})
|
|
|
|
t.Run("login then logout", func(t *testing.T) {
|
|
// Create a totally legit Login token.
|
|
loginReq := structs.ACLLoginRequest{
|
|
Auth: &structs.ACLLoginParams{
|
|
AuthMethod: method.Name,
|
|
BearerToken: "fake-db",
|
|
},
|
|
Datacenter: "dc1",
|
|
}
|
|
loginToken := structs.ACLToken{}
|
|
|
|
require.NoError(t, acl.Login(&loginReq, &loginToken))
|
|
require.NotEmpty(t, loginToken.SecretID)
|
|
|
|
// Now turn around and nuke it.
|
|
req := structs.ACLLogoutRequest{
|
|
Datacenter: "dc1",
|
|
WriteRequest: structs.WriteRequest{Token: loginToken.SecretID},
|
|
}
|
|
|
|
var ignored bool
|
|
require.NoError(t, acl.Logout(&req, &ignored))
|
|
})
|
|
}
|
|
|
|
func gatherIDs(t *testing.T, v interface{}) []string {
|
|
t.Helper()
|
|
|
|
var out []string
|
|
switch x := v.(type) {
|
|
case []*structs.ACLRole:
|
|
for _, r := range x {
|
|
out = append(out, r.ID)
|
|
}
|
|
case structs.ACLRoles:
|
|
for _, r := range x {
|
|
out = append(out, r.ID)
|
|
}
|
|
case []*structs.ACLPolicy:
|
|
for _, p := range x {
|
|
out = append(out, p.ID)
|
|
}
|
|
case structs.ACLPolicyListStubs:
|
|
for _, p := range x {
|
|
out = append(out, p.ID)
|
|
}
|
|
case []*structs.ACLToken:
|
|
for _, p := range x {
|
|
out = append(out, p.AccessorID)
|
|
}
|
|
case structs.ACLTokenListStubs:
|
|
for _, p := range x {
|
|
out = append(out, p.AccessorID)
|
|
}
|
|
case []*structs.ACLAuthMethod:
|
|
for _, p := range x {
|
|
out = append(out, p.Name)
|
|
}
|
|
case structs.ACLAuthMethodListStubs:
|
|
for _, p := range x {
|
|
out = append(out, p.Name)
|
|
}
|
|
case []*structs.ACLBindingRule:
|
|
for _, p := range x {
|
|
out = append(out, p.ID)
|
|
}
|
|
case structs.ACLBindingRules:
|
|
for _, p := range x {
|
|
out = append(out, p.ID)
|
|
}
|
|
default:
|
|
t.Fatalf("unknown type: %T", x)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func TestValidateBindingRuleBindName(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
type testcase struct {
|
|
name string
|
|
bindType string
|
|
bindName string
|
|
fields string
|
|
valid bool // valid HIL, invalid contents
|
|
err bool // invalid HIL
|
|
}
|
|
|
|
for _, test := range []testcase{
|
|
{"no bind type",
|
|
"", "", "", false, false},
|
|
{"bad bind type",
|
|
"invalid", "blah", "", false, true},
|
|
// valid HIL, invalid name
|
|
{"empty",
|
|
"both", "", "", false, false},
|
|
{"just end",
|
|
"both", "}", "", false, false},
|
|
{"var without start",
|
|
"both", " item }", "item", false, false},
|
|
{"two vars missing second start",
|
|
"both", "before-${ item }after--more }", "item,more", false, false},
|
|
// names for the two types are validated differently
|
|
{"@ is disallowed",
|
|
"both", "bad@name", "", false, false},
|
|
{"leading dash",
|
|
"role", "-name", "", true, false},
|
|
{"leading dash",
|
|
"service", "-name", "", false, false},
|
|
{"trailing dash",
|
|
"role", "name-", "", true, false},
|
|
{"trailing dash",
|
|
"service", "name-", "", false, false},
|
|
{"inner dash",
|
|
"both", "name-end", "", true, false},
|
|
{"upper case",
|
|
"role", "NAME", "", true, false},
|
|
{"upper case",
|
|
"service", "NAME", "", false, false},
|
|
// valid HIL, valid name
|
|
{"no vars",
|
|
"both", "nothing", "", true, false},
|
|
{"just var",
|
|
"both", "${item}", "item", true, false},
|
|
{"var in middle",
|
|
"both", "before-${item}after", "item", true, false},
|
|
{"two vars",
|
|
"both", "before-${item}after-${more}", "item,more", true, false},
|
|
// bad
|
|
{"no bind name",
|
|
"both", "", "", false, false},
|
|
{"just start",
|
|
"both", "${", "", false, true},
|
|
{"backwards",
|
|
"both", "}${", "", false, true},
|
|
{"no varname",
|
|
"both", "${}", "", false, true},
|
|
{"missing map key",
|
|
"both", "${item}", "", false, true},
|
|
{"var without end",
|
|
"both", "${ item ", "item", false, true},
|
|
{"two vars missing first end",
|
|
"both", "before-${ item after-${ more }", "item,more", false, true},
|
|
} {
|
|
var cases []testcase
|
|
if test.bindType == "both" {
|
|
test1 := test
|
|
test1.bindType = "role"
|
|
test2 := test
|
|
test2.bindType = "service"
|
|
cases = []testcase{test1, test2}
|
|
} else {
|
|
cases = []testcase{test}
|
|
}
|
|
|
|
for _, test := range cases {
|
|
test := test
|
|
t.Run(test.bindType+"--"+test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
valid, err := validateBindingRuleBindName(
|
|
test.bindType,
|
|
test.bindName,
|
|
strings.Split(test.fields, ","),
|
|
)
|
|
if test.err {
|
|
require.NotNil(t, err)
|
|
require.False(t, valid)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, test.valid, valid)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// upsertTestToken creates a token for testing purposes
|
|
func upsertTestToken(codec rpc.ClientCodec, masterToken string, datacenter string,
|
|
tokenModificationFn func(token *structs.ACLToken)) (*structs.ACLToken, error) {
|
|
arg := structs.ACLTokenSetRequest{
|
|
Datacenter: datacenter,
|
|
ACLToken: structs.ACLToken{
|
|
Description: "User token",
|
|
Local: false,
|
|
Policies: nil,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: masterToken},
|
|
}
|
|
|
|
if tokenModificationFn != nil {
|
|
tokenModificationFn(&arg.ACLToken)
|
|
}
|
|
|
|
var out structs.ACLToken
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.TokenSet", &arg, &out)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if out.AccessorID == "" {
|
|
return nil, fmt.Errorf("AccessorID is nil: %v", out)
|
|
}
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
func upsertTestTokenWithPolicyRules(codec rpc.ClientCodec, masterToken string, datacenter string, rules string) (*structs.ACLToken, error) {
|
|
policy, err := upsertTestPolicyWithRules(codec, masterToken, datacenter, rules)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
token, err := upsertTestToken(codec, masterToken, datacenter, func(token *structs.ACLToken) {
|
|
token.Policies = []structs.ACLTokenPolicyLink{{ID: policy.ID}}
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return token, nil
|
|
}
|
|
|
|
func retrieveTestTokenAccessorForSecret(codec rpc.ClientCodec, masterToken string, datacenter string, id string) (string, error) {
|
|
arg := structs.ACLTokenGetRequest{
|
|
TokenID: id,
|
|
TokenIDType: structs.ACLTokenSecret,
|
|
Datacenter: datacenter,
|
|
QueryOptions: structs.QueryOptions{Token: masterToken},
|
|
}
|
|
|
|
var out structs.ACLTokenResponse
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.TokenRead", &arg, &out)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if out.Token == nil {
|
|
return "", nil
|
|
}
|
|
|
|
return out.Token.AccessorID, nil
|
|
}
|
|
|
|
// retrieveTestToken returns a policy for testing purposes
|
|
func retrieveTestToken(codec rpc.ClientCodec, masterToken string, datacenter string, id string) (*structs.ACLTokenResponse, error) {
|
|
arg := structs.ACLTokenGetRequest{
|
|
Datacenter: datacenter,
|
|
TokenID: id,
|
|
TokenIDType: structs.ACLTokenAccessor,
|
|
QueryOptions: structs.QueryOptions{Token: masterToken},
|
|
}
|
|
|
|
var out structs.ACLTokenResponse
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.TokenRead", &arg, &out)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
func deleteTestToken(codec rpc.ClientCodec, masterToken string, datacenter string, tokenAccessor string) error {
|
|
arg := structs.ACLTokenDeleteRequest{
|
|
Datacenter: datacenter,
|
|
TokenID: tokenAccessor,
|
|
WriteRequest: structs.WriteRequest{Token: masterToken},
|
|
}
|
|
|
|
var ignored string
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.TokenDelete", &arg, &ignored)
|
|
return err
|
|
}
|
|
|
|
func deleteTestPolicy(codec rpc.ClientCodec, masterToken string, datacenter string, policyID string) error {
|
|
arg := structs.ACLPolicyDeleteRequest{
|
|
Datacenter: datacenter,
|
|
PolicyID: policyID,
|
|
WriteRequest: structs.WriteRequest{Token: masterToken},
|
|
}
|
|
|
|
var ignored string
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.PolicyDelete", &arg, &ignored)
|
|
return err
|
|
}
|
|
|
|
func upsertTestCustomizedPolicy(codec rpc.ClientCodec, masterToken string, datacenter string, policyModificationFn func(policy *structs.ACLPolicy)) (*structs.ACLPolicy, error) {
|
|
// Make sure test policies can't collide
|
|
policyUnq, err := uuid.GenerateUUID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
arg := structs.ACLPolicySetRequest{
|
|
Datacenter: datacenter,
|
|
Policy: structs.ACLPolicy{
|
|
Name: fmt.Sprintf("test-policy-%s", policyUnq),
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: masterToken},
|
|
}
|
|
|
|
if policyModificationFn != nil {
|
|
policyModificationFn(&arg.Policy)
|
|
}
|
|
|
|
var out structs.ACLPolicy
|
|
|
|
err = msgpackrpc.CallWithCodec(codec, "ACL.PolicySet", &arg, &out)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if out.ID == "" {
|
|
return nil, fmt.Errorf("ID is nil: %v", out)
|
|
}
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
// upsertTestPolicy creates a policy for testing purposes
|
|
func upsertTestPolicy(codec rpc.ClientCodec, masterToken string, datacenter string) (*structs.ACLPolicy, error) {
|
|
return upsertTestPolicyWithRules(codec, masterToken, datacenter, "")
|
|
}
|
|
|
|
func upsertTestPolicyWithRules(codec rpc.ClientCodec, masterToken string, datacenter string, rules string) (*structs.ACLPolicy, error) {
|
|
return upsertTestCustomizedPolicy(codec, masterToken, datacenter, func(policy *structs.ACLPolicy) {
|
|
policy.Rules = rules
|
|
})
|
|
}
|
|
|
|
// retrieveTestPolicy returns a policy for testing purposes
|
|
func retrieveTestPolicy(codec rpc.ClientCodec, masterToken string, datacenter string, id string) (*structs.ACLPolicyResponse, error) {
|
|
arg := structs.ACLPolicyGetRequest{
|
|
Datacenter: datacenter,
|
|
PolicyID: id,
|
|
QueryOptions: structs.QueryOptions{Token: masterToken},
|
|
}
|
|
|
|
var out structs.ACLPolicyResponse
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.PolicyRead", &arg, &out)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
func deleteTestRole(codec rpc.ClientCodec, masterToken string, datacenter string, roleID string) error {
|
|
arg := structs.ACLRoleDeleteRequest{
|
|
Datacenter: datacenter,
|
|
RoleID: roleID,
|
|
WriteRequest: structs.WriteRequest{Token: masterToken},
|
|
}
|
|
|
|
var ignored string
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.RoleDelete", &arg, &ignored)
|
|
return err
|
|
}
|
|
|
|
func deleteTestRoleByName(codec rpc.ClientCodec, masterToken string, datacenter string, roleName string) error {
|
|
resp, err := retrieveTestRoleByName(codec, masterToken, datacenter, roleName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resp.Role == nil {
|
|
return nil
|
|
}
|
|
|
|
return deleteTestRole(codec, masterToken, datacenter, resp.Role.ID)
|
|
}
|
|
|
|
// upsertTestRole creates a role for testing purposes
|
|
func upsertTestRole(codec rpc.ClientCodec, masterToken string, datacenter string) (*structs.ACLRole, error) {
|
|
return upsertTestCustomizedRole(codec, masterToken, datacenter, nil)
|
|
}
|
|
|
|
func upsertTestCustomizedRole(codec rpc.ClientCodec, masterToken string, datacenter string, modify func(role *structs.ACLRole)) (*structs.ACLRole, error) {
|
|
// Make sure test roles can't collide
|
|
roleUnq, err := uuid.GenerateUUID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
arg := structs.ACLRoleSetRequest{
|
|
Datacenter: datacenter,
|
|
Role: structs.ACLRole{
|
|
Name: fmt.Sprintf("test-role-%s", roleUnq),
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: masterToken},
|
|
}
|
|
|
|
if modify != nil {
|
|
modify(&arg.Role)
|
|
}
|
|
|
|
var out structs.ACLRole
|
|
|
|
err = msgpackrpc.CallWithCodec(codec, "ACL.RoleSet", &arg, &out)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if out.ID == "" {
|
|
return nil, fmt.Errorf("ID is nil: %v", out)
|
|
}
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
func retrieveTestRole(codec rpc.ClientCodec, masterToken string, datacenter string, id string) (*structs.ACLRoleResponse, error) {
|
|
arg := structs.ACLRoleGetRequest{
|
|
Datacenter: datacenter,
|
|
RoleID: id,
|
|
QueryOptions: structs.QueryOptions{Token: masterToken},
|
|
}
|
|
|
|
var out structs.ACLRoleResponse
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.RoleRead", &arg, &out)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
func retrieveTestRoleByName(codec rpc.ClientCodec, masterToken string, datacenter string, name string) (*structs.ACLRoleResponse, error) {
|
|
arg := structs.ACLRoleGetRequest{
|
|
Datacenter: datacenter,
|
|
RoleName: name,
|
|
QueryOptions: structs.QueryOptions{Token: masterToken},
|
|
}
|
|
|
|
var out structs.ACLRoleResponse
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.RoleRead", &arg, &out)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
func deleteTestAuthMethod(codec rpc.ClientCodec, masterToken string, datacenter string, methodName string) error {
|
|
arg := structs.ACLAuthMethodDeleteRequest{
|
|
Datacenter: datacenter,
|
|
AuthMethodName: methodName,
|
|
WriteRequest: structs.WriteRequest{Token: masterToken},
|
|
}
|
|
|
|
var ignored string
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.AuthMethodDelete", &arg, &ignored)
|
|
return err
|
|
}
|
|
func upsertTestAuthMethod(
|
|
codec rpc.ClientCodec, masterToken string, datacenter string,
|
|
sessionID string,
|
|
) (*structs.ACLAuthMethod, error) {
|
|
return upsertTestCustomizedAuthMethod(codec, masterToken, datacenter, func(method *structs.ACLAuthMethod) {
|
|
method.Config = map[string]interface{}{
|
|
"SessionID": sessionID,
|
|
}
|
|
})
|
|
}
|
|
|
|
func upsertTestCustomizedAuthMethod(
|
|
codec rpc.ClientCodec, masterToken string, datacenter string,
|
|
modify func(method *structs.ACLAuthMethod),
|
|
) (*structs.ACLAuthMethod, error) {
|
|
name, err := uuid.GenerateUUID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req := structs.ACLAuthMethodSetRequest{
|
|
Datacenter: datacenter,
|
|
AuthMethod: structs.ACLAuthMethod{
|
|
Name: "test-method-" + name,
|
|
Type: "testing",
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: masterToken},
|
|
}
|
|
|
|
if modify != nil {
|
|
modify(&req.AuthMethod)
|
|
}
|
|
|
|
var out structs.ACLAuthMethod
|
|
|
|
err = msgpackrpc.CallWithCodec(codec, "ACL.AuthMethodSet", &req, &out)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
func upsertTestKubernetesAuthMethod(
|
|
codec rpc.ClientCodec, masterToken string, datacenter string,
|
|
caCert, kubeHost, kubeJWT string,
|
|
) (*structs.ACLAuthMethod, error) {
|
|
name, err := uuid.GenerateUUID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if kubeHost == "" {
|
|
kubeHost = "https://abc:8443"
|
|
}
|
|
if kubeJWT == "" {
|
|
kubeJWT = goodJWT_A
|
|
}
|
|
|
|
req := structs.ACLAuthMethodSetRequest{
|
|
Datacenter: datacenter,
|
|
AuthMethod: structs.ACLAuthMethod{
|
|
Name: "test-method-" + name,
|
|
Type: "kubernetes",
|
|
Config: map[string]interface{}{
|
|
"Host": kubeHost,
|
|
"CACert": caCert,
|
|
"ServiceAccountJWT": kubeJWT,
|
|
},
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: masterToken},
|
|
}
|
|
|
|
var out structs.ACLAuthMethod
|
|
|
|
err = msgpackrpc.CallWithCodec(codec, "ACL.AuthMethodSet", &req, &out)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
func retrieveTestAuthMethod(codec rpc.ClientCodec, masterToken string, datacenter string, name string) (*structs.ACLAuthMethodResponse, error) {
|
|
arg := structs.ACLAuthMethodGetRequest{
|
|
Datacenter: datacenter,
|
|
AuthMethodName: name,
|
|
QueryOptions: structs.QueryOptions{Token: masterToken},
|
|
}
|
|
|
|
var out structs.ACLAuthMethodResponse
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.AuthMethodRead", &arg, &out)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
func deleteTestBindingRule(codec rpc.ClientCodec, masterToken string, datacenter string, ruleID string) error {
|
|
arg := structs.ACLBindingRuleDeleteRequest{
|
|
Datacenter: datacenter,
|
|
BindingRuleID: ruleID,
|
|
WriteRequest: structs.WriteRequest{Token: masterToken},
|
|
}
|
|
|
|
var ignored string
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.BindingRuleDelete", &arg, &ignored)
|
|
return err
|
|
}
|
|
|
|
func upsertTestBindingRule(
|
|
codec rpc.ClientCodec,
|
|
masterToken string,
|
|
datacenter string,
|
|
methodName string,
|
|
selector string,
|
|
bindType string,
|
|
bindName string,
|
|
) (*structs.ACLBindingRule, error) {
|
|
return upsertTestCustomizedBindingRule(codec, masterToken, datacenter, func(rule *structs.ACLBindingRule) {
|
|
rule.AuthMethod = methodName
|
|
rule.BindType = bindType
|
|
rule.BindName = bindName
|
|
rule.Selector = selector
|
|
})
|
|
}
|
|
|
|
func upsertTestCustomizedBindingRule(codec rpc.ClientCodec, masterToken string, datacenter string, modify func(rule *structs.ACLBindingRule)) (*structs.ACLBindingRule, error) {
|
|
req := structs.ACLBindingRuleSetRequest{
|
|
Datacenter: datacenter,
|
|
BindingRule: structs.ACLBindingRule{},
|
|
WriteRequest: structs.WriteRequest{Token: masterToken},
|
|
}
|
|
|
|
if modify != nil {
|
|
modify(&req.BindingRule)
|
|
}
|
|
|
|
var out structs.ACLBindingRule
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.BindingRuleSet", &req, &out)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
func retrieveTestBindingRule(codec rpc.ClientCodec, masterToken string, datacenter string, ruleID string) (*structs.ACLBindingRuleResponse, error) {
|
|
arg := structs.ACLBindingRuleGetRequest{
|
|
Datacenter: datacenter,
|
|
BindingRuleID: ruleID,
|
|
QueryOptions: structs.QueryOptions{Token: masterToken},
|
|
}
|
|
|
|
var out structs.ACLBindingRuleResponse
|
|
|
|
err := msgpackrpc.CallWithCodec(codec, "ACL.BindingRuleRead", &arg, &out)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
func requireTimeEquals(t *testing.T, expect, got *time.Time) {
|
|
t.Helper()
|
|
if expect == nil && got == nil {
|
|
return
|
|
} else if expect == nil && got != nil {
|
|
t.Fatalf("expected=NIL != got=%q", *got)
|
|
} else if expect != nil && got == nil {
|
|
t.Fatalf("expected=%q != got=NIL", *expect)
|
|
} else if !expect.Equal(*got) {
|
|
t.Fatalf("expected=%q != got=%q", *expect, *got)
|
|
}
|
|
}
|
|
|
|
func requireErrorContains(t *testing.T, err error, expectedErrorMessage string) {
|
|
t.Helper()
|
|
if err == nil {
|
|
t.Fatal("An error is expected but got nil.")
|
|
}
|
|
if !strings.Contains(err.Error(), expectedErrorMessage) {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
// 'default/admin'
|
|
const goodJWT_A = "eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImFkbWluLXRva2VuLXFsejQyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiNzM4YmMyNTEtNjUzMi0xMWU5LWI2N2YtNDhlNmM4YjhlY2I1Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6YWRtaW4ifQ.ixMlnWrAG7NVuTTKu8cdcYfM7gweS3jlKaEsIBNGOVEjPE7rtXtgMkAwjQTdYR08_0QBjkgzy5fQC5ZNyglSwONJ-bPaXGvhoH1cTnRi1dz9H_63CfqOCvQP1sbdkMeRxNTGVAyWZT76rXoCUIfHP4LY2I8aab0KN9FTIcgZRF0XPTtT70UwGIrSmRpxW38zjiy2ymWL01cc5VWGhJqVysmWmYk3wNp0h5N57H_MOrz4apQR4pKaamzskzjLxO55gpbmZFC76qWuUdexAR7DT2fpbHLOw90atN_NlLMY-VrXyW3-Ei5EhYaVreMB9PSpKwkrA4jULITohV-sxpa1LA"
|
|
|
|
// 'default/demo'
|
|
const goodJWT_B = "eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlbW8tdG9rZW4ta21iOW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVtbyIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6Ijc2MDkxYWY0LTRiNTYtMTFlOS1hYzRiLTcwOGIxMTgwMWNiZSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlbW8ifQ.ZiAHjijBAOsKdum0Aix6lgtkLkGo9_Tu87dWQ5Zfwnn3r2FejEWDAnftTft1MqqnMzivZ9Wyyki5ZjQRmTAtnMPJuHC-iivqY4Wh4S6QWCJ1SivBv5tMZR79t5t8mE7R1-OHwst46spru1pps9wt9jsA04d3LpV0eeKYgdPTVaQKklxTm397kIMUugA6yINIBQ3Rh8eQqBgNwEmL4iqyYubzHLVkGkoP9MJikFI05vfRiHtYr-piXz6JFDzXMQj9rW6xtMmrBSn79ChbyvC5nz-Nj2rJPnHsb_0rDUbmXY5PpnMhBpdSH-CbZ4j8jsiib6DtaGJhVZeEQ1GjsFAZwQ"
|