github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/baseccl/encryption_spec.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Licensed as a CockroachDB Enterprise file under the Cockroach Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt 8 9 package baseccl 10 11 import ( 12 "bytes" 13 "fmt" 14 "path/filepath" 15 "strings" 16 "time" 17 18 "github.com/cockroachdb/cockroach/pkg/base" 19 "github.com/cockroachdb/cockroach/pkg/ccl/cliccl/cliflagsccl" 20 "github.com/cockroachdb/cockroach/pkg/util/protoutil" 21 "github.com/cockroachdb/errors" 22 "github.com/spf13/pflag" 23 ) 24 25 // DefaultRotationPeriod is the rotation period used if not specified. 26 const DefaultRotationPeriod = time.Hour * 24 * 7 // 1 week, give or take time changes. 27 28 // Special value of key paths to mean "no encryption". We do not accept empty fields. 29 const plaintextFieldValue = "plain" 30 31 // StoreEncryptionSpec contains the details that can be specified in the cli via 32 // the --enterprise-encryption flag. 33 type StoreEncryptionSpec struct { 34 Path string 35 KeyPath string 36 OldKeyPath string 37 RotationPeriod time.Duration 38 } 39 40 // Convert to a serialized EncryptionOptions protobuf. 41 func (es StoreEncryptionSpec) toEncryptionOptions() ([]byte, error) { 42 opts := EncryptionOptions{ 43 KeySource: EncryptionKeySource_KeyFiles, 44 KeyFiles: &EncryptionKeyFiles{ 45 CurrentKey: es.KeyPath, 46 OldKey: es.OldKeyPath, 47 }, 48 DataKeyRotationPeriod: int64(es.RotationPeriod / time.Second), 49 } 50 51 return protoutil.Marshal(&opts) 52 } 53 54 // String returns a fully parsable version of the encryption spec. 55 func (es StoreEncryptionSpec) String() string { 56 // All fields are set. 57 return fmt.Sprintf("path=%s,key=%s,old-key=%s,rotation-period=%s", 58 es.Path, es.KeyPath, es.OldKeyPath, es.RotationPeriod) 59 } 60 61 // NewStoreEncryptionSpec parses the string passed in and returns a new 62 // StoreEncryptionSpec if parsing succeeds. 63 // TODO(mberhault): we should share the parsing code with the StoreSpec. 64 func NewStoreEncryptionSpec(value string) (StoreEncryptionSpec, error) { 65 const pathField = "path" 66 var es StoreEncryptionSpec 67 es.RotationPeriod = DefaultRotationPeriod 68 69 used := make(map[string]struct{}) 70 for _, split := range strings.Split(value, ",") { 71 if len(split) == 0 { 72 continue 73 } 74 subSplits := strings.SplitN(split, "=", 2) 75 if len(subSplits) == 1 { 76 return StoreEncryptionSpec{}, fmt.Errorf("field not in the form <key>=<value>: %s", split) 77 } 78 field := strings.ToLower(subSplits[0]) 79 value := subSplits[1] 80 if _, ok := used[field]; ok { 81 return StoreEncryptionSpec{}, fmt.Errorf("%s field was used twice in encryption definition", field) 82 } 83 used[field] = struct{}{} 84 85 if len(field) == 0 { 86 return StoreEncryptionSpec{}, fmt.Errorf("empty field") 87 } 88 if len(value) == 0 { 89 return StoreEncryptionSpec{}, fmt.Errorf("no value specified for %s", field) 90 } 91 92 switch field { 93 case pathField: 94 var err error 95 es.Path, err = base.GetAbsoluteStorePath(pathField, value) 96 if err != nil { 97 return StoreEncryptionSpec{}, err 98 } 99 case "key": 100 if value == plaintextFieldValue { 101 es.KeyPath = plaintextFieldValue 102 } else { 103 var err error 104 es.KeyPath, err = base.GetAbsoluteStorePath("key", value) 105 if err != nil { 106 return StoreEncryptionSpec{}, err 107 } 108 } 109 case "old-key": 110 if value == plaintextFieldValue { 111 es.OldKeyPath = plaintextFieldValue 112 } else { 113 var err error 114 es.OldKeyPath, err = base.GetAbsoluteStorePath("old-key", value) 115 if err != nil { 116 return StoreEncryptionSpec{}, err 117 } 118 } 119 case "rotation-period": 120 var err error 121 es.RotationPeriod, err = time.ParseDuration(value) 122 if err != nil { 123 return StoreEncryptionSpec{}, errors.Wrapf(err, "could not parse rotation-duration value: %s", value) 124 } 125 default: 126 return StoreEncryptionSpec{}, fmt.Errorf("%s is not a valid enterprise-encryption field", field) 127 } 128 } 129 130 // Check that all fields are set. 131 if es.Path == "" { 132 return StoreEncryptionSpec{}, fmt.Errorf("no path specified") 133 } 134 if es.KeyPath == "" { 135 return StoreEncryptionSpec{}, fmt.Errorf("no key specified") 136 } 137 if es.OldKeyPath == "" { 138 return StoreEncryptionSpec{}, fmt.Errorf("no old-key specified") 139 } 140 141 return es, nil 142 } 143 144 // StoreEncryptionSpecList contains a slice of StoreEncryptionSpecs that implements pflag's value 145 // interface. 146 type StoreEncryptionSpecList struct { 147 Specs []StoreEncryptionSpec 148 } 149 150 var _ pflag.Value = &StoreEncryptionSpecList{} 151 152 // String returns a string representation of all the StoreEncryptionSpecs. This is part 153 // of pflag's value interface. 154 func (encl StoreEncryptionSpecList) String() string { 155 var buffer bytes.Buffer 156 for _, ss := range encl.Specs { 157 fmt.Fprintf(&buffer, "--%s=%s ", cliflagsccl.EnterpriseEncryption.Name, ss) 158 } 159 // Trim the extra space from the end if it exists. 160 if l := buffer.Len(); l > 0 { 161 buffer.Truncate(l - 1) 162 } 163 return buffer.String() 164 } 165 166 // Type returns the underlying type in string form. This is part of pflag's 167 // value interface. 168 func (encl *StoreEncryptionSpecList) Type() string { 169 return "StoreEncryptionSpec" 170 } 171 172 // Set adds a new value to the StoreEncryptionSpecValue. It is the important part of 173 // pflag's value interface. 174 func (encl *StoreEncryptionSpecList) Set(value string) error { 175 spec, err := NewStoreEncryptionSpec(value) 176 if err != nil { 177 return err 178 } 179 if encl.Specs == nil { 180 encl.Specs = []StoreEncryptionSpec{spec} 181 } else { 182 encl.Specs = append(encl.Specs, spec) 183 } 184 return nil 185 } 186 187 // PopulateStoreSpecWithEncryption iterates through the StoreEncryptionSpecList and looks 188 // for matching paths in the StoreSpecList. 189 // Any unmatched StoreEncryptionSpec causes an error. 190 // Matching stores have a few encryption-related fields set. 191 func PopulateStoreSpecWithEncryption( 192 storeSpecs base.StoreSpecList, encryptionSpecs StoreEncryptionSpecList, 193 ) error { 194 for _, es := range encryptionSpecs.Specs { 195 var found bool 196 for i := range storeSpecs.Specs { 197 if storeSpecs.Specs[i].Path != es.Path { 198 continue 199 } 200 201 // Found a matching path. 202 if storeSpecs.Specs[i].UseFileRegistry { 203 return fmt.Errorf("store with path %s already has an encryption setting", 204 storeSpecs.Specs[i].Path) 205 } 206 207 // Tell the store we absolutely need the file registry. 208 storeSpecs.Specs[i].UseFileRegistry = true 209 opts, err := es.toEncryptionOptions() 210 if err != nil { 211 return err 212 } 213 storeSpecs.Specs[i].ExtraOptions = opts 214 found = true 215 break 216 } 217 if !found { 218 return fmt.Errorf("no store with path %s found for encryption setting: %v", es.Path, es) 219 } 220 } 221 return nil 222 } 223 224 // EncryptionOptionsForStore takes a store directory and returns its ExtraOptions 225 // if a matching entry if found in the StoreEncryptionSpecList. 226 // The caller should appropriately set UseFileRegistry on a non-nil result. 227 func EncryptionOptionsForStore( 228 dir string, encryptionSpecs StoreEncryptionSpecList, 229 ) ([]byte, error) { 230 // We need an absolute path, but the input may have come in relative. 231 path, err := filepath.Abs(dir) 232 if err != nil { 233 return nil, errors.Wrapf(err, "could not find absolute path for %s ", dir) 234 } 235 236 for _, es := range encryptionSpecs.Specs { 237 if es.Path == path { 238 return es.toEncryptionOptions() 239 } 240 } 241 242 return nil, nil 243 }