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 0c2d2226c4
commit 5d44c54947
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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.
// 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"`

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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