Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Host Volumes Support: Rollup Edition #6100

Merged
merged 17 commits into from
Aug 12, 2019
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 122 additions & 13 deletions acl/acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ type ACL struct {
// We use an iradix for the purposes of ordered iteration.
wildcardNamespaces *iradix.Tree

// hostVolumes maps a named host volume to a capabilitySet
hostVolumes *iradix.Tree

// wildcardHostVolumes maps a glob pattern of host volume names to a capabilitySet
// We use an iradix for the purposes of ordered iteration.
wildcardHostVolumes *iradix.Tree

agent string
node string
operator string
Expand Down Expand Up @@ -83,6 +90,8 @@ func NewACL(management bool, policies []*Policy) (*ACL, error) {
acl := &ACL{}
nsTxn := iradix.New().Txn()
wnsTxn := iradix.New().Txn()
hvTxn := iradix.New().Txn()
whvTxn := iradix.New().Txn()

for _, policy := range policies {
NAMESPACES:
Expand Down Expand Up @@ -128,6 +137,49 @@ func NewACL(management bool, policies []*Policy) (*ACL, error) {
}
}

HOSTVOLUMES:
for _, hv := range policy.HostVolumes {
// Should the volume be matched using a glob?
globDefinition := strings.Contains(hv.Name, "*")

// Check for existing capabilities
var capabilities capabilitySet

if globDefinition {
raw, ok := whvTxn.Get([]byte(hv.Name))
if ok {
capabilities = raw.(capabilitySet)
} else {
capabilities = make(capabilitySet)
whvTxn.Insert([]byte(hv.Name), capabilities)
}
} else {
raw, ok := hvTxn.Get([]byte(hv.Name))
if ok {
capabilities = raw.(capabilitySet)
} else {
capabilities = make(capabilitySet)
hvTxn.Insert([]byte(hv.Name), capabilities)
}
}

// Deny always takes precedence
if capabilities.Check(HostVolumeCapabilityDeny) {
continue
}

// Add in all the capabilities
for _, cap := range hv.Capabilities {
if cap == HostVolumeCapabilityDeny {
// Overwrite any existing capabilities
capabilities.Clear()
capabilities.Set(HostVolumeCapabilityDeny)
continue HOSTVOLUMES
}
capabilities.Set(cap)
}
}

// Take the maximum privilege for agent, node, and operator
if policy.Agent != nil {
acl.agent = maxPrivilege(acl.agent, policy.Agent.Policy)
Expand All @@ -146,6 +198,9 @@ func NewACL(management bool, policies []*Policy) (*ACL, error) {
// Finalize the namespaces
acl.namespaces = nsTxn.Commit()
acl.wildcardNamespaces = wnsTxn.Commit()
acl.hostVolumes = hvTxn.Commit()
acl.wildcardHostVolumes = whvTxn.Commit()

return acl, nil
}

Expand All @@ -162,7 +217,7 @@ func (a *ACL) AllowNamespaceOperation(ns string, op string) bool {
}

// Check for a matching capability set
capabilities, ok := a.matchingCapabilitySet(ns)
capabilities, ok := a.matchingNamespaceCapabilitySet(ns)
if !ok {
return false
}
Expand All @@ -179,7 +234,45 @@ func (a *ACL) AllowNamespace(ns string) bool {
}

// Check for a matching capability set
capabilities, ok := a.matchingCapabilitySet(ns)
capabilities, ok := a.matchingNamespaceCapabilitySet(ns)
if !ok {
return false
}

// Check if the capability has been granted
if len(capabilities) == 0 {
return false
}

return !capabilities.Check(PolicyDeny)
}

// AllowHostVolumeOperation checks if a given operation is allowed for a host volume
func (a *ACL) AllowHostVolumeOperation(hv string, op string) bool {
// Hot path management tokens
if a.management {
return true
}

// Check for a matching capability set
capabilities, ok := a.matchingHostVolumeCapabilitySet(hv)
if !ok {
return false
}

// Check if the capability has been granted
return capabilities.Check(op)
}

// AllowHostVolume checks if any operations are allowed for a HostVolume
func (a *ACL) AllowHostVolume(ns string) bool {
// Hot path management tokens
if a.management {
return true
}

// Check for a matching capability set
capabilities, ok := a.matchingHostVolumeCapabilitySet(ns)
if !ok {
return false
}
Expand All @@ -192,31 +285,47 @@ func (a *ACL) AllowNamespace(ns string) bool {
return !capabilities.Check(PolicyDeny)
}

// matchingCapabilitySet looks for a capabilitySet that matches the namespace,
// matchingNamespaceCapabilitySet looks for a capabilitySet that matches the namespace,
// if no concrete definitions are found, then we return the closest matching
// glob.
// The closest matching glob is the one that has the smallest character
// difference between the namespace and the glob.
func (a *ACL) matchingCapabilitySet(ns string) (capabilitySet, bool) {
func (a *ACL) matchingNamespaceCapabilitySet(ns string) (capabilitySet, bool) {
// Check for a concrete matching capability set
raw, ok := a.namespaces.Get([]byte(ns))
if ok {
return raw.(capabilitySet), true
}

// We didn't find a concrete match, so lets try and evaluate globs.
return a.findClosestMatchingGlob(ns)
return a.findClosestMatchingGlob(a.wildcardNamespaces, ns)
}

// matchingHostVolumeCapabilitySet looks for a capabilitySet that matches the host volume name,
// if no concrete definitions are found, then we return the closest matching
// glob.
// The closest matching glob is the one that has the smallest character
// difference between the volume name and the glob.
func (a *ACL) matchingHostVolumeCapabilitySet(name string) (capabilitySet, bool) {
// Check for a concrete matching capability set
raw, ok := a.hostVolumes.Get([]byte(name))
if ok {
return raw.(capabilitySet), true
}

// We didn't find a concrete match, so lets try and evaluate globs.
return a.findClosestMatchingGlob(a.wildcardHostVolumes, name)
}

type matchingGlob struct {
ns string
name string
difference int
capabilitySet capabilitySet
}

func (a *ACL) findClosestMatchingGlob(ns string) (capabilitySet, bool) {
func (a *ACL) findClosestMatchingGlob(radix *iradix.Tree, ns string) (capabilitySet, bool) {
// First, find all globs that match.
matchingGlobs := a.findAllMatchingWildcards(ns)
matchingGlobs := findAllMatchingWildcards(radix, ns)

// If none match, let's return.
if len(matchingGlobs) == 0 {
Expand All @@ -238,19 +347,19 @@ func (a *ACL) findClosestMatchingGlob(ns string) (capabilitySet, bool) {
return matchingGlobs[0].capabilitySet, true
}

func (a *ACL) findAllMatchingWildcards(ns string) []matchingGlob {
func findAllMatchingWildcards(radix *iradix.Tree, name string) []matchingGlob {
var matches []matchingGlob

nsLen := len(ns)
nsLen := len(name)

a.wildcardNamespaces.Root().Walk(func(bk []byte, iv interface{}) bool {
radix.Root().Walk(func(bk []byte, iv interface{}) bool {
k := string(bk)
v := iv.(capabilitySet)

isMatch := glob.Glob(k, ns)
isMatch := glob.Glob(k, name)
if isMatch {
pair := matchingGlob{
ns: k,
name: k,
difference: nsLen - len(k) + strings.Count(k, glob.GLOB),
capabilitySet: v,
}
Expand Down
56 changes: 53 additions & 3 deletions acl/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,56 @@ func TestWildcardNamespaceMatching(t *testing.T) {
}
}

func TestWildcardHostVolumeMatching(t *testing.T) {
tests := []struct {
Policy string
Allow bool
}{
{ // Wildcard matches
Policy: `host_volume "prod-api-*" { policy = "write" }`,
Allow: true,
},
{ // Non globbed volumes are not wildcards
Policy: `host_volume "prod-api" { policy = "write" }`,
Allow: false,
},
{ // Concrete matches take precedence
Policy: `host_volume "prod-api-services" { policy = "deny" }
host_volume "prod-api-*" { policy = "write" }`,
Allow: false,
},
{
Policy: `host_volume "prod-api-*" { policy = "deny" }
host_volume "prod-api-services" { policy = "write" }`,
Allow: true,
},
{ // The closest character match wins
Policy: `host_volume "*-api-services" { policy = "deny" }
host_volume "prod-api-*" { policy = "write" }`, // 4 vs 8 chars
Allow: false,
},
{
Policy: `host_volume "prod-api-*" { policy = "write" }
host_volume "*-api-services" { policy = "deny" }`, // 4 vs 8 chars
Allow: false,
},
}

for _, tc := range tests {
t.Run(tc.Policy, func(t *testing.T) {
assert := assert.New(t)

policy, err := Parse(tc.Policy)
assert.NoError(err)
assert.NotNil(policy.HostVolumes)

acl, err := NewACL(false, []*Policy{policy})
assert.Nil(err)

assert.Equal(tc.Allow, acl.AllowHostVolume("prod-api-services"))
})
}
}
func TestACL_matchingCapabilitySet_returnsAllMatches(t *testing.T) {
tests := []struct {
Policy string
Expand Down Expand Up @@ -351,8 +401,8 @@ func TestACL_matchingCapabilitySet_returnsAllMatches(t *testing.T) {
assert.Nil(err)

var namespaces []string
for _, cs := range acl.findAllMatchingWildcards(tc.NS) {
namespaces = append(namespaces, cs.ns)
for _, cs := range findAllMatchingWildcards(acl.wildcardNamespaces, tc.NS) {
namespaces = append(namespaces, cs.name)
}

assert.Equal(tc.MatchingGlobs, namespaces)
Expand Down Expand Up @@ -404,7 +454,7 @@ func TestACL_matchingCapabilitySet_difference(t *testing.T) {
acl, err := NewACL(false, []*Policy{policy})
assert.Nil(err)

matches := acl.findAllMatchingWildcards(tc.NS)
matches := findAllMatchingWildcards(acl.wildcardNamespaces, tc.NS)
assert.Equal(tc.Difference, matches[0].difference)
})
}
Expand Down
Loading