Skip to content

Commit

Permalink
Make transit import command work for the transform backend (hashicorp…
Browse files Browse the repository at this point in the history
…#20668)

* Add import and import-version commands for the transform backend
  • Loading branch information
DeLuci authored May 25, 2023
1 parent bea964c commit 1336abd
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 14 deletions.
3 changes: 3 additions & 0 deletions changelog/20668.txt
Original file line number Diff line number Diff line change
@@ -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'.
```
15 changes: 15 additions & 0 deletions command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
41 changes: 41 additions & 0 deletions command/transform.go
Original file line number Diff line number Diff line change
@@ -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
}
76 changes: 76 additions & 0 deletions command/transform_import_key.go
Original file line number Diff line number Diff line change
@@ -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
}
59 changes: 59 additions & 0 deletions command/transform_import_key_version.go
Original file line number Diff line number Diff line change
@@ -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)
}
39 changes: 26 additions & 13 deletions command/transit_import_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"os"
"regexp"
Expand Down Expand Up @@ -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())
Expand All @@ -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:])
Expand All @@ -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))
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion command/transit_import_key_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit 1336abd

Please sign in to comment.