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

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package structs
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"fmt"
    10  	"reflect"
    11  	"regexp"
    12  	"strings"
    13  )
    14  
    15  const (
    16  	// VariablesApplyRPCMethod is the RPC method for upserting or deleting a
    17  	// variable by its namespace and path, with optional conflict detection.
    18  	//
    19  	// Args: VariablesApplyRequest
    20  	// Reply: VariablesApplyResponse
    21  	VariablesApplyRPCMethod = "Variables.Apply"
    22  
    23  	// VariablesListRPCMethod is the RPC method for listing variables within
    24  	// Nomad.
    25  	//
    26  	// Args: VariablesListRequest
    27  	// Reply: VariablesListResponse
    28  	VariablesListRPCMethod = "Variables.List"
    29  
    30  	// VariablesReadRPCMethod is the RPC method for fetching a variable
    31  	// according to its namepace and path.
    32  	//
    33  	// Args: VariablesByNameRequest
    34  	// Reply: VariablesByNameResponse
    35  	VariablesReadRPCMethod = "Variables.Read"
    36  
    37  	// maxVariableSize is the maximum size of the unencrypted contents of a
    38  	// variable. This size is deliberately set low and is not configurable, to
    39  	// discourage DoS'ing the cluster
    40  	maxVariableSize = 65536
    41  )
    42  
    43  // VariableMetadata is the metadata envelope for a Variable, it is the list
    44  // object and is shared data between an VariableEncrypted and a
    45  // VariableDecrypted object.
    46  type VariableMetadata struct {
    47  	Namespace   string
    48  	Path        string
    49  	CreateIndex uint64
    50  	CreateTime  int64
    51  	ModifyIndex uint64
    52  	ModifyTime  int64
    53  }
    54  
    55  // VariableEncrypted structs are returned from the Encrypter's encrypt
    56  // method. They are the only form that should ever be persisted to storage.
    57  type VariableEncrypted struct {
    58  	VariableMetadata
    59  	VariableData
    60  }
    61  
    62  // VariableData is the secret data for a Variable
    63  type VariableData struct {
    64  	Data  []byte // includes nonce
    65  	KeyID string // ID of root key used to encrypt this entry
    66  }
    67  
    68  // VariableDecrypted structs are returned from the Encrypter's decrypt
    69  // method. Since they contains sensitive material, they should never be
    70  // persisted to disk.
    71  type VariableDecrypted struct {
    72  	VariableMetadata
    73  	Items VariableItems
    74  }
    75  
    76  // VariableItems are the actual secrets stored in a variable. They are always
    77  // encrypted and decrypted as a single unit.
    78  type VariableItems map[string]string
    79  
    80  func (vi VariableItems) Size() uint64 {
    81  	var out uint64
    82  	for k, v := range vi {
    83  		out += uint64(len(k))
    84  		out += uint64(len(v))
    85  	}
    86  	return out
    87  }
    88  
    89  // Equal checks both the metadata and items in a VariableDecrypted struct
    90  func (vd VariableDecrypted) Equal(v2 VariableDecrypted) bool {
    91  	return vd.VariableMetadata.Equal(v2.VariableMetadata) &&
    92  		vd.Items.Equal(v2.Items)
    93  }
    94  
    95  // Equal is a convenience method to provide similar equality checking syntax
    96  // for metadata and the VariablesData or VariableItems struct
    97  func (sv VariableMetadata) Equal(sv2 VariableMetadata) bool {
    98  	return sv == sv2
    99  }
   100  
   101  // Equal performs deep equality checking on the cleartext items of a
   102  // VariableDecrypted. Uses reflect.DeepEqual
   103  func (vi VariableItems) Equal(i2 VariableItems) bool {
   104  	return reflect.DeepEqual(vi, i2)
   105  }
   106  
   107  // Equal checks both the metadata and encrypted data for a VariableEncrypted
   108  // struct
   109  func (ve VariableEncrypted) Equal(v2 VariableEncrypted) bool {
   110  	return ve.VariableMetadata.Equal(v2.VariableMetadata) &&
   111  		ve.VariableData.Equal(v2.VariableData)
   112  }
   113  
   114  // Equal performs deep equality checking on the encrypted data part of a
   115  // VariableEncrypted
   116  func (vd VariableData) Equal(d2 VariableData) bool {
   117  	return vd.KeyID == d2.KeyID &&
   118  		bytes.Equal(vd.Data, d2.Data)
   119  }
   120  
   121  func (vd VariableDecrypted) Copy() VariableDecrypted {
   122  	return VariableDecrypted{
   123  		VariableMetadata: vd.VariableMetadata,
   124  		Items:            vd.Items.Copy(),
   125  	}
   126  }
   127  
   128  func (vi VariableItems) Copy() VariableItems {
   129  	out := make(VariableItems, len(vi))
   130  	for k, v := range vi {
   131  		out[k] = v
   132  	}
   133  	return out
   134  }
   135  
   136  func (ve VariableEncrypted) Copy() VariableEncrypted {
   137  	return VariableEncrypted{
   138  		VariableMetadata: ve.VariableMetadata,
   139  		VariableData:     ve.VariableData.Copy(),
   140  	}
   141  }
   142  
   143  func (vd VariableData) Copy() VariableData {
   144  	out := make([]byte, len(vd.Data))
   145  	copy(out, vd.Data)
   146  	return VariableData{
   147  		Data:  out,
   148  		KeyID: vd.KeyID,
   149  	}
   150  }
   151  
   152  var (
   153  	// validVariablePath is used to validate a variable path. We restrict to
   154  	// RFC3986 URL-safe characters that don't conflict with the use of
   155  	// characters "@" and "." in template blocks. We also restrict the length so
   156  	// that a user can't make queries in the state store unusually expensive (as
   157  	// they are O(k) on the key length)
   158  	validVariablePath = regexp.MustCompile("^[a-zA-Z0-9-_~/]{1,128}$")
   159  )
   160  
   161  func (vd VariableDecrypted) Validate() error {
   162  
   163  	if vd.Namespace == AllNamespacesSentinel {
   164  		return errors.New("can not target wildcard (\"*\")namespace")
   165  	}
   166  
   167  	if len(vd.Items) == 0 {
   168  		return errors.New("empty variables are invalid")
   169  	}
   170  	if vd.Items.Size() > maxVariableSize {
   171  		return errors.New("variables are limited to 64KiB in total size")
   172  	}
   173  
   174  	if err := validatePath(vd.Path); err != nil {
   175  		return err
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  func validatePath(path string) error {
   182  	if len(path) == 0 {
   183  		return fmt.Errorf("variable requires path")
   184  	}
   185  	if !validVariablePath.MatchString(path) {
   186  		return fmt.Errorf("invalid path %q", path)
   187  	}
   188  
   189  	parts := strings.Split(path, "/")
   190  
   191  	if parts[0] != "nomad" {
   192  		return nil
   193  	}
   194  
   195  	// Don't allow a variable with path "nomad"
   196  	if len(parts) == 1 {
   197  		return fmt.Errorf("\"nomad\" is a reserved top-level directory path, but you may write variables to \"nomad/jobs\", \"nomad/job-templates\", or below")
   198  	}
   199  
   200  	switch {
   201  	case parts[1] == "jobs":
   202  		// Any path including "nomad/jobs" is valid
   203  		return nil
   204  	case parts[1] == "job-templates" && len(parts) == 3:
   205  		// Paths including "nomad/job-templates" is valid, provided they have single further path part
   206  		return nil
   207  	case parts[1] == "job-templates":
   208  		// Disallow exactly nomad/job-templates with no further paths
   209  		return fmt.Errorf("\"nomad/job-templates\" is a reserved directory path, but you may write variables at the level below it, for example, \"nomad/job-templates/template-name\"")
   210  	default:
   211  		// Disallow arbitrary sub-paths beneath nomad/
   212  		return fmt.Errorf("only paths at \"nomad/jobs\" or \"nomad/job-templates\" and below are valid paths under the top-level \"nomad\" directory")
   213  	}
   214  }
   215  
   216  func (vd *VariableDecrypted) Canonicalize() {
   217  	if vd.Namespace == "" {
   218  		vd.Namespace = DefaultNamespace
   219  	}
   220  }
   221  
   222  // Copy returns a fully hydrated copy of VariableMetadata that can be
   223  // manipulated while ensuring the original is not touched.
   224  func (sv *VariableMetadata) Copy() *VariableMetadata {
   225  	var out = *sv
   226  	return &out
   227  }
   228  
   229  // GetNamespace returns the variable's namespace. Used for pagination.
   230  func (sv VariableMetadata) GetNamespace() string {
   231  	return sv.Namespace
   232  }
   233  
   234  // GetID returns the variable's path. Used for pagination.
   235  func (sv VariableMetadata) GetID() string {
   236  	return sv.Path
   237  }
   238  
   239  // GetCreateIndex returns the variable's create index. Used for pagination.
   240  func (sv VariableMetadata) GetCreateIndex() uint64 {
   241  	return sv.CreateIndex
   242  }
   243  
   244  // VariablesQuota is used to track the total size of variables entries per
   245  // namespace. The total length of Variable.EncryptedData in bytes will be added
   246  // to the VariablesQuota table in the same transaction as a write, update, or
   247  // delete. This tracking effectively caps the maximum size of variables in a
   248  // given namespace to MaxInt64 bytes.
   249  type VariablesQuota struct {
   250  	Namespace   string
   251  	Size        int64
   252  	CreateIndex uint64
   253  	ModifyIndex uint64
   254  }
   255  
   256  func (svq *VariablesQuota) Copy() *VariablesQuota {
   257  	if svq == nil {
   258  		return nil
   259  	}
   260  	nq := new(VariablesQuota)
   261  	*nq = *svq
   262  	return nq
   263  }
   264  
   265  // ---------------------------------------
   266  // RPC and FSM request/response objects
   267  
   268  // VarOp constants give possible operations available in a transaction.
   269  type VarOp string
   270  
   271  const (
   272  	VarOpSet       VarOp = "set"
   273  	VarOpDelete    VarOp = "delete"
   274  	VarOpDeleteCAS VarOp = "delete-cas"
   275  	VarOpCAS       VarOp = "cas"
   276  )
   277  
   278  // VarOpResult constants give possible operations results from a transaction.
   279  type VarOpResult string
   280  
   281  const (
   282  	VarOpResultOk       VarOpResult = "ok"
   283  	VarOpResultConflict VarOpResult = "conflict"
   284  	VarOpResultRedacted VarOpResult = "conflict-redacted"
   285  	VarOpResultError    VarOpResult = "error"
   286  )
   287  
   288  // VariablesApplyRequest is used by users to operate on the variable store
   289  type VariablesApplyRequest struct {
   290  	Op  VarOp              // Operation to be performed during apply
   291  	Var *VariableDecrypted // Variable-shaped request data
   292  	WriteRequest
   293  }
   294  
   295  // VariablesApplyResponse is sent back to the user to inform them of success or failure
   296  type VariablesApplyResponse struct {
   297  	Op       VarOp              // Operation performed
   298  	Input    *VariableDecrypted // Input supplied
   299  	Result   VarOpResult        // Return status from operation
   300  	Error    error              // Error if any
   301  	Conflict *VariableDecrypted // Conflicting value if applicable
   302  	Output   *VariableDecrypted // Operation Result if successful; nil for successful deletes
   303  	WriteMeta
   304  }
   305  
   306  func (r *VariablesApplyResponse) IsOk() bool {
   307  	return r.Result == VarOpResultOk
   308  }
   309  
   310  func (r *VariablesApplyResponse) IsConflict() bool {
   311  	return r.Result == VarOpResultConflict || r.Result == VarOpResultRedacted
   312  }
   313  
   314  func (r *VariablesApplyResponse) IsError() bool {
   315  	return r.Result == VarOpResultError
   316  }
   317  
   318  func (r *VariablesApplyResponse) IsRedacted() bool {
   319  	return r.Result == VarOpResultRedacted
   320  }
   321  
   322  // VarApplyStateRequest is used by the FSM to modify the variable store
   323  type VarApplyStateRequest struct {
   324  	Op  VarOp              // Which operation are we performing
   325  	Var *VariableEncrypted // Which directory entry
   326  	WriteRequest
   327  }
   328  
   329  // VarApplyStateResponse is used by the FSM to inform the RPC layer of success or failure
   330  type VarApplyStateResponse struct {
   331  	Op            VarOp              // Which operation were we performing
   332  	Result        VarOpResult        // What happened (ok, conflict, error)
   333  	Error         error              // error if any
   334  	Conflict      *VariableEncrypted // conflicting variable if applies
   335  	WrittenSVMeta *VariableMetadata  // for making the VariablesApplyResponse
   336  	WriteMeta
   337  }
   338  
   339  func (r *VarApplyStateRequest) ErrorResponse(raftIndex uint64, err error) *VarApplyStateResponse {
   340  	return &VarApplyStateResponse{
   341  		Op:        r.Op,
   342  		Result:    VarOpResultError,
   343  		Error:     err,
   344  		WriteMeta: WriteMeta{Index: raftIndex},
   345  	}
   346  }
   347  
   348  func (r *VarApplyStateRequest) SuccessResponse(raftIndex uint64, meta *VariableMetadata) *VarApplyStateResponse {
   349  	return &VarApplyStateResponse{
   350  		Op:            r.Op,
   351  		Result:        VarOpResultOk,
   352  		WrittenSVMeta: meta,
   353  		WriteMeta:     WriteMeta{Index: raftIndex},
   354  	}
   355  }
   356  
   357  func (r *VarApplyStateRequest) ConflictResponse(raftIndex uint64, cv *VariableEncrypted) *VarApplyStateResponse {
   358  	var cvCopy VariableEncrypted
   359  	if cv != nil {
   360  		// make a copy so that we aren't sending
   361  		// the live state store version
   362  		cvCopy = cv.Copy()
   363  	}
   364  	return &VarApplyStateResponse{
   365  		Op:        r.Op,
   366  		Result:    VarOpResultConflict,
   367  		Conflict:  &cvCopy,
   368  		WriteMeta: WriteMeta{Index: raftIndex},
   369  	}
   370  }
   371  
   372  func (r *VarApplyStateResponse) IsOk() bool {
   373  	return r.Result == VarOpResultOk
   374  }
   375  
   376  func (r *VarApplyStateResponse) IsConflict() bool {
   377  	return r.Result == VarOpResultConflict
   378  }
   379  
   380  func (r *VarApplyStateResponse) IsError() bool {
   381  	// FIXME: This is brittle and requires immense faith that
   382  	// the response is properly managed.
   383  	return r.Result == VarOpResultError
   384  }
   385  
   386  type VariablesListRequest struct {
   387  	QueryOptions
   388  }
   389  
   390  type VariablesListResponse struct {
   391  	Data []*VariableMetadata
   392  	QueryMeta
   393  }
   394  
   395  type VariablesReadRequest struct {
   396  	Path string
   397  	QueryOptions
   398  }
   399  
   400  type VariablesReadResponse struct {
   401  	Data *VariableDecrypted
   402  	QueryMeta
   403  }