forked from hashicorp/vault
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Transit BYOK export capabilities (hashicorp#20736)
* Add WrapKey capabilities to keysutil This allows one keysutil to wrap another key, assuming that key has an type matching one of keysutil's allowed KeyTypes. This allows completing the BYOK import loop with Transit, allowing imported wrapping keys to export (wrap) other keys in transit, without having them leave in plaintext. Signed-off-by: Alexander Scheel <[email protected]> * Add /byok-export/:dst/:src[/:version] to Transit Still respecting exportable, we allow encrypted-only export of transit keys to another cluster using the BYOK semantics. In particular, this allows an operator to securely establish key material between two separate Transit installations. This potentially allows one cluster to be used as a source cluster (encrypting a large amount of data) and a second cluster to decrypt this data later. This might be useful in hybrid or site-specific deployments of Vault for instance. Signed-off-by: Alexander Scheel <[email protected]> * Add missing dependency to sdk/, vault/ Also updates to a newer version while we're here. Signed-off-by: Alexander Scheel <[email protected]> * Add documentation on BYOK export Signed-off-by: Alexander Scheel <[email protected]> * Add tests for BYOK export/import Signed-off-by: Alexander Scheel <[email protected]> * Add changelog entry Signed-off-by: Alexander Scheel <[email protected]> * Update website/content/api-docs/secret/transit.mdx * Update builtin/logical/transit/path_byok.go Co-authored-by: Matt Schultz <[email protected]> --------- Signed-off-by: Alexander Scheel <[email protected]> Co-authored-by: Matt Schultz <[email protected]>
- Loading branch information
1 parent
10c16cc
commit 63ccb60
Showing
10 changed files
with
589 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package transit | ||
|
||
import ( | ||
"context" | ||
"crypto/ecdsa" | ||
"crypto/ed25519" | ||
"crypto/elliptic" | ||
"errors" | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/hashicorp/vault/sdk/framework" | ||
"github.com/hashicorp/vault/sdk/helper/keysutil" | ||
"github.com/hashicorp/vault/sdk/logical" | ||
) | ||
|
||
func (b *backend) pathBYOKExportKeys() *framework.Path { | ||
return &framework.Path{ | ||
Pattern: "byok-export/" + framework.GenericNameRegex("destination") + "/" + framework.GenericNameRegex("source") + framework.OptionalParamRegex("version"), | ||
|
||
DisplayAttrs: &framework.DisplayAttributes{ | ||
OperationPrefix: operationPrefixTransit, | ||
OperationVerb: "byok", | ||
OperationSuffix: "key|key-version", | ||
}, | ||
|
||
Fields: map[string]*framework.FieldSchema{ | ||
"destination": { | ||
Type: framework.TypeString, | ||
Description: "Destination key to export to; usually the public wrapping key of another Transit instance.", | ||
}, | ||
"source": { | ||
Type: framework.TypeString, | ||
Description: "Source key to export; could be any present key within Transit.", | ||
}, | ||
"version": { | ||
Type: framework.TypeString, | ||
Description: "Optional version of the key to export, else all key versions are exported.", | ||
}, | ||
"hash": { | ||
Type: framework.TypeString, | ||
Description: "Hash function to use for inner OAEP encryption. Defaults to SHA256.", | ||
Default: "SHA256", | ||
}, | ||
}, | ||
|
||
Callbacks: map[logical.Operation]framework.OperationFunc{ | ||
logical.ReadOperation: b.pathPolicyBYOKExportRead, | ||
}, | ||
|
||
HelpSynopsis: pathBYOKExportHelpSyn, | ||
HelpDescription: pathBYOKExportHelpDesc, | ||
} | ||
} | ||
|
||
func (b *backend) pathPolicyBYOKExportRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | ||
dst := d.Get("destination").(string) | ||
src := d.Get("source").(string) | ||
version := d.Get("version").(string) | ||
hash := d.Get("hash").(string) | ||
|
||
dstP, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{ | ||
Storage: req.Storage, | ||
Name: dst, | ||
}, b.GetRandomReader()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if dstP == nil { | ||
return nil, fmt.Errorf("no such destination key to export to") | ||
} | ||
if !b.System().CachingDisabled() { | ||
dstP.Lock(false) | ||
} | ||
defer dstP.Unlock() | ||
|
||
srcP, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{ | ||
Storage: req.Storage, | ||
Name: src, | ||
}, b.GetRandomReader()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if srcP == nil { | ||
return nil, fmt.Errorf("no such source key for export") | ||
} | ||
if !b.System().CachingDisabled() { | ||
srcP.Lock(false) | ||
} | ||
defer srcP.Unlock() | ||
|
||
if !srcP.Exportable { | ||
return logical.ErrorResponse("key is not exportable"), nil | ||
} | ||
|
||
retKeys := map[string]string{} | ||
switch version { | ||
case "": | ||
for k, v := range srcP.Keys { | ||
exportKey, err := getBYOKExportKey(dstP, srcP, &v, hash) | ||
if err != nil { | ||
return nil, err | ||
} | ||
retKeys[k] = exportKey | ||
} | ||
|
||
default: | ||
var versionValue int | ||
if version == "latest" { | ||
versionValue = srcP.LatestVersion | ||
} else { | ||
version = strings.TrimPrefix(version, "v") | ||
versionValue, err = strconv.Atoi(version) | ||
if err != nil { | ||
return logical.ErrorResponse("invalid key version"), logical.ErrInvalidRequest | ||
} | ||
} | ||
|
||
if versionValue < srcP.MinDecryptionVersion { | ||
return logical.ErrorResponse("version for export is below minimum decryption version"), logical.ErrInvalidRequest | ||
} | ||
key, ok := srcP.Keys[strconv.Itoa(versionValue)] | ||
if !ok { | ||
return logical.ErrorResponse("version does not exist or cannot be found"), logical.ErrInvalidRequest | ||
} | ||
|
||
exportKey, err := getBYOKExportKey(dstP, srcP, &key, hash) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
retKeys[strconv.Itoa(versionValue)] = exportKey | ||
} | ||
|
||
resp := &logical.Response{ | ||
Data: map[string]interface{}{ | ||
"name": srcP.Name, | ||
"type": srcP.Type.String(), | ||
"keys": retKeys, | ||
}, | ||
} | ||
|
||
return resp, nil | ||
} | ||
|
||
func getBYOKExportKey(dstP *keysutil.Policy, srcP *keysutil.Policy, key *keysutil.KeyEntry, hash string) (string, error) { | ||
if dstP == nil || srcP == nil { | ||
return "", errors.New("nil policy provided") | ||
} | ||
|
||
var targetKey interface{} | ||
switch srcP.Type { | ||
case keysutil.KeyType_AES128_GCM96, keysutil.KeyType_AES256_GCM96, keysutil.KeyType_ChaCha20_Poly1305, keysutil.KeyType_HMAC: | ||
targetKey = key.Key | ||
case keysutil.KeyType_RSA2048, keysutil.KeyType_RSA3072, keysutil.KeyType_RSA4096: | ||
targetKey = key.RSAKey | ||
case keysutil.KeyType_ECDSA_P256, keysutil.KeyType_ECDSA_P384, keysutil.KeyType_ECDSA_P521: | ||
var curve elliptic.Curve | ||
switch srcP.Type { | ||
case keysutil.KeyType_ECDSA_P384: | ||
curve = elliptic.P384() | ||
case keysutil.KeyType_ECDSA_P521: | ||
curve = elliptic.P521() | ||
default: | ||
curve = elliptic.P256() | ||
} | ||
pubKey := ecdsa.PublicKey{ | ||
Curve: curve, | ||
X: key.EC_X, | ||
Y: key.EC_Y, | ||
} | ||
targetKey = &ecdsa.PrivateKey{ | ||
PublicKey: pubKey, | ||
D: key.EC_D, | ||
} | ||
case keysutil.KeyType_ED25519: | ||
targetKey = ed25519.PrivateKey(key.Key) | ||
default: | ||
return "", fmt.Errorf("unable to export to unknown key type: %v", srcP.Type) | ||
} | ||
|
||
hasher, err := parseHashFn(hash) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return dstP.WrapKey(0, targetKey, srcP.Type, hasher) | ||
} | ||
|
||
const pathBYOKExportHelpSyn = `Securely export named encryption or signing key` | ||
|
||
const pathBYOKExportHelpDesc = ` | ||
This path is used to export the named keys that are configured as | ||
exportable. | ||
Unlike the regular /export/:name[/:version] paths, this path uses | ||
the same encryption specification /import, allowing secure migration | ||
of keys between clusters to enable workloads to communicate between | ||
them. | ||
Presently this only works for RSA destination keys. | ||
` |
Oops, something went wrong.