silinternational / serverless-mfa-api

A Serverless API for registering and validating Multi-Factor Authentication methods. Currently supports Time-based One Time Passwords (TOTP) and FIDO U2F devices (YubiKeys).
MIT License
12 stars 8 forks source link

Serverless MFA API

A Serverless API for registering and validating Multi-Factor Authentication methods.

Currently supports Time-based One Time Passwords (TOTP) and FIDO U2F devices (YubiKeys).

For details about the various API endpoints, see the RAML file.

Basic Workflow

TOTP

U2F Registration

U2F Authentication

Notes about FIDO U2F

Automated Backups

While DynamoDB supports On Demand backups as well as Continuous Backups with Point-in-time Recovery (PITR), both of these methods restore to a new table rather than restoring to the existing DynamoDB table. While turning on Point-in-time Recovery is certainly not a bad idea, we have ended up using an alternate approach to make restores easier.

The shevchenkos/DynamoDbBackUp software sets up Lambda functions that are triggered each time the associated DynamoDB table is changed, and it backs up the records to an S3 bucket. We used it to set up automated backups for each of the DynamoDB tables used by this repo. We also forked it (to https://github.com/silinternational/DynamoDbBackUp) in case the original "shevchenkos/DynamoDbBackUp" repo is ever deleted, but if the original repo is available use it, as it will more likely be up-to-date.

For the shevchenkos/DynamoDbBackUp software to be able to make the necessary changes in your AWS account, you will need to set up an IAM user with an Access Key and Secret and with a policy similar to the following. Note that you will need to replace YOUR IP ADDRESS BLOCK CIDR with a real value (for the IP address or range of addresses from which you want the following commands to allowed). You may also want to narrow down the breadth of permissions granted here, further restrict statements by IP CIDR, restrict S3 paths, etc.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "iam:CreateRole",
            "Resource": "arn:aws:iam::*:role/*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "iam:*",
            "Resource": "arn:aws:iam::*:role/LambdaBackupDynamoDBToS3",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": "YOUR IP ADDRESS BLOCK CIDR"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "cloudformation:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "iam:AttachRolePolicy",
                "iam:CreatePolicy",
                "iam:CreateRole",
                "iam:DeleteRole",
                "iam:DeleteRolePolicy",
                "iam:GetRole",
                "iam:PassRole",
                "iam:PutRolePolicy"
            ],
            "Resource": [
                "arn:aws:iam:::*",
                "arn:aws:iam:::role/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "lambda:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:*"
            ],
            "Resource": [
                "arn:aws:logs:*::log-group:*:log-stream:"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::*",
                "arn:aws:s3:::*/*"
            ]
        }
    ]
}

Once you have the shevchenkos/DynamoDbBackUp software set up on your computer, you can use commands like the following to set up your automated backups.

Create the S3 bucket:

gulp deploy-s3-bucket \
    --s3bucket yourorg.backups.dynamodb.mfa-api \
    --s3region us-east-1

(Note: Some of these commands, including the one above, had to be run twice to get past an apparent race condition.)

If actually setting up these backups for your use of this library, you will need to run the following commands once for each of the production tables (currently mfa-api_prod_api-key, mfa-api_prod_totp, and mfa-api_prod_u2f)

Set up the Lambda function for backing up changes:

gulp deploy-lambda \
    --s3bucket yourorg.backups.dynamodb.mfa-api \
    --s3prefix mfa-api_prod_api-key \
    --s3region us-east-1 \
    --dbregion us-east-1 \
    --lName backup_dynamodb_mfa-api_prod_api-key \
    --lRegion us-east-1 \
    --lAlias active \
    --lRoleName LambdaBackupDynamoDBToS3 \
    --lTimeout 60

Set up the event to trigger the Lambda function when a specific DynamoDB table is changed:

gulp deploy-lambda-event \
    --dbtable mfa-api_prod_api-key \
    --dbregion us-east-1 \
    --lName backup_dynamodb_mfa-api_prod_api-key \
    --lRegion us-east-1 \
    --lAlias active

Do an initial full backup:

gulp backup-full \
    --s3bucket yourorg.backups.dynamodb.mfa-api \
    --s3prefix mfa-api_prod_api-key \
    --s3region us-east-1 \
    --dbtable mfa-api_prod_api-key \
    --dbregion us-east-1

If you want to do a restore to a specific point in time (in this example, Thu, 25 Jan 2018 22:10:00 GMT), you would run the following:

gulp restore \
    --s3bucket yourorg.backups.dynamodb.mfa-api \
    --s3prefix mfa-api_prod_totp \
    --s3region us-east-1 \
    --dbtable mfa-api_prod_totp \
    --dbregion us-east-1 \
    --restoretime 1516918200000

(Note: The restore time is a JavaScript timestamp, in milliseconds.)

Running locally

To run this locally (such as for development)...

  1. Open a terminal to THIS repo's root folder and run the following:
    • make dynamodb-tables
      • NOTE: You may need to run this twice. If it gives an error message, trying again should work. I think it's a timing issue, where it tries to create the dynamodb tables before the local dynamodb is actually up enough to be ready for interaction.
    • make dev-server
  2. Add and activate api-key entry for yourself in your local serverless-mfa-api:
    • Submit a POST to https://localhost:8080/prod/api-key with a JSON body like the following:
      { "email": "local@example.com" }

      It should return a 204 No Content response.

    • Run make list-dev-api-keys, and copy the "value" parameter's value.
    • Do a POST to https://localhost:8080/prod/api-key/activate, with a JSON body like the following:
      {
      "email": "local@example.com",
      "apiKey": "the-value-parameter-from-the-dynamo-db-table"
      }

      It should return a 200 OK with a JSON body containing an apiSecret that you will need. When copying that value, make sure you include any trailing equals signs (=).

  3. Clone the https://github.com/silinternational/idp-in-a-box repo.
  4. Put the apiSecret returned (including any trailing = signs) and the apiKey value you used in the JSON body into your local idp-in-a-box code's /docker-compose/broker/local.env file, both for the MFA_TOTP_* and MFA_U2F_* environment variables, something like this (but using YOUR values for the apiKey and apiSecret entries, not these dummy/sample values):

    MFA_TOTP_apiBaseUrl=http://localhost:8080/
    MFA_TOTP_apiKey=347a15dc60f014bdd93e4fc59aab607b022c8e19
    MFA_TOTP_apiSecret=za3c5Op8XgQcWNK16Rg6Th3ndmJ2ZTGL4uEldAJxDes=
    
    MFA_U2F_apiBaseUrl=http://localhost:8080/
    MFA_U2F_apiKey=347a15dc60f014bdd93e4fc59aab607b022c8e19
    MFA_U2F_apiSecret=za3c5Op8XgQcWNK16Rg6Th3ndmJ2ZTGL4uEldAJxDes= 
  5. Bring up the idp-in-a-box repo. See that repo's README.md for instructions.

Serverless

To start a local container for development of Serverless configuration:

docker compose run --rm dev bash

Credential Rotation

AWS Serverless User

  1. Use the Terraform CLI to taint the old access key
    terraform taint module.serverless-user.aws_iam_access_key.serverless
  2. Run a new plan on Terraform Cloud
  3. Review the new plan and apply if it is correct
  4. Copy the new key and secret from the Terraform output into GitHub Repository Secrets

Glossary