Make transit import command work for the transform backend (#20668)

* Add import and import-version commands for the transform backend
This commit is contained in:
Larroyo 2023-05-25 15:33:27 -05:00 committed by GitHub
parent f9f4b68a58
commit c32032c1f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 221 additions and 14 deletions

3
changelog/20668.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
secrets/transform: Added importing of keys and key versions into the Transform secrets engine using the command 'vault transform import' and 'vault transform import-version'.
```

View File

@ -721,6 +721,21 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) map[string]cli.Co
BaseCommand: getBaseCommand(),
}, nil
},
"transform": func() (cli.Command, error) {
return &TransformCommand{
BaseCommand: getBaseCommand(),
}, nil
},
"transform import": func() (cli.Command, error) {
return &TransformImportCommand{
BaseCommand: getBaseCommand(),
}, nil
},
"transform import-version": func() (cli.Command, error) {
return &TransformImportVersionCommand{
BaseCommand: getBaseCommand(),
}, nil
},
"transit": func() (cli.Command, error) {
return &TransitCommand{
BaseCommand: getBaseCommand(),

41
command/transform.go Normal file
View File

@ -0,0 +1,41 @@
package command
import (
"strings"
"github.com/mitchellh/cli"
)
var _ cli.Command = (*TransformCommand)(nil)
type TransformCommand struct {
*BaseCommand
}
func (c *TransformCommand) Synopsis() string {
return "Interact with Vault's Transform Secrets Engine"
}
func (c *TransformCommand) Help() string {
helpText := `
Usage: vault transform <subcommand> [options] [args]
This command has subcommands for interacting with Vault's Transform Secrets
Engine. Here are some simple examples, and more detailed examples are
available in the subcommands or the documentation.
To import a key into a new FPE transformation:
$ vault transform import transform/transformations/fpe/new-transformation @path/to/key \
template=identifier \
allowed_roles=physical-access
Please see the individual subcommand help for detailed usage information.
`
return strings.TrimSpace(helpText)
}
func (c *TransformCommand) Run(args []string) int {
return cli.RunResultHelp
}

View File

@ -0,0 +1,76 @@
package command
import (
"errors"
"regexp"
"strings"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
var (
_ cli.Command = (*TransformImportCommand)(nil)
_ cli.CommandAutocomplete = (*TransformImportCommand)(nil)
transformKeyPath = regexp.MustCompile("^(.*)/transformations/(fpe|tokenization)/([^/]*)$")
)
type TransformImportCommand struct {
*BaseCommand
}
func (c *TransformImportCommand) Synopsis() string {
return "Import a key into the Transform secrets engines."
}
func (c *TransformImportCommand) Help() string {
helpText := `
Usage: vault transform import PATH KEY [options...]
Using the Transform key wrapping system, imports key material from
the base64 encoded KEY (either directly on the CLI or via @path notation),
into a new FPE or tokenization transformation whose API path is PATH.
To import a new key version into an existing tokenization transformation,
use import_version.
The remaining options after KEY (key=value style) are passed on to
Create/Update FPE Transformation or Create/Update Tokenization Transformation
API endpoints.
For example:
$ vault transform import transform/transformations/tokenization/application-form @path/to/key \
allowed_roles=legacy-system
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *TransformImportCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP)
}
func (c *TransformImportCommand) AutocompleteArgs() complete.Predictor {
return nil
}
func (c *TransformImportCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *TransformImportCommand) Run(args []string) int {
return ImportKey(c.BaseCommand, "import", transformImportKeyPath, c.Flags(), args)
}
func transformImportKeyPath(s string, operation string) (path string, apiPath string, err error) {
parts := transformKeyPath.FindStringSubmatch(s)
if len(parts) != 4 {
return "", "", errors.New("expected transform path and key name in the form :path:/transformations/fpe|tokenization/:name:")
}
path = parts[1]
transformation := parts[2]
keyName := parts[3]
apiPath = path + "/transformations/" + transformation + "/" + keyName + "/" + operation
return path, apiPath, nil
}

View File

@ -0,0 +1,59 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package command
import (
"strings"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
var (
_ cli.Command = (*TransformImportVersionCommand)(nil)
_ cli.CommandAutocomplete = (*TransformImportVersionCommand)(nil)
)
type TransformImportVersionCommand struct {
*BaseCommand
}
func (c *TransformImportVersionCommand) Synopsis() string {
return "Import key material into a new key version in the Transform secrets engines."
}
func (c *TransformImportVersionCommand) Help() string {
helpText := `
Usage: vault transform import-version PATH KEY [...]
Using the Transform key wrapping system, imports new key material from
the base64 encoded KEY (either directly on the CLI or via @path notation),
into an existing tokenization transformation whose API path is PATH.
The remaining options after KEY (key=value style) are passed on to
Create/Update Tokenization Transformation API endpoint.
For example:
$ vault transform import-version transform/transformations/tokenization/application-form @path/to/new_version \
allowed_roles=legacy-system
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *TransformImportVersionCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP)
}
func (c *TransformImportVersionCommand) AutocompleteArgs() complete.Predictor {
return nil
}
func (c *TransformImportVersionCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *TransformImportVersionCommand) Run(args []string) int {
return ImportKey(c.BaseCommand, "import_version", transformImportKeyPath, c.Flags(), args)
}

View File

@ -10,6 +10,7 @@ import (
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"os"
"regexp"
@ -68,11 +69,25 @@ func (c *TransitImportCommand) AutocompleteFlags() complete.Flags {
}
func (c *TransitImportCommand) Run(args []string) int {
return importKey(c.BaseCommand, "import", c.Flags(), args)
return ImportKey(c.BaseCommand, "import", transitImportKeyPath, c.Flags(), args)
}
func transitImportKeyPath(s string, operation string) (path string, apiPath string, err error) {
parts := keyPath.FindStringSubmatch(s)
if len(parts) != 3 {
return "", "", errors.New("expected transit path and key name in the form :path:/keys/:name:")
}
path = parts[1]
keyName := parts[2]
apiPath = path + "/keys/" + keyName + "/" + operation
return path, apiPath, nil
}
type ImportKeyFunc func(s string, operation string) (path string, apiPath string, err error)
// error codes: 1: user error, 2: internal computation error, 3: remote api call error
func importKey(c *BaseCommand, operation string, flags *FlagSets, args []string) int {
func ImportKey(c *BaseCommand, operation string, pathFunc ImportKeyFunc, flags *FlagSets, args []string) int {
// Parse and validate the arguments.
if err := flags.Parse(args); err != nil {
c.UI.Error(err.Error())
@ -96,14 +111,11 @@ func importKey(c *BaseCommand, operation string, flags *FlagSets, args []string)
if err != nil {
c.UI.Error(fmt.Sprintf("failed to generate ephemeral key: %v", err))
}
parts := keyPath.FindStringSubmatch(args[0])
if len(parts) != 3 {
c.UI.Error("expected transit path and key name in the form :path:/keys/:name:")
path, apiPath, err := pathFunc(args[0], operation)
if err != nil {
c.UI.Error(err.Error())
return 1
}
path := parts[1]
keyName := parts[2]
keyMaterial := args[1]
if keyMaterial[0] == '@' {
keyMaterialBytes, err := os.ReadFile(keyMaterial[1:])
@ -121,7 +133,7 @@ func importKey(c *BaseCommand, operation string, flags *FlagSets, args []string)
return 1
}
// Fetch the wrapping key
c.UI.Output("Retrieving transit wrapping key.")
c.UI.Output("Retrieving wrapping key.")
wrappingKey, err := fetchWrappingKey(c, client, path)
if err != nil {
c.UI.Error(fmt.Sprintf("failed to fetch wrapping key: %v", err))
@ -138,7 +150,7 @@ func importKey(c *BaseCommand, operation string, flags *FlagSets, args []string)
c.UI.Error(fmt.Sprintf("failure wrapping source key: %v", err))
return 2
}
c.UI.Output("Encrypting ephemeral key with transit wrapping key.")
c.UI.Output("Encrypting ephemeral key with wrapping key.")
wrappedAESKey, err := rsa.EncryptOAEP(
sha256.New(),
rand.Reader,
@ -165,9 +177,10 @@ func importKey(c *BaseCommand, operation string, flags *FlagSets, args []string)
data["ciphertext"] = importCiphertext
c.UI.Output("Submitting wrapped key to Vault transit.")
c.UI.Output("Submitting wrapped key.")
// Finally, call import
_, err = client.Logical().Write(path+"/keys/"+keyName+"/"+operation, data)
_, err = client.Logical().Write(apiPath, data)
if err != nil {
c.UI.Error(fmt.Sprintf("failed to call import:%v", err))
return 3
@ -183,7 +196,7 @@ func fetchWrappingKey(c *BaseCommand, client *api.Client, path string) (any, err
return nil, fmt.Errorf("error fetching wrapping key: %w", err)
}
if resp == nil {
return nil, fmt.Errorf("transit not mounted at %s: %v", path, err)
return nil, fmt.Errorf("no mount found at %s: %v", path, err)
}
key, ok := resp.Data["public_key"]
if !ok {

View File

@ -54,5 +54,5 @@ func (c *TransitImportVersionCommand) AutocompleteFlags() complete.Flags {
}
func (c *TransitImportVersionCommand) Run(args []string) int {
return importKey(c.BaseCommand, "import_version", c.Flags(), args)
return ImportKey(c.BaseCommand, "import_version", transitImportKeyPath, c.Flags(), args)
}