71cc398197
Provide a transit-style Vault/OpenBao secrets engine whose key material is OpenPGP, so services can sign/verify/encrypt/decrypt with GPG keys that never leave the barrier — and so tools like pass can encrypt to an exported public key while delegating decryption back to Vault. - Add versioned key management (keys/<name> CRUD+list, config, rotate, import) with private material seal-wrapped under key/ and per-key locking. - Add sign/verify (detached OpenPGP) and encrypt/decrypt paths; decrypt auto-detects armored vs raw-binary ciphertext (what pass/gpg write). - Add export/<public-key|private-key>/<name>; public always exportable, private only when the key is marked exportable. - Use ProtonMail go-crypto for OpenPGP; support rsa-2048/3072/4096 and ed25519. - Clone the sibling plugin's build/packaging/CI: dual-target RPMs (vault + openbao plugin dirs), Woodpecker PR/release pipelines, and a Vault+OpenBao e2e harness. Unit tests include real gpg and pass interop.
55 lines
1.5 KiB
Go
55 lines
1.5 KiB
Go
package gpg
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/sdk/framework"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
func pathRotate(b *gpgBackend) *framework.Path {
|
|
return &framework.Path{
|
|
Pattern: "keys/" + framework.GenericNameRegex("name") + "/rotate",
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
OperationPrefix: "gpg",
|
|
OperationSuffix: "key-rotate",
|
|
},
|
|
Fields: map[string]*framework.FieldSchema{
|
|
"name": {
|
|
Type: framework.TypeString,
|
|
Description: "Name of the key.",
|
|
Required: true,
|
|
},
|
|
},
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
logical.UpdateOperation: &framework.PathOperation{Callback: b.pathRotateWrite},
|
|
},
|
|
HelpSynopsis: "Add a new version to a key.",
|
|
HelpDescription: "Generate a fresh OpenPGP entity as the new latest version. Older versions are retained for decryption and verification.",
|
|
}
|
|
}
|
|
|
|
func (b *gpgBackend) pathRotateWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
name := data.Get("name").(string)
|
|
|
|
lock := b.lockForKey(name)
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
key, err := b.getKey(ctx, req.Storage, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if key == nil {
|
|
return logical.ErrorResponse("key %q not found", name), logical.ErrInvalidRequest
|
|
}
|
|
if err := key.rotate(time.Now()); err != nil {
|
|
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
|
|
}
|
|
if err := b.putKey(ctx, req.Storage, key); err != nil {
|
|
return nil, err
|
|
}
|
|
return b.keyResponse(key)
|
|
}
|