From b21b4346b4aa38dfa0c67f7b381bc522822a22d0 Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Thu, 31 Mar 2022 09:49:11 -0700 Subject: [PATCH] Add expanded token read flag and endpoint option --- .changelog/12670.txt | 3 + agent/acl_endpoint.go | 11 + agent/acl_endpoint_test.go | 11 + agent/consul/acl_endpoint.go | 89 +++++++ agent/consul/acl_endpoint_oss.go | 6 + agent/consul/acl_endpoint_test.go | 175 +++++++++++++ agent/structs/acl.go | 27 +- agent/structs/acl_test.go | 2 +- api/acl.go | 40 +++ command/acl/token/formatter.go | 166 ++++++++++++ command/acl/token/formatter_oss_test.go | 12 + command/acl/token/formatter_test.go | 236 ++++++++++++++++++ command/acl/token/read/token_read.go | 18 +- .../FormatTokenExpanded/oss/basic.json.golden | 48 ++++ .../oss/basic.pretty-meta.golden | 34 +++ .../oss/basic.pretty.golden | 31 +++ .../oss/complex.json.golden | 191 ++++++++++++++ .../oss/complex.pretty-meta.golden | 166 ++++++++++++ .../oss/complex.pretty.golden | 163 ++++++++++++ 19 files changed, 1423 insertions(+), 6 deletions(-) create mode 100644 .changelog/12670.txt create mode 100644 command/acl/token/formatter_oss_test.go create mode 100644 command/acl/token/testdata/FormatTokenExpanded/oss/basic.json.golden create mode 100644 command/acl/token/testdata/FormatTokenExpanded/oss/basic.pretty-meta.golden create mode 100644 command/acl/token/testdata/FormatTokenExpanded/oss/basic.pretty.golden create mode 100644 command/acl/token/testdata/FormatTokenExpanded/oss/complex.json.golden create mode 100644 command/acl/token/testdata/FormatTokenExpanded/oss/complex.pretty-meta.golden create mode 100644 command/acl/token/testdata/FormatTokenExpanded/oss/complex.pretty.golden diff --git a/.changelog/12670.txt b/.changelog/12670.txt new file mode 100644 index 0000000000..85d9348c5a --- /dev/null +++ b/.changelog/12670.txt @@ -0,0 +1,3 @@ +```release-note:feature +cli: The `token read` command now supports the `-expanded` flag to display detailed role and policy information for the token. +``` diff --git a/agent/acl_endpoint.go b/agent/acl_endpoint.go index 5b9ddec3b9..54f6c3948a 100644 --- a/agent/acl_endpoint.go +++ b/agent/acl_endpoint.go @@ -378,6 +378,9 @@ func (s *HTTPHandlers) ACLTokenGet(resp http.ResponseWriter, req *http.Request, if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil { return nil, err } + if _, ok := req.URL.Query()["expanded"]; ok { + args.Expanded = true + } if args.Datacenter == "" { args.Datacenter = s.agent.config.Datacenter @@ -393,6 +396,14 @@ func (s *HTTPHandlers) ACLTokenGet(resp http.ResponseWriter, req *http.Request, return nil, acl.ErrNotFound } + if args.Expanded { + expanded := &structs.ACLTokenExpanded{ + ACLToken: out.Token, + ExpandedTokenInfo: out.ExpandedTokenInfo, + } + return expanded, nil + } + return out.Token, nil } diff --git a/agent/acl_endpoint_test.go b/agent/acl_endpoint_test.go index 5087367d83..2c6aad450e 100644 --- a/agent/acl_endpoint_test.go +++ b/agent/acl_endpoint_test.go @@ -724,6 +724,17 @@ func TestACL_HTTP(t *testing.T) { require.True(t, ok) require.Equal(t, expected, token) }) + t.Run("Read-expanded", func(t *testing.T) { + expected := tokenMap[idMap["token-test"]] + req, _ := http.NewRequest("GET", "/v1/acl/token/"+expected.AccessorID+"?token=root&expanded=true", nil) + resp := httptest.NewRecorder() + obj, err := a.srv.ACLTokenCRUD(resp, req) + require.NoError(t, err) + tokenResp, ok := obj.(*structs.ACLTokenExpanded) + require.True(t, ok) + require.Equal(t, expected, tokenResp.ACLToken) + require.Len(t, tokenResp.ExpandedPolicies, 3) + }) t.Run("Self", func(t *testing.T) { expected := tokenMap[idMap["token-test"]] req, _ := http.NewRequest("GET", "/v1/acl/token/self?token="+expected.SecretID, nil) diff --git a/agent/consul/acl_endpoint.go b/agent/consul/acl_endpoint.go index 2541c36c1b..bac938dfa8 100644 --- a/agent/consul/acl_endpoint.go +++ b/agent/consul/acl_endpoint.go @@ -325,10 +325,99 @@ func (a *ACL) TokenRead(args *structs.ACLTokenGetRequest, reply *structs.ACLToke if token == nil { return errNotFound } + + if args.Expanded { + info, err := a.lookupExpandedTokenInfo(ws, state, token) + if err != nil { + return err + } + reply.ExpandedTokenInfo = info + } + return nil }) } +func (a *ACL) lookupExpandedTokenInfo(ws memdb.WatchSet, state *state.Store, token *structs.ACLToken) (structs.ExpandedTokenInfo, error) { + policyIDs := make(map[string]struct{}) + roleIDs := make(map[string]struct{}) + identityPolicies := make(map[string]*structs.ACLPolicy) + tokenInfo := structs.ExpandedTokenInfo{} + + // Add the token's policies and node/service identity policies + for _, policy := range token.Policies { + policyIDs[policy.ID] = struct{}{} + } + for _, roleLink := range token.Roles { + roleIDs[roleLink.ID] = struct{}{} + } + + for _, identity := range token.ServiceIdentities { + policy := identity.SyntheticPolicy(&token.EnterpriseMeta) + identityPolicies[policy.ID] = policy + } + for _, identity := range token.NodeIdentities { + policy := identity.SyntheticPolicy(&token.EnterpriseMeta) + identityPolicies[policy.ID] = policy + } + + // Get any namespace default roles/policies to look up + nsPolicies, nsRoles, err := getTokenNamespaceDefaults(ws, state, &token.EnterpriseMeta) + if err != nil { + return tokenInfo, err + } + tokenInfo.NamespaceDefaultPolicyIDs = nsPolicies + tokenInfo.NamespaceDefaultRoleIDs = nsRoles + for _, id := range nsPolicies { + policyIDs[id] = struct{}{} + } + for _, id := range nsRoles { + roleIDs[id] = struct{}{} + } + + // Add each role's policies and node/service identity policies + for roleID := range roleIDs { + _, role, err := state.ACLRoleGetByID(ws, roleID, &token.EnterpriseMeta) + if err != nil { + return tokenInfo, err + } + + for _, policy := range role.Policies { + policyIDs[policy.ID] = struct{}{} + } + + for _, identity := range role.ServiceIdentities { + policy := identity.SyntheticPolicy(&role.EnterpriseMeta) + identityPolicies[policy.ID] = policy + } + for _, identity := range role.NodeIdentities { + policy := identity.SyntheticPolicy(&role.EnterpriseMeta) + identityPolicies[policy.ID] = policy + } + + tokenInfo.ExpandedRoles = append(tokenInfo.ExpandedRoles, role) + } + + var policies []*structs.ACLPolicy + for id := range policyIDs { + _, policy, err := state.ACLPolicyGetByID(ws, id, &token.EnterpriseMeta) + if err != nil { + return tokenInfo, err + } + policies = append(policies, policy) + } + for _, policy := range identityPolicies { + policies = append(policies, policy) + } + + tokenInfo.ExpandedPolicies = policies + tokenInfo.AgentACLDefaultPolicy = a.srv.config.ACLResolverSettings.ACLDefaultPolicy + tokenInfo.AgentACLDownPolicy = a.srv.config.ACLResolverSettings.ACLDownPolicy + tokenInfo.ResolvedByAgent = a.srv.config.NodeName + + return tokenInfo, nil +} + func (a *ACL) TokenClone(args *structs.ACLTokenSetRequest, reply *structs.ACLToken) error { if err := a.aclPreCheck(); err != nil { return err diff --git a/agent/consul/acl_endpoint_oss.go b/agent/consul/acl_endpoint_oss.go index 80cb54c80d..3cc9e35d4f 100644 --- a/agent/consul/acl_endpoint_oss.go +++ b/agent/consul/acl_endpoint_oss.go @@ -5,7 +5,9 @@ package consul import ( "github.com/hashicorp/consul/agent/consul/authmethod" + "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/structs" + memdb "github.com/hashicorp/go-memdb" ) func (a *ACL) tokenUpsertValidateEnterprise(token *structs.ACLToken, existing *structs.ACLToken) error { @@ -37,3 +39,7 @@ func computeTargetEnterpriseMeta( ) (*structs.EnterpriseMeta, error) { return &structs.EnterpriseMeta{}, nil } + +func getTokenNamespaceDefaults(ws memdb.WatchSet, state *state.Store, entMeta *structs.EnterpriseMeta) ([]string, []string, error) { + return nil, nil, nil +} diff --git a/agent/consul/acl_endpoint_test.go b/agent/consul/acl_endpoint_test.go index 009f1c52de..6378720702 100644 --- a/agent/consul/acl_endpoint_test.go +++ b/agent/consul/acl_endpoint_test.go @@ -204,6 +204,181 @@ func TestACLEndpoint_TokenRead(t *testing.T) { require.Nil(t, resp.Token) require.EqualError(t, err, "failed acl token lookup: index error: UUID must be 36 characters") }) + + t.Run("expanded output with role/policy", func(t *testing.T) { + p1, err := upsertTestPolicy(codec, TestDefaultInitialManagementToken, "dc1") + require.NoError(t, err) + + p2, err := upsertTestPolicy(codec, TestDefaultInitialManagementToken, "dc1") + require.NoError(t, err) + + r1, err := upsertTestCustomizedRole(codec, TestDefaultInitialManagementToken, "dc1", func(role *structs.ACLRole) { + role.Policies = []structs.ACLRolePolicyLink{ + { + ID: p2.ID, + }, + } + }) + require.NoError(t, err) + + setReq := structs.ACLTokenSetRequest{ + Datacenter: "dc1", + ACLToken: structs.ACLToken{ + Description: "foobar", + Policies: []structs.ACLTokenPolicyLink{ + { + ID: p1.ID, + }, + }, + Roles: []structs.ACLTokenRoleLink{ + { + ID: r1.ID, + }, + }, + Local: false, + }, + WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken}, + } + + setResp := structs.ACLToken{} + err = msgpackrpc.CallWithCodec(codec, "ACL.TokenSet", &setReq, &setResp) + require.NoError(t, err) + require.NotEmpty(t, setResp.AccessorID) + + req := structs.ACLTokenGetRequest{ + Datacenter: "dc1", + TokenID: setResp.AccessorID, + TokenIDType: structs.ACLTokenAccessor, + Expanded: true, + QueryOptions: structs.QueryOptions{Token: TestDefaultInitialManagementToken}, + } + resp := structs.ACLTokenResponse{} + + err = msgpackrpc.CallWithCodec(codec, "ACL.TokenRead", &req, &resp) + require.NoError(t, err) + require.NotNil(t, resp.Token) + require.ElementsMatch(t, []*structs.ACLPolicy{p1, p2}, resp.ExpandedPolicies) + require.ElementsMatch(t, []*structs.ACLRole{r1}, resp.ExpandedRoles) + }) + + t.Run("expanded output with multiple roles that share a policy", func(t *testing.T) { + p1, err := upsertTestPolicy(codec, TestDefaultInitialManagementToken, "dc1") + require.NoError(t, err) + + r1, err := upsertTestCustomizedRole(codec, TestDefaultInitialManagementToken, "dc1", func(role *structs.ACLRole) { + role.Policies = []structs.ACLRolePolicyLink{ + { + ID: p1.ID, + }, + } + }) + require.NoError(t, err) + + r2, err := upsertTestCustomizedRole(codec, TestDefaultInitialManagementToken, "dc1", func(role *structs.ACLRole) { + role.Policies = []structs.ACLRolePolicyLink{ + { + ID: p1.ID, + }, + } + }) + require.NoError(t, err) + + setReq := structs.ACLTokenSetRequest{ + Datacenter: "dc1", + ACLToken: structs.ACLToken{ + Description: "foobar", + Roles: []structs.ACLTokenRoleLink{ + { + ID: r1.ID, + }, + { + ID: r2.ID, + }, + }, + Local: false, + }, + WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken}, + } + + setResp := structs.ACLToken{} + err = msgpackrpc.CallWithCodec(codec, "ACL.TokenSet", &setReq, &setResp) + require.NoError(t, err) + require.NotEmpty(t, setResp.AccessorID) + + req := structs.ACLTokenGetRequest{ + Datacenter: "dc1", + TokenID: setResp.AccessorID, + TokenIDType: structs.ACLTokenAccessor, + Expanded: true, + QueryOptions: structs.QueryOptions{Token: TestDefaultInitialManagementToken}, + } + resp := structs.ACLTokenResponse{} + + err = msgpackrpc.CallWithCodec(codec, "ACL.TokenRead", &req, &resp) + require.NoError(t, err) + require.NotNil(t, resp.Token) + require.ElementsMatch(t, []*structs.ACLPolicy{p1}, resp.ExpandedPolicies) + require.ElementsMatch(t, []*structs.ACLRole{r1, r2}, resp.ExpandedRoles) + }) + + t.Run("expanded output with node/service identities", func(t *testing.T) { + setReq := structs.ACLTokenSetRequest{ + Datacenter: "dc1", + ACLToken: structs.ACLToken{ + Description: "foobar", + ServiceIdentities: []*structs.ACLServiceIdentity{ + { + ServiceName: "web", + Datacenters: []string{"dc1"}, + }, + { + ServiceName: "db", + Datacenters: []string{"dc2"}, + }, + }, + NodeIdentities: []*structs.ACLNodeIdentity{ + { + NodeName: "foo", + Datacenter: "dc1", + }, + { + NodeName: "bar", + Datacenter: "dc1", + }, + }, + Local: false, + }, + WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken}, + } + + var expectedPolicies []*structs.ACLPolicy + entMeta := structs.DefaultEnterpriseMetaInDefaultPartition() + for _, serviceIdentity := range setReq.ACLToken.ServiceIdentities { + expectedPolicies = append(expectedPolicies, serviceIdentity.SyntheticPolicy(entMeta)) + } + for _, serviceIdentity := range setReq.ACLToken.NodeIdentities { + expectedPolicies = append(expectedPolicies, serviceIdentity.SyntheticPolicy(entMeta)) + } + + setResp := structs.ACLToken{} + err := msgpackrpc.CallWithCodec(codec, "ACL.TokenSet", &setReq, &setResp) + require.NoError(t, err) + require.NotEmpty(t, setResp.AccessorID) + + req := structs.ACLTokenGetRequest{ + Datacenter: "dc1", + TokenID: setResp.AccessorID, + TokenIDType: structs.ACLTokenAccessor, + Expanded: true, + QueryOptions: structs.QueryOptions{Token: TestDefaultInitialManagementToken}, + } + resp := structs.ACLTokenResponse{} + + err = msgpackrpc.CallWithCodec(codec, "ACL.TokenRead", &req, &resp) + require.NoError(t, err) + require.NotNil(t, resp.Token) + require.ElementsMatch(t, expectedPolicies, resp.ExpandedPolicies) + }) } func TestACLEndpoint_TokenClone(t *testing.T) { diff --git a/agent/structs/acl.go b/agent/structs/acl.go index 42fa558215..6631fa918a 100644 --- a/agent/structs/acl.go +++ b/agent/structs/acl.go @@ -158,7 +158,8 @@ func (s *ACLServiceIdentity) SyntheticPolicy(entMeta *EnterpriseMeta) *ACLPolicy policy := &ACLPolicy{} policy.ID = hashID policy.Name = fmt.Sprintf("synthetic-policy-%s", hashID) - policy.Description = "synthetic policy" + sn := NewServiceName(s.ServiceName, entMeta) + policy.Description = fmt.Sprintf("synthetic policy for service identity %q", sn.String()) policy.Rules = rules policy.Syntax = acl.SyntaxCurrent policy.Datacenters = s.Datacenters @@ -202,7 +203,7 @@ func (s *ACLNodeIdentity) SyntheticPolicy(entMeta *EnterpriseMeta) *ACLPolicy { policy := &ACLPolicy{} policy.ID = hashID policy.Name = fmt.Sprintf("synthetic-policy-%s", hashID) - policy.Description = "synthetic policy" + policy.Description = fmt.Sprintf("synthetic policy for node identity %q", s.NodeName) policy.Rules = rules policy.Syntax = acl.SyntaxCurrent policy.Datacenters = []string{s.Datacenter} @@ -1217,7 +1218,8 @@ func (r *ACLTokenSetRequest) RequestDatacenter() string { type ACLTokenGetRequest struct { TokenID string // id used for the token lookup TokenIDType ACLTokenIDType // The Type of ID used to lookup the token - Datacenter string // The datacenter to perform the request within + Expanded bool + Datacenter string // The datacenter to perform the request within EnterpriseMeta QueryOptions } @@ -1313,9 +1315,28 @@ type ACLTokenResponse struct { Token *ACLToken Redacted bool // whether the token's secret was redacted SourceDatacenter string + + ExpandedTokenInfo QueryMeta } +type ExpandedTokenInfo struct { + ExpandedPolicies []*ACLPolicy + ExpandedRoles []*ACLRole + + NamespaceDefaultPolicyIDs []string + NamespaceDefaultRoleIDs []string + + AgentACLDefaultPolicy string + AgentACLDownPolicy string + ResolvedByAgent string +} + +type ACLTokenExpanded struct { + *ACLToken + ExpandedTokenInfo +} + // ACLTokenBatchResponse returns multiple Tokens associated with the same metadata type ACLTokenBatchResponse struct { Tokens []*ACLToken diff --git a/agent/structs/acl_test.go b/agent/structs/acl_test.go index a435d1c57c..65afc6bf19 100644 --- a/agent/structs/acl_test.go +++ b/agent/structs/acl_test.go @@ -69,7 +69,6 @@ func TestStructs_ACLServiceIdentity_SyntheticPolicy(t *testing.T) { expect := &ACLPolicy{ Syntax: acl.SyntaxCurrent, Datacenters: test.datacenters, - Description: "synthetic policy", Rules: test.expectRules, } @@ -79,6 +78,7 @@ func TestStructs_ACLServiceIdentity_SyntheticPolicy(t *testing.T) { // strip irrelevant fields before equality got.ID = "" got.Name = "" + got.Description = "" got.Hash = nil require.Equal(t, expect, got) }) diff --git a/api/acl.go b/api/acl.go index 0f44494dda..9989a50b22 100644 --- a/api/acl.go +++ b/api/acl.go @@ -62,6 +62,20 @@ type ACLToken struct { AuthMethodNamespace string `json:",omitempty"` } +type ACLTokenExpanded struct { + ExpandedPolicies []ACLPolicy + ExpandedRoles []ACLRole + + NamespaceDefaultPolicies []string + NamespaceDefaultRoles []string + + AgentACLDefaultPolicy string + AgentACLDownPolicy string + ResolvedByAgent string + + ACLToken +} + type ACLTokenListEntry struct { CreateIndex uint64 ModifyIndex uint64 @@ -788,6 +802,32 @@ func (a *ACL) TokenRead(tokenID string, q *QueryOptions) (*ACLToken, *QueryMeta, return &out, qm, nil } +// TokenReadExpanded retrieves the full token details, as well as the contents of any policies affecting the token. +// The tokenID parameter must be a valid Accessor ID of an existing token. +func (a *ACL) TokenReadExpanded(tokenID string, q *QueryOptions) (*ACLTokenExpanded, *QueryMeta, error) { + r := a.c.newRequest("GET", "/v1/acl/token/"+tokenID) + r.setQueryOptions(q) + r.params.Set("expanded", "true") + rtt, resp, err := a.c.doRequest(r) + if err != nil { + return nil, nil, err + } + defer closeResponseBody(resp) + if err := requireOK(resp); err != nil { + return nil, nil, err + } + qm := &QueryMeta{} + parseQueryMeta(resp, qm) + qm.RequestTime = rtt + + var out ACLTokenExpanded + if err := decodeBody(resp, &out); err != nil { + return nil, nil, err + } + + return &out, qm, nil +} + // TokenReadSelf retrieves the full token details of the token currently // assigned to the API Client. In this manner its possible to read a token // by its Secret ID. diff --git a/command/acl/token/formatter.go b/command/acl/token/formatter.go index e88ee28ba0..a1eb050ba8 100644 --- a/command/acl/token/formatter.go +++ b/command/acl/token/formatter.go @@ -6,17 +6,22 @@ import ( "fmt" "strings" + "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" ) const ( PrettyFormat string = "pretty" JSONFormat string = "json" + + WHITESPACE_2 string = "\t" + WHITESPACE_4 string = "\t\t" ) // Formatter defines methods provided by token command output formatter type Formatter interface { FormatToken(token *api.ACLToken) (string, error) + FormatTokenExpanded(token *api.ACLTokenExpanded) (string, error) FormatTokenList(tokens []*api.ACLTokenListEntry) (string, error) } @@ -108,6 +113,159 @@ func (f *prettyFormatter) FormatToken(token *api.ACLToken) (string, error) { return buffer.String(), nil } +func (f *prettyFormatter) FormatTokenExpanded(token *api.ACLTokenExpanded) (string, error) { + var buffer bytes.Buffer + + buffer.WriteString(fmt.Sprintf("AccessorID: %s\n", token.AccessorID)) + buffer.WriteString(fmt.Sprintf("SecretID: %s\n", token.SecretID)) + if token.Partition != "" { + buffer.WriteString(fmt.Sprintf("Partition: %s\n", token.Partition)) + } + if token.Namespace != "" { + buffer.WriteString(fmt.Sprintf("Namespace: %s\n", token.Namespace)) + } + buffer.WriteString(fmt.Sprintf("Description: %s\n", token.Description)) + buffer.WriteString(fmt.Sprintf("Local: %t\n", token.Local)) + if token.AuthMethod != "" { + buffer.WriteString(fmt.Sprintf("Auth Method: %s (Namespace: %s)\n", token.AuthMethod, token.AuthMethodNamespace)) + } + buffer.WriteString(fmt.Sprintf("Create Time: %v\n", token.CreateTime)) + if token.ExpirationTime != nil && !token.ExpirationTime.IsZero() { + buffer.WriteString(fmt.Sprintf("Expiration Time: %v\n", *token.ExpirationTime)) + } + if f.showMeta { + buffer.WriteString(fmt.Sprintf("Hash: %x\n", token.Hash)) + buffer.WriteString(fmt.Sprintf("Create Index: %d\n", token.CreateIndex)) + buffer.WriteString(fmt.Sprintf("Modify Index: %d\n", token.ModifyIndex)) + } + + policies := make(map[string]api.ACLPolicy) + roles := make(map[string]api.ACLRole) + for _, policy := range token.ExpandedPolicies { + policies[policy.ID] = policy + } + for _, role := range token.ExpandedRoles { + roles[role.ID] = role + } + + formatPolicy := func(policy api.ACLPolicy, indent string) { + buffer.WriteString(fmt.Sprintf(indent+"Policy Name: %s\n", policy.Name)) + buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"ID: %s\n", policy.ID)) + buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"Description: %s\n", policy.Description)) + buffer.WriteString(indent + WHITESPACE_2 + "Rules:\n") + buffer.WriteString(indent + WHITESPACE_4) + buffer.WriteString(strings.ReplaceAll(policy.Rules, "\n", "\n"+indent+WHITESPACE_4)) + buffer.WriteString("\n\n") + } + + if len(token.ACLToken.Policies) > 0 { + buffer.WriteString("Policies:\n") + for _, policyLink := range token.ACLToken.Policies { + formatPolicy(policies[policyLink.ID], WHITESPACE_2) + } + } + + entMeta := structs.NewEnterpriseMetaWithPartition(token.Partition, token.Namespace) + formatServiceIdentity := func(svcIdentity *api.ACLServiceIdentity, indent string) { + if len(svcIdentity.Datacenters) > 0 { + buffer.WriteString(fmt.Sprintf(indent+"Name: %s (Datacenters: %s)\n", svcIdentity.ServiceName, strings.Join(svcIdentity.Datacenters, ", "))) + } else { + buffer.WriteString(fmt.Sprintf(indent+"Name: %s (Datacenters: all)\n", svcIdentity.ServiceName)) + } + identity := structs.ACLServiceIdentity{ServiceName: svcIdentity.ServiceName, Datacenters: svcIdentity.Datacenters} + policy := identity.SyntheticPolicy(&entMeta) + buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"Description: %s\n", policy.Description)) + buffer.WriteString(indent + WHITESPACE_2 + "Rules:") + buffer.WriteString(strings.ReplaceAll(policy.Rules, "\n", "\n"+indent+WHITESPACE_4)) + buffer.WriteString("\n\n") + } + if len(token.ACLToken.ServiceIdentities) > 0 { + buffer.WriteString("Service Identities:\n") + for _, svcIdentity := range token.ACLToken.ServiceIdentities { + formatServiceIdentity(svcIdentity, WHITESPACE_2) + } + } + + formatNodeIdentity := func(nodeIdentity *api.ACLNodeIdentity, indent string) { + buffer.WriteString(fmt.Sprintf(indent+"Name: %s (Datacenter: %s)\n", nodeIdentity.NodeName, nodeIdentity.Datacenter)) + identity := structs.ACLNodeIdentity{NodeName: nodeIdentity.NodeName, Datacenter: nodeIdentity.Datacenter} + policy := identity.SyntheticPolicy(&entMeta) + buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"Description: %s\n", policy.Description)) + buffer.WriteString(indent + WHITESPACE_2 + "Rules:") + buffer.WriteString(strings.ReplaceAll(policy.Rules, "\n", "\n"+indent+WHITESPACE_4)) + buffer.WriteString("\n\n") + } + if len(token.ACLToken.NodeIdentities) > 0 { + buffer.WriteString("Node Identities:\n") + for _, nodeIdentity := range token.ACLToken.NodeIdentities { + formatNodeIdentity(nodeIdentity, WHITESPACE_2) + } + } + + formatRole := func(role api.ACLRole, indent string) { + buffer.WriteString(fmt.Sprintf(indent+"Role Name: %s\n", role.Name)) + buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"ID: %s\n", role.ID)) + buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"Description: %s\n", role.Description)) + + if len(role.Policies) > 0 { + buffer.WriteString(indent + WHITESPACE_2 + "Policies:\n") + for _, policyLink := range role.Policies { + formatPolicy(policies[policyLink.ID], indent+WHITESPACE_4) + } + } + + if len(role.ServiceIdentities) > 0 { + buffer.WriteString(indent + WHITESPACE_2 + "Service Identities:\n") + for _, svcIdentity := range role.ServiceIdentities { + formatServiceIdentity(svcIdentity, indent+WHITESPACE_4) + } + } + + if len(role.NodeIdentities) > 0 { + buffer.WriteString(indent + WHITESPACE_2 + "Node Identities:\n") + for _, nodeIdentity := range role.NodeIdentities { + formatNodeIdentity(nodeIdentity, indent+WHITESPACE_4) + } + } + } + if len(token.ACLToken.Roles) > 0 { + buffer.WriteString("Roles:\n") + for _, roleLink := range token.ACLToken.Roles { + role := roles[roleLink.ID] + formatRole(role, WHITESPACE_2) + } + } + + buffer.WriteString("=== End of Authorizer Layer 0: Token ===\n") + + if len(token.NamespaceDefaultPolicies) > 0 || len(token.NamespaceDefaultRoles) > 0 { + buffer.WriteString("=== Start of Authorizer Layer 1: Token Namespace’s Defaults (Inherited) ===\n") + buffer.WriteString(fmt.Sprintf("Description: ACL Roles inherited by all Tokens in Namespace %q\n\n", token.Namespace)) + + buffer.WriteString("Namespace Policy Defaults:\n") + for _, policyID := range token.NamespaceDefaultPolicies { + formatPolicy(policies[policyID], WHITESPACE_2) + } + + buffer.WriteString("Namespace Role Defaults:\n") + for _, roleID := range token.NamespaceDefaultRoles { + formatRole(roles[roleID], WHITESPACE_2) + } + + buffer.WriteString("=== End of Authorizer Layer 1: Token Namespace’s Defaults (Inherited) ===\n") + } + + buffer.WriteString("=== Start of Authorizer Layer 2: Agent Configuration Defaults (Inherited) ===\n") + buffer.WriteString("Description: Defined at request-time by the agent that resolves the ACL token; other agents may have different configuration defaults\n") + buffer.WriteString(fmt.Sprintf("Resolved By Agent: %q\n\n", token.ResolvedByAgent)) + buffer.WriteString(fmt.Sprintf("Default Policy: %s\n", token.AgentACLDefaultPolicy)) + buffer.WriteString(WHITESPACE_2 + "Description: Backstop rule used if no preceding layer has a matching rule (refer to default_policy option in agent configuration)\n\n") + buffer.WriteString(fmt.Sprintf("Down Policy: %s\n", token.AgentACLDownPolicy)) + buffer.WriteString(WHITESPACE_2 + "Description: Defines what to do if this Token's information cannot be read from the primary_datacenter (refer to down_policy option in agent configuration)\n\n") + + return buffer.String(), nil +} + func (f *prettyFormatter) FormatTokenList(tokens []*api.ACLTokenListEntry) (string, error) { var buffer bytes.Buffer @@ -204,3 +362,11 @@ func (f *jsonFormatter) FormatToken(token *api.ACLToken) (string, error) { } return string(b), nil } + +func (f *jsonFormatter) FormatTokenExpanded(token *api.ACLTokenExpanded) (string, error) { + b, err := json.MarshalIndent(token, "", " ") + if err != nil { + return "", fmt.Errorf("Failed to marshal token: %v", err) + } + return string(b), nil +} diff --git a/command/acl/token/formatter_oss_test.go b/command/acl/token/formatter_oss_test.go new file mode 100644 index 0000000000..d825a5ee35 --- /dev/null +++ b/command/acl/token/formatter_oss_test.go @@ -0,0 +1,12 @@ +//go:build !consulent +// +build !consulent + +package token + +import ( + "testing" +) + +func TestFormatTokenExpanded(t *testing.T) { + testFormatTokenExpanded(t, "FormatTokenExpanded/oss") +} diff --git a/command/acl/token/formatter_test.go b/command/acl/token/formatter_test.go index a267c385fb..ba93e9dc08 100644 --- a/command/acl/token/formatter_test.go +++ b/command/acl/token/formatter_test.go @@ -254,3 +254,239 @@ func TestFormatTokenList(t *testing.T) { }) } } + +type testCase struct { + tokenExpanded api.ACLTokenExpanded + overrideGoldenName string +} + +func timeRef(in time.Time) *time.Time { + return &in +} + +var expandedTokenTestCases = map[string]testCase{ + "basic": { + tokenExpanded: api.ACLTokenExpanded{ + ExpandedPolicies: []api.ACLPolicy{ + { + ID: "beb04680-815b-4d7c-9e33-3d707c24672c", + Name: "foo", + Description: "user policy on token", + Rules: `service_prefix "" { + policy = "read" +}`, + }, + { + ID: "18788457-584c-4812-80d3-23d403148a90", + Name: "bar", + Description: "other user policy on token", + Rules: `operator = "read"`, + }, + }, + AgentACLDefaultPolicy: "allow", + AgentACLDownPolicy: "deny", + ResolvedByAgent: "leader", + ACLToken: api.ACLToken{ + AccessorID: "fbd2447f-7479-4329-ad13-b021d74f86ba", + SecretID: "869c6e91-4de9-4dab-b56e-87548435f9c6", + Description: "test token", + Local: false, + CreateTime: time.Date(2020, 5, 22, 18, 52, 31, 0, time.UTC), + Hash: []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}, + CreateIndex: 42, + ModifyIndex: 100, + Policies: []*api.ACLLink{ + { + ID: "beb04680-815b-4d7c-9e33-3d707c24672c", + Name: "foo", + }, + { + ID: "18788457-584c-4812-80d3-23d403148a90", + Name: "bar", + }, + }, + }, + }, + }, + "complex": { + tokenExpanded: api.ACLTokenExpanded{ + ExpandedPolicies: []api.ACLPolicy{ + { + ID: "beb04680-815b-4d7c-9e33-3d707c24672c", + Name: "hobbiton", + Description: "user policy on token", + Rules: `service_prefix "" { + policy = "read" +}`, + }, + { + ID: "18788457-584c-4812-80d3-23d403148a90", + Name: "bywater", + Description: "other user policy on token", + Rules: `operator = "read"`, + }, + { + ID: "6204f4cd-4709-441c-ac1b-cb029e940263", + Name: "shire-policy", + Description: "policy for shire role", + Rules: `operator = "write"`, + }, + { + ID: "e86f0d1f-71b1-4690-bdfd-ff8c2cd4ae93", + Name: "west-farthing-policy", + Description: "policy for west-farthing role", + Rules: `service "foo" { + policy = "read" +}`, + }, + { + ID: "2b582ff1-4a43-457f-8a2b-30a8265e29a5", + Name: "default-policy-1", + Description: "default policy 1", + Rules: `key "foo" { policy = "write" }`, + }, + { + ID: "b55dce64-f2cc-4eb5-8e5f-50e90e63c6ea", + Name: "default-policy-2", + Description: "default policy 2", + Rules: `key "bar" { policy = "read" }`, + }, + }, + ExpandedRoles: []api.ACLRole{ + { + ID: "3b0a78fe-b9c3-40de-b8ea-7d4d6674b366", + Name: "shire", + Description: "shire role", + Policies: []*api.ACLRolePolicyLink{ + { + ID: "6204f4cd-4709-441c-ac1b-cb029e940263", + }, + }, + ServiceIdentities: []*api.ACLServiceIdentity{ + { + ServiceName: "foo", + Datacenters: []string{"middleearth-southwest"}, + }, + }, + }, + { + ID: "6c9d1e1d-34bc-4d55-80f3-add0890ad791", + Name: "west-farthing", + Description: "west-farthing role", + Policies: []*api.ACLRolePolicyLink{ + { + ID: "e86f0d1f-71b1-4690-bdfd-ff8c2cd4ae93", + }, + }, + NodeIdentities: []*api.ACLNodeIdentity{ + { + NodeName: "bar", + Datacenter: "middleearth-southwest", + }, + }, + }, + { + ID: "56033f2b-e1a6-4905-b71d-e011c862bc65", + Name: "ns-default", + Description: "default role", + Policies: []*api.ACLRolePolicyLink{ + { + ID: "b55dce64-f2cc-4eb5-8e5f-50e90e63c6ea", + }, + }, + ServiceIdentities: []*api.ACLServiceIdentity{ + { + ServiceName: "web", + Datacenters: []string{"middleearth-northeast"}, + }, + }, + NodeIdentities: []*api.ACLNodeIdentity{ + { + NodeName: "db", + Datacenter: "middleearth-northwest", + }, + }, + }, + }, + NamespaceDefaultPolicies: []string{"2b582ff1-4a43-457f-8a2b-30a8265e29a5"}, + NamespaceDefaultRoles: []string{"56033f2b-e1a6-4905-b71d-e011c862bc65"}, + AgentACLDefaultPolicy: "deny", + AgentACLDownPolicy: "extend-cache", + ResolvedByAgent: "server-1", + ACLToken: api.ACLToken{ + AccessorID: "fbd2447f-7479-4329-ad13-b021d74f86ba", + SecretID: "869c6e91-4de9-4dab-b56e-87548435f9c6", + Namespace: "foo", + Description: "test token", + Local: false, + AuthMethod: "bar", + AuthMethodNamespace: "baz", + CreateTime: time.Date(2020, 5, 22, 18, 52, 31, 0, time.UTC), + ExpirationTime: timeRef(time.Date(2020, 5, 22, 19, 52, 31, 0, time.UTC)), + Hash: []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}, + CreateIndex: 5, + ModifyIndex: 10, + Policies: []*api.ACLLink{ + { + ID: "beb04680-815b-4d7c-9e33-3d707c24672c", + Name: "hobbiton", + }, + { + ID: "18788457-584c-4812-80d3-23d403148a90", + Name: "bywater", + }, + }, + Roles: []*api.ACLLink{ + { + ID: "3b0a78fe-b9c3-40de-b8ea-7d4d6674b366", + Name: "shire", + }, + { + ID: "6c9d1e1d-34bc-4d55-80f3-add0890ad791", + Name: "west-farthing", + }, + }, + ServiceIdentities: []*api.ACLServiceIdentity{ + { + ServiceName: "gardener", + Datacenters: []string{"middleearth-northwest"}, + }, + }, + NodeIdentities: []*api.ACLNodeIdentity{ + { + NodeName: "bagend", + Datacenter: "middleearth-northwest", + }, + }, + }, + }, + }, +} + +func testFormatTokenExpanded(t *testing.T, dirPath string) { + formatters := map[string]Formatter{ + "pretty": newPrettyFormatter(false), + "pretty-meta": newPrettyFormatter(true), + // the JSON formatter ignores the showMeta + "json": newJSONFormatter(false), + } + + for name, tcase := range expandedTokenTestCases { + t.Run(name, func(t *testing.T) { + for fmtName, formatter := range formatters { + t.Run(fmtName, func(t *testing.T) { + actual, err := formatter.FormatTokenExpanded(&tcase.tokenExpanded) + require.NoError(t, err) + + gName := fmt.Sprintf("%s.%s", name, fmtName) + if tcase.overrideGoldenName != "" { + gName = tcase.overrideGoldenName + } + + expected := golden(t, path.Join(dirPath, gName), actual) + require.Equal(t, expected, actual) + }) + } + }) + } +} diff --git a/command/acl/token/read/token_read.go b/command/acl/token/read/token_read.go index 8b0616cdc1..885d7d916a 100644 --- a/command/acl/token/read/token_read.go +++ b/command/acl/token/read/token_read.go @@ -28,6 +28,7 @@ type cmd struct { self bool showMeta bool format string + expanded bool } func (c *cmd) init() { @@ -36,6 +37,8 @@ func (c *cmd) init() { "as the content hash and Raft indices should be shown for each entry") c.flags.BoolVar(&c.self, "self", false, "Indicates that the current HTTP token "+ "should be read by secret ID instead of expecting a -id option") + c.flags.BoolVar(&c.expanded, "expanded", false, "Indicates that the contents of the "+ + " policies and roles affecting the token should also be shown.") c.flags.StringVar(&c.tokenID, "id", "", "The Accessor ID of the token to read. "+ "It may be specified as a unique ID prefix but will error if the prefix "+ "matches multiple token Accessor IDs") @@ -69,6 +72,7 @@ func (c *cmd) Run(args []string) int { } var t *api.ACLToken + var expanded *api.ACLTokenExpanded if !c.self { tokenID, err := acl.GetTokenIDFromPartial(client, c.tokenID) if err != nil { @@ -76,7 +80,12 @@ func (c *cmd) Run(args []string) int { return 1 } - t, _, err = client.ACL().TokenRead(tokenID, nil) + if !c.expanded { + t, _, err = client.ACL().TokenRead(tokenID, nil) + } else { + expanded, _, err = client.ACL().TokenReadExpanded(tokenID, nil) + } + if err != nil { c.UI.Error(fmt.Sprintf("Error reading token %q: %v", tokenID, err)) return 1 @@ -94,7 +103,12 @@ func (c *cmd) Run(args []string) int { c.UI.Error(err.Error()) return 1 } - out, err := formatter.FormatToken(t) + var out string + if !c.expanded { + out, err = formatter.FormatToken(t) + } else { + out, err = formatter.FormatTokenExpanded(expanded) + } if err != nil { c.UI.Error(err.Error()) return 1 diff --git a/command/acl/token/testdata/FormatTokenExpanded/oss/basic.json.golden b/command/acl/token/testdata/FormatTokenExpanded/oss/basic.json.golden new file mode 100644 index 0000000000..cba80e455a --- /dev/null +++ b/command/acl/token/testdata/FormatTokenExpanded/oss/basic.json.golden @@ -0,0 +1,48 @@ +{ + "ExpandedPolicies": [ + { + "ID": "beb04680-815b-4d7c-9e33-3d707c24672c", + "Name": "foo", + "Description": "user policy on token", + "Rules": "service_prefix \"\" {\n policy = \"read\"\n}", + "Datacenters": null, + "Hash": null, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + { + "ID": "18788457-584c-4812-80d3-23d403148a90", + "Name": "bar", + "Description": "other user policy on token", + "Rules": "operator = \"read\"", + "Datacenters": null, + "Hash": null, + "CreateIndex": 0, + "ModifyIndex": 0 + } + ], + "ExpandedRoles": null, + "NamespaceDefaultPolicies": null, + "NamespaceDefaultRoles": null, + "AgentACLDefaultPolicy": "allow", + "AgentACLDownPolicy": "deny", + "ResolvedByAgent": "leader", + "CreateIndex": 42, + "ModifyIndex": 100, + "AccessorID": "fbd2447f-7479-4329-ad13-b021d74f86ba", + "SecretID": "869c6e91-4de9-4dab-b56e-87548435f9c6", + "Description": "test token", + "Policies": [ + { + "ID": "beb04680-815b-4d7c-9e33-3d707c24672c", + "Name": "foo" + }, + { + "ID": "18788457-584c-4812-80d3-23d403148a90", + "Name": "bar" + } + ], + "Local": false, + "CreateTime": "2020-05-22T18:52:31Z", + "Hash": "YWJjZGVmZ2g=" +} \ No newline at end of file diff --git a/command/acl/token/testdata/FormatTokenExpanded/oss/basic.pretty-meta.golden b/command/acl/token/testdata/FormatTokenExpanded/oss/basic.pretty-meta.golden new file mode 100644 index 0000000000..401c8ee8af --- /dev/null +++ b/command/acl/token/testdata/FormatTokenExpanded/oss/basic.pretty-meta.golden @@ -0,0 +1,34 @@ +AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba +SecretID: 869c6e91-4de9-4dab-b56e-87548435f9c6 +Description: test token +Local: false +Create Time: 2020-05-22 18:52:31 +0000 UTC +Hash: 6162636465666768 +Create Index: 42 +Modify Index: 100 +Policies: + Policy Name: foo + ID: beb04680-815b-4d7c-9e33-3d707c24672c + Description: user policy on token + Rules: + service_prefix "" { + policy = "read" + } + + Policy Name: bar + ID: 18788457-584c-4812-80d3-23d403148a90 + Description: other user policy on token + Rules: + operator = "read" + +=== End of Authorizer Layer 0: Token === +=== Start of Authorizer Layer 2: Agent Configuration Defaults (Inherited) === +Description: Defined at request-time by the agent that resolves the ACL token; other agents may have different configuration defaults +Resolved By Agent: "leader" + +Default Policy: allow + Description: Backstop rule used if no preceding layer has a matching rule (refer to default_policy option in agent configuration) + +Down Policy: deny + Description: Defines what to do if this Token's information cannot be read from the primary_datacenter (refer to down_policy option in agent configuration) + diff --git a/command/acl/token/testdata/FormatTokenExpanded/oss/basic.pretty.golden b/command/acl/token/testdata/FormatTokenExpanded/oss/basic.pretty.golden new file mode 100644 index 0000000000..73e1fb40b9 --- /dev/null +++ b/command/acl/token/testdata/FormatTokenExpanded/oss/basic.pretty.golden @@ -0,0 +1,31 @@ +AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba +SecretID: 869c6e91-4de9-4dab-b56e-87548435f9c6 +Description: test token +Local: false +Create Time: 2020-05-22 18:52:31 +0000 UTC +Policies: + Policy Name: foo + ID: beb04680-815b-4d7c-9e33-3d707c24672c + Description: user policy on token + Rules: + service_prefix "" { + policy = "read" + } + + Policy Name: bar + ID: 18788457-584c-4812-80d3-23d403148a90 + Description: other user policy on token + Rules: + operator = "read" + +=== End of Authorizer Layer 0: Token === +=== Start of Authorizer Layer 2: Agent Configuration Defaults (Inherited) === +Description: Defined at request-time by the agent that resolves the ACL token; other agents may have different configuration defaults +Resolved By Agent: "leader" + +Default Policy: allow + Description: Backstop rule used if no preceding layer has a matching rule (refer to default_policy option in agent configuration) + +Down Policy: deny + Description: Defines what to do if this Token's information cannot be read from the primary_datacenter (refer to down_policy option in agent configuration) + diff --git a/command/acl/token/testdata/FormatTokenExpanded/oss/complex.json.golden b/command/acl/token/testdata/FormatTokenExpanded/oss/complex.json.golden new file mode 100644 index 0000000000..36931e2192 --- /dev/null +++ b/command/acl/token/testdata/FormatTokenExpanded/oss/complex.json.golden @@ -0,0 +1,191 @@ +{ + "ExpandedPolicies": [ + { + "ID": "beb04680-815b-4d7c-9e33-3d707c24672c", + "Name": "hobbiton", + "Description": "user policy on token", + "Rules": "service_prefix \"\" {\n policy = \"read\"\n}", + "Datacenters": null, + "Hash": null, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + { + "ID": "18788457-584c-4812-80d3-23d403148a90", + "Name": "bywater", + "Description": "other user policy on token", + "Rules": "operator = \"read\"", + "Datacenters": null, + "Hash": null, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + { + "ID": "6204f4cd-4709-441c-ac1b-cb029e940263", + "Name": "shire-policy", + "Description": "policy for shire role", + "Rules": "operator = \"write\"", + "Datacenters": null, + "Hash": null, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + { + "ID": "e86f0d1f-71b1-4690-bdfd-ff8c2cd4ae93", + "Name": "west-farthing-policy", + "Description": "policy for west-farthing role", + "Rules": "service \"foo\" {\n policy = \"read\"\n}", + "Datacenters": null, + "Hash": null, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + { + "ID": "2b582ff1-4a43-457f-8a2b-30a8265e29a5", + "Name": "default-policy-1", + "Description": "default policy 1", + "Rules": "key \"foo\" { policy = \"write\" }", + "Datacenters": null, + "Hash": null, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + { + "ID": "b55dce64-f2cc-4eb5-8e5f-50e90e63c6ea", + "Name": "default-policy-2", + "Description": "default policy 2", + "Rules": "key \"bar\" { policy = \"read\" }", + "Datacenters": null, + "Hash": null, + "CreateIndex": 0, + "ModifyIndex": 0 + } + ], + "ExpandedRoles": [ + { + "ID": "3b0a78fe-b9c3-40de-b8ea-7d4d6674b366", + "Name": "shire", + "Description": "shire role", + "Policies": [ + { + "ID": "6204f4cd-4709-441c-ac1b-cb029e940263", + "Name": "" + } + ], + "ServiceIdentities": [ + { + "ServiceName": "foo", + "Datacenters": [ + "middleearth-southwest" + ] + } + ], + "Hash": null, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + { + "ID": "6c9d1e1d-34bc-4d55-80f3-add0890ad791", + "Name": "west-farthing", + "Description": "west-farthing role", + "Policies": [ + { + "ID": "e86f0d1f-71b1-4690-bdfd-ff8c2cd4ae93", + "Name": "" + } + ], + "NodeIdentities": [ + { + "NodeName": "bar", + "Datacenter": "middleearth-southwest" + } + ], + "Hash": null, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + { + "ID": "56033f2b-e1a6-4905-b71d-e011c862bc65", + "Name": "ns-default", + "Description": "default role", + "Policies": [ + { + "ID": "b55dce64-f2cc-4eb5-8e5f-50e90e63c6ea", + "Name": "" + } + ], + "ServiceIdentities": [ + { + "ServiceName": "web", + "Datacenters": [ + "middleearth-northeast" + ] + } + ], + "NodeIdentities": [ + { + "NodeName": "db", + "Datacenter": "middleearth-northwest" + } + ], + "Hash": null, + "CreateIndex": 0, + "ModifyIndex": 0 + } + ], + "NamespaceDefaultPolicies": [ + "2b582ff1-4a43-457f-8a2b-30a8265e29a5" + ], + "NamespaceDefaultRoles": [ + "56033f2b-e1a6-4905-b71d-e011c862bc65" + ], + "AgentACLDefaultPolicy": "deny", + "AgentACLDownPolicy": "extend-cache", + "ResolvedByAgent": "server-1", + "CreateIndex": 5, + "ModifyIndex": 10, + "AccessorID": "fbd2447f-7479-4329-ad13-b021d74f86ba", + "SecretID": "869c6e91-4de9-4dab-b56e-87548435f9c6", + "Description": "test token", + "Policies": [ + { + "ID": "beb04680-815b-4d7c-9e33-3d707c24672c", + "Name": "hobbiton" + }, + { + "ID": "18788457-584c-4812-80d3-23d403148a90", + "Name": "bywater" + } + ], + "Roles": [ + { + "ID": "3b0a78fe-b9c3-40de-b8ea-7d4d6674b366", + "Name": "shire" + }, + { + "ID": "6c9d1e1d-34bc-4d55-80f3-add0890ad791", + "Name": "west-farthing" + } + ], + "ServiceIdentities": [ + { + "ServiceName": "gardener", + "Datacenters": [ + "middleearth-northwest" + ] + } + ], + "NodeIdentities": [ + { + "NodeName": "bagend", + "Datacenter": "middleearth-northwest" + } + ], + "Local": false, + "AuthMethod": "bar", + "ExpirationTime": "2020-05-22T19:52:31Z", + "CreateTime": "2020-05-22T18:52:31Z", + "Hash": "YWJjZGVmZ2g=", + "Namespace": "foo", + "AuthMethodNamespace": "baz" +} \ No newline at end of file diff --git a/command/acl/token/testdata/FormatTokenExpanded/oss/complex.pretty-meta.golden b/command/acl/token/testdata/FormatTokenExpanded/oss/complex.pretty-meta.golden new file mode 100644 index 0000000000..bc8033edf9 --- /dev/null +++ b/command/acl/token/testdata/FormatTokenExpanded/oss/complex.pretty-meta.golden @@ -0,0 +1,166 @@ +AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba +SecretID: 869c6e91-4de9-4dab-b56e-87548435f9c6 +Namespace: foo +Description: test token +Local: false +Auth Method: bar (Namespace: baz) +Create Time: 2020-05-22 18:52:31 +0000 UTC +Expiration Time: 2020-05-22 19:52:31 +0000 UTC +Hash: 6162636465666768 +Create Index: 5 +Modify Index: 10 +Policies: + Policy Name: hobbiton + ID: beb04680-815b-4d7c-9e33-3d707c24672c + Description: user policy on token + Rules: + service_prefix "" { + policy = "read" + } + + Policy Name: bywater + ID: 18788457-584c-4812-80d3-23d403148a90 + Description: other user policy on token + Rules: + operator = "read" + +Service Identities: + Name: gardener (Datacenters: middleearth-northwest) + Description: synthetic policy for service identity "gardener" + Rules: + service "gardener" { + policy = "write" + } + service "gardener-sidecar-proxy" { + policy = "write" + } + service_prefix "" { + policy = "read" + } + node_prefix "" { + policy = "read" + } + +Node Identities: + Name: bagend (Datacenter: middleearth-northwest) + Description: synthetic policy for node identity "bagend" + Rules: + node "bagend" { + policy = "write" + } + service_prefix "" { + policy = "read" + } + +Roles: + Role Name: shire + ID: 3b0a78fe-b9c3-40de-b8ea-7d4d6674b366 + Description: shire role + Policies: + Policy Name: shire-policy + ID: 6204f4cd-4709-441c-ac1b-cb029e940263 + Description: policy for shire role + Rules: + operator = "write" + + Service Identities: + Name: foo (Datacenters: middleearth-southwest) + Description: synthetic policy for service identity "foo" + Rules: + service "foo" { + policy = "write" + } + service "foo-sidecar-proxy" { + policy = "write" + } + service_prefix "" { + policy = "read" + } + node_prefix "" { + policy = "read" + } + + Role Name: west-farthing + ID: 6c9d1e1d-34bc-4d55-80f3-add0890ad791 + Description: west-farthing role + Policies: + Policy Name: west-farthing-policy + ID: e86f0d1f-71b1-4690-bdfd-ff8c2cd4ae93 + Description: policy for west-farthing role + Rules: + service "foo" { + policy = "read" + } + + Node Identities: + Name: bar (Datacenter: middleearth-southwest) + Description: synthetic policy for node identity "bar" + Rules: + node "bar" { + policy = "write" + } + service_prefix "" { + policy = "read" + } + +=== End of Authorizer Layer 0: Token === +=== Start of Authorizer Layer 1: Token Namespace’s Defaults (Inherited) === +Description: ACL Roles inherited by all Tokens in Namespace "foo" + +Namespace Policy Defaults: + Policy Name: default-policy-1 + ID: 2b582ff1-4a43-457f-8a2b-30a8265e29a5 + Description: default policy 1 + Rules: + key "foo" { policy = "write" } + +Namespace Role Defaults: + Role Name: ns-default + ID: 56033f2b-e1a6-4905-b71d-e011c862bc65 + Description: default role + Policies: + Policy Name: default-policy-2 + ID: b55dce64-f2cc-4eb5-8e5f-50e90e63c6ea + Description: default policy 2 + Rules: + key "bar" { policy = "read" } + + Service Identities: + Name: web (Datacenters: middleearth-northeast) + Description: synthetic policy for service identity "web" + Rules: + service "web" { + policy = "write" + } + service "web-sidecar-proxy" { + policy = "write" + } + service_prefix "" { + policy = "read" + } + node_prefix "" { + policy = "read" + } + + Node Identities: + Name: db (Datacenter: middleearth-northwest) + Description: synthetic policy for node identity "db" + Rules: + node "db" { + policy = "write" + } + service_prefix "" { + policy = "read" + } + +=== End of Authorizer Layer 1: Token Namespace’s Defaults (Inherited) === +=== Start of Authorizer Layer 2: Agent Configuration Defaults (Inherited) === +Description: Defined at request-time by the agent that resolves the ACL token; other agents may have different configuration defaults +Resolved By Agent: "server-1" + +Default Policy: deny + Description: Backstop rule used if no preceding layer has a matching rule (refer to default_policy option in agent configuration) + +Down Policy: extend-cache + Description: Defines what to do if this Token's information cannot be read from the primary_datacenter (refer to down_policy option in agent configuration) + diff --git a/command/acl/token/testdata/FormatTokenExpanded/oss/complex.pretty.golden b/command/acl/token/testdata/FormatTokenExpanded/oss/complex.pretty.golden new file mode 100644 index 0000000000..215cf8b7a0 --- /dev/null +++ b/command/acl/token/testdata/FormatTokenExpanded/oss/complex.pretty.golden @@ -0,0 +1,163 @@ +AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba +SecretID: 869c6e91-4de9-4dab-b56e-87548435f9c6 +Namespace: foo +Description: test token +Local: false +Auth Method: bar (Namespace: baz) +Create Time: 2020-05-22 18:52:31 +0000 UTC +Expiration Time: 2020-05-22 19:52:31 +0000 UTC +Policies: + Policy Name: hobbiton + ID: beb04680-815b-4d7c-9e33-3d707c24672c + Description: user policy on token + Rules: + service_prefix "" { + policy = "read" + } + + Policy Name: bywater + ID: 18788457-584c-4812-80d3-23d403148a90 + Description: other user policy on token + Rules: + operator = "read" + +Service Identities: + Name: gardener (Datacenters: middleearth-northwest) + Description: synthetic policy for service identity "gardener" + Rules: + service "gardener" { + policy = "write" + } + service "gardener-sidecar-proxy" { + policy = "write" + } + service_prefix "" { + policy = "read" + } + node_prefix "" { + policy = "read" + } + +Node Identities: + Name: bagend (Datacenter: middleearth-northwest) + Description: synthetic policy for node identity "bagend" + Rules: + node "bagend" { + policy = "write" + } + service_prefix "" { + policy = "read" + } + +Roles: + Role Name: shire + ID: 3b0a78fe-b9c3-40de-b8ea-7d4d6674b366 + Description: shire role + Policies: + Policy Name: shire-policy + ID: 6204f4cd-4709-441c-ac1b-cb029e940263 + Description: policy for shire role + Rules: + operator = "write" + + Service Identities: + Name: foo (Datacenters: middleearth-southwest) + Description: synthetic policy for service identity "foo" + Rules: + service "foo" { + policy = "write" + } + service "foo-sidecar-proxy" { + policy = "write" + } + service_prefix "" { + policy = "read" + } + node_prefix "" { + policy = "read" + } + + Role Name: west-farthing + ID: 6c9d1e1d-34bc-4d55-80f3-add0890ad791 + Description: west-farthing role + Policies: + Policy Name: west-farthing-policy + ID: e86f0d1f-71b1-4690-bdfd-ff8c2cd4ae93 + Description: policy for west-farthing role + Rules: + service "foo" { + policy = "read" + } + + Node Identities: + Name: bar (Datacenter: middleearth-southwest) + Description: synthetic policy for node identity "bar" + Rules: + node "bar" { + policy = "write" + } + service_prefix "" { + policy = "read" + } + +=== End of Authorizer Layer 0: Token === +=== Start of Authorizer Layer 1: Token Namespace’s Defaults (Inherited) === +Description: ACL Roles inherited by all Tokens in Namespace "foo" + +Namespace Policy Defaults: + Policy Name: default-policy-1 + ID: 2b582ff1-4a43-457f-8a2b-30a8265e29a5 + Description: default policy 1 + Rules: + key "foo" { policy = "write" } + +Namespace Role Defaults: + Role Name: ns-default + ID: 56033f2b-e1a6-4905-b71d-e011c862bc65 + Description: default role + Policies: + Policy Name: default-policy-2 + ID: b55dce64-f2cc-4eb5-8e5f-50e90e63c6ea + Description: default policy 2 + Rules: + key "bar" { policy = "read" } + + Service Identities: + Name: web (Datacenters: middleearth-northeast) + Description: synthetic policy for service identity "web" + Rules: + service "web" { + policy = "write" + } + service "web-sidecar-proxy" { + policy = "write" + } + service_prefix "" { + policy = "read" + } + node_prefix "" { + policy = "read" + } + + Node Identities: + Name: db (Datacenter: middleearth-northwest) + Description: synthetic policy for node identity "db" + Rules: + node "db" { + policy = "write" + } + service_prefix "" { + policy = "read" + } + +=== End of Authorizer Layer 1: Token Namespace’s Defaults (Inherited) === +=== Start of Authorizer Layer 2: Agent Configuration Defaults (Inherited) === +Description: Defined at request-time by the agent that resolves the ACL token; other agents may have different configuration defaults +Resolved By Agent: "server-1" + +Default Policy: deny + Description: Backstop rule used if no preceding layer has a matching rule (refer to default_policy option in agent configuration) + +Down Policy: extend-cache + Description: Defines what to do if this Token's information cannot be read from the primary_datacenter (refer to down_policy option in agent configuration) +