Skip to content

Commit

Permalink
Fix metadata handling and updates (#2)
Browse files Browse the repository at this point in the history
* Fix metadata handling and updates

* Fix comment

* Change k8s auth to have the same synthax as the official provider

* Update docs
  • Loading branch information
jcbbc authored Mar 10, 2023
1 parent a77a19e commit b95a31b
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 96 deletions.
6 changes: 3 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "vaultsecret Provider"
page_title: "vaultprov Provider"
subcategory: ""
description: |-
A provider to generate secrets and have them stored directly into Vault without any copy in the Terraform State. Once the secret has been generated, its value only exist into Vault. Terraform will not track any change in the value, only in the secret attribute (metadata, etc.`).
---

# vaultsecret Provider
# vaultprov Provider

A provider to generate secrets and have them stored directly into Vault without any copy in the Terraform State. Once the secret has been generated, its value only exist into Vault. Terraform will not track any change in the value, only in the secret attribute (`metadata`, etc.`).

## Example Usage

```terraform
provider "vaultsecret" {
provider "vaultprov" {
address = "http://some.vault:8200"
auth = {
path = "auth/kubernetes/login"
Expand Down
18 changes: 9 additions & 9 deletions docs/resources/random.md → docs/resources/random_secret.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "vaultsecret_random Resource - vaultsecret"
page_title: "vaultprov_random_secret Resource - vaultprov"
subcategory: ""
description: |-
A cryptographic randomly generated secret stored as bytes in a Vault secret. The resulting Vault secret will have a custom metadata secret_type with the value random_secret and a custom metadata secret_length with the same value as the length attribute.
---

# vaultsecret_random (Resource)
# vaultprov_random_secret (Resource)

A cryptographic randomly generated secret stored as bytes in a Vault secret. The resulting Vault secret will have a custom metadata `secret_type` with the value `random_secret` and a custom metadata `secret_length` with the same value as the `length` attribute.

## Example Usage

```terraform
resource "vaultsecret_random" "example" {
path = "/secrets/foo/bar"
length = 32
metadata = {
owner = "my_team"
some-key = "some-value"
}
resource "vaultprov_random_secret" "example" {
path = "/secret/foo/bar"
length = 32
metadata = {
owner = "my_team"
some-key = "some-value"
}
}
```

Expand Down
141 changes: 67 additions & 74 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import (
"context"
"fmt"
vaultapi "github.com/blablacar/terraform-provider-vaultprov/internal/vault"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
vault "github.com/hashicorp/vault/api"
auth "github.com/hashicorp/vault/api/auth/kubernetes"
)

const providerName = "vaultprov"
Expand All @@ -25,9 +23,9 @@ type vaultSecretProvider struct {

// Provider schema struct
type providerModel struct {
Address types.String `tfsdk:"address"`
Token types.String `tfsdk:"token"`
Auth providerAuthModel `tfsdk:"auth"`
Address types.String `tfsdk:"address"`
Token types.String `tfsdk:"token"`
Auth *providerAuthModel `tfsdk:"auth"`
}

type providerAuthModel struct {
Expand All @@ -42,75 +40,6 @@ func New() func() provider.Provider {
}
}

func (p *vaultSecretProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
var config providerModel
diags := req.Config.Get(ctx, &config)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

vaultConf := vault.DefaultConfig()
vaultConf.Address = config.Address.ValueString()

client, err := vault.NewClient(vaultConf)
if err != nil {
tflog.Error(ctx, "Error creating vault client", map[string]interface{}{"address": vaultConf.Address, "error": err})
resp.Diagnostics.AddError(
"Error configuring provider",
fmt.Sprintf("Can't create vault client for %s: %s", vaultConf.Address, err.Error()),
)
return
}

if !config.Token.IsNull() {
client.SetToken(config.Token.ValueString()) //DEBUG
tflog.Warn(ctx, "Auth token provided. Ignoring other auth parameters. FOR DEBUG ONLY, DO NOT USE IN PRODUCTION.", nil)

p.vaultApi = vaultapi.NewVaultApi(client)
resp.ResourceData = p.vaultApi
return
}

role := config.Auth.Role.ValueString()
jwt := config.Auth.Jwt.ValueString()

k8sAuth, err := auth.NewKubernetesAuth(
role,
auth.WithServiceAccountToken(jwt),
)
if err != nil {
tflog.Error(ctx, "Error initializing Vault kubernetes auth method", map[string]interface{}{"role": role, "jwt": jwt, "error": err})
resp.Diagnostics.AddError(
"Error configuring provider",
fmt.Sprintf("Unable to initialize Vault Kubernetes authentication with role %s and JWT %s: %s", role, jwt, err.Error()),
)
return
}

authInfo, err := client.Auth().Login(context.Background(), k8sAuth)
if err != nil {
tflog.Error(ctx, "Error login in with Vault kubernetes auth method", map[string]interface{}{"role": role, "jwt": jwt, "error": err})
resp.Diagnostics.AddError(
"Error configuring provider",
fmt.Sprintf("Unable to log in with Vault Kubernetes authentication with role %s and JWT %s: %s", role, jwt, err.Error()),
)
return
}

if authInfo == nil {
tflog.Error(ctx, "Not auth info returned for kubernetes auth", map[string]interface{}{"role": role, "jwt": jwt})
resp.Diagnostics.AddError(
"Error configuring provider",
fmt.Sprintf("Not auth info returned for kubernetes auth with role %s and JWT %s: %s", role, jwt, err.Error()),
)
return
}

p.vaultApi = vaultapi.NewVaultApi(client)
resp.ResourceData = p.vaultApi
}

func (p *vaultSecretProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
resp.TypeName = providerName
}
Expand Down Expand Up @@ -157,3 +86,67 @@ func (p *vaultSecretProvider) Schema(ctx context.Context, req provider.SchemaReq
MarkdownDescription: "A provider to generate secrets and have them stored directly into Vault without any copy in the Terraform State. Once the secret has been generated, its value only exist into Vault. Terraform will not track any change in the value, only in the secret attribute (`metadata`, etc.`).",
}
}

func (p *vaultSecretProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
var config providerModel
diags := req.Config.Get(ctx, &config)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

vaultConf := vault.DefaultConfig()
vaultConf.Address = config.Address.ValueString()

client, err := vault.NewClient(vaultConf)
if err != nil {
tflog.Error(ctx, "Error creating vault client", map[string]interface{}{"address": vaultConf.Address, "error": err})
resp.Diagnostics.AddError(
"Error configuring provider",
fmt.Sprintf("Can't create vault client for %s: %s", vaultConf.Address, err.Error()),
)
return
}

authConf := config.Auth
if !config.Token.IsNull() {
client.SetToken(config.Token.ValueString()) //DEBUG
tflog.Warn(ctx, "Auth token provided. Ignoring other auth parameters. FOR DEBUG ONLY, DO NOT USE IN PRODUCTION.", nil)
} else if authConf != nil {
err = setupVaultClientAuth(client, authConf)
if err != nil {
tflog.Error(ctx, "Error while configuring vault client auth", map[string]interface{}{"address": vaultConf.Address, "error": err})
resp.Diagnostics.AddError(
"Error configuring provider",
fmt.Sprintf("Can't create vault client for %s: %s", vaultConf.Address, err.Error()),
)
}
}

p.vaultApi = vaultapi.NewVaultApi(client)
resp.ResourceData = p.vaultApi
}

func setupVaultClientAuth(client *vault.Client, authConf *providerAuthModel) error {
role := authConf.Role.ValueString()
jwt := authConf.Jwt.ValueString()

//We don't use auth.NewKubernetesAuth in order to have the same input parameters as the official Vault provider
// (otherwise 'path' would have to be replaced by 'mount')
loginData := map[string]interface{}{
"jwt": jwt,
"role": role,
}

path := authConf.Path.ValueString()
authInfo, err := client.Logical().Write(path, loginData)
if err != nil {
return fmt.Errorf("unable to log in with Vault Kubernetes authentication with role %s and JWT %s: %w", role, jwt, err)
}

if authInfo == nil {
return fmt.Errorf("not auth info returned for kubernetes auth with role %s and JWT %s: %s", role, jwt, err)
}

return nil
}
20 changes: 18 additions & 2 deletions internal/provider/resource_random.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
_ "github.com/hashicorp/terraform-plugin-go/tftypes"
"strconv"
)

const (
Expand Down Expand Up @@ -137,7 +138,7 @@ func (s *RandomSecret) Create(ctx context.Context, request resource.CreateReques
customMetadata[SecretTypeMetadata] = secretType
customMetadata[SecretLengthMetadata] = fmt.Sprintf("%d", secretLength)

data := map[string]string{
data := map[string]interface{}{
SecretDataKey: base64.StdEncoding.EncodeToString(key),
}

Expand Down Expand Up @@ -184,6 +185,18 @@ func (s *RandomSecret) Read(ctx context.Context, req resource.ReadRequest, resp
if len(customMetadata) > 0 {
additionalMetadata := make(map[string]attr.Value)
for k, v := range customMetadata {
if k == SecretTypeMetadata {
continue
}
if k == SecretLengthMetadata {
len, err := strconv.Atoi(v)
if err != nil {
resp.Diagnostics.AddError("Error reading secret length: "+v, fmt.Sprintf("Error while reading secret %s: %s", secretPath, err.Error()))
return
}
data.Length = types.Int64Value(int64(len))
continue
}
additionalMetadata[k] = types.StringValue(v)
}
data.Metadata, _ = types.MapValue(types.StringType, additionalMetadata)
Expand Down Expand Up @@ -211,7 +224,7 @@ func (s *RandomSecret) Update(ctx context.Context, req resource.UpdateRequest, r
return
}

// Check that path, length and type haven't changed
// Check that path, hasn't changed
if state.Path.ValueString() != plan.Path.ValueString() {
resp.Diagnostics.AddError("Error updating random key", fmt.Sprintf("Invalid path change. Random key can't have their path changed (old: %s, new: %s). Only metadata changes are authorized. Delete and recreate the key instead.", state.Path.ValueString(), plan.Path.ValueString()))
return
Expand All @@ -224,6 +237,9 @@ func (s *RandomSecret) Update(ctx context.Context, req resource.UpdateRequest, r
metadata[k] = v.(types.String).ValueString()
}

metadata[SecretTypeMetadata] = RandomSecretType
metadata[SecretLengthMetadata] = plan.Length.String()

err := s.vaultApi.UpdateSecretMetadata(secretPath, metadata)
if err != nil {
resp.Diagnostics.AddError("Error updating secret", fmt.Sprintf("Error while updating metadata for secret %s: %s", secretPath, err.Error()))
Expand Down
9 changes: 2 additions & 7 deletions internal/vault/vault_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const (

type Secret struct {
Path string
Data map[string]string
Data map[string]interface{}
Metadata map[string]string
}

Expand Down Expand Up @@ -119,7 +119,7 @@ func (c *VaultApi) ReadSecret(secretPath string) (*Secret, error) {
customMetadata[k] = v.(string)
}

data := secret.Data[SecretDataField].(map[string]string)
data := secret.Data[SecretDataField].(map[string]interface{})

vaultSecret := &Secret{
Path: secretPath,
Expand Down Expand Up @@ -147,13 +147,8 @@ func (c *VaultApi) UpdateSecretMetadata(secretPath string, metadata map[string]s
return fmt.Errorf("missing custom metadata")
}

currentMetadata := secretMetadata.Data[SecretCustomDataField].(map[string]interface{})

// Update secret's metadata from plan (only metadata can be changed)
updatedMetadata := make(map[string]string)
for k, v := range currentMetadata {
updatedMetadata[k] = v.(string)
}

for k, v := range metadata {
updatedMetadata[k] = v
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

// Provider documentation generation.
//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate --provider-name vaultsecret
//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate --provider-name vaultprov

const providerUrl = "registry.terraform.io/blablacar/vaultprov"

Expand Down

0 comments on commit b95a31b

Please sign in to comment.