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

Add Secrets Sync support to TFVP #2098

Merged
merged 26 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b37f333
add aws destination resource
vinay-gopalan Nov 15, 2023
59dfbde
complete basic secrets sync loop with AWS
vinay-gopalan Nov 17, 2023
ed3be16
add aws destination test
vinay-gopalan Nov 21, 2023
2288385
add gh sync destination and test
vinay-gopalan Nov 21, 2023
609df9e
add gcp secrets sync destination
vinay-gopalan Nov 23, 2023
116d4a7
add azure kv destination
vinay-gopalan Nov 23, 2023
8ac8149
add vercel destination
vinay-gopalan Dec 21, 2023
1016729
make updates to read methods
vinay-gopalan Dec 21, 2023
049303d
break early in secrets sync association
vinay-gopalan Dec 21, 2023
5a7f502
add import step for Azure destination
vinay-gopalan Dec 21, 2023
07a39c7
generalize secrets sync destination code into package
vinay-gopalan Jan 10, 2024
53cbbe6
add generalized method code
vinay-gopalan Jan 10, 2024
d27daa8
Add new parameters and generalized code to GCP and Azure
vinay-gopalan Jan 11, 2024
ba13bd8
fix association fields
vinay-gopalan Jan 11, 2024
fd28729
fix incorrect 1.16 provider version
vinay-gopalan Jan 11, 2024
3ec8ae8
make rest of env var fields optional
vinay-gopalan Jan 11, 2024
a7ff1c9
Merge branch 'main' into VAULT-18672/aws-secrets-sync-loop
vinay-gopalan Jan 12, 2024
f9a5ed7
format
vinay-gopalan Jan 12, 2024
20ad80a
make tags and template editable
vinay-gopalan Jan 16, 2024
86ed21b
consolidate write ops to single method and add tests for updates
vinay-gopalan Jan 16, 2024
cc4597d
set kv uri for azure on reads
vinay-gopalan Jan 16, 2024
cf042c8
add changelog entry with version requirement
vinay-gopalan Jan 16, 2024
a0638ef
Add Secrets Sync documentation (#2127)
vinay-gopalan Jan 16, 2024
4da4fbd
Vault 22912/sync config (#2125)
maxcoulombe Jan 17, 2024
44fdeb5
resolve conflicts
vinay-gopalan Jan 17, 2024
f4ea268
add sync association struct model to avoid deep nesting
vinay-gopalan Jan 17, 2024
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
20 changes: 20 additions & 0 deletions vault/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,26 @@ var (
Resource: UpdateSchemaResource(samlAuthBackendRoleResource()),
PathInventory: []string{"/auth/saml/role/{name}"},
},
"vault_aws_secrets_sync_destination": {
Resource: UpdateSchemaResource(awsSecretsSyncDestinationResource()),
PathInventory: []string{"/sys/sync/destinations/aws-sm/{name}"},
},
"vault_gh_secrets_sync_destination": {
Resource: UpdateSchemaResource(githubSecretsSyncDestinationResource()),
PathInventory: []string{"/sys/sync/destinations/gh/{name}"},
},
"vault_gcp_secrets_sync_destination": {
Resource: UpdateSchemaResource(gcpSecretsSyncDestinationResource()),
PathInventory: []string{"/sys/sync/destinations/gcp-sm/{name}"},
},
"vault_azure_secrets_sync_destination": {
Resource: UpdateSchemaResource(azureSecretsSyncDestinationResource()),
PathInventory: []string{"/sys/sync/destinations/azure-kv/{name}"},
},
"vault_secrets_sync_association": {
Resource: UpdateSchemaResource(secretsSyncAssociationResource()),
PathInventory: []string{"/sys/sync/destinations/{type}/{name}/associations/set"},
robmonte marked this conversation as resolved.
Show resolved Hide resolved
},
}
)

Expand Down
154 changes: 154 additions & 0 deletions vault/resource_aws_secrets_sync_destination.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright (c) HashiCorp, Inc.
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
// SPDX-License-Identifier: MPL-2.0

package vault

import (
"context"
"log"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/hashicorp/terraform-provider-vault/internal/consts"
"github.com/hashicorp/terraform-provider-vault/internal/provider"
)

const (
fieldAccessKeyID = "access_key_id"
fieldSecretAccessKey = "secret_access_key"
)

var awsSyncDestinationFields = []string{
fieldAccessKeyID,
fieldSecretAccessKey,
consts.FieldAWSRegion,
}

func awsSecretsSyncDestinationResource() *schema.Resource {
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
return &schema.Resource{
CreateContext: provider.MountCreateContextWrapper(awsSecretsSyncDestinationWrite, provider.VaultVersion115),
ReadContext: provider.ReadContextWrapper(awsSecretsSyncDestinationRead),
DeleteContext: awsSecretsSyncDestinationDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: map[string]*schema.Schema{
consts.FieldName: {
Type: schema.TypeString,
Required: true,
Description: "Unique name of the AWS destination.",
ForceNew: true,
},
fieldAccessKeyID: {
Type: schema.TypeString,
Optional: true,
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
Sensitive: true,
Description: "Access key id to authenticate against the AWS secrets manager.",
ForceNew: true,
},
fieldSecretAccessKey: {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
Description: "Secret access key to authenticate against the AWS secrets " +
"manager.",
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
ForceNew: true,
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
},
consts.FieldRegion: {
Type: schema.TypeString,
Optional: true,
Description: "Region where to manage the secrets manager entries.",
ForceNew: true,
},
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
},
}
}

func awsSecretsSyncDestinationWrite(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
client, e := provider.GetClient(d, meta)
if e != nil {
return diag.FromErr(e)
}

name := d.Get(consts.FieldName).(string)
path := awsSecretsSyncDestinationPath(name)

data := map[string]interface{}{}

for _, k := range awsSyncDestinationFields {
data[k] = d.Get(k)
}

log.Printf("[DEBUG] Writing AWS sync destination to %q", path)
_, err := client.Logical().WriteWithContext(ctx, path, data)
if err != nil {
return diag.Errorf("error enabling AWS sync destination %q: %s", path, err)
}
log.Printf("[DEBUG] Enabled AWS sync destination %q", path)

d.SetId(name)

return awsSecretsSyncDestinationRead(ctx, d, meta)
}

func awsSecretsSyncDestinationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, e := provider.GetClient(d, meta)
if e != nil {
return diag.FromErr(e)
}
name := d.Id()
path := awsSecretsSyncDestinationPath(name)

log.Printf("[DEBUG] Reading AWS sync destination")
resp, err := client.Logical().ReadWithContext(ctx, path)
if err != nil {
return diag.Errorf("error reading AWS sync destination from %q: %s", path, err)
}
log.Printf("[DEBUG] Read AWS sync destination")

if resp == nil {
log.Printf("[WARN] No info found at %q; removing from state.", path)
d.SetId("")
return nil
}

if err := d.Set(consts.FieldName, name); err != nil {
return diag.FromErr(err)
}

for _, k := range awsSyncDestinationFields {
if v, ok := resp.Data[k]; ok {
if err := d.Set(k, v); err != nil {
return diag.Errorf("error setting state key %q: err=%s", k, err)
}
}
}

// set sensitive fields that will not be returned from Vault

return nil
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
}

func awsSecretsSyncDestinationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, e := provider.GetClient(d, meta)
if e != nil {
return diag.FromErr(e)
}

path := awsSecretsSyncDestinationPath(d.Id())

log.Printf("[DEBUG] Deleting AWS sync destination at %q", path)
_, err := client.Logical().DeleteWithContext(ctx, path)
if err != nil {
return diag.Errorf("error deleting AWS sync destination at %q: %s", path, err)
}
log.Printf("[DEBUG] Deleted AWS sync destination at %q", path)

return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Recreate scenarios like modifying the region do not work at the moment with the following error:

│ Namespace: my-ns-1
│ URL: DELETE http://localhost:8200/v1/sys/sync/destinations/aws-sm/my-dest-1
│ Code: 400. Errors:
│ 
│ * store cannot be deleted because it is still managing secrets

I believe that with @robmonte 's new ?purge parameter available when deleting an application we could gracefully handle the teardown flow by passing this param and polling the destination until the purge operation gets completed by the backend. This can be a separate ticket/PR but food for thought on a possible solution!


func awsSecretsSyncDestinationPath(name string) string {
return "sys/sync/destinations/aws-sm/" + name
}
57 changes: 57 additions & 0 deletions vault/resource_aws_secrets_sync_destination_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package vault

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"

"github.com/hashicorp/terraform-provider-vault/internal/consts"
"github.com/hashicorp/terraform-provider-vault/internal/provider"
"github.com/hashicorp/terraform-provider-vault/testutil"
)

func TestAWSSecretsSyncDestination(t *testing.T) {
destName := acctest.RandomWithPrefix("tf-sync-dest")

resourceName := "vault_aws_secrets_sync_destination.test"

accessKey, secretKey := testutil.GetTestAWSCreds(t)
region := testutil.GetTestAWSRegion(t)
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories,
PreCheck: func() {
testutil.TestAccPreCheck(t)
SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion115)
}, PreventPostDestroyRefresh: true,
Steps: []resource.TestStep{
{
Config: testAWSSecretsSyncDestinationConfig(accessKey, secretKey, region, destName),
Check: resource.ComposeTestCheckFunc(

resource.TestCheckResourceAttr(resourceName, consts.FieldName, destName),
resource.TestCheckResourceAttr(resourceName, fieldAccessKeyID, accessKey),
resource.TestCheckResourceAttr(resourceName, fieldSecretAccessKey, secretKey),
resource.TestCheckResourceAttr(resourceName, consts.FieldRegion, region),
),
},
},
})
}

func testAWSSecretsSyncDestinationConfig(accessKey, secretKey, region, destName string) string {
ret := fmt.Sprintf(`
resource "vault_aws_secrets_sync_destination" "test" {
name = "%s"
access_key_id = "%s"
secret_access_key = "%s"
region = "%s"
}
`, destName, accessKey, secretKey, region)

return ret
}
Loading