Skip to content

Commit

Permalink
WTL-852 Add Terraform configuration (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
apgrucza authored Sep 8, 2021
1 parent 18b00ac commit 912a036
Show file tree
Hide file tree
Showing 20 changed files with 468 additions and 44 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ tests/logs/*
tests/config
/distributions
packaged.yaml
.terraform*
terraform.tfstate*
/infra/terraform/modules/_lambda/packages/*
71 changes: 27 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,7 @@ The build script prompts you for the required configuration parameters, which ar

A generic package retrieves its configuration at runtime from the [AWS Systems Manager](https://aws.amazon.com/systems-manager/) Parameter Store and [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/). Currently, support for this type of package has been added for OKTA Native only. To disable all issued JWTs, rotate the key pair using the Secrets Manager console.

Generic packages are available for download from the Releases page of this GitHub repository. Or, to build a generic package yourself, execute:

```bash
./build.sh package
```

The supported values of `package` are:

* `okta_native` - builds a generic Lambda package for OKTA Native authentication
* `rotate_key_pair` - builds a Lambda package for rotating the RSA keys in AWS Secrets Manager
Generic packages are pre-built and available for download from the Releases page of this GitHub repository. [Terraform modules](./infra/terraform/README.md) are available for downloading and deploying generic packages and their configuration resources.

## Identity Provider Guides

Expand Down Expand Up @@ -146,43 +137,15 @@ The supported values of `package` are:
1. Client Id from the application created in our previous step (can be found at the bottom of the general tab)
1. Base Url
1. This is named the 'Org URL' and can be found in the top right of the Dashboard tab.
1. Decide on whether you want to use a custom package or a generic package
* For a custom package:
1. Execute `./build.sh` in the downloaded directory. NPM will run to download dependencies and an RSA key will be generated.
1. Choose `OKTA Native` as the authorization method and enter the values for Base URL (Org URL), Client ID, PKCE Code Verifier Length, Redirect URI, and Session Duration
1. Find the resulting `zip` file in your distribution folder
* For a generic package:
1. Create the parameters below in the AWS Systems Manager Parameter Store in the `us-east-1` region. Replace `{name}` with the name that you will give the Lambda authentication function.
* `/{name}/base-url` (e.g. `https://my-org.okta.com/oauth2/default`)
* `/{name}/client-id` (from the OKTA application)
* `/{name}/domain-name` (e.g. `my-site.cloudfront.net`)
* `/{name}/callback-path` (e.g. `/callback`)
* `/{name}/session-duration` (in seconds)
* `/{name}/pkce-code-verifier-length` (from 43 to 128)
* `/{name}/scope` (e.g. `openid email`)
1. Download the latest `okta_native_*.zip` asset from the Releases page
1. Upload the `zip` file using the AWS Lambda console and jump to the [configuration step](#configure-lambda-and-cloudfront)
1. To use the [generic package](#generic-packages), jump straight to [Terraform Modules for CloudFront Authentication](./infra/terraform/README.md).
1. Execute `./build.sh` in the downloaded directory. NPM will run to download dependencies and a RSA key will be generated.
1. Choose `OKTA Native` as the authorization method and enter the values for Base URL (Org URL), Client ID, PKCE Code Verifier Length, Redirect URI, and Session Duration
1. Upload the resulting `zip` file found in your distribution folder using the AWS Lambda console and jump to the [configuration step](#configure-lambda-and-cloudfront)

## Configure Lambda and CloudFront

See [Manual Deployment](https://github.com/Widen/cloudfront-auth/wiki/Manual-Deployment) __*or*__ [AWS SAM Deployment](https://github.com/Widen/cloudfront-auth/wiki/AWS-SAM-Deployment)

If deploying a generic package, you also need to follow the steps below in the `us-east-1` region. In these steps, replace `{name}` with the name of the Lambda authentication function you created and `{account_id}` with the AWS Account ID number.

1. Modify the role on the Lambda authentication function to include a policy that:
* allows the action `secretsmanager:GetSecretValue` on resource `arn:aws:secretsmanager:us-east-1:{account_id}:secret:{name}/*`
* allows the action `ssm:GetParametersByPath` on resource `arn:aws:ssm:us-east-1:{account_id}:parameter/{name}`
1. Execute `./build.sh rotate_key_pair`
1. Create a Lambda rotation function with the following configuration:
* Function code: Use the `rotate_key_pair.zip` file found in the distributions folder
* Runtime: **Node.js 14.x** or later
* Handler: `index.handler`
* Timeout: 30 sec
1. Modify the role on the Lambda rotation function to include a policy that:
* allows the action `secretsmanager:PutSecretValue` on resource `arn:aws:secretsmanager:us-east-1:{account_id}:secret:{name}/*`
1. Create a secret in AWS Secrets Manager named `{name}/key-pair`
1. Enable secret rotation on the secret and associate it with the Lambda rotation function

## Authorization Method Examples

* [Use Google Groups to authorize users](https://github.com/Widen/cloudfront-auth/wiki/Google-Groups-Setup)
Expand All @@ -199,10 +162,30 @@ Detailed instructions on testing your function can be found [in the Wiki](https:

## Build Requirements

* [npm](https://www.npmjs.com/) ^5.6.0
* [node](https://nodejs.org/en/) ^10.0
* [npm](https://www.npmjs.com/) ^7.20.0
* [node](https://nodejs.org/en/) ^14.0
* [openssl](https://www.openssl.org)

## Building Generic Packages

If you need to build a generic package yourself, execute:

```bash
./build.sh package
```

The supported values of `package` are:

* `okta_native` - builds a generic Lambda package for OKTA Native authentication
* `rotate_key_pair` - builds a Lambda package for rotating the RSA keys in AWS Secrets Manager

GitHub Actions automatically creates a new GitHub release when the repository owner pushes a tag that begins with `v`:

```sh
git tag -a -m "Target AWS Lambda Node.js 14.x runtime" v3.0.0
git push origin v3.0.0
```

## Contributing

All contributions are welcome. Please create an issue in order open up communication with the community.
Expand Down
68 changes: 68 additions & 0 deletions infra/terraform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Terraform Modules for CloudFront Authentication

This directory contains the _Terraform_ configuration for adding authentication to a CloudFront distribution. Currently only OKTA Native authentication is supported.

## Usage

The Terraform modules for each identity provider are in the [modules](./modules) directory. Refer to the [examples](./examples) directory for Terraform configuration that you can include in your project and adapt. Refer to the `variables.tf` file of the module to see all the available input variables. Below is an example for OKTA Native.

1. Call the module in your Terraform configuration. CloudFront uses the `us-east-1` region, so you must pass a `us-east-1` provider to the module.

```hcl
module "cloudfront_auth_okta_native" {
source = "github.com/iress/cloudfront-auth//infra/terraform/modules/okta_native"
# Lambda function version to deploy (see the Releases page of this GitHub repository)
release_version = "v3.0.0"
name = "my-website-auth"
org_url = "https://my-org.okta.com/oauth2/default"
client_id = "Nf2qSD9wXKU9ph8an22T"
domain_name = "my-cloudfront-site.example.com"
# aws.global_services is a us-east-1 provider
providers = {
aws = aws.global_services
}
}
```
1. Add a [lambda_function_association](https://www.terraform.io/docs/providers/aws/r/cloudfront_distribution.html#lambda_function_association) to your [aws_cloudfront_distribution](https://www.terraform.io/docs/providers/aws/r/cloudfront_distribution.html) resource:
```hcl
resource "aws_cloudfront_distribution" "distribution" {
# ... other configuration ...
# lambda_function_association is also supported by ordered_cache_behavior
default_cache_behavior {
# ... other configuration ...
lambda_function_association {
event_type = "viewer-request"
lambda_arn = module.cloudfront_auth_okta_native.auth_lambda_arn
include_body = false
}
}
}
```
## Requirements
This module requires [wget](https://www.gnu.org/software/wget/) to be installed on the machine or container that runs Terraform.
## Logs
Logs are written to CloudWatch. The table below shows where the logs can be found, where {name} is the value of the `name` input variable in the Terraform module.
| Function | Log group name | Region |
|----------|----------------|--------|
| Authentication | /aws/lambda/us-east-1.{name} | The region closest to the user who made the request to the website
| Secret rotation | /aws/lambda/{name}-rotation | us-east-1
## Destroying
The first time you run `terraform destroy` you may receive the following error:
*Lambda was unable to delete arn:aws:lambda:us-east-1:553479592532:function:my-website-auth:1 because it is a replicated function. Please see our [documentation](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-delete-replicas.html) for Deleting Lambda@Edge Functions and Replicas.*
When this occurs, wait (up to a few hours) for CloudFront to delete the Lambda function replicas, then run `terraform destroy` again.
13 changes: 13 additions & 0 deletions infra/terraform/examples/okta_native/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module "cloudfront_auth_okta_native" {
source = "github.com/iress/cloudfront-auth//infra/terraform/modules/okta_native"

release_version = "v3.0.0"
name = "my-website-auth"
org_url = "https://my-org.okta.com/oauth2/default"
client_id = "Nf2qSD9wXKU9ph8an22T"
domain_name = "my-cloudfront-site.example.com"

providers = {
aws = aws.global_services
}
}
8 changes: 8 additions & 0 deletions infra/terraform/examples/okta_native/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
terraform {
required_version = ">= 1.0.0"
}

provider "aws" {
region = "us-east-1"
alias = "global_services"
}
50 changes: 50 additions & 0 deletions infra/terraform/modules/_auth/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
data "aws_caller_identity" "current" {}

data "aws_region" "current" {}

data "aws_iam_policy_document" "auth" {
statement {
actions = ["ssm:GetParametersByPath"]
resources = ["arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter/${var.name}"]
}

statement {
actions = ["secretsmanager:GetSecretValue"]
resources = ["arn:aws:secretsmanager:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:secret:${var.name}/*"]
}
}

data "aws_iam_policy_document" "rotation" {
statement {
actions = ["secretsmanager:PutSecretValue"]
resources = ["arn:aws:secretsmanager:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:secret:${var.name}/*"]
}
}

module "auth" {
source = "../_lambda"

name = var.name
tags = var.tags
package_url = var.package_url
timeout = 5
iam_policy_override_json = data.aws_iam_policy_document.auth.json
lambda_at_edge = true
}

module "rotation" {
source = "../_lambda"

name = "${var.name}-rotation"
tags = var.tags
package_url = "https://github.com/iress/cloudfront-auth/releases/download/${var.release_version}/rotate_key_pair.zip"
timeout = 30
iam_policy_override_json = data.aws_iam_policy_document.rotation.json
}

resource "aws_lambda_permission" "allow_secrets_manager" {
statement_id = "AllowExecutionFromSecretsManager"
action = "lambda:InvokeFunction"
function_name = module.rotation.lambda_arn
principal = "secretsmanager.amazonaws.com"
}
4 changes: 4 additions & 0 deletions infra/terraform/modules/_auth/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
output "auth_lambda_arn" {
value = module.auth.lambda_arn
description = "The Amazon Resource Name (ARN) identifying the authentication Lambda Function Version"
}
10 changes: 10 additions & 0 deletions infra/terraform/modules/_auth/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Provider passed in should always be us-east-1

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3"
}
}
}
14 changes: 14 additions & 0 deletions infra/terraform/modules/_auth/secrets.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
resource "aws_secretsmanager_secret" "key_pair" {
name = "${var.name}/key-pair"
recovery_window_in_days = 0
tags = var.tags
}

resource "aws_secretsmanager_secret_rotation" "key_pair" {
secret_id = aws_secretsmanager_secret.key_pair.id
rotation_lambda_arn = module.rotation.lambda_arn

rotation_rules {
automatically_after_days = var.key_pair_rotation_period_days
}
}
24 changes: 24 additions & 0 deletions infra/terraform/modules/_auth/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
variable "release_version" {
description = "The name of the GitHub release version to deploy"
type = string
}

variable "name" {
description = "A name for the AWS resources created by this module"
type = string
}

variable "tags" {
description = "Tags to add to each resource"
type = map(string)
}

variable "package_url" {
description = "The URL of the Lambda authentication function package"
type = string
}

variable "key_pair_rotation_period_days" {
description = "The number of days between automatic scheduled rotations of the key pair"
type = number
}
38 changes: 38 additions & 0 deletions infra/terraform/modules/_lambda/iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
data "aws_iam_policy_document" "assume_role" {
statement {
actions = ["sts:AssumeRole"]

principals {
type = "Service"
identifiers = var.lambda_at_edge ? ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] : ["lambda.amazonaws.com"]
}
}
}

data "aws_iam_policy_document" "execution" {
override_json = var.iam_policy_override_json

statement {
sid = "logs"

actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]

resources = ["arn:aws:logs:*:*:*"]
}
}

resource "aws_iam_role" "lambda" {
name = var.name
assume_role_policy = data.aws_iam_policy_document.assume_role.json
tags = var.tags
}

resource "aws_iam_role_policy" "execution" {
name = var.name
role = aws_iam_role.lambda.id
policy = data.aws_iam_policy_document.execution.json
}
32 changes: 32 additions & 0 deletions infra/terraform/modules/_lambda/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
locals {
package_directory = "${path.module}/packages"
package_filename = basename(var.package_url)
}

# Always download the lambda package if it does not exist
resource "null_resource" "download" {
triggers = {
always_run = uuid()
}

provisioner "local-exec" {
command = "test -f ${local.package_directory}/${local.package_filename} || (mkdir -p ${local.package_directory} && wget -P ${local.package_directory} ${var.package_url})"
}
}

resource "aws_lambda_function" "main" {
filename = "${local.package_directory}/${local.package_filename}"
function_name = var.name
role = aws_iam_role.lambda.arn
handler = "index.handler"
source_code_hash = base64sha256(var.package_url)
runtime = "nodejs14.x"
timeout = var.timeout
publish = var.lambda_at_edge
tags = var.tags

# Ensure the lambda function is created after the package is downloaded
depends_on = [
null_resource.download
]
}
4 changes: 4 additions & 0 deletions infra/terraform/modules/_lambda/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
output "lambda_arn" {
value = var.lambda_at_edge ? aws_lambda_function.main.qualified_arn : aws_lambda_function.main.arn
description = "The Amazon Resource Name (ARN) identifying the Lambda Function Version"
}
10 changes: 10 additions & 0 deletions infra/terraform/modules/_lambda/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Provider passed in should always be us-east-1

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3"
}
}
}
Loading

0 comments on commit 912a036

Please sign in to comment.