Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
小 白蛋
Nomad
Commits
4def9298
Commit
4def9298
authored
7 years ago
by
Armon Dadgar
Browse files
Options
Download
Email Patches
Plain Diff
acl: Adding compiled ACL object
parent
39fa8c55
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
acl/acl.go
+205
-0
acl/acl.go
acl/acl_test.go
+193
-0
acl/acl_test.go
with
398 additions
and
0 deletions
+398
-0
acl/acl.go
0 → 100644
+
205
-
0
View file @
4def9298
package
acl
import
(
iradix
"github.com/hashicorp/go-immutable-radix"
)
// capabilitySet is a type wrapper to help managing a set of capabilities
type
capabilitySet
map
[
string
]
struct
{}
func
(
c
capabilitySet
)
Check
(
k
string
)
bool
{
_
,
ok
:=
c
[
k
]
return
ok
}
func
(
c
capabilitySet
)
Set
(
k
string
)
{
c
[
k
]
=
struct
{}{}
}
func
(
c
capabilitySet
)
Clear
()
{
for
cap
:=
range
c
{
delete
(
c
,
cap
)
}
}
// ACL object is used to convert a set of policies into a structure that
// can be efficiently evaluated to determine if an action is allowed.
type
ACL
struct
{
// management tokens are allowed to do anything
management
bool
// namespaces maps a namespace to a capabilitySet
namespaces
*
iradix
.
Tree
agent
string
node
string
operator
string
}
// maxPrivilege returns the policy which grants the most privilege
func
maxPrivilege
(
a
,
b
string
)
string
{
switch
{
case
a
==
PolicyDeny
||
b
==
PolicyDeny
:
return
PolicyDeny
case
a
==
PolicyWrite
||
b
==
PolicyWrite
:
return
PolicyWrite
case
a
==
PolicyRead
||
b
==
PolicyRead
:
return
PolicyRead
default
:
return
""
}
}
// NewACL compiles a set of policies into an ACL object
func
NewACL
(
management
bool
,
policies
[]
*
Policy
)
(
*
ACL
,
error
)
{
// Hot-path management tokens
if
management
{
return
&
ACL
{
management
:
true
},
nil
}
// Create the ACL object
acl
:=
&
ACL
{}
nsTxn
:=
iradix
.
New
()
.
Txn
()
for
_
,
policy
:=
range
policies
{
NAMESPACES
:
for
_
,
ns
:=
range
policy
.
Namespaces
{
// Check for existing capabilities
var
capabilities
capabilitySet
raw
,
ok
:=
nsTxn
.
Get
([]
byte
(
ns
.
Name
))
if
ok
{
capabilities
=
raw
.
(
capabilitySet
)
}
else
{
capabilities
=
make
(
capabilitySet
)
nsTxn
.
Insert
([]
byte
(
ns
.
Name
),
capabilities
)
}
// Deny always takes precedence
if
capabilities
.
Check
(
NamespaceCapabilityDeny
)
{
continue
NAMESPACES
}
// Add in all the capabilities
for
_
,
cap
:=
range
ns
.
Capabilities
{
if
cap
==
NamespaceCapabilityDeny
{
// Overwrite any existing capabilities
capabilities
.
Clear
()
capabilities
.
Set
(
NamespaceCapabilityDeny
)
continue
NAMESPACES
}
capabilities
.
Set
(
cap
)
}
}
// Take the maximum privilege for agent, node, and operator
if
policy
.
Agent
!=
nil
{
acl
.
agent
=
maxPrivilege
(
acl
.
agent
,
policy
.
Agent
.
Policy
)
}
if
policy
.
Node
!=
nil
{
acl
.
node
=
maxPrivilege
(
acl
.
node
,
policy
.
Node
.
Policy
)
}
if
policy
.
Operator
!=
nil
{
acl
.
operator
=
maxPrivilege
(
acl
.
operator
,
policy
.
Operator
.
Policy
)
}
}
// Finalize the namespaces
acl
.
namespaces
=
nsTxn
.
Commit
()
return
acl
,
nil
}
// AllowNamespaceOperation checks if a given operation is allowed for a namespace
func
(
a
*
ACL
)
AllowNamespaceOperation
(
ns
string
,
op
string
)
bool
{
// Hot path management tokens
if
a
.
management
{
return
true
}
// Check for a matching capability set
raw
,
ok
:=
a
.
namespaces
.
Get
([]
byte
(
ns
))
if
!
ok
{
return
false
}
// Check if the capability has been granted
capabilities
:=
raw
.
(
capabilitySet
)
return
capabilities
.
Check
(
op
)
}
// AllowAgentRead checks if read operations are allowed for an agent
func
(
a
*
ACL
)
AllowAgentRead
()
bool
{
switch
{
case
a
.
management
:
return
true
case
a
.
agent
==
PolicyWrite
:
return
true
case
a
.
agent
==
PolicyRead
:
return
true
default
:
return
false
}
}
// AllowAgentWrite checks if write operations are allowed for an agent
func
(
a
*
ACL
)
AllowAgentWrite
()
bool
{
switch
{
case
a
.
management
:
return
true
case
a
.
agent
==
PolicyWrite
:
return
true
default
:
return
false
}
}
// AllowNodeRead checks if read operations are allowed for a node
func
(
a
*
ACL
)
AllowNodeRead
()
bool
{
switch
{
case
a
.
management
:
return
true
case
a
.
node
==
PolicyWrite
:
return
true
case
a
.
node
==
PolicyRead
:
return
true
default
:
return
false
}
}
// AllowNodeWrite checks if write operations are allowed for a node
func
(
a
*
ACL
)
AllowNodeWrite
()
bool
{
switch
{
case
a
.
management
:
return
true
case
a
.
node
==
PolicyWrite
:
return
true
default
:
return
false
}
}
// AllowOperatorRead checks if read operations are allowed for a operator
func
(
a
*
ACL
)
AllowOperatorRead
()
bool
{
switch
{
case
a
.
management
:
return
true
case
a
.
operator
==
PolicyWrite
:
return
true
case
a
.
operator
==
PolicyRead
:
return
true
default
:
return
false
}
}
// AllowOperatorWrite checks if write operations are allowed for a operator
func
(
a
*
ACL
)
AllowOperatorWrite
()
bool
{
switch
{
case
a
.
management
:
return
true
case
a
.
operator
==
PolicyWrite
:
return
true
default
:
return
false
}
}
This diff is collapsed.
Click to expand it.
acl/acl_test.go
0 → 100644
+
193
-
0
View file @
4def9298
package
acl
import
(
"testing"
"github.com/stretchr/testify/assert"
)
func
TestCapabilitySet
(
t
*
testing
.
T
)
{
var
cs
capabilitySet
=
make
(
map
[
string
]
struct
{})
// Check no capabilities by default
if
cs
.
Check
(
PolicyDeny
)
{
t
.
Fatalf
(
"unexpected check"
)
}
// Do a set and check
cs
.
Set
(
PolicyDeny
)
if
!
cs
.
Check
(
PolicyDeny
)
{
t
.
Fatalf
(
"missing check"
)
}
// Clear and check
cs
.
Clear
()
if
cs
.
Check
(
PolicyDeny
)
{
t
.
Fatalf
(
"unexpected check"
)
}
}
func
TestMaxPrivilege
(
t
*
testing
.
T
)
{
type
tcase
struct
{
Privilege
string
PrecedenceOver
[]
string
}
tcases
:=
[]
tcase
{
{
PolicyDeny
,
[]
string
{
PolicyDeny
,
PolicyWrite
,
PolicyRead
,
""
},
},
{
PolicyWrite
,
[]
string
{
PolicyWrite
,
PolicyRead
,
""
},
},
{
PolicyRead
,
[]
string
{
PolicyRead
,
""
},
},
}
for
idx1
,
tc
:=
range
tcases
{
for
idx2
,
po
:=
range
tc
.
PrecedenceOver
{
if
maxPrivilege
(
tc
.
Privilege
,
po
)
!=
tc
.
Privilege
{
t
.
Fatalf
(
"failed %d %d"
,
idx1
,
idx2
)
}
if
maxPrivilege
(
po
,
tc
.
Privilege
)
!=
tc
.
Privilege
{
t
.
Fatalf
(
"failed %d %d"
,
idx1
,
idx2
)
}
}
}
}
func
TestACLManagement
(
t
*
testing
.
T
)
{
// Create management ACL
acl
,
err
:=
NewACL
(
true
,
nil
)
assert
.
Nil
(
t
,
err
)
// Check default namespace rights
assert
.
Equal
(
t
,
true
,
acl
.
AllowNamespaceOperation
(
"default"
,
NamespaceCapabilityListJobs
))
assert
.
Equal
(
t
,
true
,
acl
.
AllowNamespaceOperation
(
"default"
,
NamespaceCapabilitySubmitJob
))
// Check non-specified namespace
assert
.
Equal
(
t
,
true
,
acl
.
AllowNamespaceOperation
(
"foo"
,
NamespaceCapabilityListJobs
))
// Check the other simpler operations
assert
.
Equal
(
t
,
true
,
acl
.
AllowAgentRead
())
assert
.
Equal
(
t
,
true
,
acl
.
AllowAgentWrite
())
assert
.
Equal
(
t
,
true
,
acl
.
AllowNodeRead
())
assert
.
Equal
(
t
,
true
,
acl
.
AllowNodeWrite
())
assert
.
Equal
(
t
,
true
,
acl
.
AllowOperatorRead
())
assert
.
Equal
(
t
,
true
,
acl
.
AllowOperatorWrite
())
}
func
TestACLMerge
(
t
*
testing
.
T
)
{
// Merge read + write policy
p1
,
err
:=
Parse
(
readAll
)
assert
.
Nil
(
t
,
err
)
p2
,
err
:=
Parse
(
writeAll
)
assert
.
Nil
(
t
,
err
)
acl
,
err
:=
NewACL
(
false
,
[]
*
Policy
{
p1
,
p2
})
assert
.
Nil
(
t
,
err
)
// Check default namespace rights
assert
.
Equal
(
t
,
true
,
acl
.
AllowNamespaceOperation
(
"default"
,
NamespaceCapabilityListJobs
))
assert
.
Equal
(
t
,
true
,
acl
.
AllowNamespaceOperation
(
"default"
,
NamespaceCapabilitySubmitJob
))
// Check non-specified namespace
assert
.
Equal
(
t
,
false
,
acl
.
AllowNamespaceOperation
(
"foo"
,
NamespaceCapabilityListJobs
))
// Check the other simpler operations
assert
.
Equal
(
t
,
true
,
acl
.
AllowAgentRead
())
assert
.
Equal
(
t
,
true
,
acl
.
AllowAgentWrite
())
assert
.
Equal
(
t
,
true
,
acl
.
AllowNodeRead
())
assert
.
Equal
(
t
,
true
,
acl
.
AllowNodeWrite
())
assert
.
Equal
(
t
,
true
,
acl
.
AllowOperatorRead
())
assert
.
Equal
(
t
,
true
,
acl
.
AllowOperatorWrite
())
// Merge read + blank
p3
,
err
:=
Parse
(
""
)
assert
.
Nil
(
t
,
err
)
acl
,
err
=
NewACL
(
false
,
[]
*
Policy
{
p1
,
p3
})
assert
.
Nil
(
t
,
err
)
// Check default namespace rights
assert
.
Equal
(
t
,
true
,
acl
.
AllowNamespaceOperation
(
"default"
,
NamespaceCapabilityListJobs
))
assert
.
Equal
(
t
,
false
,
acl
.
AllowNamespaceOperation
(
"default"
,
NamespaceCapabilitySubmitJob
))
// Check non-specified namespace
assert
.
Equal
(
t
,
false
,
acl
.
AllowNamespaceOperation
(
"foo"
,
NamespaceCapabilityListJobs
))
// Check the other simpler operations
assert
.
Equal
(
t
,
true
,
acl
.
AllowAgentRead
())
assert
.
Equal
(
t
,
false
,
acl
.
AllowAgentWrite
())
assert
.
Equal
(
t
,
true
,
acl
.
AllowNodeRead
())
assert
.
Equal
(
t
,
false
,
acl
.
AllowNodeWrite
())
assert
.
Equal
(
t
,
true
,
acl
.
AllowOperatorRead
())
assert
.
Equal
(
t
,
false
,
acl
.
AllowOperatorWrite
())
// Merge read + deny
p4
,
err
:=
Parse
(
denyAll
)
assert
.
Nil
(
t
,
err
)
acl
,
err
=
NewACL
(
false
,
[]
*
Policy
{
p1
,
p4
})
assert
.
Nil
(
t
,
err
)
// Check default namespace rights
assert
.
Equal
(
t
,
false
,
acl
.
AllowNamespaceOperation
(
"default"
,
NamespaceCapabilityListJobs
))
assert
.
Equal
(
t
,
false
,
acl
.
AllowNamespaceOperation
(
"default"
,
NamespaceCapabilitySubmitJob
))
// Check non-specified namespace
assert
.
Equal
(
t
,
false
,
acl
.
AllowNamespaceOperation
(
"foo"
,
NamespaceCapabilityListJobs
))
// Check the other simpler operations
assert
.
Equal
(
t
,
false
,
acl
.
AllowAgentRead
())
assert
.
Equal
(
t
,
false
,
acl
.
AllowAgentWrite
())
assert
.
Equal
(
t
,
false
,
acl
.
AllowNodeRead
())
assert
.
Equal
(
t
,
false
,
acl
.
AllowNodeWrite
())
assert
.
Equal
(
t
,
false
,
acl
.
AllowOperatorRead
())
assert
.
Equal
(
t
,
false
,
acl
.
AllowOperatorWrite
())
}
var
readAll
=
`
namespace "default" {
policy = "read"
}
agent {
policy = "read"
}
node {
policy = "read"
}
operator {
policy = "read"
}
`
var
writeAll
=
`
namespace "default" {
policy = "write"
}
agent {
policy = "write"
}
node {
policy = "write"
}
operator {
policy = "write"
}
`
var
denyAll
=
`
namespace "default" {
policy = "deny"
}
agent {
policy = "deny"
}
node {
policy = "deny"
}
operator {
policy = "deny"
}
`
This diff is collapsed.
Click to expand it.
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment
Menu
Projects
Groups
Snippets
Help