Skip to content

Commit

Permalink
Add support for AWS Identity Center (#19)
Browse files Browse the repository at this point in the history
* Add support for AWS Identity Center

- Support creating and deleting permission sets
- Remote info will be imported for permission sets so that the parent
accounts can be referenced and modified as desired

* Update docs and examples

- Updated README / remote_resource.tf
- Ran `make docs`

* Use opal-go 1.0.23
  • Loading branch information
ken-opal authored May 26, 2023
1 parent 2fc8997 commit 24eb7c5
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 17 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Terraform Opal Provider
[![Terraform Provider Tests](https://github.com/opalsecurity/terraform-provider-opal/actions/workflows/test.yml/badge.svg)](https://github.com/opalsecurity/terraform-provider-opal/actions/workflows/test.yml)

This project is under **active development** and is not yet ready for use.
[![Terraform Provider Tests](https://github.com/opalsecurity/terraform-provider-opal/actions/workflows/test.yml/badge.svg)](https://github.com/opalsecurity/terraform-provider-opal/actions/workflows/test.yml)

## Installation

Expand All @@ -23,7 +22,8 @@ provider "opal" {

## Development

Go `>= 1.18` and terraform `>= 0.14` is required for development. It's recommended that you use a [`dev_overrides` block](https://www.terraform.io/cli/config/config-file) while developing:
Go `>= 1.20` and terraform `>= 0.14` is required for development. It's recommended that you use a [`dev_overrides` block](https://www.terraform.io/cli/config/config-file) while developing:

```hcl
provider_installation {
dev_overrides {
Expand All @@ -38,17 +38,20 @@ provider_installation {
```

You can also source your local `OPAL_AUTH_TOKEN` while developing by using [direnv](https://direnv.net) (installable via homebrew) and creating a `.envrc.local` file:

```bash
# Get an auth token from https://app.opal.dev/settings#api or your Opal installation.
export OPAL_AUTH_TOKEN=YOUR_TOKEN_HERE
```

You can build the plugin using:

```
make build
```

Your `dev_overrides` configured above should tell your local terraform installation how to resolve the plugin:

```
$ cd examples/
$ terraform apply
Expand All @@ -70,4 +73,4 @@ If you don't see the above warning when running terraform commands, something is
The `docs/` folder is entirely generated. Make changes to `templates/` or the go source files instead. `docs/`content is generated from:

- The source code, i.e. `Description` and `Name` fields in the resource schema
- The `templates/` folder, which serves as the basis for `docs/`. See [tfplugindocs](https://github.com/hashicorp/terraform-plugin-docs#templates) for more on the templating fields.
- The `templates/` folder, which serves as the basis for `docs/`. See [tfplugindocs](https://github.com/hashicorp/terraform-plugin-docs#templates) for more on the templating fields.
23 changes: 23 additions & 0 deletions docs/resources/resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,19 @@ resource "opal_resource" "aws_iam_role_example" {
}
}
resource "opal_resource" "aws_permission_set" {
name = "AWS permission set"
// ...
remote_info {
aws_permission_set {
# Note: This can reference your AWS terraform files
account_id = "234234234234"
arn = "arn:aws:sso:::permissionSet/ssoins-123123123abcdefg/ps-abc123abc123abcd"
}
}
}
resource "opal_resource" "okta_app_example" {
name = "Okta app"
// ...
Expand Down Expand Up @@ -175,6 +188,7 @@ Optional:
- `aws_ec2_instance` (Block List, Max: 1) The remote_info for an AWS EC2 instance. (see [below for nested schema](#nestedblock--remote_info--aws_ec2_instance))
- `aws_eks_cluster` (Block List, Max: 1) The remote_info for an AWS EKS cluster. (see [below for nested schema](#nestedblock--remote_info--aws_eks_cluster))
- `aws_iam_role` (Block List, Max: 1) The remote_info for an AWS IAM role. (see [below for nested schema](#nestedblock--remote_info--aws_iam_role))
- `aws_permission_set` (Block List, Max: 1) The remote_info for an AWS permission set. (see [below for nested schema](#nestedblock--remote_info--aws_permission_set))
- `aws_rds_instance` (Block List, Max: 1) The remote_info for an AWS RDS instance. (see [below for nested schema](#nestedblock--remote_info--aws_rds_instance))
- `github_repo` (Block List, Max: 1) The remote_info for a Github repo. (see [below for nested schema](#nestedblock--remote_info--github_repo))
- `gitlab_project` (Block List, Max: 1) The remote_info for a Gitlab project. (see [below for nested schema](#nestedblock--remote_info--gitlab_project))
Expand Down Expand Up @@ -208,6 +222,15 @@ Required:
- `arn` (String) The ARN of the IAM role.


<a id="nestedblock--remote_info--aws_permission_set"></a>
### Nested Schema for `remote_info.aws_permission_set`

Required:

- `account_id` (String) The ID of the AWS account.
- `arn` (String) The ARN of the permission set.


<a id="nestedblock--remote_info--aws_rds_instance"></a>
### Nested Schema for `remote_info.aws_rds_instance`

Expand Down
13 changes: 13 additions & 0 deletions examples/resources/remote_resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ resource "opal_resource" "aws_iam_role_example" {
}
}

resource "opal_resource" "aws_permission_set" {
name = "AWS permission set"
// ...

remote_info {
aws_permission_set {
# Note: This can reference your AWS terraform files
account_id = "234234234234"
arn = "arn:aws:sso:::permissionSet/ssoins-123123123abcdefg/ps-abc123abc123abcd"
}
}
}

resource "opal_resource" "okta_app_example" {
name = "Okta app"
// ...
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/hashicorp/terraform-plugin-docs v0.13.0
github.com/hashicorp/terraform-plugin-log v0.7.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1
github.com/opalsecurity/opal-go v1.0.21
github.com/opalsecurity/opal-go v1.0.23
github.com/pkg/errors v0.9.1
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/opalsecurity/opal-go v1.0.21 h1:tKOfn+QrzBZvZy7ZiOmnulil1sF73LWDbHiZ3fDWvQc=
github.com/opalsecurity/opal-go v1.0.21/go.mod h1:cdQrqgbdjsZHjrKV8ibsle6r6c7jRBKryI1x3oSVm1I=
github.com/opalsecurity/opal-go v1.0.23 h1:TUZ9XoHlVBTNfhgytPOA9QEWhHoYMrKZcRRnVE9rF3c=
github.com/opalsecurity/opal-go v1.0.23/go.mod h1:cdQrqgbdjsZHjrKV8ibsle6r6c7jRBKryI1x3oSVm1I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down
39 changes: 29 additions & 10 deletions opal/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package opal

import (
"context"
"encoding/json"

"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform-plugin-log/tflog"
Expand Down Expand Up @@ -326,7 +327,7 @@ func resourceResourceUpdateReviewerStages(ctx context.Context, d *schema.Resourc
reviewerStage := rawReviewerStage.(map[string]any)
requireManagerApproval := reviewerStage["require_manager_approval"].(bool)
operator := reviewerStage["operator"].(string)
reviewersI := reviewerStage["reviewer"].(any)
reviewersI := reviewerStage["reviewer"]
reviewerIds, err := extractReviewerIDs(reviewersI)
if err != nil {
return diagFromErr(ctx, err)
Expand Down Expand Up @@ -380,14 +381,8 @@ func resourceResourceRead(ctx context.Context, d *schema.ResourceData, m any) di
if err != nil {
return diagFromErr(ctx, err)
}

visibilityGroups := make([]any, 0, len(visibility.VisibilityGroupIds))
for _, groupID := range visibility.VisibilityGroupIds {
visibilityGroups = append(visibilityGroups, map[string]any{
"id": groupID,
})
}
d.Set("visibility", visibility.Visibility)

flattenedGroups := make([]any, 0, len(visibility.VisibilityGroupIds))
for _, groupID := range visibility.VisibilityGroupIds {
flattenedGroups = append(flattenedGroups, map[string]any{"id": groupID})
Expand Down Expand Up @@ -416,14 +411,38 @@ func resourceResourceRead(ctx context.Context, d *schema.ResourceData, m any) di
}
d.Set("reviewer_stage", reviewerStagesI)

if resource.Metadata != nil {
remoteInfoIList := make([]any, 0, 1)
switch *resource.ResourceType {
case opal.RESOURCETYPEENUM_AWS_SSO_PERMISSION_SET:
// TODO: Handle other AWS Orgs resource types
var metadata opal.AwsPermissionSetMetadata
if err := json.Unmarshal([]byte(*resource.Metadata), &metadata); err != nil {
return diagFromErr(ctx, err)
}
permissionSetIList := make([]any, 0, 1)
permissionSetIList = append(permissionSetIList, map[string]any{
"arn": metadata.AwsPermissionSet.Arn,
"account_id": metadata.AwsPermissionSet.AccountId,
})
remoteInfoIList = append(remoteInfoIList, map[string]any{
"aws_permission_set": permissionSetIList,
})
}

if len(remoteInfoIList) == 1 {
d.Set("remote_info", remoteInfoIList)
}
}

return nil
}

func resourceResourceUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
client := m.(*opal.APIClient)

// Note that metadata, app_id, and resource_type force a recreation, so we do not need to
// worry about those values here.
// Note that fields like metadata, app_id, resource_type, and remote_info
// force a recreation, so we do not need to worry about those values here.
hasBasicChange := false
updateInfo := opal.NewUpdateResourceInfo(d.Id())
if d.HasChange("name") {
Expand Down
35 changes: 35 additions & 0 deletions opal/resource_remote_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package opal

import (
"errors"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/opalsecurity/opal-go"
)
Expand Down Expand Up @@ -96,6 +97,28 @@ func resourceRemoteInfoElem() *schema.Resource {
},
},
},
"aws_permission_set": {
Description: "The remote_info for an AWS permission set.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"arn": {
Description: "The ARN of the permission set.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"account_id": {
Description: "The ID of the AWS account.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
},
},
"github_repo": {
Description: "The remote_info for a Github repo.",
Type: schema.TypeList,
Expand Down Expand Up @@ -255,6 +278,18 @@ func parseResourceRemoteInfo(remoteInfoI interface{}) (*opal.ResourceRemoteInfo,
}, nil
}
}
if awsPermissionSetI, ok := remoteInfoMap["aws_permission_set"]; ok {
awsPermissionSetIList := awsPermissionSetI.([]interface{})
if len(awsPermissionSetIList) == 1 {
awsPermissionSet := awsPermissionSetIList[0].(map[string]any)
return &opal.ResourceRemoteInfo{
AwsPermissionSet: &opal.ResourceRemoteInfoAwsPermissionSet{
Arn: awsPermissionSet["arn"].(string),
AccountId: awsPermissionSet["account_id"].(string),
},
}, nil
}
}
if githubRepoI, ok := remoteInfoMap["github_repo"]; ok {
githubRepoIList := githubRepoI.([]interface{})

Expand Down

0 comments on commit 24eb7c5

Please sign in to comment.