d3c7d57209
* Move internal/ to sdk/ * Add a readme to the SDK folder
1589 lines
38 KiB
Go
1589 lines
38 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/testrpc"
|
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
|
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"
|
|
})
|
|
defer os.RemoveAll(dir1)
|
|
defer s1.Shutdown()
|
|
codec := rpcClient(t, s1)
|
|
defer codec.Close()
|
|
|
|
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
|
|
token, err := upsertTestToken(codec, "root", "dc1")
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
t.Run("exists and matches what we created", func(t *testing.T) {
|
|
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("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"
|
|
})
|
|
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")
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
req := structs.ACLTokenSetRequest{
|
|
Datacenter: "dc1",
|
|
ACLToken: structs.ACLToken{AccessorID: t1.AccessorID},
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
}
|
|
|
|
t2 := structs.ACLToken{}
|
|
|
|
err = acl.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)
|
|
}
|
|
|
|
func TestACLEndpoint_TokenSet(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 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)
|
|
})
|
|
}
|
|
|
|
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"
|
|
})
|
|
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"
|
|
// 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)
|
|
|
|
existingToken, err := upsertTestToken(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
acl2 := ACL{srv: s2}
|
|
|
|
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 := msgpackrpc.CallWithCodec(codec, "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"
|
|
})
|
|
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")
|
|
require.NoError(t, err)
|
|
|
|
t2, err := upsertTestToken(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
|
|
req := structs.ACLTokenListRequest{
|
|
Datacenter: "dc1",
|
|
QueryOptions: structs.QueryOptions{Token: "root"},
|
|
}
|
|
|
|
resp := structs.ACLTokenListResponse{}
|
|
|
|
err = acl.TokenList(&req, &resp)
|
|
require.NoError(t, err)
|
|
|
|
tokens := []string{t1.AccessorID, t2.AccessorID}
|
|
var retrievedTokens []string
|
|
|
|
for _, v := range resp.Tokens {
|
|
retrievedTokens = append(retrievedTokens, v.AccessorID)
|
|
}
|
|
require.Subset(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"
|
|
})
|
|
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")
|
|
require.NoError(t, err)
|
|
|
|
t2, err := upsertTestToken(codec, "root", "dc1")
|
|
require.NoError(t, err)
|
|
|
|
acl := ACL{srv: s1}
|
|
tokens := []string{t1.AccessorID, t2.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{p1.ID, p2.ID}
|
|
var retrievedPolicies []string
|
|
|
|
for _, v := range resp.Policies {
|
|
retrievedPolicies = append(retrievedPolicies, v.ID)
|
|
}
|
|
require.Subset(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) (*structs.ACLToken, error) {
|
|
arg := structs.ACLTokenSetRequest{
|
|
Datacenter: datacenter,
|
|
ACLToken: structs.ACLToken{
|
|
Description: "User token",
|
|
Local: false,
|
|
Policies: nil,
|
|
},
|
|
WriteRequest: structs.WriteRequest{Token: masterToken},
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|