You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/credential/vault/vault_capabilities.go

274 lines
5.9 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package vault
import (
"fmt"
"sort"
"strings"
)
type capabilities uint
const noCapabilities = capabilities(0)
const (
denyCapability capabilities = 1 << iota
createCapability
readCapability
updateCapability
deleteCapability
listCapability
sudoCapability
rootCapability
)
var string2Cap = map[string]capabilities{
"deny": denyCapability,
"create": createCapability,
"read": readCapability,
"update": updateCapability,
"delete": deleteCapability,
"list": listCapability,
"sudo": sudoCapability,
"root": rootCapability,
}
func (c capabilities) goStrings() []string {
if c == noCapabilities {
return nil
}
var caps []string
if c&denyCapability == denyCapability {
caps = append(caps, "denyCapability")
}
if c&createCapability == createCapability {
caps = append(caps, "createCapability")
}
if c&readCapability == readCapability {
caps = append(caps, "readCapability")
}
if c&updateCapability == updateCapability {
caps = append(caps, "updateCapability")
}
if c&deleteCapability == deleteCapability {
caps = append(caps, "deleteCapability")
}
if c&listCapability == listCapability {
caps = append(caps, "listCapability")
}
if c&sudoCapability == sudoCapability {
caps = append(caps, "sudoCapability")
}
if c&rootCapability == rootCapability {
caps = append(caps, "rootCapability")
}
return caps
}
func (c capabilities) GoString() string {
if c == noCapabilities {
return "vault.capabilities{}"
}
caps := c.goStrings()
var b strings.Builder
b.WriteString("vault.capabilities{ ")
b.WriteString(strings.Join(caps, " | "))
b.WriteString(" }")
return b.String()
}
func (c capabilities) strings() []string {
if c == noCapabilities {
return nil
}
var caps []string
if c&denyCapability == denyCapability {
caps = append(caps, "deny")
}
if c&createCapability == createCapability {
caps = append(caps, "create")
}
if c&readCapability == readCapability {
caps = append(caps, "read")
}
if c&updateCapability == updateCapability {
caps = append(caps, "update")
}
if c&deleteCapability == deleteCapability {
caps = append(caps, "delete")
}
if c&listCapability == listCapability {
caps = append(caps, "list")
}
if c&sudoCapability == sudoCapability {
caps = append(caps, "sudo")
}
if c&rootCapability == rootCapability {
caps = append(caps, "root")
}
return caps
}
func (c capabilities) String() string {
if c == noCapabilities {
return "[]"
}
caps := c.strings()
var b strings.Builder
b.WriteString(`["`)
b.WriteString(strings.Join(caps, `", "`))
b.WriteString(`"]`)
return b.String()
}
func (c capabilities) hasDeny() bool {
return c&denyCapability == denyCapability
}
func (c capabilities) missing(required capabilities) capabilities {
return required &^ c
}
type pathCapabilities map[string]capabilities
func newPathCapabilities(results map[string][]string) pathCapabilities {
pc := make(pathCapabilities, len(results))
for path, caps := range results {
for _, cap := range caps {
pc[path] |= string2Cap[cap]
}
}
return pc
}
// has reports if the path has all of the required capabilities.
// has always returns false if deny has been set on the path.
// deny is ignored if it is included in the list of required capabilities.
func (pc pathCapabilities) has(path string, required ...capabilities) bool {
if pc[path].hasDeny() {
return false
}
var allRequiredCaps capabilities
for _, cap := range required {
allRequiredCaps |= cap
}
// deny cannot be in the required list
allRequiredCaps &^= denyCapability
return pc[path]&allRequiredCaps != 0
}
// get returns the capabilities for path and a boolean to indicate if the
// path was set.
func (pc pathCapabilities) get(path string) (capabilities, bool) {
c, ok := pc[path]
return c, ok
}
// union creates and returns a new pathCapabilities instance, z. union sets
// z[p] = x[p] | y[p] for all p, where p is a path from the set of paths
// from x and y.
func (pc pathCapabilities) union(y pathCapabilities) (z pathCapabilities) {
if len(pc) == 0 && len(y) == 0 {
return
}
max := func(a, b int) int {
if a < b {
return a
}
return b
}
z = make(pathCapabilities, max(len(pc), len(y)))
for p := range pc {
z[p] = pc[p] | y[p]
}
for p := range y {
z[p] = pc[p] | y[p]
}
return
}
func (pc pathCapabilities) missing(required pathCapabilities) pathCapabilities {
switch {
case len(required) == 0:
return nil
case len(pc) == 0:
z := make(pathCapabilities, len(required))
for p := range required {
z[p] = required[p]
}
return z
}
var z pathCapabilities
for p, c := range required {
mc := pc[p].missing(c)
if mc != noCapabilities {
if z == nil {
z = make(pathCapabilities)
}
z[p] = mc
}
}
return z
}
func (pc pathCapabilities) vaultPolicy() string {
if len(pc) == 0 {
return ""
}
var paths []string
for path := range pc {
if pc[path] != noCapabilities {
paths = append(paths, path)
}
}
if len(paths) == 0 {
return ""
}
sort.Strings(paths)
var sep string
b := new(strings.Builder)
for _, path := range paths {
b.WriteString(sep)
fmt.Fprintf(b, "path %q {\n", path)
fmt.Fprintf(b, "\tcapabilities = %s\n", pc[path])
b.WriteString("}\n")
sep = "\n"
}
return b.String()
}
func (pc pathCapabilities) paths() []string {
var paths []string
for path := range pc {
paths = append(paths, path)
}
return paths
}
func (pc pathCapabilities) String() string {
var b strings.Builder
for k, v := range pc {
b.WriteString(k)
b.WriteString(": ")
b.WriteString(strings.Join(v.strings(), "|"))
b.WriteString(", ")
}
return strings.TrimSuffix(b.String(), ", ")
}
var requiredCapabilities = pathCapabilities{
"auth/token/lookup-self": readCapability,
"auth/token/renew-self": updateCapability,
"auth/token/revoke-self": updateCapability,
"sys/leases/renew": updateCapability,
"sys/leases/revoke": updateCapability,
}