2022-05-12 20:29:52 +00:00
package structs
2022-05-19 18:19:56 +00:00
import (
2022-06-27 19:51:01 +00:00
"bytes"
2022-06-14 17:28:10 +00:00
"errors"
2022-05-25 19:05:30 +00:00
"fmt"
2022-05-27 13:33:41 +00:00
"reflect"
2022-09-12 20:37:33 +00:00
"regexp"
2022-07-19 20:17:34 +00:00
"strings"
2022-05-19 18:19:56 +00:00
)
2022-05-12 20:29:52 +00:00
2022-05-27 13:33:41 +00:00
const (
2022-08-26 18:03:56 +00:00
// VariablesApplyRPCMethod is the RPC method for upserting or deleting a
// variable by its namespace and path, with optional conflict detection.
2022-05-27 13:33:41 +00:00
//
2022-08-26 18:03:56 +00:00
// Args: VariablesApplyRequest
// Reply: VariablesApplyResponse
VariablesApplyRPCMethod = "Variables.Apply"
2022-05-27 13:33:41 +00:00
2022-08-26 18:03:56 +00:00
// VariablesListRPCMethod is the RPC method for listing variables within
// Nomad.
2022-05-27 13:33:41 +00:00
//
2022-08-26 18:03:56 +00:00
// Args: VariablesListRequest
// Reply: VariablesListResponse
VariablesListRPCMethod = "Variables.List"
2022-05-27 13:33:41 +00:00
2022-10-10 14:28:46 +00:00
// VariablesReadRPCMethod is the RPC method for fetching a variable
2022-08-26 18:03:56 +00:00
// according to its namepace and path.
2022-05-27 13:33:41 +00:00
//
2022-08-26 18:03:56 +00:00
// Args: VariablesByNameRequest
// Reply: VariablesByNameResponse
VariablesReadRPCMethod = "Variables.Read"
2022-07-20 18:46:43 +00:00
2022-08-26 18:03:56 +00:00
// maxVariableSize is the maximum size of the unencrypted contents of a
// variable. This size is deliberately set low and is not configurable, to
// discourage DoS'ing the cluster
2023-01-31 18:32:36 +00:00
maxVariableSize = 65536
2022-05-27 13:33:41 +00:00
)
2022-08-26 18:03:56 +00:00
// VariableMetadata is the metadata envelope for a Variable, it is the list
// object and is shared data between an VariableEncrypted and a
// VariableDecrypted object.
type VariableMetadata struct {
2022-05-12 20:29:52 +00:00
Namespace string
Path string
CreateIndex uint64
2022-06-27 19:51:01 +00:00
CreateTime int64
2022-05-12 20:29:52 +00:00
ModifyIndex uint64
2022-06-27 19:51:01 +00:00
ModifyTime int64
2022-06-14 17:28:10 +00:00
}
2022-05-12 20:29:52 +00:00
2022-08-26 18:03:56 +00:00
// VariableEncrypted structs are returned from the Encrypter's encrypt
2022-06-14 17:28:10 +00:00
// method. They are the only form that should ever be persisted to storage.
2022-08-26 18:03:56 +00:00
type VariableEncrypted struct {
VariableMetadata
VariableData
2022-05-12 20:29:52 +00:00
}
2022-08-26 18:03:56 +00:00
// VariableData is the secret data for a Variable
type VariableData struct {
2022-05-12 20:29:52 +00:00
Data [ ] byte // includes nonce
KeyID string // ID of root key used to encrypt this entry
}
2022-08-26 18:03:56 +00:00
// VariableDecrypted structs are returned from the Encrypter's decrypt
2022-06-14 17:28:10 +00:00
// method. Since they contains sensitive material, they should never be
// persisted to disk.
2022-08-26 18:03:56 +00:00
type VariableDecrypted struct {
VariableMetadata
Items VariableItems
2022-06-14 17:28:10 +00:00
}
2022-08-26 18:03:56 +00:00
// VariableItems are the actual secrets stored in a variable. They are always
// encrypted and decrypted as a single unit.
type VariableItems map [ string ] string
2022-06-14 17:28:10 +00:00
2022-08-26 18:03:56 +00:00
func ( svi VariableItems ) Size ( ) uint64 {
2022-07-20 18:46:43 +00:00
var out uint64
for k , v := range svi {
out += uint64 ( len ( k ) )
out += uint64 ( len ( v ) )
}
return out
}
2022-10-10 14:28:46 +00:00
// Equal checks both the metadata and items in a VariableDecrypted struct
func ( v1 VariableDecrypted ) Equal ( v2 VariableDecrypted ) bool {
return v1 . VariableMetadata . Equal ( v2 . VariableMetadata ) &&
v1 . Items . Equal ( v2 . Items )
2022-06-14 17:28:10 +00:00
}
2022-10-10 14:28:46 +00:00
// Equal is a convenience method to provide similar equality checking syntax
2022-08-26 18:03:56 +00:00
// for metadata and the VariablesData or VariableItems struct
2022-10-10 14:28:46 +00:00
func ( sv VariableMetadata ) Equal ( sv2 VariableMetadata ) bool {
2022-06-14 17:28:10 +00:00
return sv == sv2
}
2022-10-10 14:28:46 +00:00
// Equal performs deep equality checking on the cleartext items of a
2022-08-26 18:03:56 +00:00
// VariableDecrypted. Uses reflect.DeepEqual
2022-10-10 14:28:46 +00:00
func ( i1 VariableItems ) Equal ( i2 VariableItems ) bool {
2022-06-27 19:51:01 +00:00
return reflect . DeepEqual ( i1 , i2 )
}
2022-10-10 14:28:46 +00:00
// Equal checks both the metadata and encrypted data for a VariableEncrypted
2022-08-26 18:03:56 +00:00
// struct
2022-10-10 14:28:46 +00:00
func ( v1 VariableEncrypted ) Equal ( v2 VariableEncrypted ) bool {
return v1 . VariableMetadata . Equal ( v2 . VariableMetadata ) &&
v1 . VariableData . Equal ( v2 . VariableData )
2022-06-27 19:51:01 +00:00
}
2022-10-10 14:28:46 +00:00
// Equal performs deep equality checking on the encrypted data part of a
2022-08-26 18:03:56 +00:00
// VariableEncrypted
2022-10-10 14:28:46 +00:00
func ( d1 VariableData ) Equal ( d2 VariableData ) bool {
2022-06-27 19:51:01 +00:00
return d1 . KeyID == d2 . KeyID &&
bytes . Equal ( d1 . Data , d2 . Data )
2022-06-14 17:28:10 +00:00
}
2022-08-26 18:03:56 +00:00
func ( sv VariableDecrypted ) Copy ( ) VariableDecrypted {
return VariableDecrypted {
VariableMetadata : sv . VariableMetadata ,
Items : sv . Items . Copy ( ) ,
2022-05-13 17:11:27 +00:00
}
2022-06-27 19:51:01 +00:00
}
2022-08-26 18:03:56 +00:00
func ( sv VariableItems ) Copy ( ) VariableItems {
out := make ( VariableItems , len ( sv ) )
2022-06-27 19:51:01 +00:00
for k , v := range sv {
out [ k ] = v
2022-05-13 17:11:27 +00:00
}
2022-06-14 17:28:10 +00:00
return out
2022-05-13 17:11:27 +00:00
}
2022-08-26 18:03:56 +00:00
func ( sv VariableEncrypted ) Copy ( ) VariableEncrypted {
return VariableEncrypted {
VariableMetadata : sv . VariableMetadata ,
VariableData : sv . VariableData . Copy ( ) ,
2022-06-27 19:51:01 +00:00
}
}
2022-06-14 17:28:10 +00:00
2022-08-26 18:03:56 +00:00
func ( sv VariableData ) Copy ( ) VariableData {
2022-06-27 19:51:01 +00:00
out := make ( [ ] byte , len ( sv . Data ) )
copy ( out , sv . Data )
2022-08-26 18:03:56 +00:00
return VariableData {
2022-06-27 19:51:01 +00:00
Data : out ,
KeyID : sv . KeyID ,
}
2022-05-27 13:33:41 +00:00
}
2022-09-12 20:37:33 +00:00
var (
// validVariablePath is used to validate a variable path. We restrict to
// RFC3986 URL-safe characters that don't conflict with the use of
// characters "@" and "." in template blocks. We also restrict the length so
// that a user can't make queries in the state store unusually expensive (as
// they are O(k) on the key length)
validVariablePath = regexp . MustCompile ( "^[a-zA-Z0-9-_~/]{1,128}$" )
)
func ( v VariableDecrypted ) Validate ( ) error {
2022-07-19 20:17:34 +00:00
2023-02-02 15:37:40 +00:00
if v . Namespace == AllNamespacesSentinel {
return errors . New ( "can not target wildcard (\"*\")namespace" )
2022-07-19 20:17:34 +00:00
}
2022-09-12 20:37:33 +00:00
if len ( v . Items ) == 0 {
2022-06-14 17:28:10 +00:00
return errors . New ( "empty variables are invalid" )
}
2022-09-12 20:37:33 +00:00
if v . Items . Size ( ) > maxVariableSize {
2023-01-31 18:32:36 +00:00
return errors . New ( "variables are limited to 64KiB in total size" )
2022-07-20 18:46:43 +00:00
}
2023-02-02 15:37:40 +00:00
if err := validatePath ( v . Path ) ; err != nil {
return err
2022-07-12 15:15:57 +00:00
}
2023-02-02 15:37:40 +00:00
2022-05-27 13:33:41 +00:00
return nil
}
2023-02-02 15:37:40 +00:00
func validatePath ( path string ) error {
if len ( path ) == 0 {
return fmt . Errorf ( "variable requires path" )
}
if ! validVariablePath . MatchString ( path ) {
return fmt . Errorf ( "invalid path %q" , path )
}
parts := strings . Split ( path , "/" )
if parts [ 0 ] != "nomad" {
return nil
}
// Don't allow a variable with path "nomad"
if len ( parts ) == 1 {
return fmt . Errorf ( "\"nomad\" is a reserved top-level directory path, but you may write variables to \"nomad/jobs\", \"nomad/job-templates\", or below" )
}
switch {
case parts [ 1 ] == "jobs" :
// Any path including "nomad/jobs" is valid
return nil
case parts [ 1 ] == "job-templates" && len ( parts ) == 3 :
// Paths including "nomad/job-templates" is valid, provided they have single further path part
return nil
case parts [ 1 ] == "job-templates" :
// Disallow exactly nomad/job-templates with no further paths
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\"" )
default :
// Disallow arbitrary sub-paths beneath nomad/
return fmt . Errorf ( "only paths at \"nomad/jobs\" or \"nomad/job-templates\" and below are valid paths under the top-level \"nomad\" directory" )
}
}
2022-08-26 18:03:56 +00:00
func ( sv * VariableDecrypted ) Canonicalize ( ) {
2022-05-27 13:33:41 +00:00
if sv . Namespace == "" {
sv . Namespace = DefaultNamespace
}
}
2022-08-26 18:03:56 +00:00
// GetNamespace returns the variable's namespace. Used for pagination.
func ( sv * VariableMetadata ) Copy ( ) * VariableMetadata {
var out VariableMetadata = * sv
2022-06-27 19:51:01 +00:00
return & out
}
2022-08-26 18:03:56 +00:00
// GetNamespace returns the variable's namespace. Used for pagination.
func ( sv VariableMetadata ) GetNamespace ( ) string {
2022-05-27 13:33:41 +00:00
return sv . Namespace
}
2022-08-26 18:03:56 +00:00
// GetID returns the variable's path. Used for pagination.
func ( sv VariableMetadata ) GetID ( ) string {
2022-05-27 13:33:41 +00:00
return sv . Path
}
2022-08-26 18:03:56 +00:00
// GetCreateIndex returns the variable's create index. Used for pagination.
func ( sv VariableMetadata ) GetCreateIndex ( ) uint64 {
2022-05-27 13:33:41 +00:00
return sv . CreateIndex
}
2022-08-26 18:03:56 +00:00
// VariablesQuota is used to track the total size of variables entries per
// namespace. The total length of Variable.EncryptedData in bytes will be added
// to the VariablesQuota table in the same transaction as a write, update, or
// delete. This tracking effectively caps the maximum size of variables in a
// given namespace to MaxInt64 bytes.
type VariablesQuota struct {
2022-05-12 20:29:52 +00:00
Namespace string
2022-08-02 13:32:09 +00:00
Size int64
2022-05-12 20:29:52 +00:00
CreateIndex uint64
ModifyIndex uint64
}
2022-08-26 18:03:56 +00:00
func ( svq * VariablesQuota ) Copy ( ) * VariablesQuota {
2022-06-27 17:17:22 +00:00
if svq == nil {
return nil
}
2022-08-26 18:03:56 +00:00
nq := new ( VariablesQuota )
2022-06-27 17:17:22 +00:00
* nq = * svq
return nq
}
2022-08-15 15:19:53 +00:00
// ---------------------------------------
// RPC and FSM request/response objects
2022-08-26 18:03:56 +00:00
// VarOp constants give possible operations available in a transaction.
type VarOp string
2022-08-15 15:19:53 +00:00
const (
2022-08-26 18:03:56 +00:00
VarOpSet VarOp = "set"
VarOpDelete VarOp = "delete"
VarOpDeleteCAS VarOp = "delete-cas"
VarOpCAS VarOp = "cas"
2022-08-15 15:19:53 +00:00
)
2022-08-26 18:03:56 +00:00
// VarOpResult constants give possible operations results from a transaction.
type VarOpResult string
2022-08-15 15:19:53 +00:00
const (
2022-08-26 18:03:56 +00:00
VarOpResultOk VarOpResult = "ok"
VarOpResultConflict VarOpResult = "conflict"
VarOpResultRedacted VarOpResult = "conflict-redacted"
VarOpResultError VarOpResult = "error"
2022-08-15 15:19:53 +00:00
)
2022-08-26 18:03:56 +00:00
// VariablesApplyRequest is used by users to operate on the variable store
type VariablesApplyRequest struct {
Op VarOp // Operation to be performed during apply
Var * VariableDecrypted // Variable-shaped request data
2022-05-27 13:33:41 +00:00
WriteRequest
}
2022-08-26 18:03:56 +00:00
// VariablesApplyResponse is sent back to the user to inform them of success or failure
type VariablesApplyResponse struct {
Op VarOp // Operation performed
Input * VariableDecrypted // Input supplied
Result VarOpResult // Return status from operation
Error error // Error if any
Conflict * VariableDecrypted // Conflicting value if applicable
Output * VariableDecrypted // Operation Result if successful; nil for successful deletes
2022-08-15 15:19:53 +00:00
WriteMeta
}
2022-08-26 18:03:56 +00:00
func ( r * VariablesApplyResponse ) IsOk ( ) bool {
return r . Result == VarOpResultOk
2022-06-27 19:51:01 +00:00
}
2022-08-26 18:03:56 +00:00
func ( r * VariablesApplyResponse ) IsConflict ( ) bool {
return r . Result == VarOpResultConflict || r . Result == VarOpResultRedacted
2022-08-15 15:19:53 +00:00
}
2022-08-26 18:03:56 +00:00
func ( r * VariablesApplyResponse ) IsError ( ) bool {
return r . Result == VarOpResultError
2022-08-15 15:19:53 +00:00
}
2022-08-26 18:03:56 +00:00
func ( r * VariablesApplyResponse ) IsRedacted ( ) bool {
return r . Result == VarOpResultRedacted
2022-08-15 15:19:53 +00:00
}
2022-08-26 18:03:56 +00:00
// VarApplyStateRequest is used by the FSM to modify the variable store
type VarApplyStateRequest struct {
Op VarOp // Which operation are we performing
Var * VariableEncrypted // Which directory entry
2022-05-12 20:29:52 +00:00
WriteRequest
}
2022-08-26 18:03:56 +00:00
// VarApplyStateResponse is used by the FSM to inform the RPC layer of success or failure
type VarApplyStateResponse struct {
Op VarOp // Which operation were we performing
Result VarOpResult // What happened (ok, conflict, error)
Error error // error if any
Conflict * VariableEncrypted // conflicting variable if applies
WrittenSVMeta * VariableMetadata // for making the VariablesApplyResponse
2022-05-12 20:29:52 +00:00
WriteMeta
}
2022-08-26 18:03:56 +00:00
func ( r * VarApplyStateRequest ) ErrorResponse ( raftIndex uint64 , err error ) * VarApplyStateResponse {
return & VarApplyStateResponse {
2022-08-15 15:19:53 +00:00
Op : r . Op ,
2022-08-26 18:03:56 +00:00
Result : VarOpResultError ,
2022-08-15 15:19:53 +00:00
Error : err ,
WriteMeta : WriteMeta { Index : raftIndex } ,
}
}
2022-08-26 18:03:56 +00:00
func ( r * VarApplyStateRequest ) SuccessResponse ( raftIndex uint64 , meta * VariableMetadata ) * VarApplyStateResponse {
return & VarApplyStateResponse {
2022-08-15 15:19:53 +00:00
Op : r . Op ,
2022-08-26 18:03:56 +00:00
Result : VarOpResultOk ,
2022-08-15 15:19:53 +00:00
WrittenSVMeta : meta ,
WriteMeta : WriteMeta { Index : raftIndex } ,
}
}
2022-08-26 18:03:56 +00:00
func ( r * VarApplyStateRequest ) ConflictResponse ( raftIndex uint64 , cv * VariableEncrypted ) * VarApplyStateResponse {
var cvCopy VariableEncrypted
2022-08-15 15:19:53 +00:00
if cv != nil {
// make a copy so that we aren't sending
// the live state store version
cvCopy = cv . Copy ( )
}
2022-08-26 18:03:56 +00:00
return & VarApplyStateResponse {
2022-08-15 15:19:53 +00:00
Op : r . Op ,
2022-08-26 18:03:56 +00:00
Result : VarOpResultConflict ,
2022-08-15 15:19:53 +00:00
Conflict : & cvCopy ,
WriteMeta : WriteMeta { Index : raftIndex } ,
}
}
2022-08-26 18:03:56 +00:00
func ( r * VarApplyStateResponse ) IsOk ( ) bool {
return r . Result == VarOpResultOk
2022-08-15 15:19:53 +00:00
}
2022-08-26 18:03:56 +00:00
func ( r * VarApplyStateResponse ) IsConflict ( ) bool {
return r . Result == VarOpResultConflict
2022-08-15 15:19:53 +00:00
}
2022-08-26 18:03:56 +00:00
func ( r * VarApplyStateResponse ) IsError ( ) bool {
2022-08-15 15:19:53 +00:00
// FIXME: This is brittle and requires immense faith that
// the response is properly managed.
2022-08-26 18:03:56 +00:00
return r . Result == VarOpResultError
2022-08-15 15:19:53 +00:00
}
2022-08-26 18:03:56 +00:00
type VariablesListRequest struct {
2022-05-12 20:29:52 +00:00
QueryOptions
}
2022-08-26 18:03:56 +00:00
type VariablesListResponse struct {
Data [ ] * VariableMetadata
2022-05-12 20:29:52 +00:00
QueryMeta
}
2022-08-26 18:03:56 +00:00
type VariablesReadRequest struct {
2022-05-12 20:29:52 +00:00
Path string
QueryOptions
}
2022-08-26 18:03:56 +00:00
type VariablesReadResponse struct {
Data * VariableDecrypted
2022-05-12 20:29:52 +00:00
QueryMeta
}