github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/cliccl/debug.go (about) 1 // Copyright 2018 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 cliccl 10 11 import ( 12 "context" 13 "encoding/json" 14 "fmt" 15 "io/ioutil" 16 "os" 17 "path/filepath" 18 "sort" 19 "time" 20 21 "github.com/cockroachdb/cockroach/pkg/base" 22 "github.com/cockroachdb/cockroach/pkg/ccl/baseccl" 23 "github.com/cockroachdb/cockroach/pkg/ccl/cliccl/cliflagsccl" 24 "github.com/cockroachdb/cockroach/pkg/ccl/storageccl/engineccl/enginepbccl" 25 "github.com/cockroachdb/cockroach/pkg/cli" 26 "github.com/cockroachdb/cockroach/pkg/storage/enginepb" 27 "github.com/cockroachdb/cockroach/pkg/util/protoutil" 28 "github.com/cockroachdb/cockroach/pkg/util/stop" 29 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 30 "github.com/cockroachdb/errors" 31 "github.com/spf13/cobra" 32 ) 33 34 // Defines CCL-specific debug commands, adds the encryption flag to debug commands in 35 // `pkg/cli/debug.go`, and registers a callback to generate encryption options. 36 37 const ( 38 // These constants are defined in libroach. They should NOT be changed. 39 plaintextKeyID = "plain" 40 keyRegistryFilename = "COCKROACHDB_DATA_KEYS" 41 fileRegistryFilename = "COCKROACHDB_REGISTRY" 42 ) 43 44 var encryptionStatusOpts struct { 45 activeStoreIDOnly bool 46 } 47 48 func init() { 49 encryptionStatusCmd := &cobra.Command{ 50 Use: "encryption-status <directory>", 51 Short: "show encryption status of a store", 52 Long: ` 53 Shows encryption status of the store located in 'directory'. 54 Encryption keys must be specified in the '--enterprise-encryption' flag. 55 56 Displays all store and data keys as well as files encrypted with each. 57 Specifying --active-store-key-id-only prints the key ID of the active store key 58 and exits. 59 `, 60 Args: cobra.ExactArgs(1), 61 RunE: cli.MaybeDecorateGRPCError(runEncryptionStatus), 62 } 63 64 encryptionActiveKeyCmd := &cobra.Command{ 65 Use: "encryption-active-key <directory>", 66 Short: "return ID of the active store key", 67 Long: ` 68 Display the algorithm and key ID of the active store key for existing data directory 'directory'. 69 Does not require knowing the key. 70 71 Some sample outputs: 72 Plaintext: # encryption not enabled 73 AES128_CTR:be235... # AES-128 encryption with store key ID 74 `, 75 Args: cobra.ExactArgs(1), 76 RunE: cli.MaybeDecorateGRPCError(runEncryptionActiveKey), 77 } 78 79 // Add commands to the root debug command. 80 // We can't add them to the lists of commands (eg: DebugCmdsForRocksDB) as cli init() is called before us. 81 cli.DebugCmd.AddCommand(encryptionStatusCmd) 82 cli.DebugCmd.AddCommand(encryptionActiveKeyCmd) 83 84 // Add the encryption flag to commands that need it. 85 f := encryptionStatusCmd.Flags() 86 cli.VarFlag(f, &storeEncryptionSpecs, cliflagsccl.EnterpriseEncryption) 87 // And other flags. 88 f.BoolVar(&encryptionStatusOpts.activeStoreIDOnly, "active-store-key-id-only", false, 89 "print active store key ID and exit") 90 91 // Add encryption flag to all OSS debug commands that want it. 92 for _, cmd := range cli.DebugCmdsForRocksDB { 93 // storeEncryptionSpecs is in start.go. 94 cli.VarFlag(cmd.Flags(), &storeEncryptionSpecs, cliflagsccl.EnterpriseEncryption) 95 } 96 97 cli.PopulateRocksDBConfigHook = fillEncryptionOptionsForStore 98 } 99 100 // fillEncryptionOptionsForStore fills the RocksDBConfig fields 101 // based on the --enterprise-encryption flag value. 102 func fillEncryptionOptionsForStore(cfg *base.StorageConfig) error { 103 opts, err := baseccl.EncryptionOptionsForStore(cfg.Dir, storeEncryptionSpecs) 104 if err != nil { 105 return err 106 } 107 108 if opts != nil { 109 cfg.ExtraOptions = opts 110 cfg.UseFileRegistry = true 111 } 112 return nil 113 } 114 115 type keyInfoByAge []*enginepbccl.KeyInfo 116 117 func (ki keyInfoByAge) Len() int { return len(ki) } 118 func (ki keyInfoByAge) Swap(i, j int) { ki[i], ki[j] = ki[j], ki[i] } 119 func (ki keyInfoByAge) Less(i, j int) bool { return ki[i].CreationTime < ki[j].CreationTime } 120 121 // JSONTime is a json-marshalable time.Time. 122 type JSONTime time.Time 123 124 // MarshalJSON marshals time.Time into json. 125 func (t JSONTime) MarshalJSON() ([]byte, error) { 126 return []byte(fmt.Sprintf("\"%s\"", time.Time(t).String())), nil 127 } 128 129 // PrettyDataKey is the final json-exportable struct for a data key. 130 type PrettyDataKey struct { 131 ID string 132 Active bool `json:",omitempty"` 133 Exposed bool `json:",omitempty"` 134 Created JSONTime 135 Files []string `json:",omitempty"` 136 } 137 138 // PrettyStoreKey is the final json-exportable struct for a store key. 139 type PrettyStoreKey struct { 140 ID string 141 Active bool `json:",omitempty"` 142 Type string 143 Created JSONTime 144 Source string 145 Files []string `json:",omitempty"` 146 DataKeys []PrettyDataKey `json:",omitempty"` 147 } 148 149 func runEncryptionStatus(cmd *cobra.Command, args []string) error { 150 stopper := stop.NewStopper() 151 defer stopper.Stop(context.Background()) 152 153 dir := args[0] 154 155 db, err := cli.OpenExistingStore(dir, stopper, true /* readOnly */) 156 if err != nil { 157 return err 158 } 159 160 registries, err := db.GetEncryptionRegistries() 161 if err != nil { 162 return err 163 } 164 165 if len(registries.FileRegistry) == 0 || len(registries.KeyRegistry) == 0 { 166 return nil 167 } 168 169 var fileRegistry enginepb.FileRegistry 170 if err := protoutil.Unmarshal(registries.FileRegistry, &fileRegistry); err != nil { 171 return err 172 } 173 174 var keyRegistry enginepbccl.DataKeysRegistry 175 if err := protoutil.Unmarshal(registries.KeyRegistry, &keyRegistry); err != nil { 176 return err 177 } 178 179 if encryptionStatusOpts.activeStoreIDOnly { 180 fmt.Println(keyRegistry.ActiveStoreKeyId) 181 return nil 182 } 183 184 // Build a map of 'key ID' -> list of files 185 fileKeyMap := make(map[string][]string) 186 187 for name, entry := range fileRegistry.Files { 188 keyID := plaintextKeyID 189 190 if entry.EnvType != enginepb.EnvType_Plaintext && len(entry.EncryptionSettings) > 0 { 191 var setting enginepbccl.EncryptionSettings 192 if err := protoutil.Unmarshal(entry.EncryptionSettings, &setting); err != nil { 193 fmt.Fprintf(os.Stderr, "could not unmarshal encryption settings for file %s: %v", name, err) 194 continue 195 } 196 keyID = setting.KeyId 197 } 198 199 fileKeyMap[keyID] = append(fileKeyMap[keyID], name) 200 } 201 202 // Build a map of 'store key ID' -> list of child data key info 203 childKeyMap := make(map[string]keyInfoByAge) 204 205 for _, dataKey := range keyRegistry.DataKeys { 206 info := dataKey.Info 207 parentKey := plaintextKeyID 208 if len(info.ParentKeyId) > 0 { 209 parentKey = info.ParentKeyId 210 } 211 childKeyMap[parentKey] = append(childKeyMap[parentKey], info) 212 } 213 214 // Make a sortable slice of store key infos. 215 storeKeyList := make(keyInfoByAge, 0) 216 for _, ki := range keyRegistry.StoreKeys { 217 storeKeyList = append(storeKeyList, ki) 218 } 219 220 storeKeys := make([]PrettyStoreKey, 0, len(storeKeyList)) 221 sort.Sort(storeKeyList) 222 for _, storeKey := range storeKeyList { 223 storeNode := PrettyStoreKey{ 224 ID: storeKey.KeyId, 225 Active: (storeKey.KeyId == keyRegistry.ActiveStoreKeyId), 226 Type: storeKey.EncryptionType.String(), 227 Created: JSONTime(timeutil.Unix(storeKey.CreationTime, 0)), 228 Source: storeKey.Source, 229 } 230 231 // Files encrypted by the store key. This should only be the data key registry. 232 if files, ok := fileKeyMap[storeKey.KeyId]; ok { 233 sort.Strings(files) 234 storeNode.Files = files 235 delete(fileKeyMap, storeKey.KeyId) 236 } 237 238 // Child keys. 239 if children, ok := childKeyMap[storeKey.KeyId]; ok { 240 storeNode.DataKeys = make([]PrettyDataKey, 0, len(children)) 241 242 sort.Sort(children) 243 for _, c := range children { 244 dataNode := PrettyDataKey{ 245 ID: c.KeyId, 246 Active: (c.KeyId == keyRegistry.ActiveDataKeyId), 247 Exposed: c.WasExposed, 248 Created: JSONTime(timeutil.Unix(c.CreationTime, 0)), 249 } 250 files, ok := fileKeyMap[c.KeyId] 251 if ok { 252 sort.Strings(files) 253 dataNode.Files = files 254 delete(fileKeyMap, c.KeyId) 255 } 256 storeNode.DataKeys = append(storeNode.DataKeys, dataNode) 257 } 258 delete(childKeyMap, storeKey.KeyId) 259 } 260 storeKeys = append(storeKeys, storeNode) 261 } 262 263 j, err := json.MarshalIndent(storeKeys, "", " ") 264 if err != nil { 265 return err 266 } 267 fmt.Printf("%s\n", j) 268 269 if len(fileKeyMap) > 0 { 270 fmt.Fprintf(os.Stderr, "WARNING: could not find key info for some files: %+v\n", fileKeyMap) 271 } 272 if len(childKeyMap) > 0 { 273 fmt.Fprintf(os.Stderr, "WARNING: could not find parent key info for some data keys: %+v\n", childKeyMap) 274 } 275 276 return nil 277 } 278 279 func runEncryptionActiveKey(cmd *cobra.Command, args []string) error { 280 keyType, keyID, err := getActiveEncryptionkey(args[0]) 281 if err != nil { 282 return err 283 } 284 285 fmt.Printf("%s:%s\n", keyType, keyID) 286 return nil 287 } 288 289 // getActiveEncryptionkey opens the file registry directly, bypassing rocksdb. 290 // This allows looking up the active encryption key ID without knowing it. 291 func getActiveEncryptionkey(dir string) (string, string, error) { 292 registryFile := filepath.Join(dir, fileRegistryFilename) 293 294 // If the data directory does not exist, we return an error. 295 if _, err := os.Stat(dir); err != nil { 296 return "", "", errors.Wrapf(err, "data directory %s does not exist", dir) 297 } 298 299 // Open the file registry. Return plaintext if it does not exist. 300 contents, err := ioutil.ReadFile(registryFile) 301 if err != nil { 302 if os.IsNotExist(err) { 303 return enginepbccl.EncryptionType_Plaintext.String(), "", nil 304 } 305 return "", "", errors.Wrapf(err, "could not open registry file %s", registryFile) 306 } 307 308 var fileRegistry enginepb.FileRegistry 309 if err := protoutil.Unmarshal(contents, &fileRegistry); err != nil { 310 return "", "", err 311 } 312 313 // Find the entry for the key registry file. 314 entry, ok := fileRegistry.Files[keyRegistryFilename] 315 if !ok { 316 return "", "", fmt.Errorf("key registry file %s was not found in the file registry", keyRegistryFilename) 317 } 318 319 if entry.EnvType == enginepb.EnvType_Plaintext || len(entry.EncryptionSettings) == 0 { 320 // Plaintext: no encryption settings to unmarshal. 321 return enginepbccl.EncryptionType_Plaintext.String(), "", nil 322 } 323 324 var setting enginepbccl.EncryptionSettings 325 if err := protoutil.Unmarshal(entry.EncryptionSettings, &setting); err != nil { 326 return "", "", fmt.Errorf("could not unmarshal encryption settings for %s: %v", keyRegistryFilename, err) 327 } 328 329 return setting.EncryptionType.String(), setting.KeyId, nil 330 }