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:
parent
f9f4b68a58
commit
c32032c1f8
|
@ -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'.
|
||||
```
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue