Skip to content

Commit

Permalink
v1.7.0 Pre-Release (#980)
Browse files Browse the repository at this point in the history
* Delete mongodbatlas.erb (#962)

* INTMDB-523: Rename exportJobID to exportID to match go client (#976)

* Updated version of atlas api client used, renamed bucketID to exportJobID

* Reverted changes to bucketID and updated exportJobID to exportID

* INTMDB-521: AWS Secrets Manager to Auth into Terraform Atlas Provider (#975)

* Add support for assume_role

* Add documentation for assume_role feature

* Add AWS parameters Env vars

* Update index.html.markdown

* Doc clean up

* typo

* Add regional behavior to endpoint sts client

* Add sts_endpoint parameter

* Update website/docs/index.html.markdown

* formatting

* formatting2

* Removed commented code

Co-authored-by: Zuhair Ahmed <[email protected]>

* Update .github_changelog_generator

Co-authored-by: Dosty Everts <[email protected]>
Co-authored-by: Zuhair Ahmed <[email protected]>
  • Loading branch information
3 people authored Dec 22, 2022
1 parent 71ee85f commit 7a57d21
Show file tree
Hide file tree
Showing 9 changed files with 340 additions and 256 deletions.
4 changes: 2 additions & 2 deletions .github_changelog_generator
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
future-release=v1.6.1
since-tag=v1.6.0
future-release=v1.7.0
since-tag=v1.6.1
date-format=%B %d, %Y
base=CHANGELOG.md
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/mwielbut/pointy v1.1.0
github.com/spf13/cast v1.5.0
github.com/terraform-providers/terraform-provider-aws v1.60.1-0.20210625132053-af2d5c0ad54f
go.mongodb.org/atlas v0.18.1-0.20221109142841-f9f8ebe7b9b9
go.mongodb.org/atlas v0.19.0
go.mongodb.org/realm v0.1.0
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,8 @@ go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6y
go.mongodb.org/atlas v0.12.0/go.mod h1:wVCnHcm/7/IfTjEB6K8K35PLG70yGz8BdkRwX0oK9/M=
go.mongodb.org/atlas v0.18.1-0.20221109142841-f9f8ebe7b9b9 h1:9m35o4kyRYjwbsIb/lPrjxJ6afPpn9zwOF5i3SIY5Lg=
go.mongodb.org/atlas v0.18.1-0.20221109142841-f9f8ebe7b9b9/go.mod h1:PFk1IGhiGjFXHGVspOK7i1U2nnPjK8wAjYwQf6FoVf4=
go.mongodb.org/atlas v0.19.0 h1:gvezG9d0KsSDaExEdTtcGqZHRvvVazzuEcBUpBXxmlg=
go.mongodb.org/atlas v0.19.0/go.mod h1:PFk1IGhiGjFXHGVspOK7i1U2nnPjK8wAjYwQf6FoVf4=
go.mongodb.org/realm v0.1.0 h1:zJiXyLaZrznQ+Pz947ziSrDKUep39DO4SfA0Fzx8M4M=
go.mongodb.org/realm v0.1.0/go.mod h1:4Vj6iy+Puo1TDERcoh4XZ+pjtwbOzPpzqy3Cwe8ZmDM=
go.mozilla.org/mozlog v0.0.0-20170222151521-4bb13139d403/go.mod h1:jHoPAGnDrCy6kaI2tAze5Prf0Nr0w/oNkROt2lw3n3o=
Expand Down
1 change: 1 addition & 0 deletions mongodbatlas/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Config struct {
PrivateKey string
BaseURL string
RealmBaseURL string
AssumeRole *AssumeRole
}

// MongoDBClient client
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ func dataSourceMongoDBAtlasCloudBackupSnapshotsExportJobRead(ctx context.Context
ids := decodeStateID(d.Id())
projectID := ids["project_id"]
clusterName := ids["cluster_name"]
exportJobID := ids["export_job_id"]
exportID := ids["export_job_id"]

exportJob, _, err := conn.CloudProviderSnapshotExportJobs.Get(ctx, projectID, clusterName, exportJobID)
exportJob, _, err := conn.CloudProviderSnapshotExportJobs.Get(ctx, projectID, clusterName, exportID)
if err != nil {
return diag.Errorf("error getting snapshot export job information: %s", err)
}
Expand Down
297 changes: 296 additions & 1 deletion mongodbatlas/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,28 @@ package mongodbatlas
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"hash/crc32"
"log"
"os"
"reflect"
"regexp"
"sort"
"strconv"
"strings"

"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/secretsmanager"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mwielbut/pointy"
"github.com/spf13/cast"
matlas "go.mongodb.org/atlas/mongodbatlas"
Expand All @@ -24,6 +35,11 @@ var (
baseURL = ""
)

type SecretData struct {
PublicKey string `json:"public_key"`
PrivateKey string `json:"private_key"`
}

// Provider returns the provider to be use by the code.
func Provider() *schema.Provider {
provider := &schema.Provider{
Expand Down Expand Up @@ -67,6 +83,51 @@ func Provider() *schema.Provider {
Optional: true,
Description: "MongoDB Atlas Base URL default to gov",
},
"assume_role": assumeRoleSchema(),
"secret_name": {
Type: schema.TypeString,
Optional: true,
},
"region": {
Type: schema.TypeString,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
"AWS_REGION",
"TF_VAR_AWS_REGION",
}, ""),
Optional: true,
},
"sts_endpoint": {
Type: schema.TypeString,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
"STS_ENDPOINT",
"TF_VAR_STS_ENDPOINT",
}, ""),
Optional: true,
},
"aws_access_key_id": {
Type: schema.TypeString,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
"AWS_ACCESS_KEY_ID",
"TF_VAR_AWS_ACCESS_KEY_ID",
}, ""),
Optional: true,
},
"aws_secret_access_key": {
Type: schema.TypeString,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
"AWS_SECRET_ACCESS_KEY",
"TF_VAR_AWS_SECRET_ACCESS_KEY",
}, ""),
Optional: true,
},
"aws_session_token": {
Type: schema.TypeString,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
"AWS_SESSION_TOKEN",
"TF_VAR_AWS_SESSION_TOKEN",
}, ""),
Optional: true,
},
},
DataSourcesMap: getDataSourcesMap(),
ResourcesMap: getResourcesMap(),
Expand Down Expand Up @@ -226,9 +287,79 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}
RealmBaseURL: d.Get("realm_base_url").(string),
}

if v, ok := d.GetOk("assume_role"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
config.AssumeRole = expandAssumeRole(v.([]interface{})[0].(map[string]interface{}))
secret := d.Get("secret_name").(string)
region := d.Get("region").(string)
awsAccessKeyID := d.Get("aws_access_key_id").(string)
awsSecretAccessKey := d.Get("aws_secret_access_key").(string)
awsSessionToken := d.Get("aws_session_token").(string)
endpoint := d.Get("sts_endpoint").(string)
config, _ = configureCredentialsSTS(&config, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint)
}

return config.NewClient(ctx)
}

func configureCredentialsSTS(config *Config, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint string) (Config, error) {
ep, _ := endpoints.GetSTSRegionalEndpoint("regional")
sess := session.Must(session.NewSession(&aws.Config{
Region: aws.String(region),
Credentials: credentials.NewStaticCredentials(awsAccessKeyID, awsSecretAccessKey, awsSessionToken),
STSRegionalEndpoint: ep,
Endpoint: &endpoint,
}))

creds := stscreds.NewCredentials(sess, config.AssumeRole.RoleARN)

_, _ = sess.Config.Credentials.Get()
_, _ = creds.Get()
secretString := secretsManagerGetSecretValue(sess, &aws.Config{Credentials: creds, Region: aws.String(region)}, secret)

var secretData SecretData
err := json.Unmarshal([]byte(secretString), &secretData)
if err != nil {
return *config, nil
}
config.PublicKey = secretData.PublicKey
config.PrivateKey = secretData.PrivateKey
return *config, nil
}

func secretsManagerGetSecretValue(sess *session.Session, creds *aws.Config, secret string) string {
svc := secretsmanager.New(sess, creds)
input := &secretsmanager.GetSecretValueInput{
SecretId: aws.String(secret),
VersionStage: aws.String("AWSCURRENT"),
}

result, err := svc.GetSecretValue(input)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case secretsmanager.ErrCodeResourceNotFoundException:
fmt.Println(secretsmanager.ErrCodeResourceNotFoundException, aerr.Error())
case secretsmanager.ErrCodeInvalidParameterException:
fmt.Println(secretsmanager.ErrCodeInvalidParameterException, aerr.Error())
case secretsmanager.ErrCodeInvalidRequestException:
fmt.Println(secretsmanager.ErrCodeInvalidRequestException, aerr.Error())
case secretsmanager.ErrCodeDecryptionFailure:
fmt.Println(secretsmanager.ErrCodeDecryptionFailure, aerr.Error())
case secretsmanager.ErrCodeInternalServiceError:
fmt.Println(secretsmanager.ErrCodeInternalServiceError, aerr.Error())
default:
fmt.Println(aerr.Error())
}
} else {
fmt.Println(err.Error())
}
return ""
}

fmt.Println(result)
return *result.SecretString
}

func encodeStateID(values map[string]string) string {
encode := func(e string) string { return base64.StdEncoding.EncodeToString([]byte(e)) }
encodedValues := make([]string, 0)
Expand Down Expand Up @@ -391,3 +522,167 @@ func HashCodeString(s string) int {
// v == MinInt
return 0
}

// assumeRoleSchema From aws provider.go
func assumeRoleSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"duration": {
Type: schema.TypeString,
Optional: true,
Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.",
ValidateFunc: validAssumeRoleDuration,
ConflictsWith: []string{"assume_role.0.duration_seconds"},
},
"duration_seconds": {
Type: schema.TypeInt,
Optional: true,
Deprecated: "Use assume_role.duration instead",
Description: "The duration, in seconds, of the role session.",
ValidateFunc: validation.IntBetween(900, 43200),
ConflictsWith: []string{"assume_role.0.duration"},
},
"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+=,.@:/\-]*`), ""),
),
},
"policy": {
Type: schema.TypeString,
Optional: true,
Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.",
ValidateFunc: validation.StringIsJSON,
},
"policy_arns": {
Type: schema.TypeSet,
Optional: true,
Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"role_arn": {
Type: schema.TypeString,
Optional: true,
Description: "Amazon Resource Name (ARN) of an IAM Role to assume prior to making API calls.",
},
"session_name": {
Type: schema.TypeString,
Optional: true,
Description: "An identifier for the assumed role session.",
ValidateFunc: validAssumeRoleSessionName,
},
"source_identity": {
Type: schema.TypeString,
Optional: true,
Description: "Source identity specified by the principal assuming the role.",
ValidateFunc: validAssumeRoleSourceIdentity,
},
"tags": {
Type: schema.TypeMap,
Optional: true,
Description: "Assume role session tags.",
Elem: &schema.Schema{Type: schema.TypeString},
},
"transitive_tag_keys": {
Type: schema.TypeSet,
Optional: true,
Description: "Assume role session tag keys to pass to any subsequent sessions.",
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
}
}

var validAssumeRoleSessionName = validation.All(
validation.StringLenBetween(2, 64),
validation.StringMatch(regexp.MustCompile(`[\w+=,.@\-]*`), ""),
)

var validAssumeRoleSourceIdentity = validation.All(
validation.StringLenBetween(2, 64),
validation.StringMatch(regexp.MustCompile(`[\w+=,.@\-]*`), ""),
)

// validAssumeRoleDuration validates a string can be parsed as a valid time.Duration
// and is within a minimum of 15 minutes and maximum of 12 hours
func validAssumeRoleDuration(v interface{}, k string) (ws []string, errors []error) {
duration, err := time.ParseDuration(v.(string))

if err != nil {
errors = append(errors, fmt.Errorf("%q cannot be parsed as a duration: %w", k, err))
return
}

if duration.Minutes() < 15 || duration.Hours() > 12 {
errors = append(errors, fmt.Errorf("duration %q must be between 15 minutes (15m) and 12 hours (12h), inclusive", k))
}

return
}

type AssumeRole struct {
RoleARN string
Duration time.Duration
ExternalID string
Policy string
PolicyARNs []string
SessionName string
SourceIdentity string
Tags map[string]string
TransitiveTagKeys []string
}

func expandAssumeRole(tfMap map[string]interface{}) *AssumeRole {
if tfMap == nil {
return nil
}

assumeRole := AssumeRole{}

if v, ok := tfMap["duration"].(string); ok && v != "" {
duration, _ := time.ParseDuration(v)
assumeRole.Duration = duration
} else if v, ok := tfMap["duration_seconds"].(int); ok && v != 0 {
assumeRole.Duration = time.Duration(v) * time.Second
}

if v, ok := tfMap["external_id"].(string); ok && v != "" {
assumeRole.ExternalID = v
}

if v, ok := tfMap["policy"].(string); ok && v != "" {
assumeRole.Policy = v
}

if v, ok := tfMap["policy_arns"].(*schema.Set); ok && v.Len() > 0 {
assumeRole.PolicyARNs = expandStringList(v.List())
}

if v, ok := tfMap["role_arn"].(string); ok && v != "" {
assumeRole.RoleARN = v
}

if v, ok := tfMap["session_name"].(string); ok && v != "" {
assumeRole.SessionName = v
}

if v, ok := tfMap["source_identity"].(string); ok && v != "" {
assumeRole.SourceIdentity = v
}

if v, ok := tfMap["transitive_tag_keys"].(*schema.Set); ok && v.Len() > 0 {
assumeRole.TransitiveTagKeys = expandStringList(v.List())
}

return &assumeRole
}
Loading

0 comments on commit 7a57d21

Please sign in to comment.