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(),
|
BaseCommand: getBaseCommand(),
|
||||||
}, nil
|
}, 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) {
|
"transit": func() (cli.Command, error) {
|
||||||
return &TransitCommand{
|
return &TransitCommand{
|
||||||
BaseCommand: getBaseCommand(),
|
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"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -68,11 +69,25 @@ func (c *TransitImportCommand) AutocompleteFlags() complete.Flags {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TransitImportCommand) Run(args []string) int {
|
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
|
// 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.
|
// Parse and validate the arguments.
|
||||||
if err := flags.Parse(args); err != nil {
|
if err := flags.Parse(args); err != nil {
|
||||||
c.UI.Error(err.Error())
|
c.UI.Error(err.Error())
|
||||||
|
@ -96,14 +111,11 @@ func importKey(c *BaseCommand, operation string, flags *FlagSets, args []string)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.UI.Error(fmt.Sprintf("failed to generate ephemeral key: %v", err))
|
c.UI.Error(fmt.Sprintf("failed to generate ephemeral key: %v", err))
|
||||||
}
|
}
|
||||||
parts := keyPath.FindStringSubmatch(args[0])
|
path, apiPath, err := pathFunc(args[0], operation)
|
||||||
if len(parts) != 3 {
|
if err != nil {
|
||||||
c.UI.Error("expected transit path and key name in the form :path:/keys/:name:")
|
c.UI.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
path := parts[1]
|
|
||||||
keyName := parts[2]
|
|
||||||
|
|
||||||
keyMaterial := args[1]
|
keyMaterial := args[1]
|
||||||
if keyMaterial[0] == '@' {
|
if keyMaterial[0] == '@' {
|
||||||
keyMaterialBytes, err := os.ReadFile(keyMaterial[1:])
|
keyMaterialBytes, err := os.ReadFile(keyMaterial[1:])
|
||||||
|
@ -121,7 +133,7 @@ func importKey(c *BaseCommand, operation string, flags *FlagSets, args []string)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
// Fetch the wrapping key
|
// Fetch the wrapping key
|
||||||
c.UI.Output("Retrieving transit wrapping key.")
|
c.UI.Output("Retrieving wrapping key.")
|
||||||
wrappingKey, err := fetchWrappingKey(c, client, path)
|
wrappingKey, err := fetchWrappingKey(c, client, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.UI.Error(fmt.Sprintf("failed to fetch wrapping key: %v", err))
|
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))
|
c.UI.Error(fmt.Sprintf("failure wrapping source key: %v", err))
|
||||||
return 2
|
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(
|
wrappedAESKey, err := rsa.EncryptOAEP(
|
||||||
sha256.New(),
|
sha256.New(),
|
||||||
rand.Reader,
|
rand.Reader,
|
||||||
|
@ -165,9 +177,10 @@ func importKey(c *BaseCommand, operation string, flags *FlagSets, args []string)
|
||||||
|
|
||||||
data["ciphertext"] = importCiphertext
|
data["ciphertext"] = importCiphertext
|
||||||
|
|
||||||
c.UI.Output("Submitting wrapped key to Vault transit.")
|
c.UI.Output("Submitting wrapped key.")
|
||||||
// Finally, call import
|
// Finally, call import
|
||||||
_, err = client.Logical().Write(path+"/keys/"+keyName+"/"+operation, data)
|
|
||||||
|
_, err = client.Logical().Write(apiPath, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.UI.Error(fmt.Sprintf("failed to call import:%v", err))
|
c.UI.Error(fmt.Sprintf("failed to call import:%v", err))
|
||||||
return 3
|
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)
|
return nil, fmt.Errorf("error fetching wrapping key: %w", err)
|
||||||
}
|
}
|
||||||
if resp == nil {
|
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"]
|
key, ok := resp.Data["public_key"]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -54,5 +54,5 @@ func (c *TransitImportVersionCommand) AutocompleteFlags() complete.Flags {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TransitImportVersionCommand) Run(args []string) int {
|
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