Skip to content

Commit 7a57d21

Browse files
martinstibbeevertsdZuhairahmed
authored
v1.7.0 Pre-Release (#980)
* 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]>
1 parent 71ee85f commit 7a57d21

9 files changed

+340
-256
lines changed

.github_changelog_generator

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
future-release=v1.6.1
2-
since-tag=v1.6.0
1+
future-release=v1.7.0
2+
since-tag=v1.6.1
33
date-format=%B %d, %Y
44
base=CHANGELOG.md

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ require (
1111
github.com/mwielbut/pointy v1.1.0
1212
github.com/spf13/cast v1.5.0
1313
github.com/terraform-providers/terraform-provider-aws v1.60.1-0.20210625132053-af2d5c0ad54f
14-
go.mongodb.org/atlas v0.18.1-0.20221109142841-f9f8ebe7b9b9
14+
go.mongodb.org/atlas v0.19.0
1515
go.mongodb.org/realm v0.1.0
1616
)
1717

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,8 @@ go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6y
921921
go.mongodb.org/atlas v0.12.0/go.mod h1:wVCnHcm/7/IfTjEB6K8K35PLG70yGz8BdkRwX0oK9/M=
922922
go.mongodb.org/atlas v0.18.1-0.20221109142841-f9f8ebe7b9b9 h1:9m35o4kyRYjwbsIb/lPrjxJ6afPpn9zwOF5i3SIY5Lg=
923923
go.mongodb.org/atlas v0.18.1-0.20221109142841-f9f8ebe7b9b9/go.mod h1:PFk1IGhiGjFXHGVspOK7i1U2nnPjK8wAjYwQf6FoVf4=
924+
go.mongodb.org/atlas v0.19.0 h1:gvezG9d0KsSDaExEdTtcGqZHRvvVazzuEcBUpBXxmlg=
925+
go.mongodb.org/atlas v0.19.0/go.mod h1:PFk1IGhiGjFXHGVspOK7i1U2nnPjK8wAjYwQf6FoVf4=
924926
go.mongodb.org/realm v0.1.0 h1:zJiXyLaZrznQ+Pz947ziSrDKUep39DO4SfA0Fzx8M4M=
925927
go.mongodb.org/realm v0.1.0/go.mod h1:4Vj6iy+Puo1TDERcoh4XZ+pjtwbOzPpzqy3Cwe8ZmDM=
926928
go.mozilla.org/mozlog v0.0.0-20170222151521-4bb13139d403/go.mod h1:jHoPAGnDrCy6kaI2tAze5Prf0Nr0w/oNkROt2lw3n3o=

mongodbatlas/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Config struct {
2020
PrivateKey string
2121
BaseURL string
2222
RealmBaseURL string
23+
AssumeRole *AssumeRole
2324
}
2425

2526
// MongoDBClient client

mongodbatlas/data_source_mongodbatlas_cloud_backup_snapshot_export_job.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@ func dataSourceMongoDBAtlasCloudBackupSnapshotsExportJobRead(ctx context.Context
104104
ids := decodeStateID(d.Id())
105105
projectID := ids["project_id"]
106106
clusterName := ids["cluster_name"]
107-
exportJobID := ids["export_job_id"]
107+
exportID := ids["export_job_id"]
108108

109-
exportJob, _, err := conn.CloudProviderSnapshotExportJobs.Get(ctx, projectID, clusterName, exportJobID)
109+
exportJob, _, err := conn.CloudProviderSnapshotExportJobs.Get(ctx, projectID, clusterName, exportID)
110110
if err != nil {
111111
return diag.Errorf("error getting snapshot export job information: %s", err)
112112
}

mongodbatlas/provider.go

Lines changed: 296 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,28 @@ package mongodbatlas
33
import (
44
"context"
55
"encoding/base64"
6+
"encoding/json"
67
"fmt"
78
"hash/crc32"
89
"log"
910
"os"
1011
"reflect"
12+
"regexp"
1113
"sort"
1214
"strconv"
1315
"strings"
14-
16+
"time"
17+
18+
"github.com/aws/aws-sdk-go/aws"
19+
"github.com/aws/aws-sdk-go/aws/awserr"
20+
"github.com/aws/aws-sdk-go/aws/credentials"
21+
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
22+
"github.com/aws/aws-sdk-go/aws/endpoints"
23+
"github.com/aws/aws-sdk-go/aws/session"
24+
"github.com/aws/aws-sdk-go/service/secretsmanager"
1525
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1626
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
27+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1728
"github.com/mwielbut/pointy"
1829
"github.com/spf13/cast"
1930
matlas "go.mongodb.org/atlas/mongodbatlas"
@@ -24,6 +35,11 @@ var (
2435
baseURL = ""
2536
)
2637

38+
type SecretData struct {
39+
PublicKey string `json:"public_key"`
40+
PrivateKey string `json:"private_key"`
41+
}
42+
2743
// Provider returns the provider to be use by the code.
2844
func Provider() *schema.Provider {
2945
provider := &schema.Provider{
@@ -67,6 +83,51 @@ func Provider() *schema.Provider {
6783
Optional: true,
6884
Description: "MongoDB Atlas Base URL default to gov",
6985
},
86+
"assume_role": assumeRoleSchema(),
87+
"secret_name": {
88+
Type: schema.TypeString,
89+
Optional: true,
90+
},
91+
"region": {
92+
Type: schema.TypeString,
93+
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
94+
"AWS_REGION",
95+
"TF_VAR_AWS_REGION",
96+
}, ""),
97+
Optional: true,
98+
},
99+
"sts_endpoint": {
100+
Type: schema.TypeString,
101+
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
102+
"STS_ENDPOINT",
103+
"TF_VAR_STS_ENDPOINT",
104+
}, ""),
105+
Optional: true,
106+
},
107+
"aws_access_key_id": {
108+
Type: schema.TypeString,
109+
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
110+
"AWS_ACCESS_KEY_ID",
111+
"TF_VAR_AWS_ACCESS_KEY_ID",
112+
}, ""),
113+
Optional: true,
114+
},
115+
"aws_secret_access_key": {
116+
Type: schema.TypeString,
117+
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
118+
"AWS_SECRET_ACCESS_KEY",
119+
"TF_VAR_AWS_SECRET_ACCESS_KEY",
120+
}, ""),
121+
Optional: true,
122+
},
123+
"aws_session_token": {
124+
Type: schema.TypeString,
125+
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
126+
"AWS_SESSION_TOKEN",
127+
"TF_VAR_AWS_SESSION_TOKEN",
128+
}, ""),
129+
Optional: true,
130+
},
70131
},
71132
DataSourcesMap: getDataSourcesMap(),
72133
ResourcesMap: getResourcesMap(),
@@ -226,9 +287,79 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}
226287
RealmBaseURL: d.Get("realm_base_url").(string),
227288
}
228289

290+
if v, ok := d.GetOk("assume_role"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
291+
config.AssumeRole = expandAssumeRole(v.([]interface{})[0].(map[string]interface{}))
292+
secret := d.Get("secret_name").(string)
293+
region := d.Get("region").(string)
294+
awsAccessKeyID := d.Get("aws_access_key_id").(string)
295+
awsSecretAccessKey := d.Get("aws_secret_access_key").(string)
296+
awsSessionToken := d.Get("aws_session_token").(string)
297+
endpoint := d.Get("sts_endpoint").(string)
298+
config, _ = configureCredentialsSTS(&config, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint)
299+
}
300+
229301
return config.NewClient(ctx)
230302
}
231303

304+
func configureCredentialsSTS(config *Config, secret, region, awsAccessKeyID, awsSecretAccessKey, awsSessionToken, endpoint string) (Config, error) {
305+
ep, _ := endpoints.GetSTSRegionalEndpoint("regional")
306+
sess := session.Must(session.NewSession(&aws.Config{
307+
Region: aws.String(region),
308+
Credentials: credentials.NewStaticCredentials(awsAccessKeyID, awsSecretAccessKey, awsSessionToken),
309+
STSRegionalEndpoint: ep,
310+
Endpoint: &endpoint,
311+
}))
312+
313+
creds := stscreds.NewCredentials(sess, config.AssumeRole.RoleARN)
314+
315+
_, _ = sess.Config.Credentials.Get()
316+
_, _ = creds.Get()
317+
secretString := secretsManagerGetSecretValue(sess, &aws.Config{Credentials: creds, Region: aws.String(region)}, secret)
318+
319+
var secretData SecretData
320+
err := json.Unmarshal([]byte(secretString), &secretData)
321+
if err != nil {
322+
return *config, nil
323+
}
324+
config.PublicKey = secretData.PublicKey
325+
config.PrivateKey = secretData.PrivateKey
326+
return *config, nil
327+
}
328+
329+
func secretsManagerGetSecretValue(sess *session.Session, creds *aws.Config, secret string) string {
330+
svc := secretsmanager.New(sess, creds)
331+
input := &secretsmanager.GetSecretValueInput{
332+
SecretId: aws.String(secret),
333+
VersionStage: aws.String("AWSCURRENT"),
334+
}
335+
336+
result, err := svc.GetSecretValue(input)
337+
if err != nil {
338+
if aerr, ok := err.(awserr.Error); ok {
339+
switch aerr.Code() {
340+
case secretsmanager.ErrCodeResourceNotFoundException:
341+
fmt.Println(secretsmanager.ErrCodeResourceNotFoundException, aerr.Error())
342+
case secretsmanager.ErrCodeInvalidParameterException:
343+
fmt.Println(secretsmanager.ErrCodeInvalidParameterException, aerr.Error())
344+
case secretsmanager.ErrCodeInvalidRequestException:
345+
fmt.Println(secretsmanager.ErrCodeInvalidRequestException, aerr.Error())
346+
case secretsmanager.ErrCodeDecryptionFailure:
347+
fmt.Println(secretsmanager.ErrCodeDecryptionFailure, aerr.Error())
348+
case secretsmanager.ErrCodeInternalServiceError:
349+
fmt.Println(secretsmanager.ErrCodeInternalServiceError, aerr.Error())
350+
default:
351+
fmt.Println(aerr.Error())
352+
}
353+
} else {
354+
fmt.Println(err.Error())
355+
}
356+
return ""
357+
}
358+
359+
fmt.Println(result)
360+
return *result.SecretString
361+
}
362+
232363
func encodeStateID(values map[string]string) string {
233364
encode := func(e string) string { return base64.StdEncoding.EncodeToString([]byte(e)) }
234365
encodedValues := make([]string, 0)
@@ -391,3 +522,167 @@ func HashCodeString(s string) int {
391522
// v == MinInt
392523
return 0
393524
}
525+
526+
// assumeRoleSchema From aws provider.go
527+
func assumeRoleSchema() *schema.Schema {
528+
return &schema.Schema{
529+
Type: schema.TypeList,
530+
Optional: true,
531+
MaxItems: 1,
532+
Elem: &schema.Resource{
533+
Schema: map[string]*schema.Schema{
534+
"duration": {
535+
Type: schema.TypeString,
536+
Optional: true,
537+
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.",
538+
ValidateFunc: validAssumeRoleDuration,
539+
ConflictsWith: []string{"assume_role.0.duration_seconds"},
540+
},
541+
"duration_seconds": {
542+
Type: schema.TypeInt,
543+
Optional: true,
544+
Deprecated: "Use assume_role.duration instead",
545+
Description: "The duration, in seconds, of the role session.",
546+
ValidateFunc: validation.IntBetween(900, 43200),
547+
ConflictsWith: []string{"assume_role.0.duration"},
548+
},
549+
"external_id": {
550+
Type: schema.TypeString,
551+
Optional: true,
552+
Description: "A unique identifier that might be required when you assume a role in another account.",
553+
ValidateFunc: validation.All(
554+
validation.StringLenBetween(2, 1224),
555+
validation.StringMatch(regexp.MustCompile(`[\w+=,.@:/\-]*`), ""),
556+
),
557+
},
558+
"policy": {
559+
Type: schema.TypeString,
560+
Optional: true,
561+
Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.",
562+
ValidateFunc: validation.StringIsJSON,
563+
},
564+
"policy_arns": {
565+
Type: schema.TypeSet,
566+
Optional: true,
567+
Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.",
568+
Elem: &schema.Schema{
569+
Type: schema.TypeString,
570+
},
571+
},
572+
"role_arn": {
573+
Type: schema.TypeString,
574+
Optional: true,
575+
Description: "Amazon Resource Name (ARN) of an IAM Role to assume prior to making API calls.",
576+
},
577+
"session_name": {
578+
Type: schema.TypeString,
579+
Optional: true,
580+
Description: "An identifier for the assumed role session.",
581+
ValidateFunc: validAssumeRoleSessionName,
582+
},
583+
"source_identity": {
584+
Type: schema.TypeString,
585+
Optional: true,
586+
Description: "Source identity specified by the principal assuming the role.",
587+
ValidateFunc: validAssumeRoleSourceIdentity,
588+
},
589+
"tags": {
590+
Type: schema.TypeMap,
591+
Optional: true,
592+
Description: "Assume role session tags.",
593+
Elem: &schema.Schema{Type: schema.TypeString},
594+
},
595+
"transitive_tag_keys": {
596+
Type: schema.TypeSet,
597+
Optional: true,
598+
Description: "Assume role session tag keys to pass to any subsequent sessions.",
599+
Elem: &schema.Schema{Type: schema.TypeString},
600+
},
601+
},
602+
},
603+
}
604+
}
605+
606+
var validAssumeRoleSessionName = validation.All(
607+
validation.StringLenBetween(2, 64),
608+
validation.StringMatch(regexp.MustCompile(`[\w+=,.@\-]*`), ""),
609+
)
610+
611+
var validAssumeRoleSourceIdentity = validation.All(
612+
validation.StringLenBetween(2, 64),
613+
validation.StringMatch(regexp.MustCompile(`[\w+=,.@\-]*`), ""),
614+
)
615+
616+
// validAssumeRoleDuration validates a string can be parsed as a valid time.Duration
617+
// and is within a minimum of 15 minutes and maximum of 12 hours
618+
func validAssumeRoleDuration(v interface{}, k string) (ws []string, errors []error) {
619+
duration, err := time.ParseDuration(v.(string))
620+
621+
if err != nil {
622+
errors = append(errors, fmt.Errorf("%q cannot be parsed as a duration: %w", k, err))
623+
return
624+
}
625+
626+
if duration.Minutes() < 15 || duration.Hours() > 12 {
627+
errors = append(errors, fmt.Errorf("duration %q must be between 15 minutes (15m) and 12 hours (12h), inclusive", k))
628+
}
629+
630+
return
631+
}
632+
633+
type AssumeRole struct {
634+
RoleARN string
635+
Duration time.Duration
636+
ExternalID string
637+
Policy string
638+
PolicyARNs []string
639+
SessionName string
640+
SourceIdentity string
641+
Tags map[string]string
642+
TransitiveTagKeys []string
643+
}
644+
645+
func expandAssumeRole(tfMap map[string]interface{}) *AssumeRole {
646+
if tfMap == nil {
647+
return nil
648+
}
649+
650+
assumeRole := AssumeRole{}
651+
652+
if v, ok := tfMap["duration"].(string); ok && v != "" {
653+
duration, _ := time.ParseDuration(v)
654+
assumeRole.Duration = duration
655+
} else if v, ok := tfMap["duration_seconds"].(int); ok && v != 0 {
656+
assumeRole.Duration = time.Duration(v) * time.Second
657+
}
658+
659+
if v, ok := tfMap["external_id"].(string); ok && v != "" {
660+
assumeRole.ExternalID = v
661+
}
662+
663+
if v, ok := tfMap["policy"].(string); ok && v != "" {
664+
assumeRole.Policy = v
665+
}
666+
667+
if v, ok := tfMap["policy_arns"].(*schema.Set); ok && v.Len() > 0 {
668+
assumeRole.PolicyARNs = expandStringList(v.List())
669+
}
670+
671+
if v, ok := tfMap["role_arn"].(string); ok && v != "" {
672+
assumeRole.RoleARN = v
673+
}
674+
675+
if v, ok := tfMap["session_name"].(string); ok && v != "" {
676+
assumeRole.SessionName = v
677+
}
678+
679+
if v, ok := tfMap["source_identity"].(string); ok && v != "" {
680+
assumeRole.SourceIdentity = v
681+
}
682+
683+
if v, ok := tfMap["transitive_tag_keys"].(*schema.Set); ok && v.Len() > 0 {
684+
assumeRole.TransitiveTagKeys = expandStringList(v.List())
685+
}
686+
687+
return &assumeRole
688+
}

0 commit comments

Comments
 (0)