Add support for returning ACL secret IDs for accessors with acl:write (#10546)

This commit is contained in:
Evan Culver 2021-07-08 15:13:08 -07:00 committed by GitHub
parent dcb90fb832
commit 5ff191ad99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 68 additions and 2 deletions

3
.changelog/10546.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
acl: Return secret ID when listing tokens if accessor has `acl:write`
```

View File

@ -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)

View File

@ -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
} }
} }

View File

@ -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) {

View File

@ -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,

View File

@ -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)

View File

@ -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"`

View File

@ -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)

View File

@ -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))
} }

View File

@ -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,

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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": [
{ {

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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": [
{ {