Files
unkinben f56bb6be29
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
Add terraform-provider-gpgvaultsecret
A terraform-plugin-framework provider for the vault-plugin-secrets-gpg engine,
managing engine mounts and OpenPGP keys on Vault/OpenBao.

- gpg_secret_backend resource: mount the engine (+ optional plugin catalog
  registration when a sha256 is given; deregisters on destroy).
- gpg_key resource: create/configure a key (algorithm, identity, exportable,
  deletion_allowed, min_decryption_version); computed public_key/fingerprint/
  key_id/latest_version; destroy auto-enables deletion; import <backend>/<name>.
- gpg_key data source: read a key's metadata + armored public key.
- Talks to Vault/OpenBao via hashicorp/vault/api; address/token fall back to
  VAULT_ADDR/VAULT_TOKEN. Unit tests plus an e2e running real terraform
  apply/destroy against a Vault dev server + the gpg plugin. Release publishes a
  zip to the artifactapi terraform-unkin registry on v* tags.
2026-07-16 23:33:04 +10:00

126 lines
4.6 KiB
Go

package provider
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)
var _ datasource.DataSource = &keyDataSource{}
type keyDataSource struct {
client *vaultClient
}
type keyDataSourceModel struct {
Backend types.String `tfsdk:"backend"`
Name types.String `tfsdk:"name"`
Algorithm types.String `tfsdk:"algorithm"`
Identity types.String `tfsdk:"identity"`
Exportable types.Bool `tfsdk:"exportable"`
DeletionAllowed types.Bool `tfsdk:"deletion_allowed"`
MinDecryptionVersion types.Int64 `tfsdk:"min_decryption_version"`
LatestVersion types.Int64 `tfsdk:"latest_version"`
Fingerprint types.String `tfsdk:"fingerprint"`
KeyID types.String `tfsdk:"key_id"`
PublicKey types.String `tfsdk:"public_key"`
}
func NewKeyDataSource() datasource.DataSource {
return &keyDataSource{}
}
func (d *keyDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_key"
}
func (d *keyDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Reads an OpenPGP key's metadata and armored public key from a gpg secrets engine mount.",
Attributes: map[string]schema.Attribute{
"backend": schema.StringAttribute{
Description: "Mount path of the gpg secrets engine.",
Required: true,
},
"name": schema.StringAttribute{
Description: "Name of the key.",
Required: true,
},
"algorithm": schema.StringAttribute{Description: "Key algorithm.", Computed: true},
"identity": schema.StringAttribute{Description: "OpenPGP User ID.", Computed: true},
"exportable": schema.BoolAttribute{Description: "Whether the private key is exportable.", Computed: true},
"deletion_allowed": schema.BoolAttribute{Description: "Whether the key may be deleted.", Computed: true},
"min_decryption_version": schema.Int64Attribute{Description: "Minimum usable version for decryption/verification.", Computed: true},
"latest_version": schema.Int64Attribute{Description: "Current (highest) key version.", Computed: true},
"fingerprint": schema.StringAttribute{Description: "OpenPGP fingerprint of the latest version.", Computed: true},
"key_id": schema.StringAttribute{Description: "OpenPGP key ID of the latest version.", Computed: true},
"public_key": schema.StringAttribute{Description: "Armored public key of the latest version.", Computed: true},
},
}
}
func (d *keyDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
client, ok := req.ProviderData.(*vaultClient)
if !ok {
resp.Diagnostics.AddError("unexpected provider data type", fmt.Sprintf("got %T", req.ProviderData))
return
}
d.client = client
}
func (d *keyDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var config keyDataSourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
if resp.Diagnostics.HasError() {
return
}
backend := strings.Trim(config.Backend.ValueString(), "/")
name := config.Name.ValueString()
data, err := d.client.readKey(ctx, backend, name)
if err != nil {
resp.Diagnostics.AddError("failed to read gpg key", err.Error())
return
}
if data == nil {
resp.Diagnostics.AddError("gpg key not found", fmt.Sprintf("no key %q in backend %q", name, backend))
return
}
if s, ok := toString(data["algorithm"]); ok {
config.Algorithm = types.StringValue(s)
}
if s, ok := toString(data["identity"]); ok {
config.Identity = types.StringValue(s)
}
if b, ok := toBool(data["exportable"]); ok {
config.Exportable = types.BoolValue(b)
}
if b, ok := toBool(data["deletion_allowed"]); ok {
config.DeletionAllowed = types.BoolValue(b)
}
if n, ok := toInt64(data["min_decryption_version"]); ok {
config.MinDecryptionVersion = types.Int64Value(n)
}
if n, ok := toInt64(data["latest_version"]); ok {
config.LatestVersion = types.Int64Value(n)
}
if s, ok := toString(data["fingerprint"]); ok {
config.Fingerprint = types.StringValue(s)
}
if s, ok := toString(data["public_key"]); ok {
config.PublicKey = types.StringValue(s)
}
config.KeyID = types.StringValue(keyIDForLatest(data))
resp.Diagnostics.Append(resp.State.Set(ctx, config)...)
}