Skip to content

Commit

Permalink
Merge release/vault-next into main (#1915)
Browse files Browse the repository at this point in the history
  • Loading branch information
vinay-gopalan authored Jun 21, 2023
1 parent 85cf1a7 commit adb64bc
Show file tree
Hide file tree
Showing 11 changed files with 600 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ jobs:
- "vault-enterprise:1.11.11-ent"
- "vault-enterprise:1.12.7-ent"
- "vault-enterprise:1.13.3-ent"
- "vault-enterprise:1.14.0-ent"
container:
image: "docker.mirror.hashicorp.services/golang:${{ needs.go-version.outputs.version }}"
services:
Expand Down
1 change: 1 addition & 0 deletions internal/consts/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ const (
VaultVersion111 = "1.11.0"
VaultVersion112 = "1.12.0"
VaultVersion113 = "1.13.0"
VaultVersion114 = "1.14.0"

/*
Vault auth methods
Expand Down
1 change: 1 addition & 0 deletions internal/provider/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ var (
VaultVersion111 = version.Must(version.NewSemver(consts.VaultVersion111))
VaultVersion112 = version.Must(version.NewSemver(consts.VaultVersion112))
VaultVersion113 = version.Must(version.NewSemver(consts.VaultVersion113))
VaultVersion114 = version.Must(version.NewSemver(consts.VaultVersion114))

TokenTTLMinRecommended = time.Minute * 15
)
Expand Down
78 changes: 78 additions & 0 deletions vault/data_source_aws_static_credentials.go
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
}
61 changes: 61 additions & 0 deletions vault/data_source_aws_static_credentials_test.go
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)
}
8 changes: 8 additions & 0 deletions vault/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ var (
Resource: UpdateSchemaResource(awsAccessCredentialsDataSource()),
PathInventory: []string{"/aws/creds"},
},
"vault_aws_static_access_credentials": {
Resource: UpdateSchemaResource(awsStaticCredDataSource()),
PathInventory: []string{"/aws/static-creds/{name}"},
},
"vault_azure_access_credentials": {
Resource: UpdateSchemaResource(azureAccessCredentialsDataSource()),
PathInventory: []string{"/azure/creds/{role}"},
Expand Down Expand Up @@ -263,6 +267,10 @@ var (
Resource: UpdateSchemaResource(awsSecretBackendRoleResource("vault_aws_secret_backend_role")),
PathInventory: []string{"/aws/roles/{name}"},
},
"vault_aws_secret_backend_static_role": {
Resource: UpdateSchemaResource(awsSecretBackendStaticRoleResource()),
PathInventory: []string{"/aws/static-roles/{name}"},
},
"vault_azure_secret_backend": {
Resource: UpdateSchemaResource(azureSecretBackendResource()),
PathInventory: []string{"/azure/config"},
Expand Down
173 changes: 173 additions & 0 deletions vault/resource_aws_secret_backend_static_role.go
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

}
Loading

0 comments on commit adb64bc

Please sign in to comment.