From 38cf918754273cbb03406698eef5bde7f4a90d67 Mon Sep 17 00:00:00 2001 From: Brad Janke Date: Wed, 28 Jul 2021 14:59:57 -0500 Subject: [PATCH] fills out main.tf with proposed solution for adding SFTP users (#2) * fills out main.tf with proposed solution for adding SFTP users. started to modify the tests a bit. i think outputs and variables are done? * minor fixes * Auto Format * Fixes tests. Uses policy documents instead of inline policies. * Auto Format Co-authored-by: cloudpossebot <11232728+cloudpossebot@users.noreply.github.com> --- README.md | 19 +++-- docs/terraform.md | 19 +++-- examples/complete/fixtures.us-east-2.tfvars | 11 +++ examples/complete/main.tf | 8 +- examples/complete/outputs.tf | 13 +--- examples/complete/variables.tf | 11 ++- examples/complete/versions.tf | 2 +- main.tf | 85 +++++++++++++++++++-- outputs.tf | 11 +-- test/src/examples_complete_test.go | 33 +++----- variables.tf | 28 ++++++- 11 files changed, 175 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index aee0191..08442cd 100644 --- a/README.md +++ b/README.md @@ -141,19 +141,26 @@ Available targets: | Name | Version | |------|---------| -| [random](#provider\_random) | >= 2.2 | +| [aws](#provider\_aws) | n/a | ## Modules | Name | Source | Version | |------|--------|---------| +| [iam\_label](#module\_iam\_label) | cloudposse/label/null | 0.24.1 | | [this](#module\_this) | cloudposse/label/null | 0.24.1 | ## Resources | Name | Type | |------|------| -| [random_integer.example](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/integer) | resource | +| [aws_iam_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_transfer_server.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/transfer_server) | resource | +| [aws_transfer_ssh_key.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/transfer_ssh_key) | resource | +| [aws_transfer_user.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/transfer_user) | resource | +| [aws_iam_policy_document.allows_s3](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | ## Inputs @@ -165,7 +172,8 @@ Available targets: | [delimiter](#input\_delimiter) | Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | | [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | | [environment](#input\_environment) | Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | -| [example](#input\_example) | Example variable | `string` | `"hello world"` | no | +| [force\_destroy](#input\_force\_destroy) | Forces the AWS Transfer Server to be destroyed | `bool` | `false` | no | +| [iam\_attributes](#input\_iam\_attributes) | Additional attributes to add to the IDs of the IAM role and policy | `list(string)` | `[]` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for default, which is `0`.
Does not affect `id_full`. | `number` | `null` | no | | [label\_key\_case](#input\_label\_key\_case) | The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | | [label\_order](#input\_label\_order) | The naming order of the id output and Name tag.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 5 elements, but at least one must be present. | `list(string)` | `null` | no | @@ -173,6 +181,8 @@ Available targets: | [name](#input\_name) | Solution name, e.g. 'app' or 'jenkins' | `string` | `null` | no | | [namespace](#input\_namespace) | Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp' | `string` | `null` | no | | [regex\_replace\_chars](#input\_regex\_replace\_chars) | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [region](#input\_region) | n/a | `string` | `"us-east-1"` | no | +| [sftp\_users](#input\_sftp\_users) | List of SFTP usernames and public keys |
map(object({
user_name = string,
public_key = string
}))
| `{}` | no | | [stage](#input\_stage) | Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | | [tags](#input\_tags) | Additional tags (e.g. `map('BusinessUnit','XYZ')` | `map(string)` | `{}` | no | @@ -180,9 +190,8 @@ Available targets: | Name | Description | |------|-------------| -| [example](#output\_example) | Example output | | [id](#output\_id) | ID of the created example | -| [random](#output\_random) | Stable random number for this example | +| [transfer\_endpoint](#output\_transfer\_endpoint) | The endpoint of the Transfer Server | diff --git a/docs/terraform.md b/docs/terraform.md index 02c474e..2319c0b 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -11,19 +11,26 @@ | Name | Version | |------|---------| -| [random](#provider\_random) | >= 2.2 | +| [aws](#provider\_aws) | n/a | ## Modules | Name | Source | Version | |------|--------|---------| +| [iam\_label](#module\_iam\_label) | cloudposse/label/null | 0.24.1 | | [this](#module\_this) | cloudposse/label/null | 0.24.1 | ## Resources | Name | Type | |------|------| -| [random_integer.example](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/integer) | resource | +| [aws_iam_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_transfer_server.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/transfer_server) | resource | +| [aws_transfer_ssh_key.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/transfer_ssh_key) | resource | +| [aws_transfer_user.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/transfer_user) | resource | +| [aws_iam_policy_document.allows_s3](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | ## Inputs @@ -35,7 +42,8 @@ | [delimiter](#input\_delimiter) | Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | | [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | | [environment](#input\_environment) | Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | -| [example](#input\_example) | Example variable | `string` | `"hello world"` | no | +| [force\_destroy](#input\_force\_destroy) | Forces the AWS Transfer Server to be destroyed | `bool` | `false` | no | +| [iam\_attributes](#input\_iam\_attributes) | Additional attributes to add to the IDs of the IAM role and policy | `list(string)` | `[]` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for default, which is `0`.
Does not affect `id_full`. | `number` | `null` | no | | [label\_key\_case](#input\_label\_key\_case) | The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | | [label\_order](#input\_label\_order) | The naming order of the id output and Name tag.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 5 elements, but at least one must be present. | `list(string)` | `null` | no | @@ -43,6 +51,8 @@ | [name](#input\_name) | Solution name, e.g. 'app' or 'jenkins' | `string` | `null` | no | | [namespace](#input\_namespace) | Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp' | `string` | `null` | no | | [regex\_replace\_chars](#input\_regex\_replace\_chars) | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [region](#input\_region) | n/a | `string` | `"us-east-1"` | no | +| [sftp\_users](#input\_sftp\_users) | List of SFTP usernames and public keys |
map(object({
user_name = string,
public_key = string
}))
| `{}` | no | | [stage](#input\_stage) | Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | | [tags](#input\_tags) | Additional tags (e.g. `map('BusinessUnit','XYZ')` | `map(string)` | `{}` | no | @@ -50,7 +60,6 @@ | Name | Description | |------|-------------| -| [example](#output\_example) | Example output | | [id](#output\_id) | ID of the created example | -| [random](#output\_random) | Stable random number for this example | +| [transfer\_endpoint](#output\_transfer\_endpoint) | The endpoint of the Transfer Server | diff --git a/examples/complete/fixtures.us-east-2.tfvars b/examples/complete/fixtures.us-east-2.tfvars index 1e0ce68..d3646c0 100644 --- a/examples/complete/fixtures.us-east-2.tfvars +++ b/examples/complete/fixtures.us-east-2.tfvars @@ -8,3 +8,14 @@ stage = "test" name = "example" +sftp_users = { + "brad" = { + user_name = "brad", + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCYN8IRq10gKJj5/y2IHYFFRXHctJ+VblcsOnwvsbUIB+PKMLLWd5ySpW30s8OFVcxpMu2VXzXVKRGGbOUbZEN7MqH9xkW8eV6tSfXsZK2osLdIQ3QG3eSyoN4gPFlDBkZSzmkb2oaaclGPGRezbzDnp+oz8IiC5ZE8aprq3Xk850fifIEEOhJtVsrL84uwgx4LGZMMQmLdf6xm2SMSMx53zDPtSnlSeMlC2qUz6LBC41gwObQDoh0j3svsENf8FS8iIkdX50NaRoZvhJU0Oud5A7bj3zz0xtKn6uQZnL9hb6ttvp2/mNe1CKBZt9hUdrn4SHPs0sbWYbQLTzp+9okcg8LCe7qnFdHH7xQGp17SAgi5f91RPOUWtqvkOC5yoVaveR82KZObU+HSCfT/PObLjdUDtWrZABp4VM/u5t9Fn6BQ+eRSAiCIqLQlizs9kpKO8LYX7CagxRJz8KtRXfhndA3nTFq35vml8rD5hKsTrtbSkycmytQZ8TF7IwuN0amRfZ7Iwb3/eLTEv6jp5PKKVprBvnjDH1ipn/AwidsKrbCCVquKg0X/7rwVLrvMuYAtlxPLqjqZpvfTwXBwLlHTEuCvuh/Y/TpjJqqxCnbY/6R4TcabHVGsA4b1kVajRbvVPZPGVcWs+XvycO4Y8KR/hZZGGxK16SVFGbrnhX1D2Q== developer@developers.local" + }, + "kenny" = { + user_name = "kenny", + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCYN8IRq10gKJj5/y2IHYFFRXHctJ+VblcsOnwvsbUIB+PKMLLWd5ySpW30s8OFVcxpMu2VXzXVKRGGbOUbZEN7MqH9xkW8eV6tSfXsZK2osLdIQ3QG3eSyoN4gPFlDBkZSzmkb2oaaclGPGRezbzDnp+oz8IiC5ZE8aprq3Xk850fifIEEOhJtVsrL84uwgx4LGZMMQmLdf6xm2SMSMx53zDPtSnlSeMlC2qUz6LBC41gwObQDoh0j3svsENf8FS8iIkdX50NaRoZvhJU0Oud5A7bj3zz0xtKn6uQZnL9hb6ttvp2/mNe1CKBZt9hUdrn4SHPs0sbWYbQLTzp+9okcg8LCe7qnFdHH7xQGp17SAgi5f91RPOUWtqvkOC5yoVaveR82KZObU+HSCfT/PObLjdUDtWrZABp4VM/u5t9Fn6BQ+eRSAiCIqLQlizs9kpKO8LYX7CagxRJz8KtRXfhndA3nTFq35vml8rD5hKsTrtbSkycmytQZ8TF7IwuN0amRfZ7Iwb3/eLTEv6jp5PKKVprBvnjDH1ipn/AwidsKrbCCVquKg0X/7rwVLrvMuYAtlxPLqjqZpvfTwXBwLlHTEuCvuh/Y/TpjJqqxCnbY/6R4TcabHVGsA4b1kVajRbvVPZPGVcWs+XvycO4Y8KR/hZZGGxK16SVFGbrnhX1D2Q== developer@developers.local" + + } +} diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 38880b7..13b9b4c 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -1,7 +1,13 @@ +provider "aws" { + region = var.region +} + module "example" { source = "../.." - example = var.example + region = var.region + + sftp_users = var.sftp_users context = module.this.context } diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index 20aa3e3..d02362d 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -3,12 +3,7 @@ output "id" { value = module.example.id } -output "example" { - description = "Output \"example\" from example module" - value = module.example.example -} - -output "random" { - description = "Output \"random\" from example module" - value = module.example.random -} +output "transfer_endpoint" { + description = "Endpoint for your SFTP connection" + value = module.example.transfer_endpoint +} \ No newline at end of file diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index 9a04f21..b767628 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -1,4 +1,11 @@ -variable "example" { - type = string +variable "region" { + type = string +} + +variable "sftp_users" { + type = map(object({ + user_name = string, + public_key = string + })) description = "The value which will be passed to the example module" } diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index 86b1b25..715ff31 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 0.12.26" + required_version = ">= 0.13.7" required_providers { local = { diff --git a/main.tf b/main.tf index 8e1225e..0a3f8eb 100644 --- a/main.tf +++ b/main.tf @@ -1,13 +1,82 @@ -resource "random_integer" "example" { - count = module.this.enabled ? 1 : 0 +locals { + enabled = module.this.enabled +} + +resource "aws_transfer_server" "default" { + identity_provider_type = "SERVICE_MANAGED" + protocols = ["SFTP"] # SFTP, FTPS, FTP + domain = "S3" # EFS, S3 + endpoint_type = "PUBLIC" # VPC, PUBLIC + force_destroy = var.force_destroy + + tags = module.this.tags +} + +resource "aws_transfer_user" "default" { + for_each = local.enabled ? var.sftp_users : {} + + server_id = aws_transfer_server.default.id + role = aws_iam_role.default.arn + + user_name = each.value.user_name + + tags = module.this.tags +} + +resource "aws_transfer_ssh_key" "default" { + for_each = local.enabled ? var.sftp_users : {} + + server_id = aws_transfer_server.default.id + + user_name = each.value.user_name + body = each.value.public_key + + depends_on = [ + aws_transfer_user.default + ] +} + +# IAM +module "iam_label" { + source = "cloudposse/label/null" + version = "0.24.1" - min = 1 - max = 50000 - keepers = { - example = var.example + attributes = var.iam_attributes + + context = module.this.context +} + +data "aws_iam_policy_document" "assume_role_policy" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["transfer.amazonaws.com"] + } } } -locals { - example = format("%v %v", var.example, join("", random_integer.example[*].result)) +data "aws_iam_policy_document" "allows_s3" { + statement { + sid = "S3AccessForAWSTransferusers" + effect = "Allow" + + actions = ["s3:*"] + + resources = [ + "arn:aws:s3:::*" + ] + } +} + +resource "aws_iam_policy" "default" { + name = module.iam_label.id + policy = data.aws_iam_policy_document.allows_s3.json +} + +resource "aws_iam_role" "default" { + name = module.iam_label.id + assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json + managed_policy_arns = [aws_iam_policy.default.arn] } diff --git a/outputs.tf b/outputs.tf index 23e08b2..4f6db08 100644 --- a/outputs.tf +++ b/outputs.tf @@ -3,12 +3,7 @@ output "id" { value = module.this.enabled ? module.this.id : null } -output "example" { - description = "Example output" - value = module.this.enabled ? local.example : null -} - -output "random" { - description = "Stable random number for this example" - value = module.this.enabled ? join("", random_integer.example[*].result) : null +output "transfer_endpoint" { + description = "The endpoint of the Transfer Server" + value = aws_transfer_server.default.endpoint } diff --git a/test/src/examples_complete_test.go b/test/src/examples_complete_test.go index cb8ed00..2b36b0c 100644 --- a/test/src/examples_complete_test.go +++ b/test/src/examples_complete_test.go @@ -1,8 +1,10 @@ package test import ( + "fmt" "math/rand" "strconv" + "strings" "testing" "time" @@ -18,8 +20,6 @@ func TestExamplesComplete(t *testing.T) { randID := strconv.Itoa(rand.Intn(100000)) attributes := []string{randID} - exampleInput := "Hello, world!" - terraformOptions := &terraform.Options{ // The path to where our Terraform code is located TerraformDir: "../../examples/complete", @@ -30,7 +30,6 @@ func TestExamplesComplete(t *testing.T) { // and AWS resources do not interfere with each other Vars: map[string]interface{}{ "attributes": attributes, - "example": exampleInput, }, } // At the end of the test, run `terraform destroy` to clean up any resources that were created @@ -41,12 +40,15 @@ func TestExamplesComplete(t *testing.T) { // Run `terraform output` to get the value of an output variable id := terraform.Output(t, terraformOptions, "id") - example := terraform.Output(t, terraformOptions, "example") - random := terraform.Output(t, terraformOptions, "random") + transfer_endpoint := terraform.Output(t, terraformOptions, "transfer_endpoint") // Verify we're getting back the outputs we expect // Ensure we get a random number appended - assert.Equal(t, exampleInput+" "+random, example) + expectedTransferEndpoint := "server.transfer.us-east-2.amazonaws.com" + assert.True(t, + strings.HasSuffix(transfer_endpoint, expectedTransferEndpoint), + fmt.Sprintf("Transfer endpoint should end with %v", expectedTransferEndpoint)) + // Ensure we get the attribute included in the ID assert.Equal(t, "eg-ue2-test-example-"+randID, id) @@ -61,23 +63,8 @@ func TestExamplesComplete(t *testing.T) { terraform.Apply(t, terraformOptions) id2 := terraform.Output(t, terraformOptions, "id") - example2 := terraform.Output(t, terraformOptions, "example") - random2 := terraform.Output(t, terraformOptions, "random") + transfer_endpoint2 := terraform.Output(t, terraformOptions, "transfer_endpoint") assert.Equal(t, id, id2, "Expected `id` to be stable") - assert.Equal(t, example, example2, "Expected `example` to be stable") - assert.Equal(t, random, random2, "Expected `random` to be stable") - - // Then we run change the example and run it a third time and - // verify that the random number changed - newExample := "Goodbye" - terraformOptions.Vars["example"] = newExample - terraform.Apply(t, terraformOptions) - - example3 := terraform.Output(t, terraformOptions, "example") - random3 := terraform.Output(t, terraformOptions, "random") - - assert.NotEqual(t, random, random3, "Expected `random` to change when `example` changed") - assert.Equal(t, newExample+" "+random3, example3, "Expected `example` to use new random number") - + assert.Equal(t, transfer_endpoint, transfer_endpoint2, "Expected `transfer_endpoint` to be stable") } diff --git a/variables.tf b/variables.tf index ff9b03b..8dc6e90 100644 --- a/variables.tf +++ b/variables.tf @@ -1,4 +1,26 @@ -variable "example" { - description = "Example variable" - default = "hello world" +variable "region" { + type = string + default = "us-east-1" } + +variable "sftp_users" { + type = map(object({ + user_name = string, + public_key = string + })) + + default = {} + description = "List of SFTP usernames and public keys" +} + +variable "force_destroy" { + type = bool + default = false + description = "Forces the AWS Transfer Server to be destroyed" +} + +variable "iam_attributes" { + type = list(string) + description = "Additional attributes to add to the IDs of the IAM role and policy" + default = [] +} \ No newline at end of file