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:
parent
5e4ca0468f
commit
e09657e1f3
|
@ -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
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue