Adds ability to define an inline policy and internal metadata on tokens (#12682)

* Adds ability to define an inline policy and internal metadata to tokens

* Update comment on fetchEntityAndDerivedPolicies

* Simplify handling of inline policy

* Update comment on InternalMeta

Co-authored-by: Brian Kassouf <briankassouf@users.noreply.github.com>

* Improve argument name

Co-authored-by: Brian Kassouf <briankassouf@users.noreply.github.com>

* Use explicit SkipIdentityInheritance token field instead of implicit InlinePolicy behavior

* Add SkipIdentityInheritance to pb struct in token store create method

* Rename SkipIdentityInheritance to NoIdentityPolicies

* Merge latest from main and make proto

Co-authored-by: Brian Kassouf <briankassouf@users.noreply.github.com>
This commit is contained in:
Austin Gebauer 2021-10-07 10:36:22 -07:00 committed by GitHub
parent 5e4ca0468f
commit e09657e1f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 648 additions and 542 deletions

View File

@ -87,12 +87,18 @@ type TokenEntry struct {
// Which named policies should be used
Policies []string `json:"policies" mapstructure:"policies" structs:"policies"`
// InlinePolicy specifies ACL rules to be applied to this token entry.
InlinePolicy string `json:"inline_policy" mapstructure:"inline_policy" structs:"inline_policy"`
// Used for audit trails, this is something like "auth/user/login"
Path string `json:"path" mapstructure:"path" structs:"path"`
// Used for auditing. This could include things like "source", "user", "ip"
Meta map[string]string `json:"meta" mapstructure:"meta" structs:"meta" sentinel:"meta"`
// InternalMeta is used to store internal metadata. This metadata will not be audit logged or returned from lookup APIs.
InternalMeta map[string]string `json:"internal_meta" mapstructure:"internal_meta" structs:"internal_meta"`
// Used for operators to be able to associate with the source
DisplayName string `json:"display_name" mapstructure:"display_name" structs:"display_name"`
@ -128,8 +134,13 @@ type TokenEntry struct {
CreationTimeDeprecated int64 `json:"CreationTime" mapstructure:"CreationTime" structs:"CreationTime" sentinel:""`
ExplicitMaxTTLDeprecated time.Duration `json:"ExplicitMaxTTL" mapstructure:"ExplicitMaxTTL" structs:"ExplicitMaxTTL" sentinel:""`
// EntityID is the ID of the entity associated with this token.
EntityID string `json:"entity_id" mapstructure:"entity_id" structs:"entity_id"`
// If NoIdentityPolicies is true, the token will not inherit
// identity policies from the associated EntityID.
NoIdentityPolicies bool `json:"no_identity_policies" mapstructure:"no_identity_policies" structs:"no_identity_policies"`
// The set of CIDRs that this token can be used with
BoundCIDRs []*sockaddr.SockAddrMarshaler `json:"bound_cidrs" sentinel:""`

File diff suppressed because it is too large Load Diff

View File

@ -235,6 +235,9 @@ message TokenEntry {
string namespace_id = 16;
string cubbyhole_id = 17;
uint32 type = 18;
map<string, string> internal_meta = 19;
string inline_policy = 20;
bool no_identity_policies = 21;
}
message LeaseOptions {

View File

@ -599,24 +599,27 @@ func LogicalTokenEntryToProtoTokenEntry(t *logical.TokenEntry) *TokenEntry {
}
return &TokenEntry{
ID: t.ID,
Accessor: t.Accessor,
Parent: t.Parent,
Policies: t.Policies,
Path: t.Path,
Meta: t.Meta,
DisplayName: t.DisplayName,
NumUses: int64(t.NumUses),
CreationTime: t.CreationTime,
TTL: int64(t.TTL),
ExplicitMaxTTL: int64(t.ExplicitMaxTTL),
Role: t.Role,
Period: int64(t.Period),
EntityID: t.EntityID,
BoundCIDRs: boundCIDRs,
NamespaceID: t.NamespaceID,
CubbyholeID: t.CubbyholeID,
Type: uint32(t.Type),
ID: t.ID,
Accessor: t.Accessor,
Parent: t.Parent,
Policies: t.Policies,
InlinePolicy: t.InlinePolicy,
Path: t.Path,
Meta: t.Meta,
InternalMeta: t.InternalMeta,
DisplayName: t.DisplayName,
NumUses: int64(t.NumUses),
CreationTime: t.CreationTime,
TTL: int64(t.TTL),
ExplicitMaxTTL: int64(t.ExplicitMaxTTL),
Role: t.Role,
Period: int64(t.Period),
EntityID: t.EntityID,
NoIdentityPolicies: t.NoIdentityPolicies,
BoundCIDRs: boundCIDRs,
NamespaceID: t.NamespaceID,
CubbyholeID: t.CubbyholeID,
Type: uint32(t.Type),
}
}
@ -636,24 +639,27 @@ func ProtoTokenEntryToLogicalTokenEntry(t *TokenEntry) (*logical.TokenEntry, err
}
return &logical.TokenEntry{
ID: t.ID,
Accessor: t.Accessor,
Parent: t.Parent,
Policies: t.Policies,
Path: t.Path,
Meta: t.Meta,
DisplayName: t.DisplayName,
NumUses: int(t.NumUses),
CreationTime: t.CreationTime,
TTL: time.Duration(t.TTL),
ExplicitMaxTTL: time.Duration(t.ExplicitMaxTTL),
Role: t.Role,
Period: time.Duration(t.Period),
EntityID: t.EntityID,
BoundCIDRs: boundCIDRs,
NamespaceID: t.NamespaceID,
CubbyholeID: t.CubbyholeID,
Type: logical.TokenType(t.Type),
ID: t.ID,
Accessor: t.Accessor,
Parent: t.Parent,
Policies: t.Policies,
InlinePolicy: t.InlinePolicy,
Path: t.Path,
Meta: t.Meta,
InternalMeta: t.InternalMeta,
DisplayName: t.DisplayName,
NumUses: int(t.NumUses),
CreationTime: t.CreationTime,
TTL: time.Duration(t.TTL),
ExplicitMaxTTL: time.Duration(t.ExplicitMaxTTL),
Role: t.Role,
Period: time.Duration(t.Period),
EntityID: t.EntityID,
NoIdentityPolicies: t.NoIdentityPolicies,
BoundCIDRs: boundCIDRs,
NamespaceID: t.NamespaceID,
CubbyholeID: t.CubbyholeID,
Type: logical.TokenType(t.Type),
}, nil
}

View File

@ -40,7 +40,7 @@ func (c *Core) Capabilities(ctx context.Context, token, path string) ([]string,
policyNames[tokenNS.ID] = te.Policies
policyCount += len(te.Policies)
entity, identityPolicies, err := c.fetchEntityAndDerivedPolicies(ctx, tokenNS, te.EntityID)
entity, identityPolicies, err := c.fetchEntityAndDerivedPolicies(ctx, tokenNS, te.EntityID, te.NoIdentityPolicies)
if err != nil {
return nil, err
}
@ -58,6 +58,17 @@ func (c *Core) Capabilities(ctx context.Context, token, path string) ([]string,
policyCount += len(nsPolicies)
}
// Add capabilities of the inline policy if it's set
policies := make([]*Policy, 0)
if te.InlinePolicy != "" {
inlinePolicy, err := ParseACLPolicy(tokenNS, te.InlinePolicy)
if err != nil {
return nil, err
}
policies = append(policies, inlinePolicy)
policyCount++
}
if policyCount == 0 {
return []string{DenyCapability}, nil
}
@ -65,7 +76,7 @@ func (c *Core) Capabilities(ctx context.Context, token, path string) ([]string,
// Construct the corresponding ACL object. ACL construction should be
// performed on the token's namespace.
tokenCtx := namespace.ContextWithNamespace(ctx, tokenNS)
acl, err := c.policyStore.ACL(tokenCtx, entity, policyNames)
acl, err := c.policyStore.ACL(tokenCtx, entity, policyNames, policies...)
if err != nil {
return nil, err
}

View File

@ -75,9 +75,9 @@ func (e extendedSystemViewImpl) SudoPrivilege(ctx context.Context, path string,
return false
}
policies := make(map[string][]string)
policyNames := make(map[string][]string)
// Add token policies
policies[te.NamespaceID] = append(policies[te.NamespaceID], te.Policies...)
policyNames[te.NamespaceID] = append(policyNames[te.NamespaceID], te.Policies...)
tokenNS, err := NamespaceByID(ctx, te.NamespaceID, e.core)
if err != nil {
@ -90,20 +90,31 @@ func (e extendedSystemViewImpl) SudoPrivilege(ctx context.Context, path string,
}
// Add identity policies from all the namespaces
entity, identityPolicies, err := e.core.fetchEntityAndDerivedPolicies(ctx, tokenNS, te.EntityID)
entity, identityPolicies, err := e.core.fetchEntityAndDerivedPolicies(ctx, tokenNS, te.EntityID, te.NoIdentityPolicies)
if err != nil {
e.core.logger.Error("failed to fetch identity policies", "error", err)
return false
}
for nsID, nsPolicies := range identityPolicies {
policies[nsID] = append(policies[nsID], nsPolicies...)
policyNames[nsID] = append(policyNames[nsID], nsPolicies...)
}
tokenCtx := namespace.ContextWithNamespace(ctx, tokenNS)
// Add the inline policy if it's set
policies := make([]*Policy, 0)
if te.InlinePolicy != "" {
inlinePolicy, err := ParseACLPolicy(tokenNS, te.InlinePolicy)
if err != nil {
e.core.logger.Error("failed to parse the token's inline policy", "error", err)
return false
}
policies = append(policies, inlinePolicy)
}
// Construct the corresponding ACL object. Derive and use a new context that
// uses the req.ClientToken's namespace
acl, err := e.core.policyStore.ACL(tokenCtx, entity, policies)
acl, err := e.core.policyStore.ACL(tokenCtx, entity, policyNames, policies...)
if err != nil {
e.core.logger.Error("failed to retrieve ACL for token's policies", "token_policies", te.Policies, "error", err)
return false

View File

@ -753,10 +753,11 @@ func (t *TemplateError) Error() string {
}
// ACL is used to return an ACL which is built using the
// named policies.
func (ps *PolicyStore) ACL(ctx context.Context, entity *identity.Entity, policyNames map[string][]string) (*ACL, error) {
var policies []*Policy
// Fetch the policies
// named policies and pre-fetched policies if given.
func (ps *PolicyStore) ACL(ctx context.Context, entity *identity.Entity, policyNames map[string][]string, additionalPolicies ...*Policy) (*ACL, error) {
var allPolicies []*Policy
// Fetch the named policies
for nsID, nsPolicyNames := range policyNames {
policyNS, err := NamespaceByID(ctx, nsID, ps.core)
if err != nil {
@ -772,14 +773,17 @@ func (ps *PolicyStore) ACL(ctx context.Context, entity *identity.Entity, policyN
return nil, fmt.Errorf("failed to get policy: %w", err)
}
if p != nil {
policies = append(policies, p)
allPolicies = append(allPolicies, p)
}
}
}
// Append any pre-fetched policies that were given
allPolicies = append(allPolicies, additionalPolicies...)
var fetchedGroups bool
var groups []*identity.Group
for i, policy := range policies {
for i, policy := range allPolicies {
if policy.Type == PolicyTypeACL && policy.Templated {
if !fetchedGroups {
fetchedGroups = true
@ -796,12 +800,12 @@ func (ps *PolicyStore) ACL(ctx context.Context, entity *identity.Entity, policyN
return nil, fmt.Errorf("error parsing templated policy %q: %w", policy.Name, err)
}
p.Name = policy.Name
policies[i] = p
allPolicies[i] = p
}
}
// Construct the ACL
acl, err := NewACL(ctx, policies)
acl, err := NewACL(ctx, allPolicies)
if err != nil {
return nil, fmt.Errorf("failed to construct ACL: %w", err)
}

View File

@ -52,10 +52,10 @@ type HandlerProperties struct {
// fetchEntityAndDerivedPolicies returns the entity object for the given entity
// ID. If the entity is merged into a different entity object, the entity into
// which the given entity ID is merged into will be returned. This function
// also returns the cumulative list of policies that the entity is entitled to.
// This list includes the policies from the entity itself and from all the
// groups in which the given entity ID is a member of.
func (c *Core) fetchEntityAndDerivedPolicies(ctx context.Context, tokenNS *namespace.Namespace, entityID string) (*identity.Entity, map[string][]string, error) {
// also returns the cumulative list of policies that the entity is entitled to
// if skipDeriveEntityPolicies is set to false. This list includes the policies from the
// entity itself and from all the groups in which the given entity ID is a member of.
func (c *Core) fetchEntityAndDerivedPolicies(ctx context.Context, tokenNS *namespace.Namespace, entityID string, skipDeriveEntityPolicies bool) (*identity.Entity, map[string][]string, error) {
if entityID == "" || c.identityStore == nil {
return nil, nil, nil
}
@ -81,7 +81,7 @@ func (c *Core) fetchEntityAndDerivedPolicies(ctx context.Context, tokenNS *names
}
policies := make(map[string][]string)
if entity != nil {
if entity != nil && !skipDeriveEntityPolicies {
// c.logger.Debug("entity successfully fetched; adding entity policies to token's policies to create ACL")
// Attach the policies on the entity
@ -172,9 +172,9 @@ func (c *Core) fetchACLTokenEntryAndEntity(ctx context.Context, req *logical.Req
}
}
policies := make(map[string][]string)
policyNames := make(map[string][]string)
// Add tokens policies
policies[te.NamespaceID] = append(policies[te.NamespaceID], te.Policies...)
policyNames[te.NamespaceID] = append(policyNames[te.NamespaceID], te.Policies...)
tokenNS, err := NamespaceByID(ctx, te.NamespaceID, c)
if err != nil {
@ -187,21 +187,21 @@ func (c *Core) fetchACLTokenEntryAndEntity(ctx context.Context, req *logical.Req
}
// Add identity policies from all the namespaces
entity, identityPolicies, err := c.fetchEntityAndDerivedPolicies(ctx, tokenNS, te.EntityID)
entity, identityPolicies, err := c.fetchEntityAndDerivedPolicies(ctx, tokenNS, te.EntityID, te.NoIdentityPolicies)
if err != nil {
return nil, nil, nil, nil, ErrInternalError
}
for nsID, nsPolicies := range identityPolicies {
policies[nsID] = append(policies[nsID], nsPolicies...)
policyNames[nsID] = append(policyNames[nsID], nsPolicies...)
}
// Attach token's namespace information to the context. Wrapping tokens by
// should be able to be used anywhere, so we also special case behavior.
var tokenCtx context.Context
if len(policies) == 1 &&
len(policies[te.NamespaceID]) == 1 &&
(policies[te.NamespaceID][0] == responseWrappingPolicyName ||
policies[te.NamespaceID][0] == controlGroupPolicyName) &&
if len(policyNames) == 1 &&
len(policyNames[te.NamespaceID]) == 1 &&
(policyNames[te.NamespaceID][0] == responseWrappingPolicyName ||
policyNames[te.NamespaceID][0] == controlGroupPolicyName) &&
(strings.HasSuffix(req.Path, "sys/wrapping/unwrap") ||
strings.HasSuffix(req.Path, "sys/wrapping/lookup") ||
strings.HasSuffix(req.Path, "sys/wrapping/rewrap")) {
@ -213,9 +213,19 @@ func (c *Core) fetchACLTokenEntryAndEntity(ctx context.Context, req *logical.Req
tokenCtx = namespace.ContextWithNamespace(ctx, tokenNS)
}
// Add the inline policy if it's set
policies := make([]*Policy, 0)
if te.InlinePolicy != "" {
inlinePolicy, err := ParseACLPolicy(tokenNS, te.InlinePolicy)
if err != nil {
return nil, nil, nil, nil, ErrInternalError
}
policies = append(policies, inlinePolicy)
}
// Construct the corresponding ACL object. ACL construction should be
// performed on the token's namespace.
acl, err := c.policyStore.ACL(tokenCtx, entity, policies)
acl, err := c.policyStore.ACL(tokenCtx, entity, policyNames, policies...)
if err != nil {
if errwrap.ContainsType(err, new(TemplateError)) {
c.logger.Warn("permission denied due to a templated policy being invalid or containing directives not satisfied by the requestor", "error", err)
@ -1056,7 +1066,7 @@ func (c *Core) handleRequest(ctx context.Context, req *logical.Request) (retResp
return nil, auth, retErr
}
_, identityPolicies, err := c.fetchEntityAndDerivedPolicies(ctx, tokenNS, resp.Auth.EntityID)
_, identityPolicies, err := c.fetchEntityAndDerivedPolicies(ctx, tokenNS, resp.Auth.EntityID, false)
if err != nil {
// Best-effort clean up on error, so we log the cleanup error as a
// warning but still return as internal error.
@ -1361,7 +1371,7 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re
resp.AddWarning(warning)
}
_, identityPolicies, err := c.fetchEntityAndDerivedPolicies(ctx, ns, auth.EntityID)
_, identityPolicies, err := c.fetchEntityAndDerivedPolicies(ctx, ns, auth.EntityID, false)
if err != nil {
return nil, nil, ErrInternalError
}

View File

@ -831,6 +831,13 @@ func (ts *TokenStore) create(ctx context.Context, entry *logical.TokenEntry) err
metrics.IncrCounter([]string{"token", "create_root"}, 1)
}
// Validate the inline policy if it's set
if entry.InlinePolicy != "" {
if _, err := ParseACLPolicy(tokenNS, entry.InlinePolicy); err != nil {
return fmt.Errorf("failed to parse inline policy for token entry: %v", err)
}
}
switch entry.Type {
case logical.TokenTypeDefault, logical.TokenTypeService:
// In case it was default, force to service
@ -906,17 +913,20 @@ func (ts *TokenStore) create(ctx context.Context, entry *logical.TokenEntry) err
// encrypt, skip persistence
entry.ID = ""
pEntry := &pb.TokenEntry{
Parent: entry.Parent,
Policies: entry.Policies,
Path: entry.Path,
Meta: entry.Meta,
DisplayName: entry.DisplayName,
CreationTime: entry.CreationTime,
TTL: int64(entry.TTL),
Role: entry.Role,
EntityID: entry.EntityID,
NamespaceID: entry.NamespaceID,
Type: uint32(entry.Type),
Parent: entry.Parent,
Policies: entry.Policies,
Path: entry.Path,
Meta: entry.Meta,
DisplayName: entry.DisplayName,
CreationTime: entry.CreationTime,
TTL: int64(entry.TTL),
Role: entry.Role,
EntityID: entry.EntityID,
NamespaceID: entry.NamespaceID,
Type: uint32(entry.Type),
InternalMeta: entry.InternalMeta,
InlinePolicy: entry.InlinePolicy,
NoIdentityPolicies: entry.NoIdentityPolicies,
}
boundCIDRs := make([]string, len(entry.BoundCIDRs))
@ -3042,7 +3052,7 @@ func (ts *TokenStore) handleLookup(ctx context.Context, req *logical.Request, da
}
if out.EntityID != "" {
_, identityPolicies, err := ts.core.fetchEntityAndDerivedPolicies(ctx, tokenNS, out.EntityID)
_, identityPolicies, err := ts.core.fetchEntityAndDerivedPolicies(ctx, tokenNS, out.EntityID, out.NoIdentityPolicies)
if err != nil {
return nil, err
}