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:
parent
0c2d2226c4
commit
5d44c54947
|
@ -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"`
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue