Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for AWS Identity Center #19

Merged
merged 3 commits into from
May 26, 2023
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
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need the remote_info during the read operation? Other resources types currently don't require it, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is so that we can set the remote_info with AWS account ID and ARN when importing from terraformer, so that we can support such a workflow like this:

  1. Use Opal to auto-import provisioned to get permission sets into our system
  2. Use terraformer to import accounts and permission sets into .tf files, with the remote_info filled so that the accounts can be updated (felt a bit weird to me that remote_info only exists when we create new remote resources)
  3. From there on, set app to manual import (to be added) and then use TF to manage permission sets

I imagine we can set this for other AWS Orgs resources too, but I just did permission sets for now

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