mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
Support applying policy to all jobs within namespace (#25871)
Workflow identities currently support ACL policies being applied to a job ID within a namespace. With this update an ACL policy can be applied to a namespace. This results in the ACL policy being applied to all jobs within the namespace.
This commit is contained in:
3
.changelog/25871.txt
Normal file
3
.changelog/25871.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
identity: Allow ACL policies to be applied to a namespace
|
||||
```
|
||||
@@ -35,14 +35,13 @@ Apply Options:
|
||||
-description
|
||||
Specifies a human readable description for the policy.
|
||||
|
||||
-namespace
|
||||
Attaches the policy to the specified namespace.
|
||||
|
||||
-job
|
||||
Attaches the policy to the specified job. Requires that -namespace is
|
||||
also set.
|
||||
|
||||
-namespace
|
||||
Attaches the policy to the specified namespace. Requires that -job is
|
||||
also set.
|
||||
|
||||
-group
|
||||
Attaches the policy to the specified task group. Requires that -namespace
|
||||
and -job are also set.
|
||||
|
||||
@@ -641,5 +641,23 @@ func (s *Authenticator) ResolvePoliciesForClaims(claims *structs.IdentityClaims)
|
||||
}
|
||||
}
|
||||
|
||||
iter, err = snap.ACLPolicyByNamespace(nil, alloc.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for {
|
||||
raw := iter.Next()
|
||||
if raw == nil {
|
||||
break
|
||||
}
|
||||
|
||||
policy := raw.(*structs.ACLPolicy)
|
||||
if policy.JobACL == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
policies = append(policies, policy)
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
@@ -1109,6 +1109,18 @@ func TestResolveClaims(t *testing.T) {
|
||||
JobID: claims.JobID,
|
||||
}
|
||||
|
||||
// policy for namespace
|
||||
policy8 := mock.ACLPolicy()
|
||||
policy8.JobACL = &structs.JobACL{
|
||||
Namespace: claims.Namespace,
|
||||
}
|
||||
|
||||
// policy for different namespace
|
||||
policy9 := mock.ACLPolicy()
|
||||
policy9.JobACL = &structs.JobACL{
|
||||
Namespace: "another",
|
||||
}
|
||||
|
||||
aclObj, err := auth.resolveClaims(claims)
|
||||
must.Nil(t, aclObj)
|
||||
must.EqError(t, err, "allocation does not exist")
|
||||
@@ -1127,7 +1139,7 @@ func TestResolveClaims(t *testing.T) {
|
||||
// Add the policies
|
||||
index++
|
||||
err = auth.getState().UpsertACLPolicies(structs.MsgTypeTestSetup, index, []*structs.ACLPolicy{
|
||||
policy0, policy1, policy2, policy3, policy4, policy5, policy6, policy7})
|
||||
policy0, policy1, policy2, policy3, policy4, policy5, policy6, policy7, policy8, policy9})
|
||||
must.NoError(t, err)
|
||||
|
||||
// Re-resolve and check that the resulting ACL looks reasonable
|
||||
@@ -1146,8 +1158,8 @@ func TestResolveClaims(t *testing.T) {
|
||||
|
||||
policies, err := auth.ResolvePoliciesForClaims(claims)
|
||||
must.NoError(t, err)
|
||||
must.Len(t, 3, policies)
|
||||
must.SliceContainsAll(t, policies, []*structs.ACLPolicy{policy1, policy2, policy3})
|
||||
must.Len(t, 4, policies)
|
||||
must.SliceContainsAll(t, policies, []*structs.ACLPolicy{policy1, policy2, policy3, policy8})
|
||||
|
||||
// Check the dispatch claims
|
||||
aclObj3, err := auth.resolveClaims(dispatchClaims)
|
||||
@@ -1157,8 +1169,8 @@ func TestResolveClaims(t *testing.T) {
|
||||
|
||||
dispatchPolicies, err := auth.ResolvePoliciesForClaims(dispatchClaims)
|
||||
must.NoError(t, err)
|
||||
must.Len(t, 3, dispatchPolicies)
|
||||
must.SliceContainsAll(t, dispatchPolicies, []*structs.ACLPolicy{policy1, policy2, policy3})
|
||||
must.Len(t, 4, dispatchPolicies)
|
||||
must.SliceContainsAll(t, dispatchPolicies, []*structs.ACLPolicy{policy1, policy2, policy3, policy8})
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -907,10 +907,10 @@ func (a *ACLPolicyJobACLFieldIndex) FromObject(obj interface{}) (bool, []byte, e
|
||||
if ns == "" {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
jobID := policy.JobACL.JobID
|
||||
if jobID == "" {
|
||||
return false, nil, fmt.Errorf(
|
||||
"object %#v is not a valid ACLPolicy: Namespace without JobID", obj)
|
||||
return true, []byte(ns + "\x00\x00"), nil
|
||||
}
|
||||
|
||||
val := ns + "\x00" + jobID + "\x00"
|
||||
@@ -919,19 +919,27 @@ func (a *ACLPolicyJobACLFieldIndex) FromObject(obj interface{}) (bool, []byte, e
|
||||
|
||||
// FromArgs is used to build an exact index lookup based on arguments
|
||||
func (a *ACLPolicyJobACLFieldIndex) FromArgs(args ...interface{}) ([]byte, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, fmt.Errorf("must provide two arguments")
|
||||
if len(args) < 1 || len(args) > 2 {
|
||||
return nil, fmt.Errorf("must provide one or two arguments")
|
||||
}
|
||||
arg0, ok := args[0].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("argument must be a string: %#v", args[0])
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
// Add two null characters to fully terminate a
|
||||
// namespace only entry
|
||||
return []byte(arg0 + "\x00\x00"), nil
|
||||
}
|
||||
|
||||
arg1, ok := args[1].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("argument must be a string: %#v", args[0])
|
||||
}
|
||||
|
||||
// Add the null character as a terminator
|
||||
// Add the null character as a separator between the
|
||||
// namespace and job id and one for the terminator
|
||||
arg0 += "\x00" + arg1 + "\x00"
|
||||
return []byte(arg0), nil
|
||||
}
|
||||
|
||||
@@ -6127,6 +6127,18 @@ func (s *StateStore) ACLPolicyByJob(ws memdb.WatchSet, ns, jobID string) (memdb.
|
||||
return iter, nil
|
||||
}
|
||||
|
||||
func (s *StateStore) ACLPolicyByNamespace(ws memdb.WatchSet, ns string) (memdb.ResultIterator, error) {
|
||||
txn := s.db.ReadTxn()
|
||||
|
||||
iter, err := txn.Get("acl_policy", "job", ns)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acl policy lookup failed: %v", err)
|
||||
}
|
||||
ws.Add(iter.WatchCh())
|
||||
|
||||
return iter, nil
|
||||
}
|
||||
|
||||
// ACLPolicies returns an iterator over all the acl policies
|
||||
func (s *StateStore) ACLPolicies(ws memdb.WatchSet) (memdb.ResultIterator, error) {
|
||||
txn := s.db.ReadTxn()
|
||||
|
||||
@@ -9142,6 +9142,55 @@ func TestStateStore_UpsertACLPolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateStore_ACLPolicyByNamespace(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
state := testStateStore(t)
|
||||
policy := mock.ACLPolicy()
|
||||
policy.JobACL = &structs.JobACL{
|
||||
Namespace: "default",
|
||||
}
|
||||
policy1 := mock.ACLPolicy()
|
||||
policy1.JobACL = &structs.JobACL{
|
||||
Namespace: "default",
|
||||
JobID: "test-ack-job-name",
|
||||
}
|
||||
policy2 := mock.ACLPolicy()
|
||||
policy2.JobACL = &structs.JobACL{
|
||||
Namespace: "default",
|
||||
JobID: "testing-job",
|
||||
}
|
||||
policy3 := mock.ACLPolicy()
|
||||
policy3.JobACL = &structs.JobACL{
|
||||
Namespace: "testing",
|
||||
JobID: "test-job",
|
||||
}
|
||||
|
||||
err := state.UpsertACLPolicies(structs.MsgTypeTestSetup, 1000, []*structs.ACLPolicy{policy, policy1, policy2})
|
||||
must.NoError(t, err)
|
||||
|
||||
iter, err := state.ACLPolicyByNamespace(nil, "default")
|
||||
must.NoError(t, err)
|
||||
|
||||
out := iter.Next()
|
||||
must.NotNil(t, out)
|
||||
must.Eq(t, policy, out.(*structs.ACLPolicy))
|
||||
|
||||
count := 0
|
||||
for {
|
||||
if iter.Next() == nil {
|
||||
break
|
||||
}
|
||||
|
||||
count++
|
||||
}
|
||||
must.Eq(t, 0, count)
|
||||
|
||||
iter, err = state.ACLPolicyByNamespace(nil, "testing")
|
||||
must.NoError(t, err)
|
||||
must.Nil(t, iter.Next())
|
||||
}
|
||||
|
||||
func TestStateStore_DeleteACLPolicy(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ nomad acl policy apply \
|
||||
redis-policy ./policy.hcl
|
||||
```
|
||||
|
||||
And you can apply this policy to all groups in the job by omitting both the
|
||||
You can apply this policy to all groups in the job by omitting both the
|
||||
`-group` and `-task` flag:
|
||||
|
||||
```shell-session
|
||||
@@ -164,6 +164,14 @@ nomad acl policy apply \
|
||||
redis-policy ./policy.hcl
|
||||
```
|
||||
|
||||
And you can apply this policy to all jobs in the namespace by omitting the
|
||||
`-job`, `-group`, and `-task` flag:
|
||||
|
||||
```shell-session
|
||||
nomad acl policy apply \
|
||||
-namespace default redis-policy ./policy.hcl
|
||||
```
|
||||
|
||||
### Task API
|
||||
|
||||
It can be convenient to combine workload identity with Nomad's [Task API]
|
||||
|
||||
Reference in New Issue
Block a user