diff --git a/api/secret.go b/api/secret.go index 4675f4ac6..b6517c44a 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 93d0dd512..55970ec36 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 ff5fd4563..af087eb6e 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 c4c55e00e..3da63c468 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 d9718ffc8..842d695a6 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 4008668c8..e6ec3da29 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 59975b73f..68f856f4b 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 433530194..2bd816bb3 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 51075ae3e..7d42f0dd4 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 90c0514a7..f8c292432 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 cfa2d928b..a8865dd7c 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 7a119dce1..821d534ef 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 b21489c3f..74b5ebfd8 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