From 765fe529d66db62d7d2503365e6ec96bb625d529 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 14 Jun 2018 09:49:33 -0400 Subject: [PATCH] Changes the way policies are reported in audit logs (#4747) * This changes the way policies are reported in audit logs. Previously, only policies tied to tokens would be reported. This could make it difficult to perform after-the-fact analysis based on both the initial response entry and further requests. Now, the full set of applicable policies from both the token and any derived policies from Identity are reported. To keep things consistent, token authentications now also return the full set of policies in api.Secret.Auth responses, so this both makes it easier for users to understand their actual full set, and it matches what the audit logs now report. --- api/secret.go | 91 ++++++++-- audit/format.go | 64 ++++--- builtin/credential/token/cli.go | 13 +- command/format.go | 4 +- command/util.go | 4 + http/logical_test.go | 1 + logical/auth.go | 5 + logical/translate_response.go | 44 +++-- vault/core.go | 9 +- vault/ha.go | 9 +- vault/identity_store_entities_ext_test.go | 203 ++++++++++++++++++++++ vault/logical_system.go | 6 +- vault/request_handling.go | 82 ++++++--- 13 files changed, 426 insertions(+), 109 deletions(-) diff --git a/api/secret.go b/api/secret.go index 4675f4ac67..b6517c44a9 100644 --- a/api/secret.go +++ b/api/secret.go @@ -101,7 +101,8 @@ func (s *Secret) TokenRemainingUses() (int, error) { } // TokenPolicies returns the standardized list of policies for the given secret. -// If the secret is nil or does not contain any policies, this returns nil. +// If the secret is nil or does not contain any policies, this returns nil. It +// also populates the secret's Auth info with identity/token policy info. func (s *Secret) TokenPolicies() ([]string, error) { if s == nil { return nil, nil @@ -115,25 +116,75 @@ func (s *Secret) TokenPolicies() ([]string, error) { return nil, nil } - sList, ok := s.Data["policies"].([]string) - if ok { - return sList, nil - } + var tokenPolicies []string - list, ok := s.Data["policies"].([]interface{}) - if !ok { - return nil, fmt.Errorf("unable to convert token policies to expected format") - } - - policies := make([]string, len(list)) - for i := range list { - p, ok := list[i].(string) + // Token policies + { + _, ok := s.Data["policies"] if !ok { - return nil, fmt.Errorf("unable to convert policy %v to string", list[i]) + goto TOKEN_DONE + } + + sList, ok := s.Data["policies"].([]string) + if ok { + tokenPolicies = sList + goto TOKEN_DONE + } + + list, ok := s.Data["policies"].([]interface{}) + if !ok { + return nil, fmt.Errorf("unable to convert token policies to expected format") + } + for _, v := range list { + p, ok := v.(string) + if !ok { + return nil, fmt.Errorf("unable to convert policy %v to string", v) + } + tokenPolicies = append(tokenPolicies, p) } - policies[i] = p } +TOKEN_DONE: + var identityPolicies []string + + // Identity policies + { + _, ok := s.Data["identity_policies"] + if !ok { + goto DONE + } + + sList, ok := s.Data["identity_policies"].([]string) + if ok { + identityPolicies = sList + goto DONE + } + + list, ok := s.Data["identity_policies"].([]interface{}) + if !ok { + return nil, fmt.Errorf("unable to convert identity policies to expected format") + } + for _, v := range list { + p, ok := v.(string) + if !ok { + return nil, fmt.Errorf("unable to convert policy %v to string", v) + } + identityPolicies = append(identityPolicies, p) + } + } + +DONE: + + if s.Auth == nil { + s.Auth = &SecretAuth{} + } + + policies := append(tokenPolicies, identityPolicies...) + + s.Auth.TokenPolicies = tokenPolicies + s.Auth.IdentityPolicies = identityPolicies + s.Auth.Policies = policies + return policies, nil } @@ -234,10 +285,12 @@ type SecretWrapInfo struct { // SecretAuth is the structure containing auth information if we have it. type SecretAuth struct { - ClientToken string `json:"client_token"` - Accessor string `json:"accessor"` - Policies []string `json:"policies"` - Metadata map[string]string `json:"metadata"` + ClientToken string `json:"client_token"` + Accessor string `json:"accessor"` + Policies []string `json:"policies"` + TokenPolicies []string `json:"token_policies"` + IdentityPolicies []string `json:"identity_policies"` + Metadata map[string]string `json:"metadata"` LeaseDuration int `json:"lease_duration"` Renewable bool `json:"renewable"` diff --git a/audit/format.go b/audit/format.go index 93d0dd512f..55970ec360 100644 --- a/audit/format.go +++ b/audit/format.go @@ -118,13 +118,15 @@ func (f *AuditFormatter) FormatRequest(ctx context.Context, w io.Writer, config Error: errString, Auth: AuditAuth{ - ClientToken: auth.ClientToken, - Accessor: auth.Accessor, - DisplayName: auth.DisplayName, - Policies: auth.Policies, - Metadata: auth.Metadata, - EntityID: auth.EntityID, - RemainingUses: req.ClientTokenRemainingUses, + ClientToken: auth.ClientToken, + Accessor: auth.Accessor, + DisplayName: auth.DisplayName, + Policies: auth.Policies, + TokenPolicies: auth.TokenPolicies, + IdentityPolicies: auth.IdentityPolicies, + Metadata: auth.Metadata, + EntityID: auth.EntityID, + RemainingUses: req.ClientTokenRemainingUses, }, Request: AuditRequest{ @@ -277,12 +279,14 @@ func (f *AuditFormatter) FormatResponse(ctx context.Context, w io.Writer, config var respAuth *AuditAuth if resp.Auth != nil { respAuth = &AuditAuth{ - ClientToken: resp.Auth.ClientToken, - Accessor: resp.Auth.Accessor, - DisplayName: resp.Auth.DisplayName, - Policies: resp.Auth.Policies, - Metadata: resp.Auth.Metadata, - NumUses: resp.Auth.NumUses, + ClientToken: resp.Auth.ClientToken, + Accessor: resp.Auth.Accessor, + DisplayName: resp.Auth.DisplayName, + Policies: resp.Auth.Policies, + TokenPolicies: resp.Auth.TokenPolicies, + IdentityPolicies: resp.Auth.IdentityPolicies, + Metadata: resp.Auth.Metadata, + NumUses: resp.Auth.NumUses, } } @@ -313,13 +317,15 @@ func (f *AuditFormatter) FormatResponse(ctx context.Context, w io.Writer, config Type: "response", Error: errString, Auth: AuditAuth{ - DisplayName: auth.DisplayName, - Policies: auth.Policies, - Metadata: auth.Metadata, - ClientToken: auth.ClientToken, - Accessor: auth.Accessor, - RemainingUses: req.ClientTokenRemainingUses, - EntityID: auth.EntityID, + DisplayName: auth.DisplayName, + Policies: auth.Policies, + TokenPolicies: auth.TokenPolicies, + IdentityPolicies: auth.IdentityPolicies, + Metadata: auth.Metadata, + ClientToken: auth.ClientToken, + Accessor: auth.Accessor, + RemainingUses: req.ClientTokenRemainingUses, + EntityID: auth.EntityID, }, Request: AuditRequest{ @@ -397,14 +403,16 @@ type AuditResponse struct { } type AuditAuth struct { - ClientToken string `json:"client_token"` - Accessor string `json:"accessor"` - DisplayName string `json:"display_name"` - Policies []string `json:"policies"` - Metadata map[string]string `json:"metadata"` - NumUses int `json:"num_uses,omitempty"` - RemainingUses int `json:"remaining_uses,omitempty"` - EntityID string `json:"entity_id"` + ClientToken string `json:"client_token"` + Accessor string `json:"accessor"` + DisplayName string `json:"display_name"` + Policies []string `json:"policies"` + TokenPolicies []string `json:"token_policies,omitempty"` + IdentityPolicies []string `json:"identity_policies,omitempty"` + Metadata map[string]string `json:"metadata"` + NumUses int `json:"num_uses,omitempty"` + RemainingUses int `json:"remaining_uses,omitempty"` + EntityID string `json:"entity_id"` } type AuditSecret struct { diff --git a/builtin/credential/token/cli.go b/builtin/credential/token/cli.go index ff5fd4563f..af087eb6ea 100644 --- a/builtin/credential/token/cli.go +++ b/builtin/credential/token/cli.go @@ -104,7 +104,8 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro if err != nil { return nil, errwrap.Wrapf("error accessing token accessor: {{err}}", err) } - policies, err := secret.TokenPolicies() + // This populates secret.Auth + _, err = secret.TokenPolicies() if err != nil { return nil, errwrap.Wrapf("error accessing token policies: {{err}}", err) } @@ -122,10 +123,12 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro } return &api.Secret{ Auth: &api.SecretAuth{ - ClientToken: id, - Accessor: accessor, - Policies: policies, - Metadata: metadata, + ClientToken: id, + Accessor: accessor, + Policies: secret.Auth.Policies, + TokenPolicies: secret.Auth.TokenPolicies, + IdentityPolicies: secret.Auth.IdentityPolicies, + Metadata: metadata, LeaseDuration: int(dur.Seconds()), Renewable: renewable, diff --git a/command/format.go b/command/format.go index c4c55e00e9..3da63c4685 100644 --- a/command/format.go +++ b/command/format.go @@ -218,7 +218,9 @@ func (t TableFormatter) OutputSecret(ui cli.Ui, secret *api.Secret) error { out = append(out, fmt.Sprintf("token_duration %s %s", hopeDelim, humanDurationInt(secret.Auth.LeaseDuration))) } out = append(out, fmt.Sprintf("token_renewable %s %t", hopeDelim, secret.Auth.Renewable)) - out = append(out, fmt.Sprintf("token_policies %s %v", hopeDelim, secret.Auth.Policies)) + out = append(out, fmt.Sprintf("token_policies %s %v", hopeDelim, secret.Auth.TokenPolicies)) + out = append(out, fmt.Sprintf("identity_policies %s %v", hopeDelim, secret.Auth.IdentityPolicies)) + out = append(out, fmt.Sprintf("policies %s %v", hopeDelim, secret.Auth.Policies)) for k, v := range secret.Auth.Metadata { out = append(out, fmt.Sprintf("token_meta_%s %s %v", k, hopeDelim, v)) } diff --git a/command/util.go b/command/util.go index d9718ffc87..842d695a6d 100644 --- a/command/util.go +++ b/command/util.go @@ -34,6 +34,10 @@ func RawField(secret *api.Secret, field string) interface{} { case "token_renewable": val = secret.Auth.Renewable case "token_policies": + val = secret.Auth.TokenPolicies + case "identity_policies": + val = secret.Auth.IdentityPolicies + case "policies": val = secret.Auth.Policies default: val = secret.Data[field] diff --git a/http/logical_test.go b/http/logical_test.go index 4008668c8a..e6ec3da293 100644 --- a/http/logical_test.go +++ b/http/logical_test.go @@ -207,6 +207,7 @@ func TestLogical_CreateToken(t *testing.T) { "wrap_info": nil, "auth": map[string]interface{}{ "policies": []interface{}{"root"}, + "token_policies": []interface{}{"root"}, "metadata": nil, "lease_duration": json.Number("0"), "renewable": false, diff --git a/logical/auth.go b/logical/auth.go index 59975b73f0..68f856f4be 100644 --- a/logical/auth.go +++ b/logical/auth.go @@ -29,6 +29,11 @@ type Auth struct { // is associated with. Policies []string `json:"policies" mapstructure:"policies" structs:"policies"` + // TokenPolicies and IdentityPolicies break down the list in Policies to + // help determine where a policy was sourced + TokenPolicies []string `json:"token_policies" mapstructure:"token_policies" structs:"token_policies"` + IdentityPolicies []string `json:"identity_policies" mapstructure:"identity_policies" structs:"identity_policies"` + // Metadata is used to attach arbitrary string-type metadata to // an authenticated user. This metadata will be outputted into the // audit log. diff --git a/logical/translate_response.go b/logical/translate_response.go index 433530194b..2bd816bb3f 100644 --- a/logical/translate_response.go +++ b/logical/translate_response.go @@ -27,13 +27,15 @@ func LogicalResponseToHTTPResponse(input *Response) *HTTPResponse { // set up the result structure. if input.Auth != nil { httpResp.Auth = &HTTPAuth{ - ClientToken: input.Auth.ClientToken, - Accessor: input.Auth.Accessor, - Policies: input.Auth.Policies, - Metadata: input.Auth.Metadata, - LeaseDuration: int(input.Auth.TTL.Seconds()), - Renewable: input.Auth.Renewable, - EntityID: input.Auth.EntityID, + ClientToken: input.Auth.ClientToken, + Accessor: input.Auth.Accessor, + Policies: input.Auth.Policies, + TokenPolicies: input.Auth.TokenPolicies, + IdentityPolicies: input.Auth.IdentityPolicies, + Metadata: input.Auth.Metadata, + LeaseDuration: int(input.Auth.TTL.Seconds()), + Renewable: input.Auth.Renewable, + EntityID: input.Auth.EntityID, } } @@ -56,11 +58,13 @@ func HTTPResponseToLogicalResponse(input *HTTPResponse) *Response { if input.Auth != nil { logicalResp.Auth = &Auth{ - ClientToken: input.Auth.ClientToken, - Accessor: input.Auth.Accessor, - Policies: input.Auth.Policies, - Metadata: input.Auth.Metadata, - EntityID: input.Auth.EntityID, + ClientToken: input.Auth.ClientToken, + Accessor: input.Auth.Accessor, + Policies: input.Auth.Policies, + TokenPolicies: input.Auth.TokenPolicies, + IdentityPolicies: input.Auth.IdentityPolicies, + Metadata: input.Auth.Metadata, + EntityID: input.Auth.EntityID, } logicalResp.Auth.Renewable = input.Auth.Renewable logicalResp.Auth.TTL = time.Second * time.Duration(input.Auth.LeaseDuration) @@ -81,13 +85,15 @@ type HTTPResponse struct { } type HTTPAuth struct { - ClientToken string `json:"client_token"` - Accessor string `json:"accessor"` - Policies []string `json:"policies"` - Metadata map[string]string `json:"metadata"` - LeaseDuration int `json:"lease_duration"` - Renewable bool `json:"renewable"` - EntityID string `json:"entity_id"` + ClientToken string `json:"client_token"` + Accessor string `json:"accessor"` + Policies []string `json:"policies"` + TokenPolicies []string `json:"token_policies,omitempty"` + IdentityPolicies []string `json:"identity_policies,omitempty"` + Metadata map[string]string `json:"metadata"` + LeaseDuration int `json:"lease_duration"` + Renewable bool `json:"renewable"` + EntityID string `json:"entity_id"` } type HTTPWrapInfo struct { diff --git a/vault/core.go b/vault/core.go index 51075ae3e9..7d42f0dd43 100644 --- a/vault/core.go +++ b/vault/core.go @@ -1004,7 +1004,7 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr } // Validate the token is a root token - acl, te, entity, err := c.fetchACLTokenEntryAndEntity(req) + acl, te, entity, identityPolicies, err := c.fetchACLTokenEntryAndEntity(req) if err != nil { retErr = multierror.Append(retErr, err) c.stateLock.RUnlock() @@ -1013,10 +1013,13 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr // Audit-log the request before going any further auth := &logical.Auth{ - ClientToken: req.ClientToken, + ClientToken: req.ClientToken, + Policies: identityPolicies, + IdentityPolicies: identityPolicies, } if te != nil { - auth.Policies = te.Policies + auth.TokenPolicies = te.Policies + auth.Policies = append(te.Policies, identityPolicies...) auth.Metadata = te.Meta auth.DisplayName = te.DisplayName auth.EntityID = te.EntityID diff --git a/vault/ha.go b/vault/ha.go index 90c0514a7d..f8c2924329 100644 --- a/vault/ha.go +++ b/vault/ha.go @@ -161,7 +161,7 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) { ctx := c.activeContext - acl, te, entity, err := c.fetchACLTokenEntryAndEntity(req) + acl, te, entity, identityPolicies, err := c.fetchACLTokenEntryAndEntity(req) if err != nil { retErr = multierror.Append(retErr, err) return retErr @@ -169,10 +169,13 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) { // Audit-log the request before going any further auth := &logical.Auth{ - ClientToken: req.ClientToken, + ClientToken: req.ClientToken, + Policies: identityPolicies, + IdentityPolicies: identityPolicies, } if te != nil { - auth.Policies = te.Policies + auth.TokenPolicies = te.Policies + auth.Policies = append(te.Policies, identityPolicies...) auth.Metadata = te.Meta auth.DisplayName = te.DisplayName auth.EntityID = te.EntityID diff --git a/vault/identity_store_entities_ext_test.go b/vault/identity_store_entities_ext_test.go index cfa2d928b2..a8865dd7c5 100644 --- a/vault/identity_store_entities_ext_test.go +++ b/vault/identity_store_entities_ext_test.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/vault/api" "github.com/hashicorp/vault/builtin/credential/approle" + "github.com/hashicorp/vault/helper/strutil" vaulthttp "github.com/hashicorp/vault/http" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/vault" @@ -174,3 +175,205 @@ func TestIdentityStore_EntityDisabled(t *testing.T) { t.Fatal("expected a client token") } } + +func TestIdentityStore_EntityPoliciesInInitialAuth(t *testing.T) { + // Use a TestCluster and the approle backend to get a token and entity for testing + coreConfig := &vault.CoreConfig{ + CredentialBackends: map[string]logical.Factory{ + "approle": approle.Factory, + }, + } + cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ + HandlerFunc: vaulthttp.Handler, + }) + cluster.Start() + defer cluster.Cleanup() + + core := cluster.Cores[0].Core + vault.TestWaitActive(t, core) + client := cluster.Cores[0].Client + + // Mount the auth backend + err := client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{ + Type: "approle", + }) + if err != nil { + t.Fatal(err) + } + + // Tune the mount + err = client.Sys().TuneMount("auth/approle", api.MountConfigInput{ + DefaultLeaseTTL: "5m", + MaxLeaseTTL: "5m", + }) + if err != nil { + t.Fatal(err) + } + + // Create role + resp, err := client.Logical().Write("auth/approle/role/role-period", map[string]interface{}{ + "period": "5m", + }) + if err != nil { + t.Fatal(err) + } + + // Get role_id + resp, err = client.Logical().Read("auth/approle/role/role-period/role-id") + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected a response for fetching the role-id") + } + roleID := resp.Data["role_id"] + + // Get secret_id + resp, err = client.Logical().Write("auth/approle/role/role-period/secret-id", map[string]interface{}{}) + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected a response for fetching the secret-id") + } + secretID := resp.Data["secret_id"] + + // Login + resp, err = client.Logical().Write("auth/approle/login", map[string]interface{}{ + "role_id": roleID, + "secret_id": secretID, + }) + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected a response for login") + } + if resp.Auth == nil { + t.Fatal("expected auth object from response") + } + if resp.Auth.ClientToken == "" { + t.Fatal("expected a client token") + } + if !strutil.EquivalentSlices(resp.Auth.TokenPolicies, []string{"default"}) { + t.Fatalf("policy mismatch, got token policies: %v", resp.Auth.TokenPolicies) + } + if len(resp.Auth.IdentityPolicies) > 0 { + t.Fatalf("policy mismatch, got identity policies: %v", resp.Auth.IdentityPolicies) + } + if !strutil.EquivalentSlices(resp.Auth.Policies, []string{"default"}) { + t.Fatalf("policy mismatch, got policies: %v", resp.Auth.Policies) + } + + // Check policies + client.SetToken(resp.Auth.ClientToken) + resp, err = client.Auth().Token().LookupSelf() + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected a response for token lookup") + } + entityIDRaw, ok := resp.Data["entity_id"] + if !ok { + t.Fatal("expected an entity ID") + } + entityID, ok := entityIDRaw.(string) + if !ok { + t.Fatal("entity_id not a string") + } + policiesRaw := resp.Data["policies"] + if policiesRaw == nil { + t.Fatal("expected policies, got nil") + } + var policies []string + for _, v := range policiesRaw.([]interface{}) { + policies = append(policies, v.(string)) + } + policiesRaw = resp.Data["identity_policies"] + if policiesRaw != nil { + t.Fatalf("expected nil policies, got %#v", policiesRaw) + } + if !strutil.EquivalentSlices(policies, []string{"default"}) { + t.Fatalf("policy mismatch, got policies: %v", resp.Auth.Policies) + } + + // Write more policies into the entity + client.SetToken(cluster.RootToken) + resp, err = client.Logical().Write("identity/entity/id/"+entityID, map[string]interface{}{ + "policies": []string{"foo", "bar"}, + }) + if err != nil { + t.Fatal(err) + } + + // Reauthenticate to get a token with updated policies + client.SetToken("") + resp, err = client.Logical().Write("auth/approle/login", map[string]interface{}{ + "role_id": roleID, + "secret_id": secretID, + }) + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected a response for login") + } + if resp.Auth == nil { + t.Fatal("expected auth object from response") + } + if resp.Auth.ClientToken == "" { + t.Fatal("expected a client token") + } + if !strutil.EquivalentSlices(resp.Auth.TokenPolicies, []string{"default"}) { + t.Fatalf("policy mismatch, got token policies: %v", resp.Auth.TokenPolicies) + } + if !strutil.EquivalentSlices(resp.Auth.IdentityPolicies, []string{"foo", "bar"}) { + t.Fatalf("policy mismatch, got identity policies: %v", resp.Auth.IdentityPolicies) + } + if !strutil.EquivalentSlices(resp.Auth.Policies, []string{"default", "foo", "bar"}) { + t.Fatalf("policy mismatch, got policies: %v", resp.Auth.Policies) + } + + // Validate the policies on lookup again -- this ensures that the right + // policies were encoded on the token but all were looked up successfully + client.SetToken(resp.Auth.ClientToken) + resp, err = client.Auth().Token().LookupSelf() + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("expected a response for token lookup") + } + entityIDRaw, ok = resp.Data["entity_id"] + if !ok { + t.Fatal("expected an entity ID") + } + entityID, ok = entityIDRaw.(string) + if !ok { + t.Fatal("entity_id not a string") + } + policies = nil + policiesRaw = resp.Data["policies"] + if policiesRaw == nil { + t.Fatal("expected policies, got nil") + } + for _, v := range policiesRaw.([]interface{}) { + policies = append(policies, v.(string)) + } + if !strutil.EquivalentSlices(policies, []string{"default"}) { + t.Fatalf("policy mismatch, got policies: %v", policies) + } + policies = nil + policiesRaw = resp.Data["identity_policies"] + if policiesRaw == nil { + t.Fatal("expected policies, got nil") + } + for _, v := range policiesRaw.([]interface{}) { + policies = append(policies, v.(string)) + } + if !strutil.EquivalentSlices(policies, []string{"foo", "bar"}) { + t.Fatalf("policy mismatch, got policies: %v", policies) + } + +} diff --git a/vault/logical_system.go b/vault/logical_system.go index 7a119dce1b..821d534ef4 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -3472,7 +3472,7 @@ func (b *SystemBackend) pathInternalUIMountsRead(ctx context.Context, req *logic var entity *identity.Entity // Load the ACL policies so we can walk the prefix for this mount - acl, _, entity, err = b.Core.fetchACLTokenEntryAndEntity(req) + acl, _, entity, _, err = b.Core.fetchACLTokenEntryAndEntity(req) if err != nil { return nil, err } @@ -3553,7 +3553,7 @@ func (b *SystemBackend) pathInternalUIMountRead(ctx context.Context, req *logica resp.Data["path"] = me.Path // Load the ACL policies so we can walk the prefix for this mount - acl, _, entity, err := b.Core.fetchACLTokenEntryAndEntity(req) + acl, _, entity, _, err := b.Core.fetchACLTokenEntryAndEntity(req) if err != nil { return nil, err } @@ -3574,7 +3574,7 @@ func (b *SystemBackend) pathInternalUIResultantACL(ctx context.Context, req *log return nil, nil } - acl, _, entity, err := b.Core.fetchACLTokenEntryAndEntity(req) + acl, _, entity, _, err := b.Core.fetchACLTokenEntryAndEntity(req) if err != nil { return nil, err } diff --git a/vault/request_handling.go b/vault/request_handling.go index b21489c3f3..74b5ebfd80 100644 --- a/vault/request_handling.go +++ b/vault/request_handling.go @@ -77,17 +77,17 @@ func (c *Core) fetchEntityAndDerivedPolicies(entityID string) (*identity.Entity, return entity, policies, err } -func (c *Core) fetchACLTokenEntryAndEntity(req *logical.Request) (*ACL, *logical.TokenEntry, *identity.Entity, error) { +func (c *Core) fetchACLTokenEntryAndEntity(req *logical.Request) (*ACL, *logical.TokenEntry, *identity.Entity, []string, error) { defer metrics.MeasureSince([]string{"core", "fetch_acl_and_token"}, time.Now()) // Ensure there is a client token if req.ClientToken == "" { - return nil, nil, nil, fmt.Errorf("missing client token") + return nil, nil, nil, nil, fmt.Errorf("missing client token") } if c.tokenStore == nil { c.logger.Error("token store is unavailable") - return nil, nil, nil, ErrInternalError + return nil, nil, nil, nil, ErrInternalError } // Resolve the token policy @@ -98,7 +98,7 @@ func (c *Core) fetchACLTokenEntryAndEntity(req *logical.Request) (*ACL, *logical te, err = c.tokenStore.Lookup(c.activeContext, req.ClientToken) if err != nil { c.logger.Error("failed to lookup token", "error", err) - return nil, nil, nil, ErrInternalError + return nil, nil, nil, nil, ErrInternalError } default: te = req.TokenEntry() @@ -106,7 +106,7 @@ func (c *Core) fetchACLTokenEntryAndEntity(req *logical.Request) (*ACL, *logical // Ensure the token is valid if te == nil { - return nil, nil, nil, logical.ErrPermissionDenied + return nil, nil, nil, nil, logical.ErrPermissionDenied } // CIDR checks bind all tokens except non-expiring root tokens @@ -117,7 +117,7 @@ func (c *Core) fetchACLTokenEntryAndEntity(req *logical.Request) (*ACL, *logical if c.Logger().IsDebug() { c.Logger().Debug("could not parse remote addr into sockaddr", "error", err, "remote_addr", req.Connection.RemoteAddr) } - return nil, nil, nil, logical.ErrPermissionDenied + return nil, nil, nil, nil, logical.ErrPermissionDenied } for _, cidr := range te.BoundCIDRs { if cidr.Contains(remoteSockAddr) { @@ -126,27 +126,25 @@ func (c *Core) fetchACLTokenEntryAndEntity(req *logical.Request) (*ACL, *logical } } if !valid { - return nil, nil, nil, logical.ErrPermissionDenied + return nil, nil, nil, nil, logical.ErrPermissionDenied } } - tokenPolicies := te.Policies - - entity, derivedPolicies, err := c.fetchEntityAndDerivedPolicies(te.EntityID) + entity, identityPolicies, err := c.fetchEntityAndDerivedPolicies(te.EntityID) if err != nil { - return nil, nil, nil, ErrInternalError + return nil, nil, nil, nil, ErrInternalError } - tokenPolicies = append(tokenPolicies, derivedPolicies...) + allPolicies := append(te.Policies, identityPolicies...) // Construct the corresponding ACL object - acl, err := c.policyStore.ACL(c.activeContext, tokenPolicies...) + acl, err := c.policyStore.ACL(c.activeContext, allPolicies...) if err != nil { c.logger.Error("failed to construct ACL", "error", err) - return nil, nil, nil, ErrInternalError + return nil, nil, nil, nil, ErrInternalError } - return acl, te, entity, nil + return acl, te, entity, identityPolicies, nil } func (c *Core) checkToken(ctx context.Context, req *logical.Request, unauth bool) (*logical.Auth, *logical.TokenEntry, error) { @@ -155,13 +153,14 @@ func (c *Core) checkToken(ctx context.Context, req *logical.Request, unauth bool var acl *ACL var te *logical.TokenEntry var entity *identity.Entity + var identityPolicies []string var err error // Even if unauth, if a token is provided, there's little reason not to // gather as much info as possible for the audit log and to e.g. control // trace mode for EGPs. if !unauth || (unauth && req.ClientToken != "") { - acl, te, entity, err = c.fetchACLTokenEntryAndEntity(req) + acl, te, entity, identityPolicies, err = c.fetchACLTokenEntryAndEntity(req) // In the unauth case we don't want to fail the command, since it's // unauth, we just have no information to attach to the request, so // ignore errors...this was best-effort anyways @@ -219,12 +218,15 @@ func (c *Core) checkToken(ctx context.Context, req *logical.Request, unauth bool } // Create the auth response auth := &logical.Auth{ - ClientToken: req.ClientToken, - Accessor: req.ClientTokenAccessor, + ClientToken: req.ClientToken, + Accessor: req.ClientTokenAccessor, + Policies: identityPolicies, + IdentityPolicies: identityPolicies, } if te != nil { - auth.Policies = te.Policies + auth.TokenPolicies = te.Policies + auth.Policies = append(te.Policies, identityPolicies...) auth.Metadata = te.Meta auth.DisplayName = te.DisplayName auth.EntityID = te.EntityID @@ -626,6 +628,16 @@ func (c *Core) handleRequest(ctx context.Context, req *logical.Request) (retResp return nil, auth, retErr } + _, identityPolicies, err := c.fetchEntityAndDerivedPolicies(resp.Auth.EntityID) + if err != nil { + c.tokenStore.revokeOrphan(ctx, te.ID) + return nil, nil, ErrInternalError + } + + resp.Auth.TokenPolicies = policyutil.SanitizePolicies(resp.Auth.Policies, policyutil.DoNotAddDefaultPolicy) + resp.Auth.IdentityPolicies = policyutil.SanitizePolicies(identityPolicies, policyutil.DoNotAddDefaultPolicy) + resp.Auth.Policies = policyutil.SanitizePolicies(append(resp.Auth.Policies, identityPolicies...), policyutil.DoNotAddDefaultPolicy) + if err := c.expiration.RegisterAuth(resp.Auth.CreationPath, resp.Auth); err != nil { c.tokenStore.revokeOrphan(ctx, te.ID) c.logger.Error("failed to register token lease", "request_path", req.Path, "error", err) @@ -772,10 +784,6 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re } } - if strutil.StrListSubset(auth.Policies, []string{"root"}) { - return logical.ErrorResponse("auth methods cannot create root tokens"), nil, logical.ErrInvalidRequest - } - // Determine the source of the login source := c.router.MatchingMount(req.Path) source = strings.TrimPrefix(source, credentialRoutePrefix) @@ -798,10 +806,15 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re resp.AddWarning(warning) } + // We first assign token policies to what was returned from the backend + // via auth.Policies. Then, we get the full set of policies into + // auth.Policies from the backend + entity information -- this is not + // stored in the token, but we perform sanity checks on it and return + // that information to the user. + // Generate a token te := logical.TokenEntry{ Path: req.Path, - Policies: auth.Policies, Meta: auth.Metadata, DisplayName: auth.DisplayName, CreationTime: time.Now().Unix(), @@ -811,10 +824,24 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re BoundCIDRs: auth.BoundCIDRs, } - te.Policies = policyutil.SanitizePolicies(te.Policies, true) + te.Policies = policyutil.SanitizePolicies(auth.Policies, policyutil.AddDefaultPolicy) - // Prevent internal policies from being assigned to tokens - for _, policy := range te.Policies { + _, identityPolicies, err := c.fetchEntityAndDerivedPolicies(auth.EntityID) + if err != nil { + return nil, nil, ErrInternalError + } + + auth.TokenPolicies = te.Policies + auth.IdentityPolicies = policyutil.SanitizePolicies(identityPolicies, policyutil.DoNotAddDefaultPolicy) + auth.Policies = policyutil.SanitizePolicies(append(te.Policies, identityPolicies...), policyutil.DoNotAddDefaultPolicy) + + // Prevent internal policies from being assigned to tokens. We check + // this on auth.Policies including derived ones from Identity before + // actually making the token. + for _, policy := range auth.Policies { + if policy == "root" { + return logical.ErrorResponse("auth methods cannot create root tokens"), nil, logical.ErrInvalidRequest + } if strutil.StrListContains(nonAssignablePolicies, policy) { return logical.ErrorResponse(fmt.Sprintf("cannot assign policy %q", policy)), nil, logical.ErrInvalidRequest } @@ -828,7 +855,6 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re // Populate the client token, accessor, and TTL auth.ClientToken = te.ID auth.Accessor = te.Accessor - auth.Policies = te.Policies auth.TTL = te.TTL // Register with the expiration manager