github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/structs/variables.go (about)

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