Skip to content
This repository was archived by the owner on Jan 17, 2025. It is now read-only.
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
28 changes: 28 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ provider "redshift" {
}
```

### Authentication using temporary credentials in cross-account scenario

```terraform
provider "redshift" {
host = var.redshift_host
username = var.redshift_user
temporary_credentials {
cluster_identifier = "my-cluster"
assume_role {
arn = "arn:aws:iam::012345678901:role/role-name-with-path"
}
}
}
```

<!-- schema generated by tfplugindocs -->
## Schema

Expand All @@ -57,10 +72,23 @@ Required:

Optional:

- **assume_role** (Block List, Max: 1) Optional assume role data used to obtain temporary credentials (see [below for nested schema](#nestedblock--temporary_credentials--assume_role))
- **auto_create_user** (Boolean) Create a database user with the name specified for the user if one does not exist.
- **db_groups** (Set of String) A list of the names of existing database groups that the user will join for the current session, in addition to any group memberships for an existing user. If not specified, a new user is added only to PUBLIC.
- **duration_seconds** (Number) The number of seconds until the returned temporary password expires.

<a id="nestedblock--temporary_credentials--assume_role"></a>
### Nested Schema for `temporary_credentials.assume_role`

Required:

- **arn** (String) Amazon Resource Name of an IAM Role to assume prior to making API calls.

Optional:

- **external_id** (String) A unique identifier that might be required when you assume a role in another account.
- **session_name** (String) An identifier for the assumed role session.

## Proxy Support

If your Redshift cluster is only accessible from within a VPC, you can use the `ALL_PROXY` (`all_proxy`)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
provider "redshift" {
host = var.redshift_host
username = var.redshift_user
temporary_credentials {
cluster_identifier = "my-cluster"
assume_role {
arn = "arn:aws:iam::012345678901:role/role-name-with-path"
}
}
}
66 changes: 63 additions & 3 deletions redshift/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@ import (
"context"
"fmt"
"log"
"regexp"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/redshift"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

const (
defaultProviderMaxOpenConnections = 20
defaultProviderMaxOpenConnections = 20
defaultTemporaryCredentialsAssumeRoleDurationInSeconds = 900
)

func Provider() *schema.Provider {
Expand Down Expand Up @@ -111,6 +116,7 @@ func Provider() *schema.Provider {
Description: "The number of seconds until the returned temporary password expires.",
ValidateFunc: validation.IntBetween(900, 3600),
},
"assume_role": assumeRoleSchema(),
},
},
},
Expand Down Expand Up @@ -222,9 +228,63 @@ func temporaryCredentials(username string, d *schema.ResourceData) (string, stri
}

func redshiftSdkClient(d *schema.ResourceData) (*redshift.Client, error) {
config, err := config.LoadDefaultConfig(context.TODO())
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return nil, err
}
return redshift.NewFromConfig(config), nil
if _, ok := d.GetOk("temporary_credentials.0.assume_role"); ok {
var parsedRoleArn string
if roleArn, ok := d.GetOk("temporary_credentials.0.assume_role.0.arn"); ok {
parsedRoleArn = roleArn.(string)
}
log.Printf("[DEBUG] Assuming role provided in configuration: [%s]", parsedRoleArn)
opts := func(options *stscreds.AssumeRoleOptions) {
options.Duration = time.Duration(defaultTemporaryCredentialsAssumeRoleDurationInSeconds) * time.Second
if externalID, ok := d.GetOk("temporary_credentials.0.assume_role.0.external_id"); ok {
options.ExternalID = aws.String(externalID.(string))
}
if sessionName, ok := d.GetOk("temporary_credentials.0.assume_role.0.session_name"); ok {
options.RoleSessionName = sessionName.(string)
}
}
stsClient := sts.NewFromConfig(cfg)
cfg.Credentials = stscreds.NewAssumeRoleProvider(stsClient, parsedRoleArn, opts)
}
return redshift.NewFromConfig(cfg), nil
}

func assumeRoleSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Description: "Optional assume role data used to obtain temporary credentials",
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Required: true,
Description: "Amazon Resource Name of an IAM Role to assume prior to making API calls.",
},
"external_id": {
Type: schema.TypeString,
Optional: true,
Description: "A unique identifier that might be required when you assume a role in another account.",
ValidateFunc: validation.All(
validation.StringLenBetween(2, 1224),
validation.StringMatch(regexp.MustCompile(`[\w+=,.@:\/\-]*`), ""),
),
},
"session_name": {
Type: schema.TypeString,
Optional: true,
Description: "An identifier for the assumed role session.",
ValidateFunc: validation.All(
validation.StringLenBetween(2, 64),
validation.StringMatch(regexp.MustCompile(`[\w+=,.@\-]*`), ""),
),
},
},
},
}
}
47 changes: 38 additions & 9 deletions redshift/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func testAccPreCheck(t *testing.T) {
}

func initTemporaryCredentialsProvider(t *testing.T, provider *schema.Provider) {
clusterIdentifier := getEnvOrSkip("REDSHIFT_CLUSTER_IDENTIFIER", t)
clusterIdentifier := getEnvOrSkip("REDSHIFT_TEMPORARY_CREDENTIALS_CLUSTER_IDENTIFIER", t)

sdkClient, err := stsClient(t)
if err != nil {
Expand All @@ -68,6 +68,13 @@ func initTemporaryCredentialsProvider(t *testing.T, provider *schema.Provider) {
},
},
}
if arn, ok := os.LookupEnv("REDSHIFT_TEMPORARY_CREDENTIALS_ASSUME_ROLE_ARN"); ok {
config["temporary_credentials"].([]interface{})[0].(map[string]interface{})["assume_role"] = []interface{}{
map[string]interface{}{
"arn": arn,
},
}
}
diagnostics := provider.Configure(context.Background(), terraform.NewResourceConfigRaw(config))
if diagnostics != nil {
if diagnostics.HasError() {
Expand All @@ -86,14 +93,25 @@ func stsClient(t *testing.T) (*sts.Client, error) {

func TestAccRedshiftTemporaryCredentials(t *testing.T) {
provider := Provider()
redshift_password := os.Getenv("REDSHIFT_PASSWORD")
defer os.Setenv("REDSHIFT_PASSWORD", redshift_password)
os.Unsetenv("REDSHIFT_PASSWORD")
rawUsername := os.Getenv("REDSHIFT_USER")
defer os.Setenv("REDSHIFT_USER", rawUsername)
username := strings.ToLower(permanentUsername(rawUsername))
os.Setenv("REDSHIFT_USER", username)
initTemporaryCredentialsProvider(t, provider)
assume_role_arn := os.Getenv("REDSHIFT_TEMPORARY_CREDENTIALS_ASSUME_ROLE_ARN")
defer os.Setenv("REDSHIFT_TEMPORARY_CREDENTIALS_ASSUME_ROLE_ARN", assume_role_arn)
os.Unsetenv("REDSHIFT_TEMPORARY_CREDENTIALS_ASSUME_ROLE_ARN")
prepareRedshiftTemporaryCredentialsTestCases(t, provider)
client, ok := provider.Meta().(*Client)
if !ok {
t.Fatal("Unable to initialize client")
}
db, err := client.Connect()
if err != nil {
t.Fatalf("Unable to connect to database: %s", err)
}
defer db.Close()
}

func TestAccRedshiftTemporaryCredentialsAssumeRole(t *testing.T) {
_ = getEnvOrSkip("REDSHIFT_TEMPORARY_CREDENTIALS_ASSUME_ROLE_ARN", t)
provider := Provider()
prepareRedshiftTemporaryCredentialsTestCases(t, provider)
client, ok := provider.Meta().(*Client)
if !ok {
t.Fatal("Unable to initialize client")
Expand All @@ -104,3 +122,14 @@ func TestAccRedshiftTemporaryCredentials(t *testing.T) {
}
defer db.Close()
}

func prepareRedshiftTemporaryCredentialsTestCases(t *testing.T, provider *schema.Provider) {
redshift_password := os.Getenv("REDSHIFT_PASSWORD")
defer os.Setenv("REDSHIFT_PASSWORD", redshift_password)
os.Unsetenv("REDSHIFT_PASSWORD")
rawUsername := os.Getenv("REDSHIFT_USER")
defer os.Setenv("REDSHIFT_USER", rawUsername)
username := strings.ToLower(permanentUsername(rawUsername))
os.Setenv("REDSHIFT_USER", username)
initTemporaryCredentialsProvider(t, provider)
}
4 changes: 4 additions & 0 deletions templates/index.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ The Redshift provider provides configuration management resources for

{{ tffile "examples/provider/provider_using_temporary_credentials.tf" }}

### Authentication using temporary credentials in cross-account scenario

{{ tffile "examples/provider/provider_using_temporary_credentials_cross_account.tf" }}

{{ .SchemaMarkdown | trimspace }}

## Proxy Support
Expand Down