Add support for returning ACL secret IDs for accessors with acl:write (#10546)
This commit is contained in:
parent
dcb90fb832
commit
5ff191ad99
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
acl: Return secret ID when listing tokens if accessor has `acl:write`
|
||||||
|
```
|
|
@ -859,6 +859,7 @@ func TestACL_HTTP(t *testing.T) {
|
||||||
found := false
|
found := false
|
||||||
for _, actual := range tokens {
|
for _, actual := range tokens {
|
||||||
if actual.AccessorID == tokenID {
|
if actual.AccessorID == tokenID {
|
||||||
|
require.Equal(t, expected.SecretID, actual.SecretID)
|
||||||
require.Equal(t, expected.Description, actual.Description)
|
require.Equal(t, expected.Description, actual.Description)
|
||||||
require.Equal(t, expected.Policies, actual.Policies)
|
require.Equal(t, expected.Policies, actual.Policies)
|
||||||
require.Equal(t, expected.Local, actual.Local)
|
require.Equal(t, expected.Local, actual.Local)
|
||||||
|
|
|
@ -1767,6 +1767,11 @@ func (f *aclFilter) filterTokenStub(token **structs.ACLTokenListStub) {
|
||||||
|
|
||||||
if f.authorizer.ACLRead(&entCtx) != acl.Allow {
|
if f.authorizer.ACLRead(&entCtx) != acl.Allow {
|
||||||
*token = nil
|
*token = nil
|
||||||
|
} else if f.authorizer.ACLWrite(&entCtx) != acl.Allow {
|
||||||
|
// no write permissions - redact secret
|
||||||
|
clone := *(*token)
|
||||||
|
clone.SecretID = redactedToken
|
||||||
|
*token = &clone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2066,6 +2066,36 @@ func TestACLEndpoint_TokenList(t *testing.T) {
|
||||||
}
|
}
|
||||||
require.ElementsMatch(t, gatherIDs(t, resp.Tokens), tokens)
|
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) {
|
func TestACLEndpoint_TokenBatchRead(t *testing.T) {
|
||||||
|
|
|
@ -557,6 +557,7 @@ type ACLTokens []*ACLToken
|
||||||
|
|
||||||
type ACLTokenListStub struct {
|
type ACLTokenListStub struct {
|
||||||
AccessorID string
|
AccessorID string
|
||||||
|
SecretID string
|
||||||
Description string
|
Description string
|
||||||
Policies []ACLTokenPolicyLink `json:",omitempty"`
|
Policies []ACLTokenPolicyLink `json:",omitempty"`
|
||||||
Roles []ACLTokenRoleLink `json:",omitempty"`
|
Roles []ACLTokenRoleLink `json:",omitempty"`
|
||||||
|
@ -578,6 +579,7 @@ type ACLTokenListStubs []*ACLTokenListStub
|
||||||
func (token *ACLToken) Stub() *ACLTokenListStub {
|
func (token *ACLToken) Stub() *ACLTokenListStub {
|
||||||
return &ACLTokenListStub{
|
return &ACLTokenListStub{
|
||||||
AccessorID: token.AccessorID,
|
AccessorID: token.AccessorID,
|
||||||
|
SecretID: token.SecretID,
|
||||||
Description: token.Description,
|
Description: token.Description,
|
||||||
Policies: token.Policies,
|
Policies: token.Policies,
|
||||||
Roles: token.Roles,
|
Roles: token.Roles,
|
||||||
|
|
|
@ -265,6 +265,7 @@ func TestStructs_ACLToken_Stub(t *testing.T) {
|
||||||
stub := token.Stub()
|
stub := token.Stub()
|
||||||
|
|
||||||
require.Equal(t, token.AccessorID, stub.AccessorID)
|
require.Equal(t, token.AccessorID, stub.AccessorID)
|
||||||
|
require.Equal(t, token.SecretID, stub.SecretID)
|
||||||
require.Equal(t, token.Description, stub.Description)
|
require.Equal(t, token.Description, stub.Description)
|
||||||
require.Equal(t, token.Policies, stub.Policies)
|
require.Equal(t, token.Policies, stub.Policies)
|
||||||
require.Equal(t, token.Local, stub.Local)
|
require.Equal(t, token.Local, stub.Local)
|
||||||
|
@ -286,6 +287,7 @@ func TestStructs_ACLToken_Stub(t *testing.T) {
|
||||||
|
|
||||||
stub := token.Stub()
|
stub := token.Stub()
|
||||||
require.Equal(t, token.AccessorID, stub.AccessorID)
|
require.Equal(t, token.AccessorID, stub.AccessorID)
|
||||||
|
require.Equal(t, token.SecretID, stub.SecretID)
|
||||||
require.Equal(t, token.Description, stub.Description)
|
require.Equal(t, token.Description, stub.Description)
|
||||||
require.Equal(t, token.Policies, stub.Policies)
|
require.Equal(t, token.Policies, stub.Policies)
|
||||||
require.Equal(t, token.Local, stub.Local)
|
require.Equal(t, token.Local, stub.Local)
|
||||||
|
|
|
@ -58,6 +58,7 @@ type ACLTokenListEntry struct {
|
||||||
CreateIndex uint64
|
CreateIndex uint64
|
||||||
ModifyIndex uint64
|
ModifyIndex uint64
|
||||||
AccessorID string
|
AccessorID string
|
||||||
|
SecretID string
|
||||||
Description string
|
Description string
|
||||||
Policies []*ACLTokenPolicyLink `json:",omitempty"`
|
Policies []*ACLTokenPolicyLink `json:",omitempty"`
|
||||||
Roles []*ACLTokenRoleLink `json:",omitempty"`
|
Roles []*ACLTokenRoleLink `json:",omitempty"`
|
||||||
|
|
|
@ -595,6 +595,7 @@ func TestAPI_ACLToken_List(t *testing.T) {
|
||||||
token1, ok := tokenMap[created1.AccessorID]
|
token1, ok := tokenMap[created1.AccessorID]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.NotNil(t, token1)
|
require.NotNil(t, token1)
|
||||||
|
require.Equal(t, created1.SecretID, token1.SecretID)
|
||||||
require.Equal(t, created1.Description, token1.Description)
|
require.Equal(t, created1.Description, token1.Description)
|
||||||
require.Equal(t, created1.CreateIndex, token1.CreateIndex)
|
require.Equal(t, created1.CreateIndex, token1.CreateIndex)
|
||||||
require.Equal(t, created1.ModifyIndex, token1.ModifyIndex)
|
require.Equal(t, created1.ModifyIndex, token1.ModifyIndex)
|
||||||
|
@ -604,6 +605,7 @@ func TestAPI_ACLToken_List(t *testing.T) {
|
||||||
token2, ok := tokenMap[created2.AccessorID]
|
token2, ok := tokenMap[created2.AccessorID]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.NotNil(t, token2)
|
require.NotNil(t, token2)
|
||||||
|
require.Equal(t, created2.SecretID, token2.SecretID)
|
||||||
require.Equal(t, created2.Description, token2.Description)
|
require.Equal(t, created2.Description, token2.Description)
|
||||||
require.Equal(t, created2.CreateIndex, token2.CreateIndex)
|
require.Equal(t, created2.CreateIndex, token2.CreateIndex)
|
||||||
require.Equal(t, created2.ModifyIndex, token2.ModifyIndex)
|
require.Equal(t, created2.ModifyIndex, token2.ModifyIndex)
|
||||||
|
@ -613,6 +615,7 @@ func TestAPI_ACLToken_List(t *testing.T) {
|
||||||
token3, ok := tokenMap[created3.AccessorID]
|
token3, ok := tokenMap[created3.AccessorID]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.NotNil(t, token3)
|
require.NotNil(t, token3)
|
||||||
|
require.Equal(t, created3.SecretID, token3.SecretID)
|
||||||
require.Equal(t, created3.Description, token3.Description)
|
require.Equal(t, created3.Description, token3.Description)
|
||||||
require.Equal(t, created3.CreateIndex, token3.CreateIndex)
|
require.Equal(t, created3.CreateIndex, token3.CreateIndex)
|
||||||
require.Equal(t, created3.ModifyIndex, token3.ModifyIndex)
|
require.Equal(t, created3.ModifyIndex, token3.ModifyIndex)
|
||||||
|
|
|
@ -125,6 +125,7 @@ func (f *prettyFormatter) formatTokenListEntry(token *api.ACLTokenListEntry) str
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
buffer.WriteString(fmt.Sprintf("AccessorID: %s\n", token.AccessorID))
|
buffer.WriteString(fmt.Sprintf("AccessorID: %s\n", token.AccessorID))
|
||||||
|
buffer.WriteString(fmt.Sprintf("SecretID: %s\n", token.SecretID))
|
||||||
if token.Namespace != "" {
|
if token.Namespace != "" {
|
||||||
buffer.WriteString(fmt.Sprintf("Namespace: %s\n", token.Namespace))
|
buffer.WriteString(fmt.Sprintf("Namespace: %s\n", token.Namespace))
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,7 @@ func TestFormatTokenList(t *testing.T) {
|
||||||
tokens: []*api.ACLTokenListEntry{
|
tokens: []*api.ACLTokenListEntry{
|
||||||
{
|
{
|
||||||
AccessorID: "fbd2447f-7479-4329-ad13-b021d74f86ba",
|
AccessorID: "fbd2447f-7479-4329-ad13-b021d74f86ba",
|
||||||
|
SecretID: "257ade69-748c-4022-bafd-76d27d9143f8",
|
||||||
Description: "test token",
|
Description: "test token",
|
||||||
Local: false,
|
Local: false,
|
||||||
CreateTime: time.Date(2020, 5, 22, 18, 52, 31, 0, time.UTC),
|
CreateTime: time.Date(2020, 5, 22, 18, 52, 31, 0, time.UTC),
|
||||||
|
@ -168,6 +169,7 @@ func TestFormatTokenList(t *testing.T) {
|
||||||
tokens: []*api.ACLTokenListEntry{
|
tokens: []*api.ACLTokenListEntry{
|
||||||
{
|
{
|
||||||
AccessorID: "8acc7486-ca54-4d3c-9aed-5cd85651b0ee",
|
AccessorID: "8acc7486-ca54-4d3c-9aed-5cd85651b0ee",
|
||||||
|
SecretID: "257ade69-748c-4022-bafd-76d27d9143f8",
|
||||||
Description: "legacy",
|
Description: "legacy",
|
||||||
Legacy: true,
|
Legacy: true,
|
||||||
},
|
},
|
||||||
|
@ -177,6 +179,7 @@ func TestFormatTokenList(t *testing.T) {
|
||||||
tokens: []*api.ACLTokenListEntry{
|
tokens: []*api.ACLTokenListEntry{
|
||||||
{
|
{
|
||||||
AccessorID: "fbd2447f-7479-4329-ad13-b021d74f86ba",
|
AccessorID: "fbd2447f-7479-4329-ad13-b021d74f86ba",
|
||||||
|
SecretID: "257ade69-748c-4022-bafd-76d27d9143f8",
|
||||||
Namespace: "foo",
|
Namespace: "foo",
|
||||||
Description: "test token",
|
Description: "test token",
|
||||||
Local: false,
|
Local: false,
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"CreateIndex": 42,
|
"CreateIndex": 42,
|
||||||
"ModifyIndex": 100,
|
"ModifyIndex": 100,
|
||||||
"AccessorID": "fbd2447f-7479-4329-ad13-b021d74f86ba",
|
"AccessorID": "fbd2447f-7479-4329-ad13-b021d74f86ba",
|
||||||
|
"SecretID": "257ade69-748c-4022-bafd-76d27d9143f8",
|
||||||
"Description": "test token",
|
"Description": "test token",
|
||||||
"Local": false,
|
"Local": false,
|
||||||
"CreateTime": "2020-05-22T18:52:31Z",
|
"CreateTime": "2020-05-22T18:52:31Z",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba
|
AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba
|
||||||
|
SecretID: 257ade69-748c-4022-bafd-76d27d9143f8
|
||||||
Description: test token
|
Description: test token
|
||||||
Local: false
|
Local: false
|
||||||
Create Time: 2020-05-22 18:52:31 +0000 UTC
|
Create Time: 2020-05-22 18:52:31 +0000 UTC
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba
|
AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba
|
||||||
|
SecretID: 257ade69-748c-4022-bafd-76d27d9143f8
|
||||||
Description: test token
|
Description: test token
|
||||||
Local: false
|
Local: false
|
||||||
Create Time: 2020-05-22 18:52:31 +0000 UTC
|
Create Time: 2020-05-22 18:52:31 +0000 UTC
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"CreateIndex": 5,
|
"CreateIndex": 5,
|
||||||
"ModifyIndex": 10,
|
"ModifyIndex": 10,
|
||||||
"AccessorID": "fbd2447f-7479-4329-ad13-b021d74f86ba",
|
"AccessorID": "fbd2447f-7479-4329-ad13-b021d74f86ba",
|
||||||
|
"SecretID": "257ade69-748c-4022-bafd-76d27d9143f8",
|
||||||
"Description": "test token",
|
"Description": "test token",
|
||||||
"Policies": [
|
"Policies": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba
|
AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba
|
||||||
|
SecretID: 257ade69-748c-4022-bafd-76d27d9143f8
|
||||||
Namespace: foo
|
Namespace: foo
|
||||||
Description: test token
|
Description: test token
|
||||||
Local: false
|
Local: false
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba
|
AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba
|
||||||
|
SecretID: 257ade69-748c-4022-bafd-76d27d9143f8
|
||||||
Namespace: foo
|
Namespace: foo
|
||||||
Description: test token
|
Description: test token
|
||||||
Local: false
|
Local: false
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"CreateIndex": 0,
|
"CreateIndex": 0,
|
||||||
"ModifyIndex": 0,
|
"ModifyIndex": 0,
|
||||||
"AccessorID": "8acc7486-ca54-4d3c-9aed-5cd85651b0ee",
|
"AccessorID": "8acc7486-ca54-4d3c-9aed-5cd85651b0ee",
|
||||||
|
"SecretID": "257ade69-748c-4022-bafd-76d27d9143f8",
|
||||||
"Description": "legacy",
|
"Description": "legacy",
|
||||||
"Local": false,
|
"Local": false,
|
||||||
"CreateTime": "0001-01-01T00:00:00Z",
|
"CreateTime": "0001-01-01T00:00:00Z",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
AccessorID: 8acc7486-ca54-4d3c-9aed-5cd85651b0ee
|
AccessorID: 8acc7486-ca54-4d3c-9aed-5cd85651b0ee
|
||||||
|
SecretID: 257ade69-748c-4022-bafd-76d27d9143f8
|
||||||
Description: legacy
|
Description: legacy
|
||||||
Local: false
|
Local: false
|
||||||
Create Time: 0001-01-01 00:00:00 +0000 UTC
|
Create Time: 0001-01-01 00:00:00 +0000 UTC
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
AccessorID: 8acc7486-ca54-4d3c-9aed-5cd85651b0ee
|
AccessorID: 8acc7486-ca54-4d3c-9aed-5cd85651b0ee
|
||||||
|
SecretID: 257ade69-748c-4022-bafd-76d27d9143f8
|
||||||
Description: legacy
|
Description: legacy
|
||||||
Local: false
|
Local: false
|
||||||
Create Time: 0001-01-01 00:00:00 +0000 UTC
|
Create Time: 0001-01-01 00:00:00 +0000 UTC
|
||||||
|
|
|
@ -594,13 +594,17 @@ $ curl -X GET http://127.0.0.1:8500/v1/acl/tokens
|
||||||
|
|
||||||
### Sample Response
|
### Sample Response
|
||||||
|
|
||||||
-> **Note** - The token secret IDs are not included in the listing and must be
|
-> **Note** If the token used for accessing the API has `acl:write` permissions,
|
||||||
retrieved by the [token reading endpoint](#read-a-token)
|
then the `SecretID` will contain the tokens real value. Only when accessed with
|
||||||
|
a token with only `acl:read` permissions will the `SecretID` be redacted. This
|
||||||
|
is to prevent privilege escalation whereby having `acl:read` privileges allows
|
||||||
|
for reading other secrets which given even more permissions.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"AccessorID": "6a1253d2-1785-24fd-91c2-f8e78c745511",
|
"AccessorID": "6a1253d2-1785-24fd-91c2-f8e78c745511",
|
||||||
|
"SecretID": "<hidden>",
|
||||||
"Description": "Agent token for 'my-agent'",
|
"Description": "Agent token for 'my-agent'",
|
||||||
"Policies": [
|
"Policies": [
|
||||||
{
|
{
|
||||||
|
@ -620,6 +624,7 @@ retrieved by the [token reading endpoint](#read-a-token)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"AccessorID": "00000000-0000-0000-0000-000000000002",
|
"AccessorID": "00000000-0000-0000-0000-000000000002",
|
||||||
|
"SecretID": "<hidden>",
|
||||||
"Description": "Anonymous Token",
|
"Description": "Anonymous Token",
|
||||||
"Policies": null,
|
"Policies": null,
|
||||||
"Local": false,
|
"Local": false,
|
||||||
|
@ -630,6 +635,7 @@ retrieved by the [token reading endpoint](#read-a-token)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"AccessorID": "3328f9a6-433c-02d0-6649-7d07268dfec7",
|
"AccessorID": "3328f9a6-433c-02d0-6649-7d07268dfec7",
|
||||||
|
"SecretID": "<hidden>",
|
||||||
"Description": "Bootstrap Token (Global Management)",
|
"Description": "Bootstrap Token (Global Management)",
|
||||||
"Policies": [
|
"Policies": [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue