Skip to content

Commit

Permalink
ACL: add ACL binding rule RPC and HTTP API handlers. (#15529)
Browse files Browse the repository at this point in the history
This change add the RPC ACL binding rule handlers. These handlers
are responsible for the creation, updating, reading, and deletion
of binding rules.

The write handlers are feature gated so that they can only be used
when all federated servers are running the required version.

The HTTP API handlers and API SDK have also been added where
required. This allows the endpoints to be called from the API by users
and clients.
  • Loading branch information
jrasell authored Dec 15, 2022
1 parent fc4abf2 commit 4d60dd3
Show file tree
Hide file tree
Showing 8 changed files with 1,456 additions and 2 deletions.
151 changes: 150 additions & 1 deletion api/acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ var (
// errMissingACLAuthMethodName is the generic error to use when a call is
// missing the required ACL auth-method name parameter.
errMissingACLAuthMethodName = errors.New("missing ACL auth-method name")

// errMissingACLBindingRuleID is the generic error to use when a call is
// missing the required ACL binding rule ID parameter.
errMissingACLBindingRuleID = errors.New("missing ACL binding rule ID")
)

// ACLRoles is used to query the ACL Role endpoints.
Expand Down Expand Up @@ -369,6 +373,75 @@ func (a *ACLAuthMethods) Get(authMethodName string, q *QueryOptions) (*ACLAuthMe
return &resp, qm, nil
}

// ACLBindingRules is used to query the ACL auth-methods endpoints.
type ACLBindingRules struct {
client *Client
}

// ACLBindingRules returns a new handle on the ACL auth-methods API client.
func (c *Client) ACLBindingRules() *ACLBindingRules {
return &ACLBindingRules{client: c}
}

// List is used to detail all the ACL binding rules currently stored within
// state.
func (a *ACLBindingRules) List(q *QueryOptions) ([]*ACLBindingRuleListStub, *QueryMeta, error) {
var resp []*ACLBindingRuleListStub
qm, err := a.client.query("/v1/acl/binding-rules", &resp, q)
if err != nil {
return nil, nil, err
}
return resp, qm, nil
}

// Create is used to create an ACL binding rule.
func (a *ACLBindingRules) Create(bindingRule *ACLBindingRule, w *WriteOptions) (*ACLBindingRule, *WriteMeta, error) {
var resp ACLBindingRule
wm, err := a.client.write("/v1/acl/binding-rule", bindingRule, &resp, w)
if err != nil {
return nil, nil, err
}
return &resp, wm, nil
}

// Update is used to update an existing ACL binding rule.
func (a *ACLBindingRules) Update(bindingRule *ACLBindingRule, w *WriteOptions) (*ACLBindingRule, *WriteMeta, error) {
if bindingRule.ID == "" {
return nil, nil, errMissingACLBindingRuleID
}
var resp ACLBindingRule
wm, err := a.client.write("/v1/acl/binding-rule/"+bindingRule.ID, bindingRule, &resp, w)
if err != nil {
return nil, nil, err
}
return &resp, wm, nil
}

// Delete is used to delete an ACL binding rule.
func (a *ACLBindingRules) Delete(bindingRuleID string, w *WriteOptions) (*WriteMeta, error) {
if bindingRuleID == "" {
return nil, errMissingACLBindingRuleID
}
wm, err := a.client.delete("/v1/acl/binding-rule/"+bindingRuleID, nil, nil, w)
if err != nil {
return nil, err
}
return wm, nil
}

// Get is used to look up an ACL binding rule.
func (a *ACLBindingRules) Get(bindingRuleID string, q *QueryOptions) (*ACLBindingRule, *QueryMeta, error) {
if bindingRuleID == "" {
return nil, nil, errMissingACLBindingRuleID
}
var resp ACLBindingRule
qm, err := a.client.query("/v1/acl/binding-rule/"+bindingRuleID, &resp, q)
if err != nil {
return nil, nil, err
}
return &resp, qm, nil
}

// ACLPolicyListStub is used to for listing ACL policies
type ACLPolicyListStub struct {
Name string
Expand Down Expand Up @@ -647,7 +720,6 @@ type ACLAuthMethodListStub struct {
Name string
Type string
Default bool
Hash []byte

CreateIndex uint64
ModifyIndex uint64
Expand All @@ -667,3 +739,80 @@ const (
// auth-method which uses the OIDC protocol.
ACLAuthMethodTypeOIDC = "OIDC"
)

// ACLBindingRule contains a direct relation to an ACLAuthMethod and represents
// a rule to apply when logging in via the named AuthMethod. This allows the
// transformation of OIDC provider claims, to Nomad based ACL concepts such as
// ACL Roles and Policies.
type ACLBindingRule struct {

// ID is an internally generated UUID for this rule and is controlled by
// Nomad.
ID string

// Description is a human-readable, operator set description that can
// provide additional context about the binding rule. This is an
// operational field.
Description string

// AuthMethod is the name of the auth method for which this rule applies
// to. This is required and the method must exist within state before the
// cluster administrator can create the rule.
AuthMethod string

// Selector is an expression that matches against verified identity
// attributes returned from the auth method during login. This is optional
// and when not set, provides a catch-all rule.
Selector string

// BindType adjusts how this binding rule is applied at login time. The
// valid values are ACLBindingRuleBindTypeRole and
// ACLBindingRuleBindTypePolicy.
BindType string

// BindName is the target of the binding. Can be lightly templated using
// HIL ${foo} syntax from available field names. How it is used depends
// upon the BindType.
BindName string

CreateTime time.Time
ModifyTime time.Time
CreateIndex uint64
ModifyIndex uint64
}

const (
// ACLBindingRuleBindTypeRole is the ACL binding rule bind type that only
// allows the binding rule to function if a role exists at login-time. The
// role will be specified within the ACLBindingRule.BindName parameter, and
// will identify whether this is an ID or Name.
ACLBindingRuleBindTypeRole = "role"

// ACLBindingRuleBindTypePolicy is the ACL binding rule bind type that
// assigns a policy to the generate ACL token. The role will be specified
// within the ACLBindingRule.BindName parameter, and will be the policy
// name.
ACLBindingRuleBindTypePolicy = "policy"
)

// ACLBindingRuleListStub is the stub object returned when performing a listing
// of ACL binding rules.
type ACLBindingRuleListStub struct {

// ID is an internally generated UUID for this role and is controlled by
// Nomad.
ID string

// Description is a human-readable, operator set description that can
// provide additional context about the binding role. This is an
// operational field.
Description string

// AuthMethod is the name of the auth method for which this rule applies
// to. This is required and the method must exist within state before the
// cluster administrator can create the rule.
AuthMethod string

CreateIndex uint64
ModifyIndex uint64
}
83 changes: 82 additions & 1 deletion api/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,6 @@ func TestACLAuthMethods(t *testing.T) {
must.Len(t, 1, aclAuthMethodsListResp)
must.Eq(t, authMethod.Name, aclAuthMethodsListResp[0].Name)
must.True(t, aclAuthMethodsListResp[0].Default)
must.SliceNotEmpty(t, aclAuthMethodsListResp[0].Hash)
assertQueryMeta(t, queryMeta)

// Read the auth-method.
Expand Down Expand Up @@ -655,3 +654,85 @@ func TestACLAuthMethods(t *testing.T) {
must.Len(t, 0, aclAuthMethodsListResp)
assertQueryMeta(t, queryMeta)
}

func TestACLBindingRules(t *testing.T) {
testutil.Parallel(t)

testClient, testServer, _ := makeACLClient(t, nil, nil)
defer testServer.Stop()

//
aclAuthMethod := ACLAuthMethod{
Name: "auth0",
Type: ACLAuthMethodTypeOIDC,
TokenLocality: ACLAuthMethodTokenLocalityGlobal,
MaxTokenTTL: 10 * time.Hour,
Default: true,
}
_, _, err := testClient.ACLAuthMethods().Create(&aclAuthMethod, nil)
must.NoError(t, err)

// An initial listing shouldn't return any results.
aclBindingRulesListResp, queryMeta, err := testClient.ACLBindingRules().List(nil)
must.NoError(t, err)
must.Len(t, 0, aclBindingRulesListResp)
assertQueryMeta(t, queryMeta)

// Create an ACL auth-method.
bindingRule := ACLBindingRule{
Description: "my-binding-rule",
AuthMethod: "auth0",
Selector: "nomad-engineering-team in list.groups",
BindType: "role",
BindName: "cluster-admin",
}
_, writeMeta, err := testClient.ACLBindingRules().Create(&bindingRule, nil)
must.NoError(t, err)
assertWriteMeta(t, writeMeta)

// Another listing should return one result.
aclBindingRulesListResp, queryMeta, err = testClient.ACLBindingRules().List(nil)
must.NoError(t, err)
must.Len(t, 1, aclBindingRulesListResp)
must.NotEq(t, "", aclBindingRulesListResp[0].ID)
must.Eq(t, "auth0", aclBindingRulesListResp[0].AuthMethod)
assertQueryMeta(t, queryMeta)

bindingRuleID := aclBindingRulesListResp[0].ID

// Read the binding rule.
aclBindingRuleReadResp, queryMeta, err := testClient.ACLBindingRules().Get(bindingRuleID, nil)
must.NoError(t, err)
assertQueryMeta(t, queryMeta)
must.NotNil(t, aclBindingRuleReadResp)
must.Eq(t, bindingRuleID, aclBindingRuleReadResp.ID)
must.Eq(t, bindingRule.Description, aclBindingRuleReadResp.Description)
must.Eq(t, bindingRule.AuthMethod, aclBindingRuleReadResp.AuthMethod)
must.Eq(t, bindingRule.Selector, aclBindingRuleReadResp.Selector)
must.Eq(t, bindingRule.BindType, aclBindingRuleReadResp.BindType)
must.Eq(t, bindingRule.BindName, aclBindingRuleReadResp.BindName)

// Update the binding rule description.
bindingRule.ID = bindingRuleID
bindingRule.Description = "my-binding-rule-updated"
aclBindingRuleUpdateResp, writeMeta, err := testClient.ACLBindingRules().Update(&bindingRule, nil)
must.NoError(t, err)
assertWriteMeta(t, writeMeta)
must.Eq(t, bindingRuleID, aclBindingRuleUpdateResp.ID)
must.Eq(t, bindingRule.Description, aclBindingRuleUpdateResp.Description)
must.Eq(t, bindingRule.AuthMethod, aclBindingRuleUpdateResp.AuthMethod)
must.Eq(t, bindingRule.Selector, aclBindingRuleUpdateResp.Selector)
must.Eq(t, bindingRule.BindType, aclBindingRuleUpdateResp.BindType)
must.Eq(t, bindingRule.BindName, aclBindingRuleUpdateResp.BindName)

// Delete the role.
writeMeta, err = testClient.ACLBindingRules().Delete(bindingRuleID, nil)
must.NoError(t, err)
assertWriteMeta(t, writeMeta)

// Make sure there are no ACL auth-methods now present.
aclBindingRulesListResp, queryMeta, err = testClient.ACLBindingRules().List(nil)
must.NoError(t, err)
must.Len(t, 0, aclBindingRulesListResp)
assertQueryMeta(t, queryMeta)
}
Loading

0 comments on commit 4d60dd3

Please sign in to comment.