-
Notifications
You must be signed in to change notification settings - Fork 557
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
85cf1a7
commit adb64bc
Showing
11 changed files
with
600 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package vault | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"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 awsStaticCredsAffix = "static-creds" | ||
|
||
func awsStaticCredDataSource() *schema.Resource { | ||
return &schema.Resource{ | ||
ReadContext: provider.ReadContextWrapper(awsStaticCredentialsDataSourceRead), | ||
Schema: map[string]*schema.Schema{ | ||
// backend is deprecated, but the other AWS resource types use it, and predate the deprecation. | ||
// It's probably more helpful to the end user to maintain this consistency, in this particular case. | ||
consts.FieldBackend: { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Description: "AWS Secret Backend to read credentials from.", | ||
}, | ||
consts.FieldName: { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
Description: "Name of the role.", | ||
}, | ||
consts.FieldAccessKey: { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
Description: "AWS access key ID read from Vault.", | ||
Sensitive: true, | ||
}, | ||
consts.FieldSecretKey: { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
Description: "AWS secret key read from Vault.", | ||
Sensitive: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func awsStaticCredentialsDataSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client, err := provider.GetClient(d, meta) | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
backend := d.Get(consts.FieldBackend).(string) | ||
role := d.Get(consts.FieldName).(string) | ||
fullPath := fmt.Sprintf("%s/%s/%s", backend, awsStaticCredsAffix, role) | ||
|
||
secret, err := client.Logical().ReadWithContext(ctx, fullPath) | ||
if err != nil { | ||
return diag.FromErr(fmt.Errorf("error reading from Vault: %s", err)) | ||
} | ||
log.Printf("[DEBUG] Read %q from Vault", fullPath) | ||
if secret == nil { | ||
return diag.FromErr(fmt.Errorf("no role found at %q", fullPath)) | ||
} | ||
|
||
d.SetId(fullPath) | ||
|
||
if err := d.Set(consts.FieldAccessKey, secret.Data[consts.FieldAccessKey]); err != nil { | ||
return diag.FromErr(err) | ||
} | ||
if err := d.Set(consts.FieldSecretKey, secret.Data[consts.FieldSecretKey]); err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
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 TestAccDataSourceAWSStaticCredentials(t *testing.T) { | ||
a, s := testutil.GetTestAWSCreds(t) | ||
username := testutil.SkipTestEnvUnset(t, "AWS_STATIC_USER")[0] | ||
mount := acctest.RandomWithPrefix("tf-aws-static") | ||
resourceName := "data.vault_aws_static_access_credentials.creds" | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { | ||
testutil.TestAccPreCheck(t) | ||
SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion114) | ||
|
||
}, | ||
ProviderFactories: providerFactories, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAWSStaticDataSourceConfig(mount, a, s, username), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttrSet(resourceName, consts.FieldAccessKey), | ||
resource.TestCheckResourceAttrSet(resourceName, consts.FieldSecretKey), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
const testAWSStaticDataResource = ` | ||
resource "vault_aws_secret_backend" "aws" { | ||
path = "%s" | ||
description = "Obtain AWS credentials." | ||
access_key = "%s" | ||
secret_key = "%s" | ||
} | ||
resource "vault_aws_secret_backend_static_role" "role" { | ||
backend = vault_aws_secret_backend.aws.path | ||
name = "test" | ||
username = "%s" | ||
rotation_period = "3600" | ||
} | ||
data "vault_aws_static_access_credentials" "creds" { | ||
backend = vault_aws_secret_backend.aws.path | ||
name = vault_aws_secret_backend_static_role.role.name | ||
}` | ||
|
||
func testAWSStaticDataSourceConfig(mount, access, secret, username string) string { | ||
return fmt.Sprintf(testAWSStaticDataResource, mount, access, secret, username) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package vault | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
"regexp" | ||
|
||
"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" | ||
"github.com/hashicorp/terraform-provider-vault/util" | ||
) | ||
|
||
const ( | ||
awsStaticRolesAffix = "static-roles" | ||
) | ||
|
||
// awsStaticRolesRegex matches vault paths, capturing backend and role names. This is greedy-by-default, | ||
// which means if someone does something like 'a/static-roles/static-roles/b', the ambiguity will be | ||
// resolved by putting the extras into the backend part of the path. | ||
var awsStaticRolesRegex = regexp.MustCompile("^(.+)/static-roles/(.+)$") | ||
|
||
func awsSecretBackendStaticRoleResource() *schema.Resource { | ||
fields := map[string]*schema.Schema{ | ||
// backend is deprecated, but the other AWS resource types use it, and predate the deprecation. | ||
// It's probably more helpful to the end user to maintain this consistency, in this particular case. | ||
consts.FieldBackend: { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Default: consts.MountTypeAWS, | ||
Description: "The path where the AWS secrets backend is mounted.", | ||
ValidateFunc: provider.ValidateNoLeadingTrailingSlashes, | ||
}, | ||
consts.FieldName: { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Description: "Name of the role.", | ||
ForceNew: true, | ||
}, | ||
consts.FieldUsername: { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Description: "The username of the existing AWS IAM user to manage password rotation for.", | ||
ForceNew: true, | ||
}, | ||
consts.FieldRotationPeriod: { | ||
Type: schema.TypeInt, | ||
Required: true, | ||
Description: "How often Vault should rotate the password of the user entry.", | ||
}, | ||
} | ||
return &schema.Resource{ | ||
CreateContext: provider.MountCreateContextWrapper(createUpdateAWSStaticRoleResource, provider.VaultVersion114), | ||
UpdateContext: createUpdateAWSStaticRoleResource, | ||
ReadContext: provider.ReadContextWrapper(readAWSStaticRoleResource), | ||
DeleteContext: deleteAWSStaticRoleResource, | ||
Importer: &schema.ResourceImporter{ | ||
StateContext: schema.ImportStatePassthroughContext, | ||
}, | ||
Schema: fields, | ||
} | ||
} | ||
|
||
var awsSecretBackendStaticRoleFields = []string{ | ||
consts.FieldName, | ||
consts.FieldUsername, | ||
consts.FieldRotationPeriod, | ||
} | ||
|
||
// createUpdateAWSStaticRoleResources upserts an aws static-role to our vault instance. | ||
func createUpdateAWSStaticRoleResource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client, err := provider.GetClient(d, meta) | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
backend := d.Get(consts.FieldBackend).(string) | ||
role := d.Get(consts.FieldName).(string) | ||
rolePath := fmt.Sprintf("%s/%s/%s", backend, awsStaticRolesAffix, role) | ||
log.Printf("[DEBUG] Creating AWS static role at %q", rolePath) | ||
data := map[string]interface{}{} | ||
for _, field := range awsSecretBackendStaticRoleFields { | ||
if v, ok := d.GetOk(field); ok { | ||
data[field] = v | ||
} | ||
} | ||
|
||
if _, err := client.Logical().WriteWithContext(ctx, rolePath, data); err != nil { | ||
return diag.FromErr(fmt.Errorf("error writing %q: %s", rolePath, err)) | ||
} | ||
|
||
d.SetId(rolePath) | ||
log.Printf("[DEBUG] Wrote %q", rolePath) | ||
return readAWSStaticRoleResource(ctx, d, meta) | ||
} | ||
|
||
func readAWSStaticRoleResource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client, err := provider.GetClient(d, meta) | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
path := d.Id() | ||
log.Printf("[DEBUG] Reading %q", path) | ||
backend, role, err := parseAWSStaticRolePath(path) | ||
if err != nil { | ||
return diag.FromErr(fmt.Errorf("invalid id %q; %w", path, err)) | ||
} | ||
|
||
if err := d.Set(consts.FieldBackend, backend); err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
if err := d.Set(consts.FieldName, role); err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
resp, err := client.Logical().ReadWithContext(ctx, path) | ||
if resp == nil { | ||
log.Printf("[WARN] %q not found, removing from state", path) | ||
d.SetId("") | ||
return nil | ||
} | ||
|
||
for _, field := range awsSecretBackendStaticRoleFields { | ||
if val, ok := resp.Data[field]; ok { | ||
if err := d.Set(field, val); err != nil { | ||
return diag.FromErr(fmt.Errorf("error setting state key '%s': %s", field, err)) | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func deleteAWSStaticRoleResource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client, err := provider.GetClient(d, meta) | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
rolePath := d.Id() | ||
_, err = client.Logical().DeleteWithContext(ctx, rolePath) | ||
if err != nil { | ||
if util.Is404(err) { | ||
d.SetId("") | ||
return nil | ||
} | ||
|
||
return diag.FromErr(fmt.Errorf("error deleting static role %q: %w", rolePath, err)) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// parseAWSStaticRolePath attempts to parse out the backend and role name from the provided path. | ||
func parseAWSStaticRolePath(path string) (backend, name string, err error) { | ||
ms := awsStaticRolesRegex.FindStringSubmatch(path) | ||
if ms == nil { | ||
return "", "", fmt.Errorf("no pattern match for path %s", path) | ||
} | ||
if len(ms) != 3 { // 0 = entire match, 1 = first capture group (backend), 2 = second capture group (role name) | ||
return "", "", fmt.Errorf("unexpected number (%d) of matches found, expected 3", len(ms)) | ||
} | ||
|
||
return ms[1], ms[2], nil | ||
|
||
} |
Oops, something went wrong.