Skip to content

Commit cdc154d

Browse files
authored
Merge pull request #142 from sil-org/develop
Release 3.0.0 -- Import DynamoDB tables
2 parents 2b9d1cb + 5ad2e8e commit cdc154d

File tree

9 files changed

+98
-143
lines changed

9 files changed

+98
-143
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# A Serverless MFA API with support for TOTP and WebAuthn
22

3-
This project provides a semi-generic backend API for supporting Time-based One Time Passcode (TOTP) and WebAuthn
3+
This project provides a semi-generic backend API for supporting Time-based One Time Password (TOTP) and WebAuthn
44
Passkey registration and authentication. It is intended to be run in a manner as to be shared between multiple consuming
55
applications. It uses an API key and secret to authenticate requests, and further uses that secret as the encryption
66
key. Loss of the API secret would mean loss of all credentials stored.
@@ -38,15 +38,15 @@ This endpoint has not yet been proven in production use. Proceed at your own ris
3838
1. `x-mfa-apikey` - The API Key
3939
2. `x-mfa-apisecret` - The API Key Secret
4040

41-
### Create TOTP Passcode
41+
### Create TOTP
4242

4343
`POST /totp`
4444

45-
### Delete TOTP Passcode
45+
### Delete TOTP
4646

4747
`DELETE /totp/{uuid}`
4848

49-
### Validate TOTP Passcode
49+
### Validate TOTP
5050

5151
`POST /totp/{uuid}/validate`
5252

apikey_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ func (ms *MfaSuite) TestAppRotateApiKey() {
346346
key := user.ApiKey
347347
must(db.Store(config.ApiKeyTable, key))
348348

349-
totp := ms.newPasscode(key)
349+
totp := ms.newTOTP(key)
350350

351351
newKey := newTestKey()
352352
must(db.Store(config.ApiKeyTable, newKey))
@@ -508,7 +508,7 @@ func (ms *MfaSuite) TestApiKey_ReEncryptTOTPs() {
508508
must(newKey.Activate())
509509
must(ms.app.GetDB().Store(ms.app.GetConfig().ApiKeyTable, newKey))
510510

511-
_ = ms.newPasscode(oldKey)
511+
_ = ms.newTOTP(oldKey)
512512

513513
complete, incomplete, err := newKey.ReEncryptTOTPs(storage, oldKey)
514514
ms.NoError(err)

openapi.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ paths:
651651

652652
/totp:
653653
post:
654-
summary: Create a new passcode (TOTP)
654+
summary: Create a new TOTP
655655
requestBody:
656656
content:
657657
application/json:
@@ -679,11 +679,11 @@ paths:
679679
properties:
680680
uuid:
681681
type: string
682-
description: The unique identifier for the passcode.
682+
description: The unique identifier for the TOTP.
683683
example: "aaaaaaaa-1111-aaaa-1111-aaaaaaaaaaaa"
684684
totpKey:
685685
type: string
686-
description: The passcode secret key.
686+
description: The TOTP secret key.
687687
example: "0123456789ABCDEF0123456789ABCDEF"
688688
imageUrl:
689689
type: string
@@ -695,22 +695,22 @@ paths:
695695
otpAuthUrl:
696696
type: string
697697
description: >-
698-
OTPAuthURL is an otpauth URI that contains the passcode secret key. It may also contain metadata
698+
OTPAuthURL is an otpauth URI that contains the TOTP secret key. It may also contain metadata
699699
like issuer, algorithm, and number of digits.
700700
example: "otpauth://totp/idp:john_smith?secret=0123456789ABCDEF0123456789ABCDEF&issuer=SIL%20IdP"
701701
401:
702702
$ref: "#/components/responses/UnauthorizedError"
703703
/totp/{uuid}:
704704
delete:
705-
summary: Delete a passcode (TOTP)
705+
summary: Delete a TOTP
706706
parameters:
707707
- in: path
708708
name: uuid
709709
schema:
710710
type: string
711711
format: uuid
712712
required: true
713-
description: The unique identifier for the passcode.
713+
description: The unique identifier for the TOTP.
714714
responses:
715715
"204":
716716
description: Success
@@ -720,15 +720,15 @@ paths:
720720
$ref: "#/components/responses/UnauthorizedError"
721721
/totp/{uuid}/validate:
722722
delete:
723-
summary: Validate a passcode (TOTP)
723+
summary: Validate a TOTP
724724
parameters:
725725
- in: path
726726
name: uuid
727727
schema:
728728
type: string
729729
format: uuid
730730
required: true
731-
description: The unique identifier for the passcode.
731+
description: The unique identifier for the TOTP.
732732
requestBody:
733733
content:
734734
application/json:

terraform/README.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,3 @@ Terraform is responsible to:
1010
- Create IAM user for CDK to manage AWS resources
1111
- Create IAM role for Lambda function to assume and run as
1212
- Create DynamoDB tables
13-
14-
### Note about DynamoDB tables
15-
This repo is coded in a way to create the necessary tables and use the default names based on `app_name` and
16-
`app_env`. However, if this is being deployed into an environment with existing tables, the table names can be
17-
overwritten using the `api_key_table`, `totp_table`, and `webauthn_table` variables, as well as the
18-
`create_api_key_table`, `create_totp_key_table`, and `create_webauthn_table` variables set to `false`.

terraform/main.tf

Lines changed: 71 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,37 @@
11
locals {
22
stage_for_api = var.app_env == "dev" ? var.app_env : var.app_environment
33
api_name = "${var.app_name}-${local.stage_for_api}"
4+
aws_account = data.aws_caller_identity.this.account_id
45
}
56

6-
/*
7-
* Module docs: https://registry.terraform.io/modules/silinternational/serverless-user/aws/latest
8-
* Create IAM user with permissions to create lambda function, API gateway, etc.
9-
*/
10-
module "serverless-user" {
11-
source = "silinternational/serverless-user/aws"
12-
version = "~> 0.4.2"
13-
14-
app_name = "${var.app_name}-${var.app_env}"
15-
aws_region_policy = "*"
16-
enable_api_gateway = true
17-
extra_policies = [jsonencode({
7+
data "aws_caller_identity" "this" {}
8+
9+
# CDK IAM user
10+
resource "aws_iam_user" "cdk" {
11+
name = "${var.app_name}-cdk"
12+
}
13+
14+
resource "aws_iam_access_key" "cdk" {
15+
user = aws_iam_user.cdk.name
16+
}
17+
18+
resource "aws_iam_policy" "cdk" {
19+
name = "${var.app_name}-cdk"
20+
description = "CDK deployment policy"
21+
22+
policy = jsonencode({
1823
Version = "2012-10-17"
19-
Statement = [
20-
{
21-
Effect = "Allow"
22-
Action = [
23-
"sts:AssumeRole",
24-
]
25-
Resource = "arn:aws:iam::*:role/cdk-*"
26-
},
27-
{
28-
Effect = "Allow"
29-
Action = [
30-
"ec2:CreateTags",
31-
"ec2:DeleteTags",
32-
"iam:getRolePolicy",
33-
"logs:FilterLogEvents",
34-
"apigateway:UpdateRestApiPolicy",
35-
]
36-
Resource = "*"
37-
}
38-
]
39-
})]
24+
Statement = [{
25+
Effect = "Allow"
26+
Action = "sts:AssumeRole"
27+
Resource = "arn:aws:iam::*:role/cdk-*"
28+
}]
29+
})
30+
}
31+
32+
resource "aws_iam_user_policy_attachment" "cdk" {
33+
user = aws_iam_user.cdk.name
34+
policy_arn = aws_iam_policy.cdk.arn
4035
}
4136

4237
// Set up custom domain name for easier fail-over.
@@ -73,23 +68,17 @@ resource "aws_iam_role" "lambdaRole" {
7368
})
7469
}
7570

76-
locals {
77-
api_key_table = try(var.api_key_table, one(aws_dynamodb_table.apiKeyTable[*].name))
78-
totp_table = try(var.totp_table, one(aws_dynamodb_table.totp[*].name))
79-
webauthn_table = try(var.webauthn_table, one(aws_dynamodb_table.webauthnTable[*].name))
80-
}
81-
8271
data "template_file" "lambdaRolePolicy" {
8372
template = file("${path.module}/lambda-role-policy.json")
8473
vars = {
85-
aws_account = var.aws_account_id
74+
aws_account = local.aws_account
8675
app_name = var.app_name
8776
app_env = var.app_env
88-
table_arns = join(",", compact([
89-
local.api_key_table == null ? null : "\"arn:aws:dynamodb:*:${var.aws_account_id}:table/${local.api_key_table}\"",
90-
local.webauthn_table == null ? null : "\"arn:aws:dynamodb:*:${var.aws_account_id}:table/${local.webauthn_table}\"",
91-
local.totp_table == null ? null : "\"arn:aws:dynamodb:*:${var.aws_account_id}:table/${local.totp_table}\"",
92-
]))
77+
table_arns = join(",", [
78+
aws_dynamodb_table.api_key.arn,
79+
aws_dynamodb_table.totp.arn,
80+
aws_dynamodb_table.webauthn.arn,
81+
])
9382
}
9483
}
9584

@@ -99,30 +88,37 @@ resource "aws_iam_role_policy" "lambdaRolePolicy" {
9988
policy = data.template_file.lambdaRolePolicy.rendered
10089
}
10190

102-
// Create DynamoDB tables
103-
resource "aws_dynamodb_table" "apiKeyTable" {
104-
count = var.create_api_key_table ? 1 : 0
105-
name = "${var.app_name}-${var.app_env}-api-key"
106-
billing_mode = "PAY_PER_REQUEST"
107-
hash_key = "value"
91+
// DynamoDB tables
92+
resource "aws_dynamodb_table" "api_key" {
93+
name = "mfa-api_${var.app_env}_api-key_global"
94+
billing_mode = "PAY_PER_REQUEST"
95+
hash_key = "value"
96+
deletion_protection_enabled = true
97+
stream_enabled = true
98+
stream_view_type = "NEW_IMAGE"
10899

109100
attribute {
110101
name = "value"
111102
type = "S"
112103
}
113104

114-
tags = {
115-
app_name = var.app_name
116-
app_env = var.app_env
105+
point_in_time_recovery {
106+
enabled = true
107+
}
108+
109+
replica {
110+
region_name = var.aws_region_secondary
111+
}
112+
113+
lifecycle {
114+
ignore_changes = [replica]
117115
}
118116
}
119117

120118
resource "aws_dynamodb_table" "totp" {
121-
count = var.create_totp_table ? 1 : 0
122-
123-
name = "${var.app_name}_${var.app_env}_totp_global"
124-
hash_key = "uuid"
119+
name = "mfa-api_${var.app_env}_totp_global"
125120
billing_mode = "PAY_PER_REQUEST"
121+
hash_key = "uuid"
126122
deletion_protection_enabled = true
127123
stream_enabled = true
128124
stream_view_type = "NEW_IMAGE"
@@ -145,19 +141,28 @@ resource "aws_dynamodb_table" "totp" {
145141
}
146142
}
147143

148-
resource "aws_dynamodb_table" "webauthnTable" {
149-
count = var.create_webauthn_table ? 1 : 0
150-
name = "${var.app_name}-${var.app_env}-webauthn"
151-
billing_mode = "PAY_PER_REQUEST"
152-
hash_key = "uuid"
144+
resource "aws_dynamodb_table" "webauthn" {
145+
name = "mfa-api_${var.app_env}_u2f_global"
146+
hash_key = "uuid"
147+
billing_mode = "PAY_PER_REQUEST"
148+
deletion_protection_enabled = true
149+
stream_enabled = true
150+
stream_view_type = "NEW_IMAGE"
153151

154152
attribute {
155153
name = "uuid"
156154
type = "S"
157155
}
158156

159-
tags = {
160-
app_name = var.app_name
161-
app_env = var.app_env
157+
point_in_time_recovery {
158+
enabled = true
159+
}
160+
161+
replica {
162+
region_name = var.aws_region_secondary
163+
}
164+
165+
lifecycle {
166+
ignore_changes = [replica]
162167
}
163168
}

terraform/outputs.tf

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
output "serverless_user_key_secret" {
2-
value = "${module.serverless-user.aws_access_key_id},${module.serverless-user.aws_secret_access_key}"
3-
sensitive = true
4-
}
5-
61
output "lambda_role_arn" {
72
value = aws_iam_role.lambdaRole.arn
83
}

terraform/variables.tf

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
variable "app_name" {
22
type = string
33
description = "A short name for this application, example: backup-service"
4-
default = "serverless-mfa-api-go"
4+
default = "twosv-api"
55
}
66

77
variable "app_env" {
@@ -14,11 +14,6 @@ variable "aws_access_key_id" {
1414
description = "Access Key ID for user with permissions to create resources for CDK"
1515
}
1616

17-
variable "aws_account_id" {
18-
type = string
19-
description = "AWS Account ID for use in IAM policy resource references"
20-
}
21-
2217
variable "aws_region" {
2318
description = "Primary AWS region where this lambda will be deployed"
2419
type = string
@@ -34,40 +29,6 @@ variable "aws_secret_access_key" {
3429
description = "Secret access Key ID for user with permissions to create resources for CDK"
3530
}
3631

37-
variable "api_key_table" {
38-
type = string
39-
description = "Override api key table name"
40-
default = ""
41-
}
42-
43-
variable "create_api_key_table" {
44-
type = bool
45-
default = true
46-
}
47-
48-
variable "totp_table" {
49-
description = "Override totp table name"
50-
type = string
51-
default = null
52-
}
53-
54-
variable "create_totp_table" {
55-
description = "enable the creation of a DynamoDB table for TOTP credentials"
56-
type = bool
57-
default = false
58-
}
59-
60-
variable "webauthn_table" {
61-
type = string
62-
description = "Override webauthn table name"
63-
default = ""
64-
}
65-
66-
variable "create_webauthn_table" {
67-
type = bool
68-
default = true
69-
}
70-
7132
variable "cloudflare_token" {
7233
description = "The Cloudflare limited access API token"
7334
type = string

0 commit comments

Comments
 (0)