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  }