open-consul/agent/consul/acl_endpoint_test.go
Daniel Nephin 047abdd73c acl: remove ACLDatacenter
This field has been unnecessary for a while now. It was always set to the same value
as PrimaryDatacenter. So we can remove the duplicate field and use PrimaryDatacenter
directly.

This change was made by GoLand refactor, which did most of the work for me.
2021-08-06 18:27:00 -04:00

6149 lines
170 KiB
Go

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