2118 lines
53 KiB
Go
2118 lines
53 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/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)
|
|
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)
|
|
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") {
|
|
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") {
|
|
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)
|
|
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 {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if acls.Policy == nil {
|
|
t.Fatalf("Bad: %v", acls)
|
|
}
|
|
if acls.TTL != 30*time.Second {
|
|
t.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 = 20 * 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)
|
|
})
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
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{}
|
|
|
|
require.NoError(t, acl.TokenRead(&req, &resp))
|
|
require.Nil(t, 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: 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.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.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.AccessorID)
|
|
require.Equal(t, token.Description, "foobar")
|
|
require.Equal(t, token.AccessorID, resp.AccessorID)
|
|
|
|
require.Len(t, token.Policies, 0)
|
|
})
|
|
|
|
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: 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: 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.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.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: 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", func(t *testing.T) {
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{
|
|
Description: "new-description",
|
|
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.AccessorID)
|
|
require.Equal(t, token.Description, "new-description")
|
|
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_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)
|
|
|
|
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
|
|
|
|
waitForNewACLs(t, s2)
|
|
|
|
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")
|
|
|
|
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 = 11 * time.Millisecond
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
masterTokenAccessorID, err := retrieveTestTokenAccessorForSecret(codec, "root", "dc1", "root")
|
|
require.NoError(t, err)
|
|
|
|
t.Run("normal", 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,
|
|
t3.AccessorID,
|
|
}
|
|
|
|
var retrievedTokens []string
|
|
for _, v := range resp.Tokens {
|
|
retrievedTokens = append(retrievedTokens, v.AccessorID)
|
|
}
|
|
require.ElementsMatch(t, retrievedTokens, tokens)
|
|
})
|
|
|
|
time.Sleep(20 * 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,
|
|
}
|
|
|
|
var retrievedTokens []string
|
|
for _, v := range resp.Tokens {
|
|
retrievedTokens = append(retrievedTokens, v.AccessorID)
|
|
}
|
|
require.ElementsMatch(t, retrievedTokens, 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)
|
|
|
|
var retrievedTokens []string
|
|
|
|
for _, v := range resp.Tokens {
|
|
retrievedTokens = append(retrievedTokens, v.AccessorID)
|
|
}
|
|
require.EqualValues(t, retrievedTokens, 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)
|
|
|
|
var retrievedTokens []string
|
|
|
|
for _, v := range resp.Tokens {
|
|
retrievedTokens = append(retrievedTokens, v.AccessorID)
|
|
}
|
|
require.EqualValues(t, retrievedTokens, 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)
|
|
|
|
var retrievedPolicies []string
|
|
|
|
for _, v := range resp.Policies {
|
|
retrievedPolicies = append(retrievedPolicies, v.ID)
|
|
}
|
|
require.EqualValues(t, retrievedPolicies, policies)
|
|
}
|
|
|
|
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
|
|
|
|
// Create it
|
|
{
|
|
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
|
|
}
|
|
|
|
// Update it
|
|
{
|
|
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_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,
|
|
}
|
|
var retrievedPolicies []string
|
|
|
|
for _, v := range resp.Policies {
|
|
retrievedPolicies = append(retrievedPolicies, v.ID)
|
|
}
|
|
require.ElementsMatch(t, retrievedPolicies, 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)
|
|
|
|
var retrievedPolicies []string
|
|
|
|
for _, v := range resp.Policies {
|
|
retrievedPolicies = append(retrievedPolicies, v.ID)
|
|
}
|
|
require.EqualValues(t, retrievedPolicies, policies)
|
|
}
|
|
|
|
// 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 retrieveTestTokenAccessorForSecret(codec rpc.ClientCodec, masterToken string, datacenter string, id string) (string, error) {
|
|
arg := structs.ACLTokenGetRequest{
|
|
TokenID: "root",
|
|
TokenIDType: structs.ACLTokenSecret,
|
|
Datacenter: "dc1",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
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 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
|
|
}
|
|
|
|
// upsertTestPolicy creates a policy for testing purposes
|
|
func upsertTestPolicy(codec rpc.ClientCodec, masterToken string, datacenter string) (*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},
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// 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 requireTimeEquals(t *testing.T, expect, got time.Time) {
|
|
t.Helper()
|
|
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)
|
|
}
|
|
}
|