Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include rotated secrets for cloud vault secrets VAULT-22307 #850

Merged
merged 8 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/850.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
Allows users to fetch rotating secrets using the hcp_vault_secrets_app and hcp_vault_secrets_secret data sources
```
78 changes: 41 additions & 37 deletions internal/clients/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ import (
cloud_vault "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-service/stable/2020-11-25/client"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-service/stable/2020-11-25/client/vault_service"

cloud_vault_secrets_preview "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/preview/2023-11-28/client"
secret_service_preview "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/preview/2023-11-28/client/secret_service"
cloud_vault_secrets "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/stable/2023-06-13/client"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/stable/2023-06-13/client/secret_service"

Expand All @@ -66,24 +68,25 @@ import (
type Client struct {
Config ClientConfig

Billing billing_account_service.ClientService
Boundary boundary_service.ClientService
Consul consul_service.ClientService
IAM iam_service.ClientService
Network network_service.ClientService
Operation operation_service.ClientService
Organization organization_service.ClientService
Packer packer_service.ClientService
PackerV2 packer_service_v2.ClientService
Project project_service.ClientService
ServicePrincipals service_principals_service.ClientService
Groups groups_service.ClientService
Vault vault_service.ClientService
VaultSecrets secret_service.ClientService
Waypoint waypoint_service.ClientService
Webhook webhook_service.ClientService
LogService log_service.ClientService
ResourceService resource_service.ClientService
Billing billing_account_service.ClientService
Boundary boundary_service.ClientService
Consul consul_service.ClientService
IAM iam_service.ClientService
Network network_service.ClientService
Operation operation_service.ClientService
Organization organization_service.ClientService
Packer packer_service.ClientService
PackerV2 packer_service_v2.ClientService
Project project_service.ClientService
ServicePrincipals service_principals_service.ClientService
Groups groups_service.ClientService
Vault vault_service.ClientService
VaultSecrets secret_service.ClientService
VaultSecretsPreview secret_service_preview.ClientService
Waypoint waypoint_service.ClientService
Webhook webhook_service.ClientService
LogService log_service.ClientService
ResourceService resource_service.ClientService
}

// ClientConfig specifies configuration for the client that interacts with HCP
Expand Down Expand Up @@ -158,25 +161,26 @@ func NewClient(config ClientConfig) (*Client, error) {
}

client := &Client{
Config: config,
Billing: cloud_billing.New(httpClient, nil).BillingAccountService,
Boundary: cloud_boundary.New(httpClient, nil).BoundaryService,
Consul: cloud_consul.New(httpClient, nil).ConsulService,
IAM: cloud_iam.New(httpClient, nil).IamService,
Network: cloud_network.New(httpClient, nil).NetworkService,
Operation: cloud_operation.New(httpClient, nil).OperationService,
Organization: cloud_resource_manager.New(httpClient, nil).OrganizationService,
Packer: cloud_packer.New(httpClient, nil).PackerService,
PackerV2: cloud_packer_v2.New(httpClient, nil).PackerService,
Project: cloud_resource_manager.New(httpClient, nil).ProjectService,
ServicePrincipals: cloud_iam.New(httpClient, nil).ServicePrincipalsService,
Groups: cloud_iam.New(httpClient, nil).GroupsService,
Vault: cloud_vault.New(httpClient, nil).VaultService,
VaultSecrets: cloud_vault_secrets.New(httpClient, nil).SecretService,
Waypoint: cloud_waypoint.New(httpClient, nil).WaypointService,
LogService: cloud_log_service.New(httpClient, nil).LogService,
Webhook: cloud_webhook.New(httpClient, nil).WebhookService,
ResourceService: cloud_resource_manager.New(httpClient, nil).ResourceService,
Config: config,
Billing: cloud_billing.New(httpClient, nil).BillingAccountService,
Boundary: cloud_boundary.New(httpClient, nil).BoundaryService,
Consul: cloud_consul.New(httpClient, nil).ConsulService,
IAM: cloud_iam.New(httpClient, nil).IamService,
Network: cloud_network.New(httpClient, nil).NetworkService,
Operation: cloud_operation.New(httpClient, nil).OperationService,
Organization: cloud_resource_manager.New(httpClient, nil).OrganizationService,
Packer: cloud_packer.New(httpClient, nil).PackerService,
PackerV2: cloud_packer_v2.New(httpClient, nil).PackerService,
Project: cloud_resource_manager.New(httpClient, nil).ProjectService,
ServicePrincipals: cloud_iam.New(httpClient, nil).ServicePrincipalsService,
Groups: cloud_iam.New(httpClient, nil).GroupsService,
Vault: cloud_vault.New(httpClient, nil).VaultService,
VaultSecrets: cloud_vault_secrets.New(httpClient, nil).SecretService,
VaultSecretsPreview: cloud_vault_secrets_preview.New(httpClient, nil).SecretService,
Waypoint: cloud_waypoint.New(httpClient, nil).WaypointService,
LogService: cloud_log_service.New(httpClient, nil).LogService,
Webhook: cloud_webhook.New(httpClient, nil).WebhookService,
ResourceService: cloud_resource_manager.New(httpClient, nil).ResourceService,
}

return client, nil
Expand Down
73 changes: 0 additions & 73 deletions internal/clients/vault_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,6 @@ func UpdateVaultSecretsApp(ctx context.Context, client *Client, loc *sharedmodel
return updateResp.Payload.App, nil
}

// ListVaultSecretsAppSecrets will retrieve all app secrets metadata for a Vault Secrets application.
func ListVaultSecretsAppSecrets(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, appName string) ([]*secretmodels.Secrets20230613Secret, error) {

listParams := secret_service.NewListAppSecretsParams()
listParams.Context = ctx
listParams.AppName = appName
listParams.LocationOrganizationID = loc.OrganizationID
listParams.LocationProjectID = loc.ProjectID

listResp, err := client.VaultSecrets.ListAppSecrets(listParams, nil)
if err != nil {
return nil, err
}

return listResp.Payload.Secrets, nil
}

// DeleteVaultSecretsApp will delete a Vault Secrets application.
func DeleteVaultSecretsApp(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, appName string) error {

Expand Down Expand Up @@ -122,62 +105,6 @@ func CreateVaultSecretsAppSecret(ctx context.Context, client *Client, loc *share
return createResp.Payload.Secret, nil
}

// OpenVaultSecretsAppSecret will retrieve the latest secret for a Vault Secrets app, including it's value.
func OpenVaultSecretsAppSecret(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, appName, secretName string) (*secretmodels.Secrets20230613OpenSecret, error) {
getParams := secret_service.NewOpenAppSecretParams()
getParams.Context = ctx
getParams.AppName = appName
getParams.SecretName = secretName
getParams.LocationOrganizationID = loc.OrganizationID
getParams.LocationProjectID = loc.ProjectID

var getResp *secret_service.OpenAppSecretOK
var err error
for attempt := 0; attempt < retryCount; attempt++ {
getResp, err = client.VaultSecrets.OpenAppSecret(getParams, nil)
if err != nil {
serviceErr, ok := err.(*secret_service.OpenAppSecretDefault)
if !ok {
return nil, err
}

if shouldRetryWithSleep(ctx, serviceErr, attempt, []int{http.StatusTooManyRequests}) {
continue
}
return nil, err
}
break
}
return getResp.Payload.Secret, nil
}

func OpenVaultSecretsAppSecrets(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, appName string) ([]*secretmodels.Secrets20230613OpenSecret, error) {
params := secret_service.NewOpenAppSecretsParams()
params.Context = ctx
params.AppName = appName
params.LocationOrganizationID = loc.OrganizationID
params.LocationProjectID = loc.ProjectID

var secrets *secret_service.OpenAppSecretsOK
var err error
for attempt := 0; attempt < retryCount; attempt++ {
secrets, err = client.VaultSecrets.OpenAppSecrets(params, nil)
if err != nil {
serviceErr, ok := err.(*secret_service.OpenAppSecretsDefault)
if !ok {
return nil, err
}
if shouldRetryWithSleep(ctx, serviceErr, attempt, []int{http.StatusTooManyRequests}) {
continue
}
return nil, err
}
break
}

return secrets.Payload.Secrets, nil
}

// DeleteVaultSecretsAppSecret will delete a Vault Secrets application secret.
func DeleteVaultSecretsAppSecret(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, appName, secretName string) error {

Expand Down
85 changes: 85 additions & 0 deletions internal/clients/vault_secrets_preview.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package clients

import (
"context"
"errors"
"fmt"
"net/http"
"time"

sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/preview/2023-11-28/client/secret_service"
secretmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-secrets/preview/2023-11-28/models"
"github.com/hashicorp/terraform-plugin-log/tflog"
)

// OpenVaultSecretsAppSecret will retrieve the latest secret for a Vault Secrets app, including it's value.
func OpenVaultSecretsAppSecret(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, appName, secretName string) (*secretmodels.Secrets20231128OpenSecret, error) {
getParams := secret_service.NewOpenAppSecretParamsWithContext(ctx).
WithAppName(appName).
WithSecretName(secretName).
WithOrganizationID(loc.OrganizationID).
WithProjectID(loc.ProjectID)
Comment on lines +21 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice improvement!


var getResp *secret_service.OpenAppSecretOK
var err error
for attempt := 0; attempt < retryCount; attempt++ {
getResp, err = client.VaultSecretsPreview.OpenAppSecret(getParams, nil)
if err != nil {
var serviceErr *secret_service.OpenAppSecretDefault
ok := errors.As(err, &serviceErr)
if !ok {
return nil, err
}

if shouldRetryErrorCode(serviceErr.Code(), []int{http.StatusTooManyRequests}) {
backOffDuration := getAPIBackoffDuration(serviceErr.Error())
tflog.Debug(ctx, fmt.Sprintf("The api rate limit has been exceeded, retrying in %d seconds, attempt: %d", int64(backOffDuration.Seconds()), (attempt+1)))
time.Sleep(backOffDuration)
continue
}
return nil, err
}
break
}

if getResp == nil {
return nil, errors.New("unable to get secret")
}

return getResp.GetPayload().Secret, nil
}

func OpenVaultSecretsAppSecrets(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, appName string) ([]*secretmodels.Secrets20231128OpenSecret, error) {
params := secret_service.NewOpenAppSecretsParamsWithContext(ctx).
WithAppName(appName).
WithOrganizationID(loc.OrganizationID).
WithProjectID(loc.ProjectID)

var secrets *secret_service.OpenAppSecretsOK
var err error
for attempt := 0; attempt < retryCount; attempt++ {
secrets, err = client.VaultSecretsPreview.OpenAppSecrets(params, nil)
if err != nil {
var serviceErr *secret_service.OpenAppSecretDefault
ok := errors.As(err, &serviceErr)
if !ok {
return nil, err
}
if shouldRetryWithSleep(ctx, serviceErr, attempt, []int{http.StatusTooManyRequests}) {
continue
}
return nil, err
}
break
}

if secrets == nil {
return nil, errors.New("unable to get secrets")
}

return secrets.GetPayload().Secrets, nil
}
16 changes: 14 additions & 2 deletions internal/provider/vaultsecrets/data_source_vault_secrets_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,20 @@ func (d *DataSourceVaultSecretsApp) Read(ctx context.Context, req datasource.Rea

openAppSecrets := map[string]string{}
for _, appSecret := range appSecrets {
secretName := appSecret.Name
openAppSecrets[secretName] = appSecret.Version.Value
switch {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be cleaner to switch on secret.Type instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleaner: yes

was concerned about the string type, are we going to switch to enum at some point?

Copy link
Contributor

@averche averche May 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe 🤷 It's OK to leave as is for now

case appSecret.StaticVersion != nil:
openAppSecrets[appSecret.Name] = appSecret.StaticVersion.Value
case appSecret.RotatingVersion != nil:
for name, value := range appSecret.RotatingVersion.Values {
openAppSecrets[appSecret.Name+"_"+name] = value
}
default:
resp.Diagnostics.AddError(
"Unsupported HCP Secret type",
fmt.Sprintf("HCP Secrets secret type %q is not currently supported by terraform-provider-hcp", appSecret.Type),
)
return
}
}

data.ID = data.AppName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package vaultsecrets

import (
"context"
"encoding/json"
"fmt"

sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models"
Expand All @@ -31,11 +32,11 @@ func NewVaultSecretsSecretDataSource() datasource.DataSource {
return &DataSourceVaultSecretsSecret{}
}

func (d *DataSourceVaultSecretsSecret) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
func (d *DataSourceVaultSecretsSecret) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_vault_secrets_secret"
}

func (d *DataSourceVaultSecretsSecret) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
func (d *DataSourceVaultSecretsSecret) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "The Vault Secrets secret data source retrieves a singular secret and its latest version.",
Attributes: map[string]schema.Attribute{
Expand Down Expand Up @@ -107,7 +108,31 @@ func (d *DataSourceVaultSecretsSecret) Read(ctx context.Context, req datasource.
resp.Diagnostics.AddError(err.Error(), "Unable to open secret")
return
}
secretValue := openSecret.Version.Value

// NOTE: for backwards compatibility purposes, if the secret is not a static secret (a string)
// encode the complex secret as a JSON string
var secretValue string
switch {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, switching on secret.Type might be cleaner

case openSecret.StaticVersion != nil:
secretValue = openSecret.StaticVersion.Value
case openSecret.RotatingVersion != nil:
secretData, err := json.Marshal(openSecret.RotatingVersion.Values)
if err != nil {
resp.Diagnostics.AddError(err.Error(), "could not encode rotating secret as json")
return
}
resp.Diagnostics.AddWarning(
"HCP Vault Secrets mismatched type",
"Attempted to get a rotating secret in a KV secret data source, encoding the secret values as JSON",
)
Comment on lines +124 to +127
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit unsure about this warning. We may want to keep this data source as an equivalent of vault_generic_secret data source. In this case, it should be perfectly valid to use it to fetch a rotating secret.

I think it's OK to leave it here as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets keep, was in the original discussion when we planned this out

secretValue = string(secretData)
default:
resp.Diagnostics.AddError(
"Unsupported HCP Secret type",
fmt.Sprintf("HCP Secrets secret type %q is not currently supported by terraform-provider-hcp", openSecret.Type),
)
return
}

data.ID = data.AppName
data.SecretValue = types.StringValue(secretValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,9 @@ func (r *resourceVaultsecretsSecret) Read(ctx context.Context, req resource.Read
return
}

state.SecretValue = types.StringValue(res.Version.Value)
// TODO: so the resource can only create a static secret,
// what happens when a user tries to import a rotating/other type of secret?
state.SecretValue = types.StringValue(res.StaticVersion.Value)

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
Expand Down