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.
This commit is contained in:
Jeff Mitchell
2018-06-14 09:49:33 -04:00
committed by GitHub
parent 050ab805a7
commit 765fe529d6
13 changed files with 426 additions and 109 deletions

View File

@@ -101,7 +101,8 @@ func (s *Secret) TokenRemainingUses() (int, error) {
} }
// TokenPolicies returns the standardized list of policies for the given secret. // 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) { func (s *Secret) TokenPolicies() ([]string, error) {
if s == nil { if s == nil {
return nil, nil return nil, nil
@@ -115,25 +116,75 @@ func (s *Secret) TokenPolicies() ([]string, error) {
return nil, nil return nil, nil
} }
sList, ok := s.Data["policies"].([]string) var tokenPolicies []string
if ok {
return sList, nil
}
list, ok := s.Data["policies"].([]interface{}) // Token policies
if !ok { {
return nil, fmt.Errorf("unable to convert token policies to expected format") _, ok := s.Data["policies"]
}
policies := make([]string, len(list))
for i := range list {
p, ok := list[i].(string)
if !ok { 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 return policies, nil
} }
@@ -234,10 +285,12 @@ type SecretWrapInfo struct {
// SecretAuth is the structure containing auth information if we have it. // SecretAuth is the structure containing auth information if we have it.
type SecretAuth struct { type SecretAuth struct {
ClientToken string `json:"client_token"` ClientToken string `json:"client_token"`
Accessor string `json:"accessor"` Accessor string `json:"accessor"`
Policies []string `json:"policies"` Policies []string `json:"policies"`
Metadata map[string]string `json:"metadata"` TokenPolicies []string `json:"token_policies"`
IdentityPolicies []string `json:"identity_policies"`
Metadata map[string]string `json:"metadata"`
LeaseDuration int `json:"lease_duration"` LeaseDuration int `json:"lease_duration"`
Renewable bool `json:"renewable"` Renewable bool `json:"renewable"`

View File

@@ -118,13 +118,15 @@ func (f *AuditFormatter) FormatRequest(ctx context.Context, w io.Writer, config
Error: errString, Error: errString,
Auth: AuditAuth{ Auth: AuditAuth{
ClientToken: auth.ClientToken, ClientToken: auth.ClientToken,
Accessor: auth.Accessor, Accessor: auth.Accessor,
DisplayName: auth.DisplayName, DisplayName: auth.DisplayName,
Policies: auth.Policies, Policies: auth.Policies,
Metadata: auth.Metadata, TokenPolicies: auth.TokenPolicies,
EntityID: auth.EntityID, IdentityPolicies: auth.IdentityPolicies,
RemainingUses: req.ClientTokenRemainingUses, Metadata: auth.Metadata,
EntityID: auth.EntityID,
RemainingUses: req.ClientTokenRemainingUses,
}, },
Request: AuditRequest{ Request: AuditRequest{
@@ -277,12 +279,14 @@ func (f *AuditFormatter) FormatResponse(ctx context.Context, w io.Writer, config
var respAuth *AuditAuth var respAuth *AuditAuth
if resp.Auth != nil { if resp.Auth != nil {
respAuth = &AuditAuth{ respAuth = &AuditAuth{
ClientToken: resp.Auth.ClientToken, ClientToken: resp.Auth.ClientToken,
Accessor: resp.Auth.Accessor, Accessor: resp.Auth.Accessor,
DisplayName: resp.Auth.DisplayName, DisplayName: resp.Auth.DisplayName,
Policies: resp.Auth.Policies, Policies: resp.Auth.Policies,
Metadata: resp.Auth.Metadata, TokenPolicies: resp.Auth.TokenPolicies,
NumUses: resp.Auth.NumUses, 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", Type: "response",
Error: errString, Error: errString,
Auth: AuditAuth{ Auth: AuditAuth{
DisplayName: auth.DisplayName, DisplayName: auth.DisplayName,
Policies: auth.Policies, Policies: auth.Policies,
Metadata: auth.Metadata, TokenPolicies: auth.TokenPolicies,
ClientToken: auth.ClientToken, IdentityPolicies: auth.IdentityPolicies,
Accessor: auth.Accessor, Metadata: auth.Metadata,
RemainingUses: req.ClientTokenRemainingUses, ClientToken: auth.ClientToken,
EntityID: auth.EntityID, Accessor: auth.Accessor,
RemainingUses: req.ClientTokenRemainingUses,
EntityID: auth.EntityID,
}, },
Request: AuditRequest{ Request: AuditRequest{
@@ -397,14 +403,16 @@ type AuditResponse struct {
} }
type AuditAuth struct { type AuditAuth struct {
ClientToken string `json:"client_token"` ClientToken string `json:"client_token"`
Accessor string `json:"accessor"` Accessor string `json:"accessor"`
DisplayName string `json:"display_name"` DisplayName string `json:"display_name"`
Policies []string `json:"policies"` Policies []string `json:"policies"`
Metadata map[string]string `json:"metadata"` TokenPolicies []string `json:"token_policies,omitempty"`
NumUses int `json:"num_uses,omitempty"` IdentityPolicies []string `json:"identity_policies,omitempty"`
RemainingUses int `json:"remaining_uses,omitempty"` Metadata map[string]string `json:"metadata"`
EntityID string `json:"entity_id"` NumUses int `json:"num_uses,omitempty"`
RemainingUses int `json:"remaining_uses,omitempty"`
EntityID string `json:"entity_id"`
} }
type AuditSecret struct { type AuditSecret struct {

View File

@@ -104,7 +104,8 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro
if err != nil { if err != nil {
return nil, errwrap.Wrapf("error accessing token accessor: {{err}}", err) return nil, errwrap.Wrapf("error accessing token accessor: {{err}}", err)
} }
policies, err := secret.TokenPolicies() // This populates secret.Auth
_, err = secret.TokenPolicies()
if err != nil { if err != nil {
return nil, errwrap.Wrapf("error accessing token policies: {{err}}", err) 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{ return &api.Secret{
Auth: &api.SecretAuth{ Auth: &api.SecretAuth{
ClientToken: id, ClientToken: id,
Accessor: accessor, Accessor: accessor,
Policies: policies, Policies: secret.Auth.Policies,
Metadata: metadata, TokenPolicies: secret.Auth.TokenPolicies,
IdentityPolicies: secret.Auth.IdentityPolicies,
Metadata: metadata,
LeaseDuration: int(dur.Seconds()), LeaseDuration: int(dur.Seconds()),
Renewable: renewable, Renewable: renewable,

View File

@@ -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_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_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 { for k, v := range secret.Auth.Metadata {
out = append(out, fmt.Sprintf("token_meta_%s %s %v", k, hopeDelim, v)) out = append(out, fmt.Sprintf("token_meta_%s %s %v", k, hopeDelim, v))
} }

View File

@@ -34,6 +34,10 @@ func RawField(secret *api.Secret, field string) interface{} {
case "token_renewable": case "token_renewable":
val = secret.Auth.Renewable val = secret.Auth.Renewable
case "token_policies": case "token_policies":
val = secret.Auth.TokenPolicies
case "identity_policies":
val = secret.Auth.IdentityPolicies
case "policies":
val = secret.Auth.Policies val = secret.Auth.Policies
default: default:
val = secret.Data[field] val = secret.Data[field]

View File

@@ -207,6 +207,7 @@ func TestLogical_CreateToken(t *testing.T) {
"wrap_info": nil, "wrap_info": nil,
"auth": map[string]interface{}{ "auth": map[string]interface{}{
"policies": []interface{}{"root"}, "policies": []interface{}{"root"},
"token_policies": []interface{}{"root"},
"metadata": nil, "metadata": nil,
"lease_duration": json.Number("0"), "lease_duration": json.Number("0"),
"renewable": false, "renewable": false,

View File

@@ -29,6 +29,11 @@ type Auth struct {
// is associated with. // is associated with.
Policies []string `json:"policies" mapstructure:"policies" structs:"policies"` 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 // Metadata is used to attach arbitrary string-type metadata to
// an authenticated user. This metadata will be outputted into the // an authenticated user. This metadata will be outputted into the
// audit log. // audit log.

View File

@@ -27,13 +27,15 @@ func LogicalResponseToHTTPResponse(input *Response) *HTTPResponse {
// set up the result structure. // set up the result structure.
if input.Auth != nil { if input.Auth != nil {
httpResp.Auth = &HTTPAuth{ httpResp.Auth = &HTTPAuth{
ClientToken: input.Auth.ClientToken, ClientToken: input.Auth.ClientToken,
Accessor: input.Auth.Accessor, Accessor: input.Auth.Accessor,
Policies: input.Auth.Policies, Policies: input.Auth.Policies,
Metadata: input.Auth.Metadata, TokenPolicies: input.Auth.TokenPolicies,
LeaseDuration: int(input.Auth.TTL.Seconds()), IdentityPolicies: input.Auth.IdentityPolicies,
Renewable: input.Auth.Renewable, Metadata: input.Auth.Metadata,
EntityID: input.Auth.EntityID, 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 { if input.Auth != nil {
logicalResp.Auth = &Auth{ logicalResp.Auth = &Auth{
ClientToken: input.Auth.ClientToken, ClientToken: input.Auth.ClientToken,
Accessor: input.Auth.Accessor, Accessor: input.Auth.Accessor,
Policies: input.Auth.Policies, Policies: input.Auth.Policies,
Metadata: input.Auth.Metadata, TokenPolicies: input.Auth.TokenPolicies,
EntityID: input.Auth.EntityID, IdentityPolicies: input.Auth.IdentityPolicies,
Metadata: input.Auth.Metadata,
EntityID: input.Auth.EntityID,
} }
logicalResp.Auth.Renewable = input.Auth.Renewable logicalResp.Auth.Renewable = input.Auth.Renewable
logicalResp.Auth.TTL = time.Second * time.Duration(input.Auth.LeaseDuration) logicalResp.Auth.TTL = time.Second * time.Duration(input.Auth.LeaseDuration)
@@ -81,13 +85,15 @@ type HTTPResponse struct {
} }
type HTTPAuth struct { type HTTPAuth struct {
ClientToken string `json:"client_token"` ClientToken string `json:"client_token"`
Accessor string `json:"accessor"` Accessor string `json:"accessor"`
Policies []string `json:"policies"` Policies []string `json:"policies"`
Metadata map[string]string `json:"metadata"` TokenPolicies []string `json:"token_policies,omitempty"`
LeaseDuration int `json:"lease_duration"` IdentityPolicies []string `json:"identity_policies,omitempty"`
Renewable bool `json:"renewable"` Metadata map[string]string `json:"metadata"`
EntityID string `json:"entity_id"` LeaseDuration int `json:"lease_duration"`
Renewable bool `json:"renewable"`
EntityID string `json:"entity_id"`
} }
type HTTPWrapInfo struct { type HTTPWrapInfo struct {

View File

@@ -1004,7 +1004,7 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
} }
// Validate the token is a root token // 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 { if err != nil {
retErr = multierror.Append(retErr, err) retErr = multierror.Append(retErr, err)
c.stateLock.RUnlock() 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 // Audit-log the request before going any further
auth := &logical.Auth{ auth := &logical.Auth{
ClientToken: req.ClientToken, ClientToken: req.ClientToken,
Policies: identityPolicies,
IdentityPolicies: identityPolicies,
} }
if te != nil { if te != nil {
auth.Policies = te.Policies auth.TokenPolicies = te.Policies
auth.Policies = append(te.Policies, identityPolicies...)
auth.Metadata = te.Meta auth.Metadata = te.Meta
auth.DisplayName = te.DisplayName auth.DisplayName = te.DisplayName
auth.EntityID = te.EntityID auth.EntityID = te.EntityID

View File

@@ -161,7 +161,7 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) {
ctx := c.activeContext ctx := c.activeContext
acl, te, entity, err := c.fetchACLTokenEntryAndEntity(req) acl, te, entity, identityPolicies, err := c.fetchACLTokenEntryAndEntity(req)
if err != nil { if err != nil {
retErr = multierror.Append(retErr, err) retErr = multierror.Append(retErr, err)
return retErr return retErr
@@ -169,10 +169,13 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) {
// Audit-log the request before going any further // Audit-log the request before going any further
auth := &logical.Auth{ auth := &logical.Auth{
ClientToken: req.ClientToken, ClientToken: req.ClientToken,
Policies: identityPolicies,
IdentityPolicies: identityPolicies,
} }
if te != nil { if te != nil {
auth.Policies = te.Policies auth.TokenPolicies = te.Policies
auth.Policies = append(te.Policies, identityPolicies...)
auth.Metadata = te.Meta auth.Metadata = te.Meta
auth.DisplayName = te.DisplayName auth.DisplayName = te.DisplayName
auth.EntityID = te.EntityID auth.EntityID = te.EntityID

View File

@@ -6,6 +6,7 @@ import (
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/builtin/credential/approle" "github.com/hashicorp/vault/builtin/credential/approle"
"github.com/hashicorp/vault/helper/strutil"
vaulthttp "github.com/hashicorp/vault/http" vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/vault" "github.com/hashicorp/vault/vault"
@@ -174,3 +175,205 @@ func TestIdentityStore_EntityDisabled(t *testing.T) {
t.Fatal("expected a client token") 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)
}
}

View File

@@ -3472,7 +3472,7 @@ func (b *SystemBackend) pathInternalUIMountsRead(ctx context.Context, req *logic
var entity *identity.Entity var entity *identity.Entity
// Load the ACL policies so we can walk the prefix for this mount // 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 { if err != nil {
return nil, err return nil, err
} }
@@ -3553,7 +3553,7 @@ func (b *SystemBackend) pathInternalUIMountRead(ctx context.Context, req *logica
resp.Data["path"] = me.Path resp.Data["path"] = me.Path
// Load the ACL policies so we can walk the prefix for this mount // 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 { if err != nil {
return nil, err return nil, err
} }
@@ -3574,7 +3574,7 @@ func (b *SystemBackend) pathInternalUIResultantACL(ctx context.Context, req *log
return nil, nil return nil, nil
} }
acl, _, entity, err := b.Core.fetchACLTokenEntryAndEntity(req) acl, _, entity, _, err := b.Core.fetchACLTokenEntryAndEntity(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -77,17 +77,17 @@ func (c *Core) fetchEntityAndDerivedPolicies(entityID string) (*identity.Entity,
return entity, policies, err 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()) defer metrics.MeasureSince([]string{"core", "fetch_acl_and_token"}, time.Now())
// Ensure there is a client token // Ensure there is a client token
if req.ClientToken == "" { 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 { if c.tokenStore == nil {
c.logger.Error("token store is unavailable") c.logger.Error("token store is unavailable")
return nil, nil, nil, ErrInternalError return nil, nil, nil, nil, ErrInternalError
} }
// Resolve the token policy // 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) te, err = c.tokenStore.Lookup(c.activeContext, req.ClientToken)
if err != nil { if err != nil {
c.logger.Error("failed to lookup token", "error", err) c.logger.Error("failed to lookup token", "error", err)
return nil, nil, nil, ErrInternalError return nil, nil, nil, nil, ErrInternalError
} }
default: default:
te = req.TokenEntry() te = req.TokenEntry()
@@ -106,7 +106,7 @@ func (c *Core) fetchACLTokenEntryAndEntity(req *logical.Request) (*ACL, *logical
// Ensure the token is valid // Ensure the token is valid
if te == nil { 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 // 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() { if c.Logger().IsDebug() {
c.Logger().Debug("could not parse remote addr into sockaddr", "error", err, "remote_addr", req.Connection.RemoteAddr) 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 { for _, cidr := range te.BoundCIDRs {
if cidr.Contains(remoteSockAddr) { if cidr.Contains(remoteSockAddr) {
@@ -126,27 +126,25 @@ func (c *Core) fetchACLTokenEntryAndEntity(req *logical.Request) (*ACL, *logical
} }
} }
if !valid { if !valid {
return nil, nil, nil, logical.ErrPermissionDenied return nil, nil, nil, nil, logical.ErrPermissionDenied
} }
} }
tokenPolicies := te.Policies entity, identityPolicies, err := c.fetchEntityAndDerivedPolicies(te.EntityID)
entity, derivedPolicies, err := c.fetchEntityAndDerivedPolicies(te.EntityID)
if err != nil { 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 // Construct the corresponding ACL object
acl, err := c.policyStore.ACL(c.activeContext, tokenPolicies...) acl, err := c.policyStore.ACL(c.activeContext, allPolicies...)
if err != nil { if err != nil {
c.logger.Error("failed to construct ACL", "error", err) 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) { 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 acl *ACL
var te *logical.TokenEntry var te *logical.TokenEntry
var entity *identity.Entity var entity *identity.Entity
var identityPolicies []string
var err error var err error
// Even if unauth, if a token is provided, there's little reason not to // 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 // gather as much info as possible for the audit log and to e.g. control
// trace mode for EGPs. // trace mode for EGPs.
if !unauth || (unauth && req.ClientToken != "") { 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 // 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 // unauth, we just have no information to attach to the request, so
// ignore errors...this was best-effort anyways // 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 // Create the auth response
auth := &logical.Auth{ auth := &logical.Auth{
ClientToken: req.ClientToken, ClientToken: req.ClientToken,
Accessor: req.ClientTokenAccessor, Accessor: req.ClientTokenAccessor,
Policies: identityPolicies,
IdentityPolicies: identityPolicies,
} }
if te != nil { if te != nil {
auth.Policies = te.Policies auth.TokenPolicies = te.Policies
auth.Policies = append(te.Policies, identityPolicies...)
auth.Metadata = te.Meta auth.Metadata = te.Meta
auth.DisplayName = te.DisplayName auth.DisplayName = te.DisplayName
auth.EntityID = te.EntityID auth.EntityID = te.EntityID
@@ -626,6 +628,16 @@ func (c *Core) handleRequest(ctx context.Context, req *logical.Request) (retResp
return nil, auth, retErr 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 { if err := c.expiration.RegisterAuth(resp.Auth.CreationPath, resp.Auth); err != nil {
c.tokenStore.revokeOrphan(ctx, te.ID) c.tokenStore.revokeOrphan(ctx, te.ID)
c.logger.Error("failed to register token lease", "request_path", req.Path, "error", err) 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 // Determine the source of the login
source := c.router.MatchingMount(req.Path) source := c.router.MatchingMount(req.Path)
source = strings.TrimPrefix(source, credentialRoutePrefix) source = strings.TrimPrefix(source, credentialRoutePrefix)
@@ -798,10 +806,15 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re
resp.AddWarning(warning) 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 // Generate a token
te := logical.TokenEntry{ te := logical.TokenEntry{
Path: req.Path, Path: req.Path,
Policies: auth.Policies,
Meta: auth.Metadata, Meta: auth.Metadata,
DisplayName: auth.DisplayName, DisplayName: auth.DisplayName,
CreationTime: time.Now().Unix(), CreationTime: time.Now().Unix(),
@@ -811,10 +824,24 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re
BoundCIDRs: auth.BoundCIDRs, 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 _, identityPolicies, err := c.fetchEntityAndDerivedPolicies(auth.EntityID)
for _, policy := range te.Policies { 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) { if strutil.StrListContains(nonAssignablePolicies, policy) {
return logical.ErrorResponse(fmt.Sprintf("cannot assign policy %q", policy)), nil, logical.ErrInvalidRequest 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 // Populate the client token, accessor, and TTL
auth.ClientToken = te.ID auth.ClientToken = te.ID
auth.Accessor = te.Accessor auth.Accessor = te.Accessor
auth.Policies = te.Policies
auth.TTL = te.TTL auth.TTL = te.TTL
// Register with the expiration manager // Register with the expiration manager