Skip to content

Commit

Permalink
ENG-14270: redshift iam (#559)
Browse files Browse the repository at this point in the history
* ENG-14270: add redshiftSettings and new AWS IAM authN flag

* Add documentation

* Rename authenticate_as_iam_user to authenticate_as_iam_role

* Improves description

* Improves description

---------

Co-authored-by: Wilson de Carvalho <[email protected]>
  • Loading branch information
ricardorey10 and wcmjunior authored Aug 11, 2024
1 parent d50971c commit 226745c
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 16 deletions.
5 changes: 5 additions & 0 deletions cyral/internal/repository/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ const (
RepoMongoDBServerTypeKey = "server_type"
RepoMongoDBSRVRecordName = "srv_record_name"
RepoMongoDBFlavorKey = "flavor"
// Redshift settings keys
RepoRedshiftSettingsKey = "redshift_settings"
RepoRedshiftClusterIdentifier = "cluster_identifier"
RepoRedshiftWorkgroupName = "workgroup_name"
RepoRedshiftAWSRegion = "aws_region"
)

const (
Expand Down
65 changes: 56 additions & 9 deletions cyral/internal/repository/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ type Labels []string
type RepoNodes []*RepoNode

type RepoInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Host string `json:"repoHost"`
Port uint32 `json:"repoPort"`
ConnParams *ConnParams `json:"connParams"`
Labels Labels `json:"labels"`
RepoNodes RepoNodes `json:"repoNodes,omitempty"`
MongoDBSettings *MongoDBSettings `json:"mongoDbSettings,omitempty"`
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Host string `json:"repoHost"`
Port uint32 `json:"repoPort"`
ConnParams *ConnParams `json:"connParams"`
Labels Labels `json:"labels"`
RepoNodes RepoNodes `json:"repoNodes,omitempty"`
MongoDBSettings *MongoDBSettings `json:"mongoDbSettings,omitempty"`
RedshiftSettings *RedshiftSettings `json:"redshiftSettings,omitempty"`
}

type ConnParams struct {
Expand All @@ -38,6 +39,12 @@ type MongoDBSettings struct {
Flavor string `json:"flavor,omitempty"`
}

type RedshiftSettings struct {
ClusterIdentifier string `json:"clusterIdentifier,omitempty"`
WorkgroupName string `json:"workgroupName,omitempty"`
AWSRegion string `json:"awsRegion,omitempty"`
}

type RepoNode struct {
Name string `json:"name"`
Host string `json:"host"`
Expand All @@ -60,6 +67,7 @@ func (res *RepoInfo) WriteToSchema(d *schema.ResourceData) error {
d.Set(RepoConnDrainingKey, res.ConnParams.AsInterface())
d.Set(RepoNodesKey, res.RepoNodes.AsInterface())
d.Set(RepoMongoDBSettingsKey, res.MongoDBSettings.AsInterface())
d.Set(RepoRedshiftSettingsKey, res.RedshiftSettings.AsInterface())
return nil
}

Expand All @@ -77,7 +85,18 @@ func (r *RepoInfo) ReadFromSchema(d *schema.ResourceData) error {
return fmt.Errorf("'%s' block is only allowed when '%s=%s'", RepoMongoDBSettingsKey, utils.TypeKey, MongoDB)
}
m, err := mongoDBSettingsFromInterface(mongoDBSettings)
if err != nil {
return err
}
r.MongoDBSettings = m

var redshiftSettings = d.Get(RepoRedshiftSettingsKey).(*schema.Set).List()
if r.Type != Redshift && len(redshiftSettings) > 0 {
return fmt.Errorf("'%s' block is only allowed when '%s=%s'", RepoRedshiftSettingsKey, utils.TypeKey, Redshift)
}
redshift, err := redshiftSettingsFromInterface(redshiftSettings)
r.RedshiftSettings = redshift

return err
}

Expand Down Expand Up @@ -156,6 +175,18 @@ func repoNodesFromInterface(i []interface{}) RepoNodes {
return repoNodes
}

func (r *RedshiftSettings) AsInterface() []interface{} {
if r == nil {
return nil
}

return []interface{}{map[string]interface{}{
RepoRedshiftClusterIdentifier: r.ClusterIdentifier,
RepoRedshiftWorkgroupName: r.WorkgroupName,
RepoRedshiftAWSRegion: r.AWSRegion,
}}
}

func (m *MongoDBSettings) AsInterface() []interface{} {
if m == nil {
return nil
Expand All @@ -169,6 +200,22 @@ func (m *MongoDBSettings) AsInterface() []interface{} {
}}
}

func redshiftSettingsFromInterface(i []interface{}) (*RedshiftSettings, error) {
if len(i) == 0 {
return nil, nil
}

var clusterIdentifier = i[0].(map[string]interface{})[RepoRedshiftClusterIdentifier].(string)
var workgroupName = i[0].(map[string]interface{})[RepoRedshiftWorkgroupName].(string)
var awsRegion = i[0].(map[string]interface{})[RepoRedshiftAWSRegion].(string)

return &RedshiftSettings{
ClusterIdentifier: clusterIdentifier,
WorkgroupName: workgroupName,
AWSRegion: awsRegion,
}, nil
}

func mongoDBSettingsFromInterface(i []interface{}) (*MongoDBSettings, error) {
if len(i) == 0 {
return nil, nil
Expand Down
25 changes: 25 additions & 0 deletions cyral/internal/repository/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,31 @@ func resourceSchema() *schema.Resource {
},
},
},
RepoRedshiftSettingsKey: {
Description: "Parameters related to Redshift repositories.",
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
RepoRedshiftClusterIdentifier: {
Description: "Name of the provisioned cluster.",
Type: schema.TypeString,
Optional: true,
},
RepoRedshiftWorkgroupName: {
Description: "Workgroup name for serverless cluster.",
Type: schema.TypeString,
Optional: true,
},
RepoRedshiftAWSRegion: {
Description: "Code of the AWS region where the Redshift instance is deployed.",
Type: schema.TypeString,
Optional: true,
},
},
},
},
},
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
Expand Down
66 changes: 65 additions & 1 deletion cyral/internal/repository/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,21 @@ var (
Flavor: "documentdb",
},
}

withRedshiftSettings = repository.RepoInfo{
Name: utils.AccTestName(utils.RepositoryResourceName, "repo-with-redshift-settings"),
Type: "redshift",
RepoNodes: repository.RepoNodes{
{
Host: "redshift.local",
Port: 3333,
},
},
RedshiftSettings: &repository.RedshiftSettings{
ClusterIdentifier: "myCluster",
AWSRegion: "us-east-1",
},
}
)

func TestAccRepositoryResource(t *testing.T) {
Expand All @@ -153,11 +168,13 @@ func TestAccRepositoryResource(t *testing.T) {
connDrainingConfig, "conn_draining_test")
allDynamic := setupRepositoryTest(
allRepoNodesAreDynamic, "all_repo_nodes_are_dynamic")
redshift := setupRepositoryTest(
withRedshiftSettings, "with_redshift_settings")

multiNode := setupRepositoryTest(
mixedMultipleNodesConfig, "multi_node_test")

// Should use name of the last resource created.
// Must use name of the last resource created.
importTest := resource.TestStep{
ImportState: true,
ImportStateVerify: true,
Expand All @@ -171,6 +188,7 @@ func TestAccRepositoryResource(t *testing.T) {
update,
connDrainingEmpty,
connDraining,
redshift,
allDynamic,
multiNode,
importTest,
Expand Down Expand Up @@ -256,6 +274,23 @@ func repoCheckFuctions(repo repository.RepoInfo, resName string) resource.TestCh
}...)
}

if repo.RedshiftSettings != nil {
checkFuncs = append(checkFuncs, []resource.TestCheckFunc{
resource.TestCheckResourceAttr(resourceFullName,
"redshift_settings.0.cluster_identifier",
repo.RedshiftSettings.ClusterIdentifier,
),
resource.TestCheckResourceAttr(resourceFullName,
"redshift_settings.0.workgroup_name",
repo.RedshiftSettings.WorkgroupName,
),
resource.TestCheckResourceAttr(resourceFullName,
"redshift_settings.0.aws_region",
repo.RedshiftSettings.AWSRegion,
),
}...)
}

return resource.ComposeTestCheckFunc(checkFuncs...)
}

Expand Down Expand Up @@ -307,6 +342,35 @@ func repoAsConfig(repo repository.RepoInfo, resName string) string {
)
}

if repo.RedshiftSettings != nil {
clusterIdentifier := "null"
workgroupName := "null"
awsRegion := "null"

if repo.RedshiftSettings.ClusterIdentifier != "" {
clusterIdentifier = fmt.Sprintf(`"%s"`, repo.RedshiftSettings.ClusterIdentifier)
}

if repo.RedshiftSettings.WorkgroupName != "" {
workgroupName = fmt.Sprintf(`"%s"`, repo.RedshiftSettings.WorkgroupName)
}

if repo.RedshiftSettings.AWSRegion != "" {
awsRegion = fmt.Sprintf(`"%s"`, repo.RedshiftSettings.AWSRegion)
}

config += fmt.Sprintf(`
redshift_settings {
cluster_identifier = %s
workgroup_name = %s
aws_region = %s
}`,
clusterIdentifier,
workgroupName,
awsRegion,
)
}

for _, node := range repo.RepoNodes {
name, host := "null", "null"
if node.Name != "" {
Expand Down
9 changes: 6 additions & 3 deletions cyral/internal/repository/useraccount/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ type AuthScheme struct {
}

type AuthSchemeAWSIAM struct {
RoleARN string `json:"roleARN,omitempty"`
RoleARN string `json:"roleARN,omitempty"`
AuthenticateAsIAMRole bool `json:"authenticateAsIAMRole,omitempty"`
}

type AuthSchemeAWSSecretsManager struct {
Expand Down Expand Up @@ -118,7 +119,8 @@ func (resource *UserAccountResource) WriteToSchema(d *schema.ResourceData) error
map[string]interface{}{
"aws_iam": []interface{}{
map[string]interface{}{
"role_arn": resource.AuthScheme.AWSIAM.RoleARN,
"role_arn": resource.AuthScheme.AWSIAM.RoleARN,
"authenticate_as_iam_role": resource.AuthScheme.AWSIAM.AuthenticateAsIAMRole,
},
},
},
Expand Down Expand Up @@ -259,7 +261,8 @@ func (userAccount *UserAccountResource) ReadFromSchema(d *schema.ResourceData) e
case "aws_iam":
userAccount.AuthScheme = &AuthScheme{
AWSIAM: &AuthSchemeAWSIAM{
RoleARN: m["role_arn"].(string),
RoleARN: m["role_arn"].(string),
AuthenticateAsIAMRole: m["authenticate_as_iam_role"].(bool),
},
}
case "aws_secrets_manager":
Expand Down
6 changes: 6 additions & 0 deletions cyral/internal/repository/useraccount/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ func resourceSchema() *schema.Resource {
Type: schema.TypeString,
Required: true,
},
"authenticate_as_iam_role": {
Description: "Indicates whether to access as an AWS IAM role (`true`)" +
"or a native database user (`false`). Defaults to `false`.",
Type: schema.TypeBool,
Optional: true,
},
},
},
},
Expand Down
14 changes: 11 additions & 3 deletions cyral/internal/repository/useraccount/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ func TestAccRepositoryUserAccountResource(t *testing.T) {
Name: "aws-iam-useracc",
AuthScheme: &useraccount.AuthScheme{
AWSIAM: &useraccount.AuthSchemeAWSIAM{
RoleARN: "role-arn-1",
RoleARN: "role-arn-1",
AuthenticateAsIAMRole: true,
},
},
}
Expand Down Expand Up @@ -286,7 +287,11 @@ func setupRepositoryUserAccountCheck(resName string, userAccount useraccount.Use
checkFuncs = append(checkFuncs,
resource.TestCheckResourceAttr(resFullName,
authSchemeScope+"aws_iam.0.role_arn",
authScheme.AWSIAM.RoleARN))
authScheme.AWSIAM.RoleARN),
resource.TestCheckResourceAttr(resFullName,
authSchemeScope+"aws_iam.0.authenticate_as_iam_role",
strconv.FormatBool(authScheme.AWSIAM.AuthenticateAsIAMRole)),
)
case authScheme.AWSSecretsManager != nil:
checkFuncs = append(checkFuncs,
resource.TestCheckResourceAttr(resFullName,
Expand Down Expand Up @@ -348,7 +353,10 @@ func setupRepositoryUserAccountConfig(resName string, userAccount useraccount.Us
authSchemeStr = fmt.Sprintf(`
aws_iam {
role_arn = "%s"
}`, authScheme.AWSIAM.RoleARN)
authenticate_as_iam_role = %t
}`,
authScheme.AWSIAM.RoleARN,
authScheme.AWSIAM.AuthenticateAsIAMRole)
case authScheme.AWSSecretsManager != nil:
authSchemeStr = fmt.Sprintf(`
aws_secrets_manager {
Expand Down
11 changes: 11 additions & 0 deletions docs/resources/repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ resource "cyral_repository" "multi_node_mongo_repo" {
- `connection_draining` (Block Set, Max: 1) Parameters related to connection draining. (see [below for nested schema](#nestedblock--connection_draining))
- `labels` (List of String) Labels enable you to categorize your repository.
- `mongodb_settings` (Block Set, Max: 1) Parameters related to MongoDB repositories. (see [below for nested schema](#nestedblock--mongodb_settings))
- `redshift_settings` (Block Set, Max: 1) Parameters related to Redshift repositories. (see [below for nested schema](#nestedblock--redshift_settings))

### Read-Only

Expand Down Expand Up @@ -175,3 +176,13 @@ Optional:

- `replica_set_name` (String) Name of the replica set, if applicable.
- `srv_record_name` (String) Name of a DNS SRV record which contains cluster topology details. If specified, then all `repo_node` blocks must be declared dynamic (see [`dynamic`](#dynamic)). Only supported for `server_type="sharded"` or `server_type="replicaset".

<a id="nestedblock--redshift_settings"></a>

### Nested Schema for `redshift_settings`

Optional:

- `aws_region` (String) Code of the AWS region where the Redshift instance is deployed.
- `cluster_identifier` (String) Name of the provisioned cluster.
- `workgroup_name` (String) Workgroup name for serverless cluster.
4 changes: 4 additions & 0 deletions docs/resources/repository_user_account.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ Required:

- `role_arn` (String) The AWS IAM roleARN to gain access to the database.

Optional:

- `authenticate_as_iam_role` (Boolean) Indicates whether to access as an AWS IAM role (`true`)or a native database user (`false`). Defaults to `false`.

<a id="nestedblock--auth_scheme--aws_secrets_manager"></a>

### Nested Schema for `auth_scheme.aws_secrets_manager`
Expand Down

0 comments on commit 226745c

Please sign in to comment.