From 15b16a720ffe13a174e0b55fad0aae133c3c97f6 Mon Sep 17 00:00:00 2001 From: Fabio Santos Date: Thu, 21 Mar 2024 10:58:26 +0000 Subject: [PATCH] feat: Add Opensearch Collection and policies (encryption, network, data access) --- .github/workflows/pre-commit.yml | 83 ++++++++++++++ .github/workflows/release.yml | 34 ++++++ .gitignore | 3 + .pre-commit-config.yaml | 29 +++++ .releaserc.json | 44 ++++++++ CHANGELOG.md | 3 + README.md | 186 ++++++++++++++++++++++++++++++- examples/complete/README.md | 62 +++++++++++ examples/complete/main.tf | 39 +++++++ examples/complete/outputs.tf | 19 ++++ examples/complete/variables.tf | 29 +++++ examples/complete/versions.tf | 10 ++ locals.tf | 98 ++++++++++++++++ main.tf | 51 +++++++++ outputs.tf | 78 +++++++++++++ variables.tf | 152 +++++++++++++++++++++++++ versions.tf | 9 ++ 17 files changed, 927 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/pre-commit.yml create mode 100644 .github/workflows/release.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .releaserc.json create mode 100644 CHANGELOG.md create mode 100644 examples/complete/README.md create mode 100644 examples/complete/main.tf create mode 100644 examples/complete/outputs.tf create mode 100644 examples/complete/variables.tf create mode 100644 examples/complete/versions.tf create mode 100644 locals.tf create mode 100644 main.tf create mode 100644 outputs.tf create mode 100644 variables.tf create mode 100644 versions.tf diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..cb82671 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,83 @@ +name: Pre-Commit + +on: + pull_request: + branches: + - main + - master + +env: + TERRAFORM_DOCS_VERSION: v0.16.0 + TFLINT_VERSION: v0.44.1 + +jobs: + collectInputs: + name: Collect workflow inputs + runs-on: ubuntu-latest + outputs: + directories: ${{ steps.dirs.outputs.directories }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Get root directories + id: dirs + uses: clowdhaus/terraform-composite-actions/directories@v1.8.3 + + preCommitMinVersions: + name: Min TF pre-commit + needs: collectInputs + runs-on: ubuntu-latest + strategy: + matrix: + directory: ${{ fromJson(needs.collectInputs.outputs.directories) }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Terraform min/max versions + id: minMax + uses: clowdhaus/terraform-min-max@v1.2.4 + with: + directory: ${{ matrix.directory }} + + - name: Pre-commit Terraform ${{ steps.minMax.outputs.minVersion }} + # Run only validate pre-commit check on min version supported + if: ${{ matrix.directory != '.' }} + uses: clowdhaus/terraform-composite-actions/pre-commit@v1.8.3 + with: + terraform-version: ${{ steps.minMax.outputs.minVersion }} + tflint-version: ${{ env.TFLINT_VERSION }} + args: 'terraform_validate --color=always --show-diff-on-failure --files ${{ matrix.directory }}/*' + + - name: Pre-commit Terraform ${{ steps.minMax.outputs.minVersion }} + # Run only validate pre-commit check on min version supported + if: ${{ matrix.directory == '.' }} + uses: clowdhaus/terraform-composite-actions/pre-commit@v1.8.3 + with: + terraform-version: ${{ steps.minMax.outputs.minVersion }} + tflint-version: ${{ env.TFLINT_VERSION }} + args: 'terraform_validate --color=always --show-diff-on-failure --files $(ls *.tf)' + + preCommitMaxVersion: + name: Max TF pre-commit + runs-on: ubuntu-latest + needs: collectInputs + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Terraform min/max versions + id: minMax + uses: clowdhaus/terraform-min-max@v1.2.4 + + - name: Pre-commit Terraform ${{ steps.minMax.outputs.maxVersion }} + uses: clowdhaus/terraform-composite-actions/pre-commit@v1.8.3 + with: + terraform-version: ${{ steps.minMax.outputs.maxVersion }} + tflint-version: ${{ env.TFLINT_VERSION }} + terraform-docs-version: ${{ env.TERRAFORM_DOCS_VERSION }} + install-hcledit: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d5cd8f3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,34 @@ +name: Release + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - '**/*.tf' + - '.github/workflows/release.yml' + +jobs: + release: + name: Release + runs-on: ubuntu-latest + # Skip running release workflow on forks + if: github.repository_owner == 'fdmsantos' + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Release + uses: cycjimmy/semantic-release-action@v3 + with: + semantic_version: 18.0.0 + extra_plugins: | + @semantic-release/changelog@6.0.0 + @semantic-release/git@10.0.0 + conventional-changelog-conventionalcommits@4.6.3 + env: + GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} diff --git a/.gitignore b/.gitignore index 9b8a46e..95053d3 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ override.tf.json # Ignore CLI configuration files .terraformrc terraform.rc + +# Ignore Lock Files +.terraform.lock.hcl diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..19dda01 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +repos: + - repo: https://github.com/antonbabenko/pre-commit-terraform + rev: v1.71.0 + hooks: + - id: terraform_fmt + - id: terraform_validate + - id: terraform_docs + args: + - '--args=--lockfile=false' + - id: terraform_tflint + args: + - '--args=--only=terraform_deprecated_interpolation' + - '--args=--only=terraform_deprecated_index' + - '--args=--only=terraform_unused_declarations' + - '--args=--only=terraform_comment_syntax' + - '--args=--only=terraform_documented_outputs' + - '--args=--only=terraform_documented_variables' + - '--args=--only=terraform_typed_variables' + - '--args=--only=terraform_module_pinned_source' + - '--args=--only=terraform_naming_convention' + - '--args=--only=terraform_required_version' + - '--args=--only=terraform_required_providers' + - '--args=--only=terraform_standard_module_structure' + - '--args=--only=terraform_workspace_remote' + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.2.0 + hooks: + - id: check-merge-conflict + - id: end-of-file-fixer diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..7bd71a9 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,44 @@ +{ + "branches": [ + "main" + ], + "ci": false, + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits" + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits" + } + ], + [ + "@semantic-release/github", + { + "successComment": "This ${issue.pull_request ? 'PR is included' : 'issue has been resolved'} in version ${nextRelease.version} :tada:", + "labels": false, + "releasedLabels": false + } + ], + [ + "@semantic-release/changelog", + { + "changelogFile": "CHANGELOG.md", + "changelogTitle": "# Changelog\n\nAll notable changes to this project will be documented in this file." + } + ], + [ + "@semantic-release/git", + { + "assets": [ + "CHANGELOG.md" + ], + "message": "chore(release): version ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + } + ] + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6361e43 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog + +All notable changes to this project will be documented in this file. diff --git a/README.md b/README.md index 0cea9cb..81f2d43 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,184 @@ -# terraform-aws-opensearch-serverless -Dynamic Terraform module, which creates a Opensearch Serverless Collection +# AWS Opensearch Serverless Terraform module + +[![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release) + +Dynamic Terraform module, which creates a Opensearch Serverless Collection and related resources. + +## Table of Contents + +- [AWS Opensearch Serverless Terraform module](#aws-opensearch-serverless-terraform-module) + * [Table of Contents](#table-of-contents) + * [Module versioning rule](#module-versioning-rule) + * [Features](#features) + * [How to Use](#how-to-use) + + [Basic Example](#basic-example) + + [Encryption Policy](#encryption-policy) + + [Network Policy](#network-policy) + - [VPC Access](#vpc-access) + + [Data Access Policy](#data-access-policy) + * [Examples](#examples) + * [Requirements](#requirements) + * [Providers](#providers) + * [Modules](#modules) + * [Resources](#resources) + * [Inputs](#inputs) + * [Outputs](#outputs) + * [License](#license) + + +## Module versioning rule + +| Module version | AWS Provider version | +|----------------|----------------------| +| >= 1.x.x | => 5.31 | + +## Features + +- Encryption Policy +- Network Policy +- Data Access Policy +- Opensearch Serverless VPCE + +## How to Use + +### Basic Example + +This example will create: + * Opensearch Serverless Collection + * Encryption Policy with AWS Managed KMS Key + * Public Network Policy to Both Endpoints + * Data Access Policy with all permissions to collection and all indexes + +```hcl +module "firehose" { + source = "fdmsantos/opensearch-serverless/aws" + version = "x.x.x" + name = "demo-collection" + access_policy_rules = [ + { + type = "collection" + permissions = ["All"] + principals = [data.aws_caller_identity.current.arn] + }, + { + type = "index" + permissions = ["All"] + indexes = ["*"] + principals = [data.aws_caller_identity.current.arn] + } + ] +} +``` + +### Encryption Policy + +By default, the encryption policy use AWS managed KMS Key. To Use Customer Managed KMS Key use the variable `encryption_policy_kms_key_arn` + +### Network Policy + +By default, the network policy is created with public access to dashboard and collection endpoints. +To change the network policy use variable `network_policy_type`. The supported values are: + +| Value | Description | +|----------------------------------|--------------------------------------------------------------------| +| AllPublic | Public endpoints for Dashboard and Collection | +| AllPrivate | Private endpoints for Dashboard and Collection | +| PublicCollectionPrivateDashboard | Public endpoint for Collection and Private endpoint for Collection | +| PrivateCollectionPublicDashboard | Private endpoint for Collection and Public endpoint forCollection | + +#### VPC Access + +If the variable `network_policy_type` is different from "AllPublic", the module will create Opensearch Serverless Endpoint to private access. +In this case it's necessary configure the following variables: `vpce_subnet_ids` and `vpce_vpc_id`. `vpce_security_group_ids` is optional. + +### Data Access Policy + +To configure data access policy use variable `access_policy_rules`. This variable is a list of data access rules. +Each rule contains the following fields: + +| Field | Supported Values | +|-------------|----------------------------------------------------------------------------------------------------------------------| +| type | collection;index | +| permissions | Collection Type: All;Create;Read;Update;Delete. Index Type: All;Create;Read;Update;Delete;ReadDocument;WriteDocument | +| principals | IAM Users;IAM Roles;SAML users;SAML Groups | +| principals | IAM Users;IAM Roles;SAML users;SAML Groups | +| indexes | List of indexes to be used on policy rule | + +## Examples + +- [Complete](https://github.com/fdmsantos/terraform-aws-opensearch-serverless/tree/main/examples/complete) - Creates an opensearch serverless collection with all features. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 5.31 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.31 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_opensearchserverless_access_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/opensearchserverless_access_policy) | resource | +| [aws_opensearchserverless_collection.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/opensearchserverless_collection) | resource | +| [aws_opensearchserverless_security_policy.encryption](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/opensearchserverless_security_policy) | resource | +| [aws_opensearchserverless_security_policy.network](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/opensearchserverless_security_policy) | resource | +| [aws_opensearchserverless_vpc_endpoint.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/opensearchserverless_vpc_endpoint) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [access\_policy\_description](#input\_access\_policy\_description) | Description of the access policy. | `string` | `null` | no | +| [access\_policy\_name](#input\_access\_policy\_name) | The name of the data access policy. | `string` | `null` | no | +| [access\_policy\_rules](#input\_access\_policy\_rules) | Rules to apply on access policy. |
list(object({
type = string
permissions = list(string)
principals = list(string)
indexes = optional(list(string), [])
}))
| `[]` | no | +| [create\_access\_policy](#input\_create\_access\_policy) | Controls if data access policy should be created. | `bool` | `true` | no | +| [create\_encryption\_policy](#input\_create\_encryption\_policy) | Controls if encryption policy should be created. | `bool` | `true` | no | +| [create\_network\_policy](#input\_create\_network\_policy) | Controls if network policy should be created. | `bool` | `true` | no | +| [description](#input\_description) | Description of the collection. | `string` | `null` | no | +| [encryption\_policy\_description](#input\_encryption\_policy\_description) | Description of the encryption policy. | `string` | `null` | no | +| [encryption\_policy\_kms\_key\_arn](#input\_encryption\_policy\_kms\_key\_arn) | MS Customer managed key arn to use in the encryption policy. | `string` | `null` | no | +| [encryption\_policy\_name](#input\_encryption\_policy\_name) | The name of the encryption policy. | `string` | `null` | no | +| [name](#input\_name) | Name of the collection. | `string` | n/a | yes | +| [network\_policy\_description](#input\_network\_policy\_description) | Description of the network policy. | `string` | `null` | no | +| [network\_policy\_name](#input\_network\_policy\_name) | The name of the network policy. | `string` | `null` | no | +| [network\_policy\_type](#input\_network\_policy\_type) | Type of Network Policy. Supported Values are: AllPublic, AllPrivate, PublicCollectionPrivateDashboard, PrivateCollectionPublicDashboard | `string` | `"AllPublic"` | no | +| [tags](#input\_tags) | A map of tags to assign to the collection. If configured with a provider default\_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level. | `map(string)` | `{}` | no | +| [type](#input\_type) | Type of collection. One of SEARCH, TIMESERIES, or VECTORSEARCH. Defaults to TIMESERIES. | `string` | `"TIMESERIES"` | no | +| [use\_standby\_replicas](#input\_use\_standby\_replicas) | Indicates whether standby replicas should be used for a collection. | `bool` | `true` | no | +| [vpce\_name](#input\_vpce\_name) | Name of the interface endpoint. | `string` | `null` | no | +| [vpce\_security\_group\_ids](#input\_vpce\_security\_group\_ids) | One or more security groups that define the ports, protocols, and sources for inbound traffic that you are authorizing into your endpoint. Up to 5 security groups can be provided. | `list(string)` | `null` | no | +| [vpce\_subnet\_ids](#input\_vpce\_subnet\_ids) | One or more subnet IDs from which you'll access OpenSearch Serverless. Up to 6 subnets can be provided. | `list(string)` | `[]` | no | +| [vpce\_vpc\_id](#input\_vpce\_vpc\_id) | ID of the VPC from which you'll access OpenSearch Serverless. | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [collection\_arn](#output\_collection\_arn) | Amazon Resource Name (ARN) of the collection. | +| [collection\_endpoint](#output\_collection\_endpoint) | Collection-specific endpoint used to submit index, search, and data upload requests to an OpenSearch Serverless collection. | +| [collection\_id](#output\_collection\_id) | Unique identifier for the collection. | +| [dashboard\_endpoint](#output\_dashboard\_endpoint) | Collection-specific endpoint used to access OpenSearch Dashboards. | +| [encryption\_policy\_name](#output\_encryption\_policy\_name) | Name of the encryption policy. | +| [encryption\_policy\_version](#output\_encryption\_policy\_version) | Version of the encryption policy. | +| [kms\_key\_arn](#output\_kms\_key\_arn) | The ARN of the Amazon Web Services KMS key used to encrypt the collection. | +| [network\_policy\_name](#output\_network\_policy\_name) | Name of the network policy. | +| [network\_policy\_version](#output\_network\_policy\_version) | Version of the network policy. | +| [vpce\_id](#output\_vpce\_id) | Id of the vpce. | +| [vpce\_name](#output\_vpce\_name) | Name of the interface endpoint. | + + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/fdmsantos/terraform-aws-opensearch-serverlesse/tree/main/LICENSE) for full details. diff --git a/examples/complete/README.md b/examples/complete/README.md new file mode 100644 index 0000000..cb36cb1 --- /dev/null +++ b/examples/complete/README.md @@ -0,0 +1,62 @@ +# Complete Opensearch Serverless + +Configuration in this directory creates opensearch serverless collection with all supported features. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 5.31 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.31 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [opensearch\_serverless](#module\_opensearch\_serverless) | ../../ | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [name\_prefix](#input\_name\_prefix) | Name prefix to use in resources | `string` | `"demo"` | no | +| [vpc\_azs](#input\_vpc\_azs) | Redshift AZs | `list(string)` |
[
"eu-west-1a",
"eu-west-1b",
"eu-west-1c"
]
| no | +| [vpc\_cidr](#input\_vpc\_cidr) | VPC CIDR block | `string` | `"10.0.0.0/16"` | no | +| [vpc\_private\_subnets](#input\_vpc\_private\_subnets) | VPC Private Subnets | `list(string)` |
[
"10.0.1.0/24",
"10.0.2.0/24",
"10.0.3.0/24"
]
| no | +| [vpc\_public\_subnets](#input\_vpc\_public\_subnets) | VPC Public Subnets | `list(string)` |
[
"10.0.101.0/24",
"10.0.102.0/24",
"10.0.103.0/24"
]
| no | + +## Outputs + +| Name | Description | +|------|-------------| +| [collection\_arn](#output\_collection\_arn) | Amazon Resource Name (ARN) of the collection. | +| [collection\_endpoint](#output\_collection\_endpoint) | Collection-specific endpoint used to submit index, search, and data upload requests to an OpenSearch Serverless collection. | +| [collection\_id](#output\_collection\_id) | Unique identifier for the collection. | +| [dashboard\_endpoint](#output\_dashboard\_endpoint) | Collection-specific endpoint used to access OpenSearch Dashboards. | + diff --git a/examples/complete/main.tf b/examples/complete/main.tf new file mode 100644 index 0000000..5414574 --- /dev/null +++ b/examples/complete/main.tf @@ -0,0 +1,39 @@ +data "aws_caller_identity" "current" {} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + name = "${var.name_prefix}-vpc" + cidr = var.vpc_cidr + azs = var.vpc_azs + private_subnets = var.vpc_private_subnets + public_subnets = var.vpc_public_subnets +} + +module "opensearch_serverless" { + source = "../../" + name = var.name_prefix + network_policy_type = "PublicCollectionPrivateDashboard" + vpce_vpc_id = module.vpc.vpc_id + vpce_subnet_ids = [module.vpc.private_subnets[0]] + access_policy_rules = [ + { + type = "collection" + permissions = ["All"] + principals = [data.aws_caller_identity.current.arn] + }, + { + type = "collection" + permissions = ["Read"] + principals = [data.aws_caller_identity.current.arn] + }, + { + type = "index" + permissions = ["Create", "Update", "Delete", "ReadDocument", "WriteDocument"] + indexes = ["index1", "index2"] + principals = [data.aws_caller_identity.current.arn] + } + ] + tags = { + Environment : "Dev" + } +} diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf new file mode 100644 index 0000000..e607e20 --- /dev/null +++ b/examples/complete/outputs.tf @@ -0,0 +1,19 @@ +output "collection_arn" { + description = "Amazon Resource Name (ARN) of the collection." + value = module.opensearch_serverless.collection_arn +} + +output "collection_id" { + description = "Unique identifier for the collection." + value = module.opensearch_serverless.collection_id +} + +output "collection_endpoint" { + description = "Collection-specific endpoint used to submit index, search, and data upload requests to an OpenSearch Serverless collection." + value = module.opensearch_serverless.collection_endpoint +} + +output "dashboard_endpoint" { + description = "Collection-specific endpoint used to access OpenSearch Dashboards." + value = module.opensearch_serverless.dashboard_endpoint +} diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf new file mode 100644 index 0000000..a326a5d --- /dev/null +++ b/examples/complete/variables.tf @@ -0,0 +1,29 @@ +variable "name_prefix" { + description = "Name prefix to use in resources" + type = string + default = "demo" +} + +variable "vpc_cidr" { + description = "VPC CIDR block" + type = string + default = "10.0.0.0/16" +} + +variable "vpc_azs" { + description = "Redshift AZs" + type = list(string) + default = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] +} + +variable "vpc_private_subnets" { + description = "VPC Private Subnets" + type = list(string) + default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] +} + +variable "vpc_public_subnets" { + description = "VPC Public Subnets" + type = list(string) + default = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"] +} diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf new file mode 100644 index 0000000..2fa3950 --- /dev/null +++ b/examples/complete/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.31" + } + } +} diff --git a/locals.tf b/locals.tf new file mode 100644 index 0000000..165d3b6 --- /dev/null +++ b/locals.tf @@ -0,0 +1,98 @@ +locals { + encryption_policy_name = var.encryption_policy_name != null ? var.encryption_policy_name : "${var.name}-encryption-policy" + network_policy_name = var.network_policy_name != null ? var.network_policy_name : "${var.name}-network-policy" + access_policy_name = var.access_policy_name != null ? var.access_policy_name : "${var.name}-access-policy" + + vpce_name = var.vpce_name != null ? var.vpce_name : "${var.name}-vpce" + create_vpce = var.create_network_policy && var.network_policy_type != "AllPublic" ? true : false + network_policy_vpces = local.create_vpce ? [aws_opensearchserverless_vpc_endpoint.this[0].id] : null + network_policies = { + AllPublic = [{ + Description = "Public access to collection and Dashboards endpoint for ${var.name} collection", + Rules = [ + { + ResourceType = "collection", + Resource = ["collection/${var.name}"] + }, { + ResourceType = "dashboard" + Resource = ["collection/${var.name}"] + } + ], + AllowFromPublic = true + }], + AllPrivate = [{ + Description = "VPC access to collection and Dashboards endpoint for ${var.name} collection", + Rules = [ + { + ResourceType = "collection", + Resource = ["collection/${var.name}"] + }, { + ResourceType = "dashboard" + Resource = ["collection/${var.name}"] + } + ], + AllowFromPublic = false, + SourceVPCEs = local.network_policy_vpces + }], + PublicCollectionPrivateDashboard = [{ + Description = "Public access to collection endpoint for ${var.name} collection", + Rules = [{ + ResourceType = "collection", + Resource = ["collection/${var.name}"] + }], + AllowFromPublic = true + }, { + Description = "VPC access to dashboard endpoint for ${var.name} collection", + Rules = [{ + ResourceType = "dashboard", + Resource = ["collection/${var.name}"] + }], + AllowFromPublic = false + SourceVPCEs = local.network_policy_vpces + }], + PrivateCollectionPublicDashboard = [{ + Description = "Public access to dashboard endpoint for ${var.name} collection", + Rules = [{ + ResourceType = "dashboard", + Resource = ["collection/${var.name}"] + }], + AllowFromPublic = true + }, + { + Description = "VPC access to collection endpoint for ${var.name} collection", + Rules = [{ + ResourceType = "collection", + Resource = ["collection/${var.name}"] + }], + AllowFromPublic = false + SourceVPCEs = local.network_policy_vpces + }], + } + + access_policy_collection_permissions = { + All = "aoss:*", + Create = "aoss:CreateCollectionItems" + Read = "aoss:DescribeCollectionItems" + Update = "aoss:UpdateCollectionItems" + Delete = "aoss:DeleteCollectionItems" + } + + access_policy_index_permissions = { + All = "aoss:*", + Create = "aoss:CreateIndex" + Read = "aoss:DescribeIndex" + Update = "aoss:UpdateIndex" + Delete = "aoss:DeleteIndex" + ReadDocument = "aoss:ReadDocument" + WriteDocument = "aoss:WriteDocument" + } + + access_policy = var.create_access_policy ? [for i, rule in var.access_policy_rules : { + Rules = [{ + ResourceType = rule.type + Resource = rule.type == "collection" ? ["collection/${var.name}"] : [for i, index in rule.indexes : "index/${var.name}/${index}"] + Permission = rule.type == "collection" ? [for k, permission in rule.permissions : local.access_policy_collection_permissions[permission]] : [for k, permission in rule.permissions : local.access_policy_index_permissions[permission]] + }], + Principal = rule.principals + }] : [] +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..001b531 --- /dev/null +++ b/main.tf @@ -0,0 +1,51 @@ +resource "aws_opensearchserverless_collection" "this" { + name = var.name + description = var.description + standby_replicas = var.use_standby_replicas ? "ENABLED" : "DISABLED" + type = var.type + tags = var.tags + depends_on = [aws_opensearchserverless_security_policy.encryption] +} + +resource "aws_opensearchserverless_security_policy" "encryption" { + count = var.create_encryption_policy ? 1 : 0 + name = local.encryption_policy_name + type = "encryption" + description = var.encryption_policy_description + policy = jsonencode(merge({ + "Rules" = [ + { + "Resource" = ["collection/${var.name}"] # local.encryption_policy_collections + "ResourceType" = "collection" + } + ] }, var.encryption_policy_kms_key_arn == null ? { + AWSOwnedKey = true + } : {}, var.encryption_policy_kms_key_arn != null ? { + KmsARN = var.encryption_policy_kms_key_arn + } : {} + )) +} + +resource "aws_opensearchserverless_security_policy" "network" { + count = var.create_network_policy ? 1 : 0 + name = local.network_policy_name + type = "network" + description = var.network_policy_description + policy = jsonencode(local.network_policies[var.network_policy_type]) +} + +resource "aws_opensearchserverless_vpc_endpoint" "this" { + count = local.create_vpce ? 1 : 0 + name = local.vpce_name + subnet_ids = var.vpce_subnet_ids + vpc_id = var.vpce_vpc_id + security_group_ids = var.vpce_security_group_ids +} + +resource "aws_opensearchserverless_access_policy" "this" { + count = var.create_access_policy ? 1 : 0 + name = local.access_policy_name + type = "data" + description = var.access_policy_description + policy = jsonencode(local.access_policy) +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..619f553 --- /dev/null +++ b/outputs.tf @@ -0,0 +1,78 @@ +###### +# OpenSearch Collection +###### +output "collection_arn" { + description = "Amazon Resource Name (ARN) of the collection." + value = aws_opensearchserverless_collection.this.arn +} + +output "collection_id" { + description = "Unique identifier for the collection." + value = aws_opensearchserverless_collection.this.id +} + +output "collection_endpoint" { + description = "Collection-specific endpoint used to submit index, search, and data upload requests to an OpenSearch Serverless collection." + value = aws_opensearchserverless_collection.this.collection_endpoint +} + +output "dashboard_endpoint" { + description = "Collection-specific endpoint used to access OpenSearch Dashboards." + value = aws_opensearchserverless_collection.this.dashboard_endpoint +} + +output "kms_key_arn" { + description = "The ARN of the Amazon Web Services KMS key used to encrypt the collection." + value = aws_opensearchserverless_collection.this.kms_key_arn +} +####### +## Encryption Policy +####### +output "encryption_policy_version" { + description = "Version of the encryption policy." + value = var.create_encryption_policy ? aws_opensearchserverless_security_policy.encryption[0].policy_version : null +} + +output "encryption_policy_name" { + description = "Name of the encryption policy." + value = var.create_encryption_policy ? aws_opensearchserverless_security_policy.encryption[0].name : null +} + +####### +## Network Policy +####### +output "network_policy_version" { + description = "Version of the network policy." + value = var.create_network_policy ? aws_opensearchserverless_security_policy.network[0].policy_version : null +} + +output "network_policy_name" { + description = "Name of the network policy." + value = var.create_network_policy ? aws_opensearchserverless_security_policy.network[0].name : null +} + +####### +## Vpce +####### +output "vpce_name" { + description = "Name of the interface endpoint." + value = local.create_vpce ? aws_opensearchserverless_vpc_endpoint.this[0].name : null +} + +output "vpce_id" { + description = "Id of the vpce." + value = local.create_vpce ? aws_opensearchserverless_vpc_endpoint.this[0].id : null +} + +####### +## Data Access Policy +####### +#output "access_policy_version" { +# description = "Version of the data access policy." +# value = var.create_access_policy ? aws_opensearchserverless_access_policy.this[0].policy_version : null +#} +# +#output "access_policy_name" { +# description = "Name of the data create_access_policy policy." +# value = var.create_network_policy ? aws_opensearchserverless_access_policy.this[0].name : null +#} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..3595a21 --- /dev/null +++ b/variables.tf @@ -0,0 +1,152 @@ +###### +# OpenSearch Collection +###### +variable "name" { + description = "Name of the collection." + type = string +} + +variable "description" { + description = "Description of the collection." + type = string + default = null +} + +variable "use_standby_replicas" { + description = "Indicates whether standby replicas should be used for a collection." + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to assign to the collection. If configured with a provider default_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level." + type = map(string) + default = {} +} + +variable "type" { + description = "Type of collection. One of SEARCH, TIMESERIES, or VECTORSEARCH. Defaults to TIMESERIES." + type = string + default = "TIMESERIES" + validation { + error_message = "Please use a valid type!" + condition = contains(["SEARCH", "TIMESERIES", "VECTORSEARCH"], var.type) + } +} + +###### +# Encryption Policy +###### +variable "create_encryption_policy" { + description = "Controls if encryption policy should be created." + type = bool + default = true +} + +variable "encryption_policy_name" { + description = "The name of the encryption policy." + type = string + default = null +} + +variable "encryption_policy_description" { + description = "Description of the encryption policy." + type = string + default = null +} + +variable "encryption_policy_kms_key_arn" { + description = "MS Customer managed key arn to use in the encryption policy." + type = string + default = null +} + +###### +# Network Policy +###### +variable "create_network_policy" { + description = "Controls if network policy should be created." + type = bool + default = true +} + +variable "network_policy_name" { + description = "The name of the network policy." + type = string + default = null +} + +variable "network_policy_description" { + description = "Description of the network policy." + type = string + default = null +} + +variable "network_policy_type" { + description = "Type of Network Policy. Supported Values are: AllPublic, AllPrivate, PublicCollectionPrivateDashboard, PrivateCollectionPublicDashboard" + type = string + default = "AllPublic" + validation { + error_message = "Please use a valid type!" + condition = contains(["AllPublic", "AllPrivate", "PublicCollectionPrivateDashboard", "PrivateCollectionPublicDashboard"], var.network_policy_type) + } +} + +###### +# VPCE +###### +variable "vpce_name" { + description = "Name of the interface endpoint." + type = string + default = null +} + +variable "vpce_subnet_ids" { + description = "One or more subnet IDs from which you'll access OpenSearch Serverless. Up to 6 subnets can be provided." + type = list(string) + default = [] +} + +variable "vpce_vpc_id" { + description = "ID of the VPC from which you'll access OpenSearch Serverless." + type = string + default = null +} + +variable "vpce_security_group_ids" { + description = "One or more security groups that define the ports, protocols, and sources for inbound traffic that you are authorizing into your endpoint. Up to 5 security groups can be provided." + type = list(string) + default = null +} + +###### +# Data Access Policy +###### +variable "create_access_policy" { + description = "Controls if data access policy should be created." + type = bool + default = true +} + +variable "access_policy_name" { + description = "The name of the data access policy." + type = string + default = null +} + +variable "access_policy_description" { + description = "Description of the access policy." + type = string + default = null +} + +variable "access_policy_rules" { + description = "Rules to apply on access policy." + type = list(object({ + type = string + permissions = list(string) + principals = list(string) + indexes = optional(list(string), []) + })) + default = [] +} diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..71a46e4 --- /dev/null +++ b/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 0.13.1" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.31" + } + } +}