Add subscribe capability to policies (#22474)

* Add `subscribe` capability to policies

... and `subscribe_event_types` to the policy body.

These are not currently enforced in the events system (as that
will require populating the full secrets path in the event).

Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
This commit is contained in:
Christopher Swenson
2023-08-22 11:07:32 -07:00
committed by GitHub
parent 6e41be5e04
commit 12fc5bed7c
6 changed files with 83 additions and 31 deletions

3
changelog/22474.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:feature
Add subscribe capability and subscribe_event_types to policies for events.
```

View File

@@ -322,6 +322,9 @@ func (a *ACL) Capabilities(ctx context.Context, path string) (pathCapabilities [
if capabilities&PatchCapabilityInt > 0 { if capabilities&PatchCapabilityInt > 0 {
pathCapabilities = append(pathCapabilities, PatchCapability) pathCapabilities = append(pathCapabilities, PatchCapability)
} }
if capabilities&SubscribeCapabilityInt > 0 {
pathCapabilities = append(pathCapabilities, SubscribeCapability)
}
// If "deny" is explicitly set or if the path has no capabilities at all, // If "deny" is explicitly set or if the path has no capabilities at all,
// set the path capabilities to "deny" // set the path capabilities to "deny"

View File

@@ -4164,7 +4164,8 @@ func hasMountAccess(ctx context.Context, acl *ACL, path string) bool {
perms.CapabilitiesBitmap&ReadCapabilityInt > 0, perms.CapabilitiesBitmap&ReadCapabilityInt > 0,
perms.CapabilitiesBitmap&SudoCapabilityInt > 0, perms.CapabilitiesBitmap&SudoCapabilityInt > 0,
perms.CapabilitiesBitmap&UpdateCapabilityInt > 0, perms.CapabilitiesBitmap&UpdateCapabilityInt > 0,
perms.CapabilitiesBitmap&PatchCapabilityInt > 0: perms.CapabilitiesBitmap&PatchCapabilityInt > 0,
perms.CapabilitiesBitmap&SubscribeCapabilityInt > 0:
aclCapabilitiesGiven = true aclCapabilitiesGiven = true
@@ -4484,6 +4485,9 @@ func (b *SystemBackend) pathInternalUIResultantACL(ctx context.Context, req *log
if perms.CapabilitiesBitmap&PatchCapabilityInt > 0 { if perms.CapabilitiesBitmap&PatchCapabilityInt > 0 {
capabilities = append(capabilities, PatchCapability) capabilities = append(capabilities, PatchCapability)
} }
if perms.CapabilitiesBitmap&SubscribeCapabilityInt > 0 {
capabilities = append(capabilities, SubscribeCapability)
}
// If "deny" is explicitly set or if the path has no capabilities at all, // If "deny" is explicitly set or if the path has no capabilities at all,
// set the path capabilities to "deny" // set the path capabilities to "deny"

View File

@@ -9,7 +9,7 @@ import (
"strings" "strings"
"time" "time"
multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-secure-stdlib/parseutil" "github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/hashicorp/hcl" "github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast" "github.com/hashicorp/hcl/hcl/ast"
@@ -22,15 +22,16 @@ import (
) )
const ( const (
DenyCapability = "deny" DenyCapability = "deny"
CreateCapability = "create" CreateCapability = "create"
ReadCapability = "read" ReadCapability = "read"
UpdateCapability = "update" UpdateCapability = "update"
DeleteCapability = "delete" DeleteCapability = "delete"
ListCapability = "list" ListCapability = "list"
SudoCapability = "sudo" SudoCapability = "sudo"
RootCapability = "root" RootCapability = "root"
PatchCapability = "patch" PatchCapability = "patch"
SubscribeCapability = "subscribe"
// Backwards compatibility // Backwards compatibility
OldDenyPathPolicy = "deny" OldDenyPathPolicy = "deny"
@@ -48,6 +49,7 @@ const (
ListCapabilityInt ListCapabilityInt
SudoCapabilityInt SudoCapabilityInt
PatchCapabilityInt PatchCapabilityInt
SubscribeCapabilityInt
) )
// Error constants for testing // Error constants for testing
@@ -82,14 +84,15 @@ func (p PolicyType) String() string {
} }
var cap2Int = map[string]uint32{ var cap2Int = map[string]uint32{
DenyCapability: DenyCapabilityInt, DenyCapability: DenyCapabilityInt,
CreateCapability: CreateCapabilityInt, CreateCapability: CreateCapabilityInt,
ReadCapability: ReadCapabilityInt, ReadCapability: ReadCapabilityInt,
UpdateCapability: UpdateCapabilityInt, UpdateCapability: UpdateCapabilityInt,
DeleteCapability: DeleteCapabilityInt, DeleteCapability: DeleteCapabilityInt,
ListCapability: ListCapabilityInt, ListCapability: ListCapabilityInt,
SudoCapability: SudoCapabilityInt, SudoCapability: SudoCapabilityInt,
PatchCapability: PatchCapabilityInt, PatchCapability: PatchCapabilityInt,
SubscribeCapability: SubscribeCapabilityInt,
} }
type egpPath struct { type egpPath struct {
@@ -133,13 +136,14 @@ type PathRules struct {
// These keys are used at the top level to make the HCL nicer; we store in // These keys are used at the top level to make the HCL nicer; we store in
// the ACLPermissions object though // the ACLPermissions object though
MinWrappingTTLHCL interface{} `hcl:"min_wrapping_ttl"` MinWrappingTTLHCL interface{} `hcl:"min_wrapping_ttl"`
MaxWrappingTTLHCL interface{} `hcl:"max_wrapping_ttl"` MaxWrappingTTLHCL interface{} `hcl:"max_wrapping_ttl"`
AllowedParametersHCL map[string][]interface{} `hcl:"allowed_parameters"` AllowedParametersHCL map[string][]interface{} `hcl:"allowed_parameters"`
DeniedParametersHCL map[string][]interface{} `hcl:"denied_parameters"` DeniedParametersHCL map[string][]interface{} `hcl:"denied_parameters"`
RequiredParametersHCL []string `hcl:"required_parameters"` RequiredParametersHCL []string `hcl:"required_parameters"`
MFAMethodsHCL []string `hcl:"mfa_methods"` MFAMethodsHCL []string `hcl:"mfa_methods"`
ControlGroupHCL *ControlGroupHCL `hcl:"control_group"` ControlGroupHCL *ControlGroupHCL `hcl:"control_group"`
SubscribeEventTypesHCL []string `hcl:"subscribe_event_types"`
} }
type ControlGroupHCL struct { type ControlGroupHCL struct {
@@ -185,14 +189,16 @@ type ACLPermissions struct {
MFAMethods []string MFAMethods []string
ControlGroup *ControlGroup ControlGroup *ControlGroup
GrantingPoliciesMap map[uint32][]logical.PolicyInfo GrantingPoliciesMap map[uint32][]logical.PolicyInfo
SubscribeEventTypes []string
} }
func (p *ACLPermissions) Clone() (*ACLPermissions, error) { func (p *ACLPermissions) Clone() (*ACLPermissions, error) {
ret := &ACLPermissions{ ret := &ACLPermissions{
CapabilitiesBitmap: p.CapabilitiesBitmap, CapabilitiesBitmap: p.CapabilitiesBitmap,
MinWrappingTTL: p.MinWrappingTTL, MinWrappingTTL: p.MinWrappingTTL,
MaxWrappingTTL: p.MaxWrappingTTL, MaxWrappingTTL: p.MaxWrappingTTL,
RequiredParameters: p.RequiredParameters[:], RequiredParameters: p.RequiredParameters[:],
SubscribeEventTypes: p.SubscribeEventTypes[:],
} }
switch { switch {
@@ -377,6 +383,7 @@ func parsePaths(result *Policy, list *ast.ObjectList, performTemplating bool, en
"max_wrapping_ttl", "max_wrapping_ttl",
"mfa_methods", "mfa_methods",
"control_group", "control_group",
"subscribe_event_types",
} }
if err := hclutil.CheckHCLKeys(item.Val, valid); err != nil { if err := hclutil.CheckHCLKeys(item.Val, valid); err != nil {
return multierror.Prefix(err, fmt.Sprintf("path %q:", key)) return multierror.Prefix(err, fmt.Sprintf("path %q:", key))
@@ -444,7 +451,7 @@ func parsePaths(result *Policy, list *ast.ObjectList, performTemplating bool, en
pc.Capabilities = []string{DenyCapability} pc.Capabilities = []string{DenyCapability}
pc.Permissions.CapabilitiesBitmap = DenyCapabilityInt pc.Permissions.CapabilitiesBitmap = DenyCapabilityInt
goto PathFinished goto PathFinished
case CreateCapability, ReadCapability, UpdateCapability, DeleteCapability, ListCapability, SudoCapability, PatchCapability: case CreateCapability, ReadCapability, UpdateCapability, DeleteCapability, ListCapability, SudoCapability, PatchCapability, SubscribeCapability:
pc.Permissions.CapabilitiesBitmap |= cap2Int[cap] pc.Permissions.CapabilitiesBitmap |= cap2Int[cap]
default: default:
return fmt.Errorf("path %q: invalid capability %q", key, cap) return fmt.Errorf("path %q: invalid capability %q", key, cap)
@@ -542,6 +549,9 @@ func parsePaths(result *Policy, list *ast.ObjectList, performTemplating bool, en
if len(pc.RequiredParametersHCL) > 0 { if len(pc.RequiredParametersHCL) > 0 {
pc.Permissions.RequiredParameters = pc.RequiredParametersHCL[:] pc.Permissions.RequiredParameters = pc.RequiredParametersHCL[:]
} }
if len(pc.SubscribeEventTypesHCL) > 0 {
pc.Permissions.SubscribeEventTypes = pc.SubscribeEventTypesHCL[:]
}
PathFinished: PathFinished:
paths = append(paths, &pc) paths = append(paths, &pc)

View File

@@ -497,3 +497,32 @@ path "foo/+*" {
t.Errorf("bad error: %s", err) t.Errorf("bad error: %s", err)
} }
} }
func TestPolicy_Subscribe(t *testing.T) {
policy, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
path "secret/*" {
capabilities = ["subscribe", "create", "read"]
}
`))
if err != nil {
t.Fatalf("Policies should be able to use 'subscribe' capability")
}
if policy.Paths[0].Permissions.CapabilitiesBitmap&SubscribeCapabilityInt == 0 {
t.Fatalf("Subscribe capability should be present in capabilities bitmap")
}
}
func TestPolicy_Subscribe_EventTypes(t *testing.T) {
policy, err := ParseACLPolicy(namespace.RootNamespace, strings.TrimSpace(`
path "secret/*" {
capabilities = ["subscribe"]
subscribe_event_types = ["kv-v2/data-write", "kv-v1/*"]
}
`))
if err != nil {
t.Fatalf("Should be able to subscribe to a list of event types: %v", err)
}
if strings.Join(policy.Paths[0].Permissions.SubscribeEventTypes, ",") != "kv-v2/data-write,kv-v1/*" {
t.Fatalf("ACLPermission should reflect subscribe event types, but got %v", policy.Paths[0].Permissions.SubscribeEventTypes)
}
}

View File

@@ -248,6 +248,9 @@ HTTP verbs.
- `deny` - Disallows access. This always takes precedence regardless of any - `deny` - Disallows access. This always takes precedence regardless of any
other defined capabilities, including `sudo`. other defined capabilities, including `sudo`.
- `subscribe` - Allows subscribing to [events](/vault/docs/concepts/events)
for the given path.
~> **Note:** Capabilities usually map to the HTTP verb, and not the underlying ~> **Note:** Capabilities usually map to the HTTP verb, and not the underlying
action taken. This can be a common source of confusion. Generating database action taken. This can be a common source of confusion. Generating database
credentials _creates_ database credentials, but the HTTP request is a GET which credentials _creates_ database credentials, but the HTTP request is a GET which