From f0bfebbf9c92a97e5215d96053acced81ddf65ab Mon Sep 17 00:00:00 2001 From: Chris Gilmer Date: Tue, 23 Jul 2019 11:17:03 -0700 Subject: [PATCH] First commit --- .circleci/config.yml | 23 ++++++ .gitignore | 4 + .markdownlintrc | 7 ++ .pre-commit-config.yaml | 24 ++++++ LICENSE | 29 +++++++ README.md | 28 +++++++ main.tf | 165 ++++++++++++++++++++++++++++++++++++++++ outputs.tf | 1 + variables.tf | 11 +++ 9 files changed, 292 insertions(+) create mode 100644 .circleci/config.yml create mode 100644 .gitignore create mode 100644 .markdownlintrc create mode 100644 .pre-commit-config.yaml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 main.tf create mode 100644 outputs.tf create mode 100644 variables.tf diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..73c8a10 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,23 @@ +version: 2 +jobs: + validate: + docker: + - image: trussworks/circleci-docker-primary:a18ba9987556eec2e48354848a3c9fb4d5b69ac8 + steps: + - checkout + - restore_cache: + keys: + - pre-commit-dot-cache-{{ checksum ".pre-commit-config.yaml" }} + - run: + name: Run pre-commit tests + command: pre-commit run --all-files + - save_cache: + key: pre-commit-dot-cache-{{ checksum ".pre-commit-config.yaml" }} + paths: + - ~/.cache/pre-commit + +workflows: + version: 2 + validate: + jobs: + - validate diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fa2920 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.terraform +terraform.tfstate +*.tfstate* +terraform.tfvars diff --git a/.markdownlintrc b/.markdownlintrc new file mode 100644 index 0000000..c5f2ce9 --- /dev/null +++ b/.markdownlintrc @@ -0,0 +1,7 @@ +{ + "default": true, + "first-header-h1": false, + "first-line-h1": false, + "line_length": false, + "no-multiple-blanks": false +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..8d31e32 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,24 @@ +repos: + - repo: git://github.com/pre-commit/pre-commit-hooks + rev: v2.2.3 + hooks: + - id: check-json + - id: check-merge-conflict + - id: check-yaml + - id: detect-private-key + - id: pretty-format-json + args: + - --autofix + - id: trailing-whitespace + + - repo: git://github.com/igorshubovych/markdownlint-cli + rev: v0.17.0 + hooks: + - id: markdownlint + + - repo: git://github.com/antonbabenko/pre-commit-terraform + rev: v1.12.0 + hooks: + - id: terraform_docs + - id: terraform_fmt + - id: terraform_validate_no_variables diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..efccbad --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2019, TrussWorks, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d30c3ca --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ + +Configures IAM policy to enforce MFA when accessing the AWS API. + +Creates the following resources: + +* IAM policy requiring a valid MFA security token for all API calls except those needed for managing a user's own IAM user. +* IAM group policy attachment for defining which IAM groups to enforce MFA on. +* IAM user policy attachment for defining which IAM users to enforce MFA on. + +## Usage + +```hcl +module "aws_mfa" { + source = "trussworks/logs/mfa" + + iam_groups = ["engineers"] + iam_users = ["joe"] +} +``` + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| iam\_groups | List of IAM groups to enforce MFA when accessing the AWS API. | list | `[]` | no | +| iam\_users | List of IAM users to enforce MFA when accessing the AWS API. | list | `[]` | no | + + diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..9924552 --- /dev/null +++ b/main.tf @@ -0,0 +1,165 @@ +/** + * Configures IAM policy to enforce MFA when accessing the AWS API. + * + * Creates the following resources: + * + * * IAM policy requiring a valid MFA security token for all API calls except those needed for managing a user's own IAM user. + * * IAM group policy attachment for defining which IAM groups to enforce MFA on. + * * IAM user policy attachment for defining which IAM users to enforce MFA on. + * + * ## Usage + * + * ```hcl + * module "aws_mfa" { + * source = "trussworks/logs/mfa" + * + * iam_groups = ["engineers"] + * iam_users = ["joe"] + * } + * ``` + */ + +data "aws_iam_policy_document" "main" { + statement { + sid = "AllowAllUsersToListAccounts" + effect = "Allow" + + actions = [ + "iam:ListAccountAliases", + "iam:ListUsers", + "iam:ListVirtualMFADevices", + "iam:GetAccountPasswordPolicy", + "iam:GetAccountSummary", + ] + + resources = [ + "*", + ] + } + + statement { + sid = "AllowIndividualUserToSeeAndManageOnlyTheirOwnAccountInformation" + effect = "Allow" + + actions = [ + "iam:CreateAccessKey", + "iam:DeleteAccessKey", + "iam:DeleteLoginProfile", + "iam:GetLoginProfile", + "iam:ListAccessKeys", + "iam:UpdateAccessKey", + "iam:ListSigningCertificates", + "iam:DeleteSigningCertificate", + "iam:UpdateSigningCertificate", + "iam:UploadSigningCertificate", + ] + + resources = [ + "arn:aws:iam::*:user/&{aws:username}", + ] + } + + statement { + sid = "AllowIndividualUserToListOnlyTheirOwnMFA" + effect = "Allow" + + actions = [ + "iam:ListMFADevices", + ] + + resources = [ + "arn:aws:iam::*:mfa/*", + "arn:aws:iam::*:user/&{aws:username}", + ] + } + + statement { + sid = "AllowIndividualUserToManageTheirOwnMFA" + effect = "Allow" + + actions = [ + "iam:CreateVirtualMFADevice", + "iam:DeleteVirtualMFADevice", + "iam:EnableMFADevice", + "iam:ResyncMFADevice", + ] + + resources = [ + "arn:aws:iam::*:mfa/&{aws:username}", + "arn:aws:iam::*:user/&{aws:username}", + ] + } + + statement { + sid = "AllowIndividualUserToDeactivateOnlyTheirOwnMFAOnlyWhenUsingMFA" + effect = "Allow" + + actions = [ + "iam:DeactivateMFADevice", + ] + + resources = [ + "arn:aws:iam::*:mfa/&{aws:username}", + "arn:aws:iam::*:user/&{aws:username}", + ] + + condition { + test = "Bool" + variable = "aws:MultiFactorAuthPresent" + values = ["true"] + } + } + + statement { + sid = "BlockMostAccessUnlessSignedInWithMFA" + effect = "Deny" + + not_actions = [ + "iam:ChangePassword", + "iam:CreateLoginProfile", + "iam:CreateVirtualMFADevice", + "iam:DeleteVirtualMFADevice", + "iam:ListVirtualMFADevices", + "iam:EnableMFADevice", + "iam:ResyncMFADevice", + "iam:ListAccountAliases", + "iam:ListUsers", + "iam:ListSSHPublicKeys", + "iam:ListAccessKeys", + "iam:ListServiceSpecificCredentials", + "iam:ListMFADevices", + "iam:GetAccountSummary", + "sts:GetSessionToken", + ] + + resources = [ + "*", + ] + + condition { + test = "BoolIfExists" + variable = "aws:MultiFactorAuthPresent" + values = ["false"] + } + } +} + +resource "aws_iam_policy" "main" { + #Use alphanumeric and '+=,.@-_' characters. Maximum 128 characters. + name = "enforce-mfa" + path = "/" + description = "Requires valid MFA security token for all API calls except those needed for managing a user's own IAM user." + policy = "${data.aws_iam_policy_document.main.json}" +} + +resource "aws_iam_group_policy_attachment" "main" { + count = "${length(var.iam_groups)}" + group = "${element(var.iam_groups, count.index)}" + policy_arn = "${aws_iam_policy.main.arn}" +} + +resource "aws_iam_user_policy_attachment" "main" { + count = "${length(var.iam_users)}" + user = "${element(var.iam_users, count.index)}" + policy_arn = "${aws_iam_policy.main.arn}" +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/outputs.tf @@ -0,0 +1 @@ + diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..7d43c57 --- /dev/null +++ b/variables.tf @@ -0,0 +1,11 @@ +variable "iam_groups" { + description = "List of IAM groups to enforce MFA when accessing the AWS API." + type = "list" + default = [] +} + +variable "iam_users" { + description = "List of IAM users to enforce MFA when accessing the AWS API." + type = "list" + default = [] +}