Add Transit BYOK wrapping key endpoint (#15271)
* add wrapping key endpoint * change how wrapping key is stored * move wrapping key func to backend * refactor wrapping key generation * Initial unit tests for Transit wrapping key endpoint * Wire up wrapping key unit tests to actual implementation. * Clean up Transit BYOK wrapping key tests and imports. * Fix Transit wrapping key endpoint formatting. * Update transit wrapping key to use lock manager for safe concurrent use. * Rename some Transit wrapping key variables. Ensure the Transit wrapping key is correctly typed and formatted in a unit test. * Fix spacing issue in Transit wrapping key endpoint help string. Co-authored-by: rculpepper <rculpepper@hashicorp.com>
This commit is contained in:
parent
9d608a2e38
commit
5aabe4a5f8
|
@ -46,6 +46,7 @@ func Backend(ctx context.Context, conf *logical.BackendConfig) (*backend, error)
|
|||
b.pathConfig(),
|
||||
b.pathRotate(),
|
||||
b.pathRewrap(),
|
||||
b.pathWrappingKey(),
|
||||
b.pathKeys(),
|
||||
b.pathListKeys(),
|
||||
b.pathExportKeys(),
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package transit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/helper/keysutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
)
|
||||
|
||||
const WrappingKeyName = "wrapping-key"
|
||||
|
||||
func (b *backend) pathWrappingKey() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "wrapping_key",
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.pathWrappingKeyRead,
|
||||
},
|
||||
HelpSynopsis: pathWrappingKeyHelpSyn,
|
||||
HelpDescription: pathWrappingKeyHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathWrappingKeyRead(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
|
||||
polReq := keysutil.PolicyRequest{
|
||||
Upsert: true,
|
||||
Storage: req.Storage,
|
||||
Name: fmt.Sprintf("import/%s", WrappingKeyName),
|
||||
KeyType: keysutil.KeyType_RSA4096,
|
||||
Derived: false,
|
||||
Convergent: false,
|
||||
Exportable: false,
|
||||
AllowPlaintextBackup: false,
|
||||
AutoRotatePeriod: 0,
|
||||
}
|
||||
p, _, err := b.GetPolicy(ctx, polReq, b.GetRandomReader())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p == nil {
|
||||
return nil, fmt.Errorf("error retrieving wrapping key: returned policy was nil")
|
||||
}
|
||||
if b.System().CachingDisabled() {
|
||||
p.Unlock()
|
||||
}
|
||||
|
||||
wrappingKey := p.Keys[strconv.Itoa(p.LatestVersion)]
|
||||
|
||||
derBytes, err := x509.MarshalPKIXPublicKey(wrappingKey.RSAKey.Public())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling RSA public key: %w", err)
|
||||
}
|
||||
pemBlock := &pem.Block{
|
||||
Type: "PUBLIC KEY",
|
||||
Bytes: derBytes,
|
||||
}
|
||||
pemBytes := pem.EncodeToMemory(pemBlock)
|
||||
if pemBytes == nil || len(pemBytes) == 0 {
|
||||
return nil, fmt.Errorf("failed to PEM-encode RSA public key")
|
||||
}
|
||||
|
||||
publicKeyString := string(pemBytes)
|
||||
|
||||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"public_key": publicKeyString,
|
||||
},
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
const (
|
||||
pathWrappingKeyHelpSyn = "Returns the public key to use for wrapping imported keys"
|
||||
pathWrappingKeyHelpDesc = "This path is used to retrieve the RSA-4096 wrapping key " +
|
||||
"for wrapping keys that are being imported into transit."
|
||||
)
|
|
@ -0,0 +1,72 @@
|
|||
package transit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
)
|
||||
|
||||
const (
|
||||
storagePath = "policy/import/" + WrappingKeyName
|
||||
)
|
||||
|
||||
func TestTransit_WrappingKey(t *testing.T) {
|
||||
// Set up shared backend for subtests
|
||||
b, s := createBackendWithStorage(t)
|
||||
|
||||
// Ensure the key does not exist before requesting it.
|
||||
keyEntry, err := s.Get(context.Background(), storagePath)
|
||||
if err != nil {
|
||||
t.Fatalf("error retrieving wrapping key from storage: %s", err)
|
||||
}
|
||||
if keyEntry != nil {
|
||||
t.Fatal("wrapping key unexpectedly exists")
|
||||
}
|
||||
|
||||
// Generate the key pair by requesting the public key.
|
||||
req := &logical.Request{
|
||||
Storage: s,
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "wrapping_key",
|
||||
}
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected request error: %s", err)
|
||||
}
|
||||
if resp == nil || resp.Data == nil || resp.Data["public_key"] == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
pubKeyPEM := resp.Data["public_key"]
|
||||
|
||||
// Ensure the returned key is a 4096-bit RSA key.
|
||||
pubKeyBlock, _ := pem.Decode([]byte(pubKeyPEM.(string)))
|
||||
rawPubKey, err := x509.ParsePKIXPublicKey(pubKeyBlock.Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse public wrapping key: %s", err)
|
||||
}
|
||||
wrappingKey, ok := rawPubKey.(*rsa.PublicKey)
|
||||
if !ok || wrappingKey.Size() != 512 {
|
||||
t.Fatal("public wrapping key is not a 4096-bit RSA key")
|
||||
}
|
||||
|
||||
// Request the wrapping key again to ensure it isn't regenerated.
|
||||
req = &logical.Request{
|
||||
Storage: s,
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "wrapping_key",
|
||||
}
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected request error: %s", err)
|
||||
}
|
||||
if resp == nil || resp.Data == nil || resp.Data["public_key"] == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
if resp.Data["public_key"] != pubKeyPEM {
|
||||
t.Fatal("wrapping key public component changed between requests")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue