github.com/hernad/nomad@v1.6.112/nomad/structs/acl.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package structs
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"regexp"
    12  	"strconv"
    13  	"time"
    14  
    15  	"github.com/hashicorp/go-bexpr"
    16  	"github.com/hashicorp/go-multierror"
    17  	"github.com/hashicorp/go-set"
    18  	lru "github.com/hashicorp/golang-lru/v2"
    19  	"github.com/hernad/nomad/helper"
    20  	"github.com/hernad/nomad/helper/pointer"
    21  	"github.com/hernad/nomad/helper/uuid"
    22  	"github.com/hernad/nomad/lib/lang"
    23  	"golang.org/x/crypto/blake2b"
    24  	"golang.org/x/exp/slices"
    25  	"oss.indeed.com/go/libtime"
    26  )
    27  
    28  const (
    29  	// ACLUpsertPoliciesRPCMethod is the RPC method for batch creating or
    30  	// modifying ACL policies.
    31  	//
    32  	// Args: ACLPolicyUpsertRequest
    33  	// Reply: GenericResponse
    34  	ACLUpsertPoliciesRPCMethod = "ACL.UpsertPolicies"
    35  
    36  	// ACLUpsertTokensRPCMethod is the RPC method for batch creating or
    37  	// modifying ACL tokens.
    38  	//
    39  	// Args: ACLTokenUpsertRequest
    40  	// Reply: ACLTokenUpsertResponse
    41  	ACLUpsertTokensRPCMethod = "ACL.UpsertTokens"
    42  
    43  	// ACLDeleteTokensRPCMethod is the RPC method for batch deleting ACL
    44  	// tokens.
    45  	//
    46  	// Args: ACLTokenDeleteRequest
    47  	// Reply: GenericResponse
    48  	ACLDeleteTokensRPCMethod = "ACL.DeleteTokens"
    49  
    50  	// ACLUpsertRolesRPCMethod is the RPC method for batch creating or
    51  	// modifying ACL roles.
    52  	//
    53  	// Args: ACLRolesUpsertRequest
    54  	// Reply: ACLRolesUpsertResponse
    55  	ACLUpsertRolesRPCMethod = "ACL.UpsertRoles"
    56  
    57  	// ACLDeleteRolesByIDRPCMethod the RPC method for batch deleting ACL
    58  	// roles by their ID.
    59  	//
    60  	// Args: ACLRolesDeleteByIDRequest
    61  	// Reply: ACLRolesDeleteByIDResponse
    62  	ACLDeleteRolesByIDRPCMethod = "ACL.DeleteRolesByID"
    63  
    64  	// ACLListRolesRPCMethod is the RPC method for listing ACL roles.
    65  	//
    66  	// Args: ACLRolesListRequest
    67  	// Reply: ACLRolesListResponse
    68  	ACLListRolesRPCMethod = "ACL.ListRoles"
    69  
    70  	// ACLGetRolesByIDRPCMethod is the RPC method for detailing a number of ACL
    71  	// roles using their ID. This is an internal only RPC endpoint and used by
    72  	// the ACL Role replication process.
    73  	//
    74  	// Args: ACLRolesByIDRequest
    75  	// Reply: ACLRolesByIDResponse
    76  	ACLGetRolesByIDRPCMethod = "ACL.GetRolesByID"
    77  
    78  	// ACLGetRoleByIDRPCMethod is the RPC method for detailing an individual
    79  	// ACL role using its ID.
    80  	//
    81  	// Args: ACLRoleByIDRequest
    82  	// Reply: ACLRoleByIDResponse
    83  	ACLGetRoleByIDRPCMethod = "ACL.GetRoleByID"
    84  
    85  	// ACLGetRoleByNameRPCMethod is the RPC method for detailing an individual
    86  	// ACL role using its name.
    87  	//
    88  	// Args: ACLRoleByNameRequest
    89  	// Reply: ACLRoleByNameResponse
    90  	ACLGetRoleByNameRPCMethod = "ACL.GetRoleByName"
    91  
    92  	// ACLUpsertAuthMethodsRPCMethod is the RPC method for batch creating or
    93  	// modifying auth methods.
    94  	//
    95  	// Args: ACLAuthMethodsUpsertRequest
    96  	// Reply: ACLAuthMethodUpsertResponse
    97  	ACLUpsertAuthMethodsRPCMethod = "ACL.UpsertAuthMethods"
    98  
    99  	// ACLDeleteAuthMethodsRPCMethod is the RPC method for batch deleting auth
   100  	// methods.
   101  	//
   102  	// Args: ACLAuthMethodDeleteRequest
   103  	// Reply: ACLAuthMethodDeleteResponse
   104  	ACLDeleteAuthMethodsRPCMethod = "ACL.DeleteAuthMethods"
   105  
   106  	// ACLListAuthMethodsRPCMethod is the RPC method for listing auth methods.
   107  	//
   108  	// Args: ACLAuthMethodListRequest
   109  	// Reply: ACLAuthMethodListResponse
   110  	ACLListAuthMethodsRPCMethod = "ACL.ListAuthMethods"
   111  
   112  	// ACLGetAuthMethodRPCMethod is the RPC method for detailing an individual
   113  	// auth method using its name.
   114  	//
   115  	// Args: ACLAuthMethodGetRequest
   116  	// Reply: ACLAuthMethodGetResponse
   117  	ACLGetAuthMethodRPCMethod = "ACL.GetAuthMethod"
   118  
   119  	// ACLGetAuthMethodsRPCMethod is the RPC method for getting multiple auth
   120  	// methods using their names.
   121  	//
   122  	// Args: ACLAuthMethodsGetRequest
   123  	// Reply: ACLAuthMethodsGetResponse
   124  	ACLGetAuthMethodsRPCMethod = "ACL.GetAuthMethods"
   125  
   126  	// ACLUpsertBindingRulesRPCMethod is the RPC method for batch creating or
   127  	// modifying binding rules.
   128  	//
   129  	// Args: ACLBindingRulesUpsertRequest
   130  	// Reply: ACLBindingRulesUpsertResponse
   131  	ACLUpsertBindingRulesRPCMethod = "ACL.UpsertBindingRules"
   132  
   133  	// ACLDeleteBindingRulesRPCMethod is the RPC method for batch deleting
   134  	// binding rules.
   135  	//
   136  	// Args: ACLBindingRulesDeleteRequest
   137  	// Reply: ACLBindingRulesDeleteResponse
   138  	ACLDeleteBindingRulesRPCMethod = "ACL.DeleteBindingRules"
   139  
   140  	// ACLListBindingRulesRPCMethod is the RPC method listing binding rules.
   141  	//
   142  	// Args: ACLBindingRulesListRequest
   143  	// Reply: ACLBindingRulesListResponse
   144  	ACLListBindingRulesRPCMethod = "ACL.ListBindingRules"
   145  
   146  	// ACLGetBindingRulesRPCMethod is the RPC method for getting multiple
   147  	// binding rules using their IDs.
   148  	//
   149  	// Args: ACLBindingRulesRequest
   150  	// Reply: ACLBindingRulesResponse
   151  	ACLGetBindingRulesRPCMethod = "ACL.GetBindingRules"
   152  
   153  	// ACLGetBindingRuleRPCMethod is the RPC method for detailing an individual
   154  	// binding rule using its ID.
   155  	//
   156  	// Args: ACLBindingRuleRequest
   157  	// Reply: ACLBindingRuleResponse
   158  	ACLGetBindingRuleRPCMethod = "ACL.GetBindingRule"
   159  
   160  	// ACLOIDCAuthURLRPCMethod is the RPC method for starting the OIDC login
   161  	// workflow. It generates the OIDC provider URL which will be used for user
   162  	// authentication.
   163  	//
   164  	// Args: ACLOIDCAuthURLRequest
   165  	// Reply: ACLOIDCAuthURLResponse
   166  	ACLOIDCAuthURLRPCMethod = "ACL.OIDCAuthURL"
   167  
   168  	// ACLOIDCCompleteAuthRPCMethod is the RPC method for completing the OIDC
   169  	// login workflow. It exchanges the OIDC provider token for a Nomad ACL
   170  	// token with roles as defined within the remote provider.
   171  	//
   172  	// Args: ACLOIDCCompleteAuthRequest
   173  	// Reply: ACLOIDCCompleteAuthResponse
   174  	ACLOIDCCompleteAuthRPCMethod = "ACL.OIDCCompleteAuth"
   175  
   176  	// ACLLoginRPCMethod is the RPC method for performing a non-OIDC login
   177  	// workflow. It exchanges the provided token for a Nomad ACL token with
   178  	// roles as defined within the remote provider.
   179  	//
   180  	// Args: ACLLoginRequest
   181  	// Reply: ACLLoginResponse
   182  	ACLLoginRPCMethod = "ACL.Login"
   183  )
   184  
   185  const (
   186  	// ACLMaxExpiredBatchSize is the maximum number of expired ACL tokens that
   187  	// will be garbage collected in a single trigger. This number helps limit
   188  	// the replication pressure due to expired token deletion. If there are a
   189  	// large number of expired tokens pending garbage collection, this value is
   190  	// a potential limiting factor.
   191  	ACLMaxExpiredBatchSize = 4096
   192  
   193  	// maxACLRoleDescriptionLength limits an ACL roles description length.
   194  	maxACLRoleDescriptionLength = 256
   195  
   196  	// maxACLBindingRuleDescriptionLength limits an ACL binding rules
   197  	// description length and should be used to validate the object.
   198  	maxACLBindingRuleDescriptionLength = 256
   199  
   200  	// ACLAuthMethodTokenLocalityLocal is the ACLAuthMethod.TokenLocality that
   201  	// will generate ACL tokens which can only be used on the local cluster the
   202  	// request was made.
   203  	ACLAuthMethodTokenLocalityLocal = "local"
   204  
   205  	// ACLAuthMethodTokenLocalityGlobal is the ACLAuthMethod.TokenLocality that
   206  	// will generate ACL tokens which can be used on all federated clusters.
   207  	ACLAuthMethodTokenLocalityGlobal = "global"
   208  
   209  	// ACLAuthMethodTypeOIDC the ACLAuthMethod.Type and represents an
   210  	// auth-method which uses the OIDC protocol.
   211  	ACLAuthMethodTypeOIDC = "OIDC"
   212  
   213  	// ACLAuthMethodTypeJWT the ACLAuthMethod.Type and represents an auth-method
   214  	// which uses the JWT type.
   215  	ACLAuthMethodTypeJWT = "JWT"
   216  )
   217  
   218  var (
   219  	// ValidACLRoleName is used to validate an ACL role name.
   220  	ValidACLRoleName = regexp.MustCompile("^[a-zA-Z0-9-]{1,128}$")
   221  
   222  	// ValidACLAuthMethod is used to validate an ACL auth method name.
   223  	ValidACLAuthMethod = regexp.MustCompile("^[a-zA-Z0-9-]{1,128}$")
   224  
   225  	// ValitACLAuthMethodTypes lists supported auth method types.
   226  	ValidACLAuthMethodTypes = []string{ACLAuthMethodTypeOIDC, ACLAuthMethodTypeJWT}
   227  )
   228  
   229  type ACLCacheEntry[T any] lang.Pair[T, time.Time]
   230  
   231  func (e ACLCacheEntry[T]) Age() time.Duration {
   232  	return time.Since(e.Second)
   233  }
   234  
   235  func (e ACLCacheEntry[T]) Get() T {
   236  	return e.First
   237  }
   238  
   239  // An ACLCache caches ACL tokens by their policy content.
   240  type ACLCache[T any] struct {
   241  	*lru.TwoQueueCache[string, ACLCacheEntry[T]]
   242  	clock libtime.Clock
   243  }
   244  
   245  func (c *ACLCache[T]) Add(key string, item T) {
   246  	c.AddAtTime(key, item, c.clock.Now())
   247  }
   248  
   249  func (c *ACLCache[T]) AddAtTime(key string, item T, now time.Time) {
   250  	c.TwoQueueCache.Add(key, ACLCacheEntry[T]{
   251  		First:  item,
   252  		Second: now,
   253  	})
   254  }
   255  
   256  func NewACLCache[T any](size int) *ACLCache[T] {
   257  	c, err := lru.New2Q[string, ACLCacheEntry[T]](size)
   258  	if err != nil {
   259  		panic(err) // not possible
   260  	}
   261  	return &ACLCache[T]{
   262  		TwoQueueCache: c,
   263  		clock:         libtime.SystemClock(),
   264  	}
   265  }
   266  
   267  // ACLTokenRoleLink is used to link an ACL token to an ACL role. The ACL token
   268  // can therefore inherit all the ACL policy permissions that the ACL role
   269  // contains.
   270  type ACLTokenRoleLink struct {
   271  
   272  	// ID is the ACLRole.ID UUID. This field is immutable and represents the
   273  	// absolute truth for the link.
   274  	ID string
   275  
   276  	// Name is the human friendly identifier for the ACL role and is a
   277  	// convenience field for operators. This field is always resolved to the
   278  	// ID and discarded before the token is stored in state. This is because
   279  	// operators can change the name of an ACL role.
   280  	Name string
   281  }
   282  
   283  // Canonicalize performs basic canonicalization on the ACL token object. It is
   284  // important for callers to understand certain fields such as AccessorID are
   285  // set if it is empty, so copies should be taken if needed before calling this
   286  // function.
   287  func (a *ACLToken) Canonicalize() {
   288  
   289  	// If the accessor ID is empty, it means this is creation of a new token,
   290  	// therefore we need to generate base information.
   291  	if a.AccessorID == "" {
   292  
   293  		a.AccessorID = uuid.Generate()
   294  		a.SecretID = uuid.Generate()
   295  		a.CreateTime = time.Now().UTC()
   296  
   297  		// If the user has not set the expiration time, but has provided a TTL, we
   298  		// calculate and populate the former filed.
   299  		if a.ExpirationTime == nil && a.ExpirationTTL != 0 {
   300  			a.ExpirationTime = pointer.Of(a.CreateTime.Add(a.ExpirationTTL))
   301  		}
   302  	}
   303  }
   304  
   305  // Validate is used to check a token for reasonableness
   306  func (a *ACLToken) Validate(minTTL, maxTTL time.Duration, existing *ACLToken) error {
   307  	var mErr multierror.Error
   308  
   309  	// The human friendly name of an ACL token cannot exceed 256 characters.
   310  	if len(a.Name) > maxTokenNameLength {
   311  		mErr.Errors = append(mErr.Errors, errors.New("token name too long"))
   312  	}
   313  
   314  	// The type of an ACL token must be set. An ACL token of type client must
   315  	// have associated policies or roles, whereas a management token cannot be
   316  	// associated with policies.
   317  	switch a.Type {
   318  	case ACLClientToken:
   319  		if len(a.Policies) == 0 && len(a.Roles) == 0 {
   320  			mErr.Errors = append(mErr.Errors, errors.New("client token missing policies or roles"))
   321  		}
   322  	case ACLManagementToken:
   323  		if len(a.Policies) != 0 || len(a.Roles) != 0 {
   324  			mErr.Errors = append(mErr.Errors, errors.New("management token cannot be associated with policies or roles"))
   325  		}
   326  	default:
   327  		mErr.Errors = append(mErr.Errors, errors.New("token type must be client or management"))
   328  	}
   329  
   330  	// There are different validation rules depending on whether the ACL token
   331  	// is being created or updated.
   332  	switch existing {
   333  	case nil:
   334  		if a.ExpirationTTL < 0 {
   335  			mErr.Errors = append(mErr.Errors,
   336  				fmt.Errorf("token expiration TTL '%s' should not be negative", a.ExpirationTTL))
   337  		}
   338  
   339  		if a.ExpirationTime != nil && !a.ExpirationTime.IsZero() {
   340  
   341  			if a.CreateTime.After(*a.ExpirationTime) {
   342  				mErr.Errors = append(mErr.Errors, errors.New("expiration time cannot be before create time"))
   343  			}
   344  
   345  			// Create a time duration which details the time-til-expiry, so we can
   346  			// check this against the regions max and min values.
   347  			expiresIn := a.ExpirationTime.Sub(a.CreateTime)
   348  			if expiresIn > maxTTL {
   349  				mErr.Errors = append(mErr.Errors,
   350  					fmt.Errorf("expiration time cannot be more than %s in the future (was %s)",
   351  						maxTTL, expiresIn))
   352  
   353  			} else if expiresIn < minTTL {
   354  				mErr.Errors = append(mErr.Errors,
   355  					fmt.Errorf("expiration time cannot be less than %s in the future (was %s)",
   356  						minTTL, expiresIn))
   357  			}
   358  		}
   359  	default:
   360  		if existing.Global != a.Global {
   361  			mErr.Errors = append(mErr.Errors, errors.New("cannot toggle global mode"))
   362  		}
   363  		if existing.ExpirationTTL != a.ExpirationTTL {
   364  			mErr.Errors = append(mErr.Errors, errors.New("cannot update expiration TTL"))
   365  		}
   366  		if existing.ExpirationTime != a.ExpirationTime {
   367  			mErr.Errors = append(mErr.Errors, errors.New("cannot update expiration time"))
   368  		}
   369  	}
   370  
   371  	return mErr.ErrorOrNil()
   372  }
   373  
   374  // HasExpirationTime checks whether the ACL token has an expiration time value
   375  // set.
   376  func (a *ACLToken) HasExpirationTime() bool {
   377  	if a == nil || a.ExpirationTime == nil {
   378  		return false
   379  	}
   380  	return !a.ExpirationTime.IsZero()
   381  }
   382  
   383  // IsExpired compares the ACLToken.ExpirationTime against the passed t to
   384  // identify whether the token is considered expired. The function can be called
   385  // without checking whether the ACL token has an expiry time.
   386  func (a *ACLToken) IsExpired(t time.Time) bool {
   387  
   388  	// Check the token has an expiration time before potentially modifying the
   389  	// supplied time. This allows us to avoid extra work, if it isn't needed.
   390  	if !a.HasExpirationTime() {
   391  		return false
   392  	}
   393  
   394  	// Check and ensure the time location is set to UTC. This is vital for
   395  	// consistency with multi-region global tokens.
   396  	if t.Location() != time.UTC {
   397  		t = t.UTC()
   398  	}
   399  
   400  	return a.ExpirationTime.Before(t) || t.IsZero()
   401  }
   402  
   403  // HasRoles checks if a given set of role IDs are assigned to the ACL token. It
   404  // does not account for management tokens, therefore it is the responsibility
   405  // of the caller to perform this check, if required.
   406  func (a *ACLToken) HasRoles(roleIDs []string) bool {
   407  
   408  	// Generate a set of role IDs that the token is assigned.
   409  	roleSet := set.FromFunc(a.Roles, func(roleLink *ACLTokenRoleLink) string { return roleLink.ID })
   410  
   411  	// Iterate the role IDs within the request and check whether these are
   412  	// present within the token assignment.
   413  	for _, roleID := range roleIDs {
   414  		if !roleSet.Contains(roleID) {
   415  			return false
   416  		}
   417  	}
   418  	return true
   419  }
   420  
   421  // MarshalJSON implements the json.Marshaler interface and allows
   422  // ACLToken.ExpirationTTL to be marshaled correctly.
   423  func (a *ACLToken) MarshalJSON() ([]byte, error) {
   424  	type Alias ACLToken
   425  	exported := &struct {
   426  		ExpirationTTL string
   427  		*Alias
   428  	}{
   429  		ExpirationTTL: a.ExpirationTTL.String(),
   430  		Alias:         (*Alias)(a),
   431  	}
   432  	if a.ExpirationTTL == 0 {
   433  		exported.ExpirationTTL = ""
   434  	}
   435  	return json.Marshal(exported)
   436  }
   437  
   438  // UnmarshalJSON implements the json.Unmarshaler interface and allows
   439  // ACLToken.ExpirationTTL to be unmarshalled correctly.
   440  func (a *ACLToken) UnmarshalJSON(data []byte) (err error) {
   441  	type Alias ACLToken
   442  	aux := &struct {
   443  		ExpirationTTL interface{}
   444  		Hash          string
   445  		*Alias
   446  	}{
   447  		Alias: (*Alias)(a),
   448  	}
   449  
   450  	if err = json.Unmarshal(data, &aux); err != nil {
   451  		return err
   452  	}
   453  	if aux.ExpirationTTL != nil {
   454  		switch v := aux.ExpirationTTL.(type) {
   455  		case string:
   456  			if v != "" {
   457  				if a.ExpirationTTL, err = time.ParseDuration(v); err != nil {
   458  					return err
   459  				}
   460  			}
   461  		case float64:
   462  			a.ExpirationTTL = time.Duration(v)
   463  		}
   464  
   465  	}
   466  	if aux.Hash != "" {
   467  		a.Hash = []byte(aux.Hash)
   468  	}
   469  	return nil
   470  }
   471  
   472  // ACLRole is an abstraction for the ACL system which allows the grouping of
   473  // ACL policies into a single object. ACL tokens can be created and linked to
   474  // a role; the token then inherits all the permissions granted by the policies.
   475  type ACLRole struct {
   476  
   477  	// ID is an internally generated UUID for this role and is controlled by
   478  	// Nomad.
   479  	ID string
   480  
   481  	// Name is unique across the entire set of federated clusters and is
   482  	// supplied by the operator on role creation. The name can be modified by
   483  	// updating the role and including the Nomad generated ID. This update will
   484  	// not affect tokens created and linked to this role. This is a required
   485  	// field.
   486  	Name string
   487  
   488  	// Description is a human-readable, operator set description that can
   489  	// provide additional context about the role. This is an operational field.
   490  	Description string
   491  
   492  	// Policies is an array of ACL policy links. Although currently policies
   493  	// can only be linked using their name, in the future we will want to add
   494  	// IDs also and thus allow operators to specify either a name, an ID, or
   495  	// both.
   496  	Policies []*ACLRolePolicyLink
   497  
   498  	// Hash is the hashed value of the role and is generated using all fields
   499  	// above this point.
   500  	Hash []byte
   501  
   502  	CreateIndex uint64
   503  	ModifyIndex uint64
   504  }
   505  
   506  // ACLRolePolicyLink is used to link a policy to an ACL role. We use a struct
   507  // rather than a list of strings as in the future we will want to add IDs to
   508  // policies and then link via these.
   509  type ACLRolePolicyLink struct {
   510  
   511  	// Name is the ACLPolicy.Name value which will be linked to the ACL role.
   512  	Name string
   513  }
   514  
   515  // SetHash is used to compute and set the hash of the ACL role. This should be
   516  // called every and each time a user specified field on the role is changed
   517  // before updating the Nomad state store.
   518  func (a *ACLRole) SetHash() []byte {
   519  
   520  	// Initialize a 256bit Blake2 hash (32 bytes).
   521  	hash, err := blake2b.New256(nil)
   522  	if err != nil {
   523  		panic(err)
   524  	}
   525  
   526  	// Write all the user set fields.
   527  	_, _ = hash.Write([]byte(a.Name))
   528  	_, _ = hash.Write([]byte(a.Description))
   529  
   530  	for _, policyLink := range a.Policies {
   531  		_, _ = hash.Write([]byte(policyLink.Name))
   532  	}
   533  
   534  	// Finalize the hash.
   535  	hashVal := hash.Sum(nil)
   536  
   537  	// Set and return the hash.
   538  	a.Hash = hashVal
   539  	return hashVal
   540  }
   541  
   542  // Validate ensure the ACL role contains valid information which meets Nomad's
   543  // internal requirements. This does not include any state calls, such as
   544  // ensuring the linked policies exist.
   545  func (a *ACLRole) Validate() error {
   546  
   547  	var mErr multierror.Error
   548  
   549  	if !ValidACLRoleName.MatchString(a.Name) {
   550  		mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid name '%s'", a.Name))
   551  	}
   552  
   553  	if len(a.Description) > maxACLRoleDescriptionLength {
   554  		mErr.Errors = append(mErr.Errors, fmt.Errorf("description longer than %d", maxACLRoleDescriptionLength))
   555  	}
   556  
   557  	if len(a.Policies) < 1 {
   558  		mErr.Errors = append(mErr.Errors, errors.New("at least one policy should be specified"))
   559  	}
   560  
   561  	return mErr.ErrorOrNil()
   562  }
   563  
   564  // Canonicalize performs basic canonicalization on the ACL role object. It is
   565  // important for callers to understand certain fields such as ID are set if it
   566  // is empty, so copies should be taken if needed before calling this function.
   567  func (a *ACLRole) Canonicalize() {
   568  	if a.ID == "" {
   569  		a.ID = uuid.Generate()
   570  	}
   571  }
   572  
   573  // Equal performs an equality check on the two service registrations. It
   574  // handles nil objects.
   575  func (a *ACLRole) Equal(o *ACLRole) bool {
   576  	if a == nil || o == nil {
   577  		return a == o
   578  	}
   579  	if len(a.Hash) == 0 {
   580  		a.SetHash()
   581  	}
   582  	if len(o.Hash) == 0 {
   583  		o.SetHash()
   584  	}
   585  	return bytes.Equal(a.Hash, o.Hash)
   586  }
   587  
   588  // Copy creates a deep copy of the ACL role. This copy can then be safely
   589  // modified. It handles nil objects.
   590  func (a *ACLRole) Copy() *ACLRole {
   591  	if a == nil {
   592  		return nil
   593  	}
   594  
   595  	c := new(ACLRole)
   596  	*c = *a
   597  
   598  	c.Policies = slices.Clone(a.Policies)
   599  	c.Hash = slices.Clone(a.Hash)
   600  
   601  	return c
   602  }
   603  
   604  // Stub converts the ACLRole object into a ACLRoleListStub object.
   605  func (a *ACLRole) Stub() *ACLRoleListStub {
   606  	return &ACLRoleListStub{
   607  		ID:          a.ID,
   608  		Name:        a.Name,
   609  		Description: a.Description,
   610  		Policies:    a.Policies,
   611  		Hash:        a.Hash,
   612  		CreateIndex: a.CreateIndex,
   613  		ModifyIndex: a.ModifyIndex,
   614  	}
   615  }
   616  
   617  // ACLRoleListStub is the stub object returned when performing a listing of ACL
   618  // roles. While it might not currently be different to the full response
   619  // object, it allows us to future-proof the RPC in the event the ACLRole object
   620  // grows over time.
   621  type ACLRoleListStub struct {
   622  
   623  	// ID is an internally generated UUID for this role and is controlled by
   624  	// Nomad.
   625  	ID string
   626  
   627  	// Name is unique across the entire set of federated clusters and is
   628  	// supplied by the operator on role creation. The name can be modified by
   629  	// updating the role and including the Nomad generated ID. This update will
   630  	// not affect tokens created and linked to this role. This is a required
   631  	// field.
   632  	Name string
   633  
   634  	// Description is a human-readable, operator set description that can
   635  	// provide additional context about the role. This is an operational field.
   636  	Description string
   637  
   638  	// Policies is an array of ACL policy links. Although currently policies
   639  	// can only be linked using their name, in the future we will want to add
   640  	// IDs also and thus allow operators to specify either a name, an ID, or
   641  	// both.
   642  	Policies []*ACLRolePolicyLink
   643  
   644  	// Hash is the hashed value of the role and is generated using all fields
   645  	// above this point.
   646  	Hash []byte
   647  
   648  	CreateIndex uint64
   649  	ModifyIndex uint64
   650  }
   651  
   652  // ACLRolesUpsertRequest is the request object used to upsert one or more ACL
   653  // roles.
   654  type ACLRolesUpsertRequest struct {
   655  	ACLRoles []*ACLRole
   656  
   657  	// AllowMissingPolicies skips the ACL Role policy link verification and is
   658  	// used by the replication process. The replication cannot ensure policies
   659  	// are present before ACL Roles are replicated.
   660  	AllowMissingPolicies bool
   661  
   662  	WriteRequest
   663  }
   664  
   665  // ACLRolesUpsertResponse is the response object when one or more ACL roles
   666  // have been successfully upserted into state.
   667  type ACLRolesUpsertResponse struct {
   668  	ACLRoles []*ACLRole
   669  	WriteMeta
   670  }
   671  
   672  // ACLRolesDeleteByIDRequest is the request object to delete one or more ACL
   673  // roles using the role ID.
   674  type ACLRolesDeleteByIDRequest struct {
   675  	ACLRoleIDs []string
   676  	WriteRequest
   677  }
   678  
   679  // ACLRolesDeleteByIDResponse is the response object when performing a deletion
   680  // of one or more ACL roles using the role ID.
   681  type ACLRolesDeleteByIDResponse struct {
   682  	WriteMeta
   683  }
   684  
   685  // ACLRolesListRequest is the request object when performing ACL role listings.
   686  type ACLRolesListRequest struct {
   687  	QueryOptions
   688  }
   689  
   690  // ACLRolesListResponse is the response object when performing ACL role
   691  // listings.
   692  type ACLRolesListResponse struct {
   693  	ACLRoles []*ACLRoleListStub
   694  	QueryMeta
   695  }
   696  
   697  // ACLRolesByIDRequest is the request object when performing a lookup of
   698  // multiple roles by the ID.
   699  type ACLRolesByIDRequest struct {
   700  	ACLRoleIDs []string
   701  	QueryOptions
   702  }
   703  
   704  // ACLRolesByIDResponse is the response object when performing a lookup of
   705  // multiple roles by their IDs.
   706  type ACLRolesByIDResponse struct {
   707  	ACLRoles map[string]*ACLRole
   708  	QueryMeta
   709  }
   710  
   711  // ACLRoleByIDRequest is the request object to perform a lookup of an ACL
   712  // role using a specific ID.
   713  type ACLRoleByIDRequest struct {
   714  	RoleID string
   715  	QueryOptions
   716  }
   717  
   718  // ACLRoleByIDResponse is the response object when performing a lookup of an
   719  // ACL role matching a specific ID.
   720  type ACLRoleByIDResponse struct {
   721  	ACLRole *ACLRole
   722  	QueryMeta
   723  }
   724  
   725  // ACLRoleByNameRequest is the request object to perform a lookup of an ACL
   726  // role using a specific name.
   727  type ACLRoleByNameRequest struct {
   728  	RoleName string
   729  	QueryOptions
   730  }
   731  
   732  // ACLRoleByNameResponse is the response object when performing a lookup of an
   733  // ACL role matching a specific name.
   734  type ACLRoleByNameResponse struct {
   735  	ACLRole *ACLRole
   736  	QueryMeta
   737  }
   738  
   739  // ACLAuthMethod is used to capture the properties of an authentication method
   740  // used for single sing-on
   741  type ACLAuthMethod struct {
   742  	Name          string
   743  	Type          string
   744  	TokenLocality string // is the token valid locally or globally?
   745  	MaxTokenTTL   time.Duration
   746  	Default       bool
   747  	Config        *ACLAuthMethodConfig
   748  
   749  	Hash []byte
   750  
   751  	CreateTime  time.Time
   752  	ModifyTime  time.Time
   753  	CreateIndex uint64
   754  	ModifyIndex uint64
   755  }
   756  
   757  // SetHash is used to compute and set the hash of the ACL auth method. This
   758  // should be called every and each time a user specified field on the method is
   759  // changed before updating the Nomad state store.
   760  func (a *ACLAuthMethod) SetHash() []byte {
   761  
   762  	// Initialize a 256bit Blake2 hash (32 bytes).
   763  	hash, err := blake2b.New256(nil)
   764  	if err != nil {
   765  		panic(err)
   766  	}
   767  
   768  	_, _ = hash.Write([]byte(a.Name))
   769  	_, _ = hash.Write([]byte(a.Type))
   770  	_, _ = hash.Write([]byte(a.TokenLocality))
   771  	_, _ = hash.Write([]byte(a.MaxTokenTTL.String()))
   772  	_, _ = hash.Write([]byte(strconv.FormatBool(a.Default)))
   773  
   774  	if a.Config != nil {
   775  		_, _ = hash.Write([]byte(a.Config.OIDCDiscoveryURL))
   776  		_, _ = hash.Write([]byte(a.Config.OIDCClientID))
   777  		_, _ = hash.Write([]byte(a.Config.OIDCClientSecret))
   778  		for _, ba := range a.Config.BoundAudiences {
   779  			_, _ = hash.Write([]byte(ba))
   780  		}
   781  		for _, uri := range a.Config.AllowedRedirectURIs {
   782  			_, _ = hash.Write([]byte(uri))
   783  		}
   784  		for _, pem := range a.Config.DiscoveryCaPem {
   785  			_, _ = hash.Write([]byte(pem))
   786  		}
   787  		for _, sa := range a.Config.SigningAlgs {
   788  			_, _ = hash.Write([]byte(sa))
   789  		}
   790  		for k, v := range a.Config.ClaimMappings {
   791  			_, _ = hash.Write([]byte(k))
   792  			_, _ = hash.Write([]byte(v))
   793  		}
   794  		for k, v := range a.Config.ListClaimMappings {
   795  			_, _ = hash.Write([]byte(k))
   796  			_, _ = hash.Write([]byte(v))
   797  		}
   798  	}
   799  
   800  	// Finalize the hash.
   801  	hashVal := hash.Sum(nil)
   802  
   803  	// Set and return the hash.
   804  	a.Hash = hashVal
   805  	return hashVal
   806  }
   807  
   808  // MarshalJSON implements the json.Marshaler interface and allows
   809  // ACLAuthMethod.MaxTokenTTL to be marshaled correctly.
   810  func (a *ACLAuthMethod) MarshalJSON() ([]byte, error) {
   811  	type Alias ACLAuthMethod
   812  	exported := &struct {
   813  		MaxTokenTTL string
   814  		*Alias
   815  	}{
   816  		MaxTokenTTL: a.MaxTokenTTL.String(),
   817  		Alias:       (*Alias)(a),
   818  	}
   819  	if a.MaxTokenTTL == 0 {
   820  		exported.MaxTokenTTL = ""
   821  	}
   822  	return json.Marshal(exported)
   823  }
   824  
   825  // UnmarshalJSON implements the json.Unmarshaler interface and allows
   826  // ACLAuthMethod.MaxTokenTTL to be unmarshalled correctly.
   827  func (a *ACLAuthMethod) UnmarshalJSON(data []byte) (err error) {
   828  	type Alias ACLAuthMethod
   829  	aux := &struct {
   830  		MaxTokenTTL interface{}
   831  		*Alias
   832  	}{
   833  		Alias: (*Alias)(a),
   834  	}
   835  	if err = json.Unmarshal(data, &aux); err != nil {
   836  		return err
   837  	}
   838  	if aux.MaxTokenTTL != nil {
   839  		switch v := aux.MaxTokenTTL.(type) {
   840  		case string:
   841  			if a.MaxTokenTTL, err = time.ParseDuration(v); err != nil {
   842  				return err
   843  			}
   844  		case float64:
   845  			a.MaxTokenTTL = time.Duration(v)
   846  		}
   847  	}
   848  	return nil
   849  }
   850  
   851  func (a *ACLAuthMethod) Stub() *ACLAuthMethodStub {
   852  	return &ACLAuthMethodStub{
   853  		Name:        a.Name,
   854  		Type:        a.Type,
   855  		Default:     a.Default,
   856  		Hash:        a.Hash,
   857  		CreateIndex: a.CreateIndex,
   858  		ModifyIndex: a.ModifyIndex,
   859  	}
   860  }
   861  
   862  func (a *ACLAuthMethod) Equal(other *ACLAuthMethod) bool {
   863  	if a == nil || other == nil {
   864  		return a == other
   865  	}
   866  	if len(a.Hash) == 0 {
   867  		a.SetHash()
   868  	}
   869  	if len(other.Hash) == 0 {
   870  		other.SetHash()
   871  	}
   872  	return bytes.Equal(a.Hash, other.Hash)
   873  
   874  }
   875  
   876  // Copy creates a deep copy of the ACL auth method. This copy can then be safely
   877  // modified. It handles nil objects.
   878  func (a *ACLAuthMethod) Copy() *ACLAuthMethod {
   879  	if a == nil {
   880  		return nil
   881  	}
   882  
   883  	c := new(ACLAuthMethod)
   884  	*c = *a
   885  
   886  	c.Hash = slices.Clone(a.Hash)
   887  	c.Config = a.Config.Copy()
   888  
   889  	return c
   890  }
   891  
   892  // Canonicalize performs basic canonicalization on the ACL auth method object.
   893  func (a *ACLAuthMethod) Canonicalize() {
   894  	t := time.Now().UTC()
   895  
   896  	if a.CreateTime.IsZero() {
   897  		a.CreateTime = t
   898  	}
   899  	a.ModifyTime = t
   900  }
   901  
   902  // Merge merges auth method a with method b. It sets all required empty fields
   903  // of method a to corresponding values of method b, except for "default" and
   904  // "name."
   905  func (a *ACLAuthMethod) Merge(b *ACLAuthMethod) {
   906  	if b != nil {
   907  		a.Type = helper.Merge(a.Type, b.Type)
   908  		a.TokenLocality = helper.Merge(a.TokenLocality, b.TokenLocality)
   909  		a.MaxTokenTTL = helper.Merge(a.MaxTokenTTL, b.MaxTokenTTL)
   910  		a.Config = helper.Merge(a.Config, b.Config)
   911  	}
   912  }
   913  
   914  // Validate returns an error is the ACLAuthMethod is invalid.
   915  //
   916  // TODO revisit possible other validity conditions in the future
   917  func (a *ACLAuthMethod) Validate(minTTL, maxTTL time.Duration) error {
   918  	var mErr multierror.Error
   919  
   920  	if !ValidACLAuthMethod.MatchString(a.Name) {
   921  		mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid name '%s'", a.Name))
   922  	}
   923  
   924  	if !slices.Contains([]string{"local", "global"}, a.TokenLocality) {
   925  		mErr.Errors = append(
   926  			mErr.Errors, fmt.Errorf("invalid token locality '%s'", a.TokenLocality))
   927  	}
   928  
   929  	if !slices.Contains(ValidACLAuthMethodTypes, a.Type) {
   930  		mErr.Errors = append(
   931  			mErr.Errors, fmt.Errorf("invalid token type '%s'", a.Type))
   932  	}
   933  
   934  	if minTTL > a.MaxTokenTTL || a.MaxTokenTTL > maxTTL {
   935  		mErr.Errors = append(mErr.Errors, fmt.Errorf(
   936  			"invalid MaxTokenTTL value '%s' (should be between %s and %s)",
   937  			a.MaxTokenTTL.String(), minTTL.String(), maxTTL.String()))
   938  	}
   939  
   940  	return mErr.ErrorOrNil()
   941  }
   942  
   943  // TokenLocalityIsGlobal returns whether the auth method creates global ACL
   944  // tokens or not.
   945  func (a *ACLAuthMethod) TokenLocalityIsGlobal() bool { return a.TokenLocality == "global" }
   946  
   947  // ACLAuthMethodConfig is used to store configuration of an auth method
   948  type ACLAuthMethodConfig struct {
   949  	// A list of PEM-encoded public keys to use to authenticate signatures
   950  	// locally
   951  	JWTValidationPubKeys []string
   952  
   953  	// JSON Web Key Sets url for authenticating signatures
   954  	JWKSURL string
   955  
   956  	// The OIDC Discovery URL, without any .well-known component (base path)
   957  	OIDCDiscoveryURL string
   958  
   959  	// The OAuth Client ID configured with the OIDC provider
   960  	OIDCClientID string
   961  
   962  	// The OAuth Client Secret configured with the OIDC provider
   963  	OIDCClientSecret string
   964  
   965  	// List of OIDC scopes
   966  	OIDCScopes []string
   967  
   968  	// List of auth claims that are valid for login
   969  	BoundAudiences []string
   970  
   971  	// The value against which to match the iss claim in a JWT
   972  	BoundIssuer []string
   973  
   974  	// A list of allowed values for redirect_uri
   975  	AllowedRedirectURIs []string
   976  
   977  	// PEM encoded CA certs for use by the TLS client used to talk with the
   978  	// OIDC Discovery URL.
   979  	DiscoveryCaPem []string
   980  
   981  	// PEM encoded CA cert for use by the TLS client used to talk with the JWKS
   982  	// URL
   983  	JWKSCACert string
   984  
   985  	// A list of supported signing algorithms
   986  	SigningAlgs []string
   987  
   988  	// Duration in seconds of leeway when validating expiration of a token to
   989  	// account for clock skew
   990  	ExpirationLeeway time.Duration
   991  
   992  	// Duration in seconds of leeway when validating not before values of a
   993  	// token to account for clock skew.
   994  	NotBeforeLeeway time.Duration
   995  
   996  	// Duration in seconds of leeway when validating all claims to account for
   997  	// clock skew.
   998  	ClockSkewLeeway time.Duration
   999  
  1000  	// Mappings of claims (key) that will be copied to a metadata field
  1001  	// (value).
  1002  	ClaimMappings     map[string]string
  1003  	ListClaimMappings map[string]string
  1004  }
  1005  
  1006  func (a *ACLAuthMethodConfig) Copy() *ACLAuthMethodConfig {
  1007  	if a == nil {
  1008  		return nil
  1009  	}
  1010  
  1011  	c := new(ACLAuthMethodConfig)
  1012  	*c = *a
  1013  
  1014  	c.JWTValidationPubKeys = slices.Clone(a.JWTValidationPubKeys)
  1015  	c.OIDCScopes = slices.Clone(a.OIDCScopes)
  1016  	c.BoundAudiences = slices.Clone(a.BoundAudiences)
  1017  	c.BoundIssuer = slices.Clone(a.BoundIssuer)
  1018  	c.AllowedRedirectURIs = slices.Clone(a.AllowedRedirectURIs)
  1019  	c.DiscoveryCaPem = slices.Clone(a.DiscoveryCaPem)
  1020  	c.SigningAlgs = slices.Clone(a.SigningAlgs)
  1021  
  1022  	return c
  1023  }
  1024  
  1025  // MarshalJSON implements the json.Marshaler interface and allows
  1026  // time.Diration fields to be marshaled correctly.
  1027  func (a *ACLAuthMethodConfig) MarshalJSON() ([]byte, error) {
  1028  	type Alias ACLAuthMethodConfig
  1029  	exported := &struct {
  1030  		ExpirationLeeway string
  1031  		NotBeforeLeeway  string
  1032  		ClockSkewLeeway  string
  1033  		*Alias
  1034  	}{
  1035  		ExpirationLeeway: a.ExpirationLeeway.String(),
  1036  		NotBeforeLeeway:  a.NotBeforeLeeway.String(),
  1037  		ClockSkewLeeway:  a.ClockSkewLeeway.String(),
  1038  		Alias:            (*Alias)(a),
  1039  	}
  1040  	if a.ExpirationLeeway == 0 {
  1041  		exported.ExpirationLeeway = ""
  1042  	}
  1043  	if a.NotBeforeLeeway == 0 {
  1044  		exported.NotBeforeLeeway = ""
  1045  	}
  1046  	if a.ClockSkewLeeway == 0 {
  1047  		exported.ClockSkewLeeway = ""
  1048  	}
  1049  	return json.Marshal(exported)
  1050  }
  1051  
  1052  // UnmarshalJSON implements the json.Unmarshaler interface and allows
  1053  // time.Duration fields to be unmarshalled correctly.
  1054  func (a *ACLAuthMethodConfig) UnmarshalJSON(data []byte) (err error) {
  1055  	type Alias ACLAuthMethodConfig
  1056  	aux := &struct {
  1057  		ExpirationLeeway any
  1058  		NotBeforeLeeway  any
  1059  		ClockSkewLeeway  any
  1060  		*Alias
  1061  	}{
  1062  		Alias: (*Alias)(a),
  1063  	}
  1064  	if err = json.Unmarshal(data, &aux); err != nil {
  1065  		return err
  1066  	}
  1067  	if aux.ExpirationLeeway != nil {
  1068  		switch v := aux.ExpirationLeeway.(type) {
  1069  		case string:
  1070  			if v != "" {
  1071  				if a.ExpirationLeeway, err = time.ParseDuration(v); err != nil {
  1072  					return err
  1073  				}
  1074  			}
  1075  		case float64:
  1076  			a.ExpirationLeeway = time.Duration(v)
  1077  		default:
  1078  			return fmt.Errorf("unexpected ExpirationLeeway type: %v", v)
  1079  		}
  1080  	}
  1081  	if aux.NotBeforeLeeway != nil {
  1082  		switch v := aux.NotBeforeLeeway.(type) {
  1083  		case string:
  1084  			if v != "" {
  1085  				if a.NotBeforeLeeway, err = time.ParseDuration(v); err != nil {
  1086  					return err
  1087  				}
  1088  			}
  1089  		case float64:
  1090  			a.NotBeforeLeeway = time.Duration(v)
  1091  		default:
  1092  			return fmt.Errorf("unexpected NotBeforeLeeway type: %v", v)
  1093  		}
  1094  	}
  1095  	if aux.ClockSkewLeeway != nil {
  1096  		switch v := aux.ClockSkewLeeway.(type) {
  1097  		case string:
  1098  			if v != "" {
  1099  				if a.ClockSkewLeeway, err = time.ParseDuration(v); err != nil {
  1100  					return err
  1101  				}
  1102  			}
  1103  		case float64:
  1104  			a.ClockSkewLeeway = time.Duration(v)
  1105  		default:
  1106  			return fmt.Errorf("unexpected ClockSkewLeeway type: %v", v)
  1107  		}
  1108  	}
  1109  	return nil
  1110  }
  1111  
  1112  // ACLAuthClaims is the claim mapping of the OIDC auth method in a format that
  1113  // can be used with go-bexpr. This structure is used during rule binding
  1114  // evaluation.
  1115  type ACLAuthClaims struct {
  1116  	Value map[string]string   `bexpr:"value"`
  1117  	List  map[string][]string `bexpr:"list"`
  1118  }
  1119  
  1120  // ACLAuthMethodStub is used for listing ACL auth methods
  1121  type ACLAuthMethodStub struct {
  1122  	Name    string
  1123  	Type    string
  1124  	Default bool
  1125  
  1126  	// Hash is the hashed value of the auth-method and is generated using all
  1127  	// fields from the full object except the create and modify times and
  1128  	// indexes.
  1129  	Hash []byte
  1130  
  1131  	CreateIndex uint64
  1132  	ModifyIndex uint64
  1133  }
  1134  
  1135  // ACLAuthMethodListRequest is used to list auth methods
  1136  type ACLAuthMethodListRequest struct {
  1137  	QueryOptions
  1138  }
  1139  
  1140  // ACLAuthMethodListResponse is used to list auth methods
  1141  type ACLAuthMethodListResponse struct {
  1142  	AuthMethods []*ACLAuthMethodStub
  1143  	QueryMeta
  1144  }
  1145  
  1146  // ACLAuthMethodGetRequest is used to query a specific auth method
  1147  type ACLAuthMethodGetRequest struct {
  1148  	MethodName string
  1149  	QueryOptions
  1150  }
  1151  
  1152  // ACLAuthMethodGetResponse is used to return a single auth method
  1153  type ACLAuthMethodGetResponse struct {
  1154  	AuthMethod *ACLAuthMethod
  1155  	QueryMeta
  1156  }
  1157  
  1158  // ACLAuthMethodsGetRequest is used to query a set of auth methods
  1159  type ACLAuthMethodsGetRequest struct {
  1160  	Names []string
  1161  	QueryOptions
  1162  }
  1163  
  1164  // ACLAuthMethodsGetResponse is used to return a set of auth methods
  1165  type ACLAuthMethodsGetResponse struct {
  1166  	AuthMethods map[string]*ACLAuthMethod
  1167  	QueryMeta
  1168  }
  1169  
  1170  // ACLAuthMethodUpsertRequest is used to upsert a set of auth methods
  1171  type ACLAuthMethodUpsertRequest struct {
  1172  	AuthMethods []*ACLAuthMethod
  1173  	WriteRequest
  1174  }
  1175  
  1176  // ACLAuthMethodUpsertResponse is a response of the upsert ACL auth methods
  1177  // operation
  1178  type ACLAuthMethodUpsertResponse struct {
  1179  	AuthMethods []*ACLAuthMethod
  1180  	WriteMeta
  1181  }
  1182  
  1183  // ACLAuthMethodDeleteRequest is used to delete a set of auth methods by their
  1184  // name
  1185  type ACLAuthMethodDeleteRequest struct {
  1186  	Names []string
  1187  	WriteRequest
  1188  }
  1189  
  1190  // ACLAuthMethodDeleteResponse is a response of the delete ACL auth methods
  1191  // operation
  1192  type ACLAuthMethodDeleteResponse struct {
  1193  	WriteMeta
  1194  }
  1195  
  1196  type ACLWhoAmIResponse struct {
  1197  	Identity *AuthenticatedIdentity
  1198  	QueryMeta
  1199  }
  1200  
  1201  // ACLBindingRule contains a direct relation to an ACLAuthMethod and represents
  1202  // a rule to apply when logging in via the named AuthMethod. This allows the
  1203  // transformation of OIDC provider claims, to Nomad based ACL concepts such as
  1204  // ACL Roles and Policies.
  1205  type ACLBindingRule struct {
  1206  
  1207  	// ID is an internally generated UUID for this rule and is controlled by
  1208  	// Nomad.
  1209  	ID string
  1210  
  1211  	// Description is a human-readable, operator set description that can
  1212  	// provide additional context about the binding role. This is an
  1213  	// operational field.
  1214  	Description string
  1215  
  1216  	// AuthMethod is the name of the auth method for which this rule applies
  1217  	// to. This is required and the method must exist within state before the
  1218  	// cluster administrator can create the rule.
  1219  	AuthMethod string
  1220  
  1221  	// Selector is an expression that matches against verified identity
  1222  	// attributes returned from the auth method during login. This is optional
  1223  	// and when not set, provides a catch-all rule.
  1224  	Selector string
  1225  
  1226  	// BindType adjusts how this binding rule is applied at login time. The
  1227  	// valid values are ACLBindingRuleBindTypeRole,
  1228  	// ACLBindingRuleBindTypePolicy, and ACLBindingRuleBindTypeManagement.
  1229  	BindType string
  1230  
  1231  	// BindName is the target of the binding. Can be lightly templated using
  1232  	// HIL ${foo} syntax from available field names. How it is used depends
  1233  	// upon the BindType.
  1234  	BindName string
  1235  
  1236  	// Hash is the hashed value of the binding rule and is generated using all
  1237  	// fields from the full object except the create and modify times and
  1238  	// indexes.
  1239  	Hash []byte
  1240  
  1241  	CreateTime  time.Time
  1242  	ModifyTime  time.Time
  1243  	CreateIndex uint64
  1244  	ModifyIndex uint64
  1245  }
  1246  
  1247  const (
  1248  	// ACLBindingRuleBindTypeRole is the ACL binding rule bind type that only
  1249  	// allows the binding rule to function if a role exists at login-time. The
  1250  	// role will be specified within the ACLBindingRule.BindName parameter, and
  1251  	// will identify whether this is an ID or Name.
  1252  	ACLBindingRuleBindTypeRole = "role"
  1253  
  1254  	// ACLBindingRuleBindTypePolicy is the ACL binding rule bind type that
  1255  	// assigns a policy to the generate ACL token. The role will be specified
  1256  	// within the ACLBindingRule.BindName parameter, and will be the policy
  1257  	// name.
  1258  	ACLBindingRuleBindTypePolicy = "policy"
  1259  
  1260  	// ACLBindingRuleBindTypeManagement is the ACL binding rule bind type that
  1261  	// will generate management ACL tokens when matched.
  1262  	ACLBindingRuleBindTypeManagement = "management"
  1263  )
  1264  
  1265  // Canonicalize performs basic canonicalization on the ACL token object. It is
  1266  // important for callers to understand certain fields such as ID are set if it
  1267  // is empty, so copies should be taken if needed before calling this function.
  1268  func (a *ACLBindingRule) Canonicalize() {
  1269  
  1270  	now := time.Now().UTC()
  1271  
  1272  	// If the ID is empty, it means this is creation of a new binding rule,
  1273  	// therefore we need to generate base information.
  1274  	if a.ID == "" {
  1275  		a.ID = uuid.Generate()
  1276  		a.CreateTime = now
  1277  	}
  1278  
  1279  	// The fact this function is being called indicates we are attempting an
  1280  	// upsert into state. Therefore, update the modify time.
  1281  	a.ModifyTime = now
  1282  }
  1283  
  1284  // Validate ensures the ACL binding rule contains valid information which meets
  1285  // Nomad's internal requirements.
  1286  func (a *ACLBindingRule) Validate() error {
  1287  
  1288  	var mErr multierror.Error
  1289  
  1290  	if a.AuthMethod == "" {
  1291  		mErr.Errors = append(mErr.Errors, errors.New("auth method is missing"))
  1292  	}
  1293  	if len(a.Description) > maxACLBindingRuleDescriptionLength {
  1294  		mErr.Errors = append(mErr.Errors, fmt.Errorf("description longer than %d", maxACLRoleDescriptionLength))
  1295  	}
  1296  
  1297  	// Depending on the bind type, we have some specific validation. Catching
  1298  	// the empty string also provides easier to understand feedback to the
  1299  	// user.
  1300  	switch a.BindType {
  1301  	case "":
  1302  		mErr.Errors = append(mErr.Errors, errors.New("bind type is missing"))
  1303  	case ACLBindingRuleBindTypeRole, ACLBindingRuleBindTypePolicy:
  1304  		if a.BindName == "" {
  1305  			mErr.Errors = append(mErr.Errors, errors.New("bind name is missing"))
  1306  		}
  1307  	case ACLBindingRuleBindTypeManagement:
  1308  		if a.BindName != "" {
  1309  			mErr.Errors = append(mErr.Errors, errors.New("bind name should be empty"))
  1310  		}
  1311  	default:
  1312  		mErr.Errors = append(mErr.Errors, fmt.Errorf("unsupported bind type: %q", a.BindType))
  1313  	}
  1314  
  1315  	// If there is a selector configured, ensure that go-bexpr can parse this.
  1316  	// Otherwise, the user will get an ambiguous failure when attempting to
  1317  	// login.
  1318  	if a.Selector != "" {
  1319  		if _, err := bexpr.CreateEvaluator(a.Selector, nil); err != nil {
  1320  			mErr.Errors = append(mErr.Errors, fmt.Errorf("selector is invalid: %v", err))
  1321  		}
  1322  	}
  1323  
  1324  	return mErr.ErrorOrNil()
  1325  }
  1326  
  1327  // Merge merges binding rule a with b. It sets all required empty fields of rule
  1328  // a to corresponding values of rule b, except for "ID" which must be provided.
  1329  func (a *ACLBindingRule) Merge(b *ACLBindingRule) {
  1330  	a.BindName = helper.Merge(a.BindName, b.BindName)
  1331  	a.BindType = helper.Merge(a.BindType, b.BindType)
  1332  	a.AuthMethod = helper.Merge(a.AuthMethod, b.AuthMethod)
  1333  }
  1334  
  1335  // SetHash is used to compute and set the hash of the ACL binding rule. This
  1336  // should be called every and each time a user specified field on the method is
  1337  // changed before updating the Nomad state store.
  1338  func (a *ACLBindingRule) SetHash() []byte {
  1339  
  1340  	// Initialize a 256bit Blake2 hash (32 bytes).
  1341  	hash, err := blake2b.New256(nil)
  1342  	if err != nil {
  1343  		panic(err)
  1344  	}
  1345  
  1346  	_, _ = hash.Write([]byte(a.ID))
  1347  	_, _ = hash.Write([]byte(a.Description))
  1348  	_, _ = hash.Write([]byte(a.AuthMethod))
  1349  	_, _ = hash.Write([]byte(a.Selector))
  1350  	_, _ = hash.Write([]byte(a.BindType))
  1351  	_, _ = hash.Write([]byte(a.BindName))
  1352  
  1353  	// Finalize the hash.
  1354  	hashVal := hash.Sum(nil)
  1355  
  1356  	// Set and return the hash.
  1357  	a.Hash = hashVal
  1358  	return hashVal
  1359  }
  1360  
  1361  // Equal performs an equality check on the two ACL binding rules. It handles
  1362  // nil objects.
  1363  func (a *ACLBindingRule) Equal(other *ACLBindingRule) bool {
  1364  	if a == nil || other == nil {
  1365  		return a == other
  1366  	}
  1367  	if len(a.Hash) == 0 {
  1368  		a.SetHash()
  1369  	}
  1370  	if len(other.Hash) == 0 {
  1371  		other.SetHash()
  1372  	}
  1373  	return bytes.Equal(a.Hash, other.Hash)
  1374  }
  1375  
  1376  // Copy creates a deep copy of the ACL binding rule. This copy can then be
  1377  // safely modified. It handles nil objects.
  1378  func (a *ACLBindingRule) Copy() *ACLBindingRule {
  1379  	if a == nil {
  1380  		return nil
  1381  	}
  1382  
  1383  	c := new(ACLBindingRule)
  1384  	*c = *a
  1385  	c.Hash = slices.Clone(a.Hash)
  1386  
  1387  	return c
  1388  }
  1389  
  1390  // Stub converts the ACLBindingRule object into a ACLBindingRuleListStub
  1391  // object.
  1392  func (a *ACLBindingRule) Stub() *ACLBindingRuleListStub {
  1393  	return &ACLBindingRuleListStub{
  1394  		ID:          a.ID,
  1395  		Description: a.Description,
  1396  		AuthMethod:  a.AuthMethod,
  1397  		Hash:        a.Hash,
  1398  		CreateIndex: a.CreateIndex,
  1399  		ModifyIndex: a.ModifyIndex,
  1400  	}
  1401  }
  1402  
  1403  // ACLBindingRuleListStub is the stub object returned when performing a listing
  1404  // of ACL binding rules.
  1405  type ACLBindingRuleListStub struct {
  1406  
  1407  	// ID is an internally generated UUID for this role and is controlled by
  1408  	// Nomad.
  1409  	ID string
  1410  
  1411  	// Description is a human-readable, operator set description that can
  1412  	// provide additional context about the binding role. This is an
  1413  	// operational field.
  1414  	Description string
  1415  
  1416  	// AuthMethod is the name of the auth method for which this rule applies
  1417  	// to. This is required and the method must exist within state before the
  1418  	// cluster administrator can create the rule.
  1419  	AuthMethod string
  1420  
  1421  	// Hash is the hashed value of the binding rule and is generated using all
  1422  	// fields from the full object except the create and modify times and
  1423  	// indexes.
  1424  	Hash []byte
  1425  
  1426  	CreateIndex uint64
  1427  	ModifyIndex uint64
  1428  }
  1429  
  1430  // ACLBindingRulesUpsertRequest is used to upsert a set of ACL binding rules.
  1431  type ACLBindingRulesUpsertRequest struct {
  1432  	ACLBindingRules []*ACLBindingRule
  1433  
  1434  	// AllowMissingAuthMethods skips the ACL binding rule auth method link
  1435  	// verification and is used by the replication process. The replication
  1436  	// cannot ensure auth methods are present before ACL binding rules are
  1437  	// replicated.
  1438  	AllowMissingAuthMethods bool
  1439  
  1440  	WriteRequest
  1441  }
  1442  
  1443  // ACLBindingRulesUpsertResponse is a response of the upsert ACL binding rules
  1444  // operation.
  1445  type ACLBindingRulesUpsertResponse struct {
  1446  	ACLBindingRules []*ACLBindingRule
  1447  	WriteMeta
  1448  }
  1449  
  1450  // ACLBindingRulesDeleteRequest is used to delete a set of ACL binding rules by
  1451  // their IDs.
  1452  type ACLBindingRulesDeleteRequest struct {
  1453  	ACLBindingRuleIDs []string
  1454  	WriteRequest
  1455  }
  1456  
  1457  // ACLBindingRulesDeleteResponse is a response of the delete ACL binding rules
  1458  // operation.
  1459  type ACLBindingRulesDeleteResponse struct {
  1460  	WriteMeta
  1461  }
  1462  
  1463  // ACLBindingRulesListRequest  is the request object when performing ACL
  1464  // binding rules listings.
  1465  type ACLBindingRulesListRequest struct {
  1466  	QueryOptions
  1467  }
  1468  
  1469  // ACLBindingRulesListResponse is the response object when performing ACL
  1470  // binding rule listings.
  1471  type ACLBindingRulesListResponse struct {
  1472  	ACLBindingRules []*ACLBindingRuleListStub
  1473  	QueryMeta
  1474  }
  1475  
  1476  // ACLBindingRulesRequest is the request object when performing a lookup of
  1477  // multiple binding rules by the ID.
  1478  type ACLBindingRulesRequest struct {
  1479  	ACLBindingRuleIDs []string
  1480  	QueryOptions
  1481  }
  1482  
  1483  // ACLBindingRulesResponse is the response object when performing a lookup of
  1484  // multiple binding rules by their IDs.
  1485  type ACLBindingRulesResponse struct {
  1486  	ACLBindingRules map[string]*ACLBindingRule
  1487  	QueryMeta
  1488  }
  1489  
  1490  // ACLBindingRuleRequest is the request object to perform a lookup of an ACL
  1491  // binding rule using a specific ID.
  1492  type ACLBindingRuleRequest struct {
  1493  	ACLBindingRuleID string
  1494  	QueryOptions
  1495  }
  1496  
  1497  // ACLBindingRuleResponse is the response object when performing a lookup of an
  1498  // ACL binding rule matching a specific ID.
  1499  type ACLBindingRuleResponse struct {
  1500  	ACLBindingRule *ACLBindingRule
  1501  	QueryMeta
  1502  }
  1503  
  1504  // ACLOIDCAuthURLRequest is the request to make when starting the OIDC
  1505  // authentication login flow.
  1506  type ACLOIDCAuthURLRequest struct {
  1507  
  1508  	// AuthMethodName is the OIDC auth-method to use. This is a required
  1509  	// parameter.
  1510  	AuthMethodName string
  1511  
  1512  	// RedirectURI is the URL that authorization should redirect to. This is a
  1513  	// required parameter.
  1514  	RedirectURI string
  1515  
  1516  	// ClientNonce is a randomly generated string to prevent replay attacks. It
  1517  	// is up to the client to generate this and Go integrations should use the
  1518  	// oidc.NewID function within the hashicorp/cap library. This must then be
  1519  	// passed back to ACLOIDCCompleteAuthRequest. This is a required parameter.
  1520  	ClientNonce string
  1521  
  1522  	// WriteRequest is used due to the requirement by the RPC forwarding
  1523  	// mechanism. This request doesn't write anything to Nomad's internal
  1524  	// state.
  1525  	WriteRequest
  1526  }
  1527  
  1528  // Validate ensures the request object contains all the required fields in
  1529  // order to start the OIDC authentication flow.
  1530  func (a *ACLOIDCAuthURLRequest) Validate() error {
  1531  
  1532  	var mErr multierror.Error
  1533  
  1534  	if a.AuthMethodName == "" {
  1535  		mErr.Errors = append(mErr.Errors, errors.New("missing auth method name"))
  1536  	}
  1537  	if a.ClientNonce == "" {
  1538  		mErr.Errors = append(mErr.Errors, errors.New("missing client nonce"))
  1539  	}
  1540  	if a.RedirectURI == "" {
  1541  		mErr.Errors = append(mErr.Errors, errors.New("missing redirect URI"))
  1542  	}
  1543  	return mErr.ErrorOrNil()
  1544  }
  1545  
  1546  // ACLOIDCAuthURLResponse is the response when starting the OIDC authentication
  1547  // login flow.
  1548  type ACLOIDCAuthURLResponse struct {
  1549  
  1550  	// AuthURL is URL to begin authorization and is where the user logging in
  1551  	// should go.
  1552  	AuthURL string
  1553  }
  1554  
  1555  // ACLOIDCCompleteAuthRequest is the request object to begin completing the
  1556  // OIDC auth cycle after receiving the callback from the OIDC provider.
  1557  type ACLOIDCCompleteAuthRequest struct {
  1558  
  1559  	// AuthMethodName is the name of the auth method being used to login via
  1560  	// OIDC. This will match ACLOIDCAuthURLRequest.AuthMethodName. This is a
  1561  	// required parameter.
  1562  	AuthMethodName string
  1563  
  1564  	// ClientNonce, State, and Code are provided from the parameters given to
  1565  	// the redirect URL. These are all required parameters.
  1566  	ClientNonce string
  1567  	State       string
  1568  	Code        string
  1569  
  1570  	// RedirectURI is the URL that authorization should redirect to. This is a
  1571  	// required parameter.
  1572  	RedirectURI string
  1573  
  1574  	WriteRequest
  1575  }
  1576  
  1577  // Validate ensures the request object contains all the required fields in
  1578  // order to complete the OIDC authentication flow.
  1579  func (a *ACLOIDCCompleteAuthRequest) Validate() error {
  1580  
  1581  	var mErr multierror.Error
  1582  
  1583  	if a.AuthMethodName == "" {
  1584  		mErr.Errors = append(mErr.Errors, errors.New("missing auth method name"))
  1585  	}
  1586  	if a.ClientNonce == "" {
  1587  		mErr.Errors = append(mErr.Errors, errors.New("missing client nonce"))
  1588  	}
  1589  	if a.State == "" {
  1590  		mErr.Errors = append(mErr.Errors, errors.New("missing state"))
  1591  	}
  1592  	if a.Code == "" {
  1593  		mErr.Errors = append(mErr.Errors, errors.New("missing code"))
  1594  	}
  1595  	if a.RedirectURI == "" {
  1596  		mErr.Errors = append(mErr.Errors, errors.New("missing redirect URI"))
  1597  	}
  1598  	return mErr.ErrorOrNil()
  1599  }
  1600  
  1601  // ACLLoginResponse is the response when the auth flow has been
  1602  // completed successfully.
  1603  type ACLLoginResponse struct {
  1604  	ACLToken *ACLToken
  1605  	WriteMeta
  1606  }
  1607  
  1608  // ACLLoginRequest is the request object to begin auth with an external
  1609  // token provider.
  1610  type ACLLoginRequest struct {
  1611  
  1612  	// AuthMethodName is the name of the auth method being used to login. This
  1613  	// is a required parameter.
  1614  	AuthMethodName string
  1615  
  1616  	// LoginToken is the 3rd party token that we use to exchange for Nomad ACL
  1617  	// Token in order to authenticate. This is a required parameter.
  1618  	LoginToken string
  1619  
  1620  	WriteRequest
  1621  }
  1622  
  1623  // Validate ensures the request object contains all the required fields in
  1624  // order to complete the authentication flow.
  1625  func (a *ACLLoginRequest) Validate() error {
  1626  
  1627  	var mErr multierror.Error
  1628  
  1629  	if a.AuthMethodName == "" {
  1630  		mErr.Errors = append(mErr.Errors, errors.New("missing auth method name"))
  1631  	}
  1632  	if a.LoginToken == "" {
  1633  		mErr.Errors = append(mErr.Errors, errors.New("missing login token"))
  1634  	}
  1635  	return mErr.ErrorOrNil()
  1636  }