├── .cfnlintrc ├── .coveragerc ├── .env ├── .flake8 ├── .gitignore ├── .python-version ├── .vscode ├── launch.json └── settings.json ├── .yamllint.yaml ├── Makefile ├── Pipfile ├── Pipfile.lock ├── README.md ├── default-sg-remediation.sam.yaml ├── media ├── WritingAwsLambdasInPython.xml ├── debugger.png ├── example_flow.png ├── exception_flow.png ├── github.png ├── reactive_flow.png ├── test_run.png └── versent_logo.png ├── src ├── __init__.py ├── requirements.txt └── revokedefaultsg │ ├── __init__.py │ └── app.py └── tests ├── resources └── incoming_event.json └── unit └── test_revoke_default_sg.py /.cfnlintrc: -------------------------------------------------------------------------------- 1 | --- 2 | templates: 3 | - ./**/*.sam.yaml 4 | ignore_checks: 5 | - W3002 6 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | sort = Cover 3 | 4 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PYTHONPATH=./src -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E501,E402,E127 3 | exclude = 4 | .aws-sam 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | __pycache__ 3 | .coverage 4 | pytestdebug.log 5 | .aws-sam/ -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.7.8 2 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python: Run Current File", 6 | "type": "python", 7 | "request": "launch", 8 | "program": "${file}", 9 | "console": "integratedTerminal", 10 | "env": { 11 | "PYTHONPATH": "./src" 12 | } 13 | }, 14 | { 15 | "name": "Python: pytest", 16 | "type": "python", 17 | "request": "launch", 18 | "module": "pytest", 19 | "cwd": "${workspaceRoot}", 20 | "env": { 21 | "PYTHONPATH": "./src" 22 | }, 23 | "envFile": "${workspaceRoot}/.env", 24 | "console": "integratedTerminal", 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.organizeImports": true 4 | }, 5 | "python.envFile": "${workspaceFolder}/.env", 6 | "python.linting.enabled": true, 7 | "python.linting.flake8Enabled": true, 8 | "python.linting.flake8Args": ["--ignore=E501"], 9 | "python.linting.pylintEnabled": false, 10 | "python.terminal.activateEnvironment": true, 11 | "python.testing.pytestEnabled": true, 12 | "python.testing.pytestArgs": [ 13 | "tests" 14 | ], 15 | "python.testing.unittestEnabled": false, 16 | "python.testing.nosetestsEnabled": false 17 | } -------------------------------------------------------------------------------- /.yamllint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | line-length: 6 | max: 150 7 | level: warning 8 | 9 | ignore: | 10 | .aws-sam 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help install-dependencies create-artifact-bucket check test deploy 2 | 3 | INPUT_TEMPLATE_FILE := default-sg-remediation.sam.yaml 4 | OUTPUT_TEMPLATE_FILE := .aws-sam/default-sg-remediation-output.yaml 5 | ARTIFACT_BUCKET := default-sg-remediation-artifacts 6 | STACK_NAME := default-sg-remediation 7 | SOURCE_FILES := $(shell find . -type f -path './src/*') 8 | MANIFEST_FILE := ./src/requirements.txt 9 | 10 | help: ## This help 11 | @grep -E -h "^[a-zA-Z_-]+:.*?## " $(MAKEFILE_LIST) \ 12 | | sort \ 13 | | awk -v width=36 'BEGIN {FS = ":.*?## "} {printf "\033[36m%-*s\033[0m %s\n", width, $$1, $$2}' 14 | 15 | install-dependencies: ## Install pipenv and dependencies 16 | @echo '*** installing dependencies ***' 17 | pip3 install pipenv 18 | pipenv install --dev 19 | @echo '*** dependencies installed ***' 20 | 21 | create-artifact-bucket: ## Create bucket to upload stack to 22 | aws s3 mb s3://${ARTIFACT_BUCKET} 23 | 24 | check: ## Run linters 25 | @echo '*** running checks ***' 26 | flake8 27 | yamllint -f parsable . 28 | cfn-lint -f parseable 29 | @echo '*** all checks passing ***' 30 | 31 | test: check ## Run tests 32 | @echo '*** running tests ***' 33 | PYTHONPATH=./src pytest --cov=src --cov-branch --cov-report term-missing 34 | @echo '*** all tests passing ***' 35 | 36 | .aws-sam/build/template.yaml: $(INPUT_TEMPLATE_FILE) $(SOURCE_FILES) ## sam-build target and dependencies 37 | @echo '*** running SAM build ***' 38 | SAM_CLI_TELEMETRY=0 \ 39 | sam build \ 40 | --template-file $(INPUT_TEMPLATE_FILE) \ 41 | --manifest $(MANIFEST_FILE) \ 42 | --debug 43 | @echo '*** done SAM building ***' 44 | 45 | $(OUTPUT_TEMPLATE_FILE): $(INPUT_TEMPLATE) .aws-sam/build/template.yaml 46 | @echo '*** running SAM package ***' 47 | SAM_CLI_TELEMETRY=0 \ 48 | sam package \ 49 | --s3-bucket $(ARTIFACT_BUCKET) \ 50 | --output-template-file "$(OUTPUT_TEMPLATE_FILE)" \ 51 | --debug 52 | @echo '*** done SAM packaging ***' 53 | 54 | deploy: test $(OUTPUT_TEMPLATE_FILE) ## Deploy stack to AWS 55 | @echo '*** running SAM deploy ***' 56 | SAM_CLI_TELEMETRY=0 \ 57 | sam deploy \ 58 | --template-file $(OUTPUT_TEMPLATE_FILE) \ 59 | --stack-name $(STACK_NAME) \ 60 | --s3-bucket $(ARTIFACT_BUCKET) \ 61 | --capabilities "CAPABILITY_IAM" \ 62 | --no-fail-on-empty-changeset 63 | @echo '*** done SAM deploying ***' -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [packages] 7 | aws-sam-cli = "*" 8 | 9 | [dev-packages] 10 | cfn-lint = "*" 11 | flake8 = "*" 12 | pytest = "*" 13 | pytest-cov = "*" 14 | yamllint = "*" 15 | 16 | [requires] 17 | python_version = "3.7" 18 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "0dee3aaf82c88537aa81d107eebf2eb358249267c348c2b37bd355f02ffe9936" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "arrow": { 20 | "hashes": [ 21 | "sha256:a24c1de90850f6fb2033fd6bf8a11f281e84cb54825e5eabdda219e673b52aac", 22 | "sha256:eb5d339f00072cc297d7de252a2e75f272085d1231a3723f1026d1fa91367118" 23 | ], 24 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 25 | "version": "==0.15.6" 26 | }, 27 | "attrs": { 28 | "hashes": [ 29 | "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", 30 | "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" 31 | ], 32 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 33 | "version": "==19.3.0" 34 | }, 35 | "aws-lambda-builders": { 36 | "hashes": [ 37 | "sha256:036cabf8aae2482aca4b37e4f6c36a2dd1df65b05eed6de605410c30ca3e4a63", 38 | "sha256:660028424c8ce4677debbec33f56e37498f71170da80cc91fa322d1b2071bb0a", 39 | "sha256:f4c5c7a14652a6f6256cb0254e45ae5aa413dd4b62b9854cd5a3f01c4fe9c889" 40 | ], 41 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 42 | "version": "==0.9.0" 43 | }, 44 | "aws-sam-cli": { 45 | "hashes": [ 46 | "sha256:57e954e9dced9c53e69f92e6f6dbae26845c9b5f211cee0bee5b0997cf6b23cf", 47 | "sha256:5b93a34ce4f8fc2fb22f9571acfb49df8a5889d8ed575c2ddf555292e67b65d8" 48 | ], 49 | "index": "pypi", 50 | "version": "==0.52.0" 51 | }, 52 | "aws-sam-translator": { 53 | "hashes": [ 54 | "sha256:9259944109f3a99c1bb6b2f0bbf486b31c0ce1618b67e4b29e17c16f3d2f0c71", 55 | "sha256:a8df6a828c3b27c4c81cfe66dc08e14333a7d06f7a82ae502a72892e1f32edac", 56 | "sha256:f6b67545a87ec1e276bd5bf06abcc84332c4eb9dfa2fd415113e07a908fe55bb" 57 | ], 58 | "version": "==1.24.0" 59 | }, 60 | "binaryornot": { 61 | "hashes": [ 62 | "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061", 63 | "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4" 64 | ], 65 | "version": "==0.4.4" 66 | }, 67 | "boto3": { 68 | "hashes": [ 69 | "sha256:2e58368d32171f42100353007fb48fe583ea9deafd0ec556aa2bc1ce1582d06d", 70 | "sha256:c774003dc13d6de74b5e19d2b84d625da4456e64bd97f44baa1fcf40d808d29a" 71 | ], 72 | "version": "==1.13.19" 73 | }, 74 | "botocore": { 75 | "hashes": [ 76 | "sha256:5cb537e7a4cf2d59a2a8dfbbc8e14ec3bc5b640eb81a1bf3bb0523c0a75e6b1b", 77 | "sha256:7b8b1f082665c8670b9aa70143ee527c5d04939fe027a63ac5958359be20ccb0" 78 | ], 79 | "version": "==1.16.19" 80 | }, 81 | "certifi": { 82 | "hashes": [ 83 | "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", 84 | "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" 85 | ], 86 | "version": "==2020.4.5.1" 87 | }, 88 | "chardet": { 89 | "hashes": [ 90 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 91 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 92 | ], 93 | "version": "==3.0.4" 94 | }, 95 | "chevron": { 96 | "hashes": [ 97 | "sha256:95b0a055ef0ada5eb061d60be64a7f70670b53372ccd221d1b88adf1c41a9094", 98 | "sha256:f95054a8b303268ebf3efd6bdfc8c1b428d3fc92327913b4e236d062ec61c989" 99 | ], 100 | "version": "==0.13.1" 101 | }, 102 | "click": { 103 | "hashes": [ 104 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 105 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 106 | ], 107 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 108 | "version": "==7.1.2" 109 | }, 110 | "cookiecutter": { 111 | "hashes": [ 112 | "sha256:1316a52e1c1f08db0c9efbf7d876dbc01463a74b155a0d83e722be88beda9a3e", 113 | "sha256:ed8f54a8fc79b6864020d773ce11539b5f08e4617f353de1f22d23226f6a0d36" 114 | ], 115 | "version": "==1.6.0" 116 | }, 117 | "dateparser": { 118 | "hashes": [ 119 | "sha256:1b1f0e3034f82d1f92b45fa445826da6a36d67af8a1169e04869685594276011", 120 | "sha256:fb5bfde4795fa4b179fe05c2c25b3981f785de26bec37e247dee1079c63d5689" 121 | ], 122 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 123 | "version": "==0.7.4" 124 | }, 125 | "docker": { 126 | "hashes": [ 127 | "sha256:1c2ddb7a047b2599d1faec00889561316c674f7099427b9c51e8cb804114b553", 128 | "sha256:ddae66620ab5f4bce769f64bcd7934f880c8abe6aa50986298db56735d0f722e" 129 | ], 130 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 131 | "version": "==4.2.0" 132 | }, 133 | "docutils": { 134 | "hashes": [ 135 | "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", 136 | "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", 137 | "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" 138 | ], 139 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 140 | "version": "==0.15.2" 141 | }, 142 | "flask": { 143 | "hashes": [ 144 | "sha256:1a21ccca71cee5e55b6a367cc48c6eb47e3c447f76e64d41f3f3f931c17e7c96", 145 | "sha256:ed1330220a321138de53ec7c534c3d90cf2f7af938c7880fc3da13aa46bf870f" 146 | ], 147 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 148 | "version": "==1.0.4" 149 | }, 150 | "future": { 151 | "hashes": [ 152 | "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" 153 | ], 154 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 155 | "version": "==0.18.2" 156 | }, 157 | "idna": { 158 | "hashes": [ 159 | "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", 160 | "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" 161 | ], 162 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 163 | "version": "==2.9" 164 | }, 165 | "importlib-metadata": { 166 | "hashes": [ 167 | "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", 168 | "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" 169 | ], 170 | "markers": "python_version < '3.8'", 171 | "version": "==1.6.0" 172 | }, 173 | "itsdangerous": { 174 | "hashes": [ 175 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", 176 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" 177 | ], 178 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 179 | "version": "==1.1.0" 180 | }, 181 | "jinja2": { 182 | "hashes": [ 183 | "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", 184 | "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" 185 | ], 186 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 187 | "version": "==2.11.2" 188 | }, 189 | "jinja2-time": { 190 | "hashes": [ 191 | "sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40", 192 | "sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa" 193 | ], 194 | "version": "==0.2.0" 195 | }, 196 | "jmespath": { 197 | "hashes": [ 198 | "sha256:695cb76fa78a10663425d5b73ddc5714eb711157e52704d69be03b1a02ba4fec", 199 | "sha256:cca55c8d153173e21baa59983015ad0daf603f9cb799904ff057bfb8ff8dc2d9" 200 | ], 201 | "version": "==0.9.5" 202 | }, 203 | "jsonschema": { 204 | "hashes": [ 205 | "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", 206 | "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" 207 | ], 208 | "version": "==3.2.0" 209 | }, 210 | "markupsafe": { 211 | "hashes": [ 212 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 213 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 214 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 215 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 216 | "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", 217 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 218 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 219 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 220 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 221 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 222 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 223 | "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", 224 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 225 | "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", 226 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 227 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 228 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 229 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 230 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 231 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 232 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 233 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 234 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 235 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 236 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 237 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 238 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 239 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 240 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 241 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 242 | "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", 243 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", 244 | "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" 245 | ], 246 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 247 | "version": "==1.1.1" 248 | }, 249 | "poyo": { 250 | "hashes": [ 251 | "sha256:3e2ca8e33fdc3c411cd101ca395668395dd5dc7ac775b8e809e3def9f9fe041a", 252 | "sha256:e26956aa780c45f011ca9886f044590e2d8fd8b61db7b1c1cf4e0869f48ed4dd" 253 | ], 254 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 255 | "version": "==0.5.0" 256 | }, 257 | "pyrsistent": { 258 | "hashes": [ 259 | "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3" 260 | ], 261 | "version": "==0.16.0" 262 | }, 263 | "python-dateutil": { 264 | "hashes": [ 265 | "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", 266 | "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" 267 | ], 268 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 269 | "version": "==2.8.0" 270 | }, 271 | "pytz": { 272 | "hashes": [ 273 | "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", 274 | "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048" 275 | ], 276 | "version": "==2020.1" 277 | }, 278 | "pyyaml": { 279 | "hashes": [ 280 | "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", 281 | "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", 282 | "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", 283 | "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", 284 | "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", 285 | "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", 286 | "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", 287 | "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", 288 | "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", 289 | "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", 290 | "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" 291 | ], 292 | "version": "==5.3.1" 293 | }, 294 | "regex": { 295 | "hashes": [ 296 | "sha256:1386e75c9d1574f6aa2e4eb5355374c8e55f9aac97e224a8a5a6abded0f9c927", 297 | "sha256:27ff7325b297fb6e5ebb70d10437592433601c423f5acf86e5bc1ee2919b9561", 298 | "sha256:329ba35d711e3428db6b45a53b1b13a0a8ba07cbbcf10bbed291a7da45f106c3", 299 | "sha256:3a9394197664e35566242686d84dfd264c07b20f93514e2e09d3c2b3ffdf78fe", 300 | "sha256:51f17abbe973c7673a61863516bdc9c0ef467407a940f39501e786a07406699c", 301 | "sha256:579ea215c81d18da550b62ff97ee187b99f1b135fd894a13451e00986a080cad", 302 | "sha256:70c14743320a68c5dac7fc5a0f685be63bc2024b062fe2aaccc4acc3d01b14a1", 303 | "sha256:7e61be8a2900897803c293247ef87366d5df86bf701083b6c43119c7c6c99108", 304 | "sha256:8044d1c085d49673aadb3d7dc20ef5cb5b030c7a4fa253a593dda2eab3059929", 305 | "sha256:89d76ce33d3266173f5be80bd4efcbd5196cafc34100fdab814f9b228dee0fa4", 306 | "sha256:99568f00f7bf820c620f01721485cad230f3fb28f57d8fbf4a7967ec2e446994", 307 | "sha256:a7c37f048ec3920783abab99f8f4036561a174f1314302ccfa4e9ad31cb00eb4", 308 | "sha256:c2062c7d470751b648f1cacc3f54460aebfc261285f14bc6da49c6943bd48bdd", 309 | "sha256:c9bce6e006fbe771a02bda468ec40ffccbf954803b470a0345ad39c603402577", 310 | "sha256:ce367d21f33e23a84fb83a641b3834dd7dd8e9318ad8ff677fbfae5915a239f7", 311 | "sha256:ce450ffbfec93821ab1fea94779a8440e10cf63819be6e176eb1973a6017aff5", 312 | "sha256:ce5cc53aa9fbbf6712e92c7cf268274eaff30f6bd12a0754e8133d85a8fb0f5f", 313 | "sha256:d466967ac8e45244b9dfe302bbe5e3337f8dc4dec8d7d10f5e950d83b140d33a", 314 | "sha256:d881c2e657c51d89f02ae4c21d9adbef76b8325fe4d5cf0e9ad62f850f3a98fd", 315 | "sha256:e565569fc28e3ba3e475ec344d87ed3cd8ba2d575335359749298a0899fe122e", 316 | "sha256:ea55b80eb0d1c3f1d8d784264a6764f931e172480a2f1868f2536444c5f01e01" 317 | ], 318 | "version": "==2020.5.14" 319 | }, 320 | "requests": { 321 | "hashes": [ 322 | "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", 323 | "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" 324 | ], 325 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 326 | "version": "==2.23.0" 327 | }, 328 | "s3transfer": { 329 | "hashes": [ 330 | "sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13", 331 | "sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db" 332 | ], 333 | "version": "==0.3.3" 334 | }, 335 | "serverlessrepo": { 336 | "hashes": [ 337 | "sha256:0c340d0e4437b5043eed2f2aafcb8fd6b16ab3d62ace19e70186542f4f7ac0f5", 338 | "sha256:7b58bd86f4ef1d0189fdaee17b7a322c59ef5bbf5373a3d2ceaf440886e35236" 339 | ], 340 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 341 | "version": "==0.1.9" 342 | }, 343 | "six": { 344 | "hashes": [ 345 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 346 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 347 | ], 348 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 349 | "version": "==1.15.0" 350 | }, 351 | "tomlkit": { 352 | "hashes": [ 353 | "sha256:32c10cc16ded7e4101c79f269910658cc2a0be5913f1252121c3cd603051c269", 354 | "sha256:96e6369288571799a3052c1ef93b9de440e1ab751aa045f435b55e9d3bcd0690" 355 | ], 356 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 357 | "version": "==0.5.8" 358 | }, 359 | "tzlocal": { 360 | "hashes": [ 361 | "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44", 362 | "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4" 363 | ], 364 | "version": "==2.1" 365 | }, 366 | "urllib3": { 367 | "hashes": [ 368 | "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", 369 | "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" 370 | ], 371 | "markers": "python_version != '3.4'", 372 | "version": "==1.25.9" 373 | }, 374 | "websocket-client": { 375 | "hashes": [ 376 | "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549", 377 | "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010" 378 | ], 379 | "version": "==0.57.0" 380 | }, 381 | "werkzeug": { 382 | "hashes": [ 383 | "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", 384 | "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" 385 | ], 386 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 387 | "version": "==1.0.1" 388 | }, 389 | "wheel": { 390 | "hashes": [ 391 | "sha256:8788e9155fe14f54164c1b9eb0a319d98ef02c160725587ad60f14ddc57b6f96", 392 | "sha256:df277cb51e61359aba502208d680f90c0493adec6f0e848af94948778aed386e" 393 | ], 394 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 395 | "version": "==0.34.2" 396 | }, 397 | "whichcraft": { 398 | "hashes": [ 399 | "sha256:acdbb91b63d6a15efbd6430d1d7b2d36e44a71697e93e19b7ded477afd9fce87", 400 | "sha256:deda9266fbb22b8c64fd3ee45c050d61139cd87419765f588e37c8d23e236dd9" 401 | ], 402 | "version": "==0.6.1" 403 | }, 404 | "zipp": { 405 | "hashes": [ 406 | "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", 407 | "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" 408 | ], 409 | "markers": "python_version >= '3.6'", 410 | "version": "==3.1.0" 411 | } 412 | }, 413 | "develop": { 414 | "attrs": { 415 | "hashes": [ 416 | "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", 417 | "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" 418 | ], 419 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 420 | "version": "==19.3.0" 421 | }, 422 | "aws-sam-translator": { 423 | "hashes": [ 424 | "sha256:9259944109f3a99c1bb6b2f0bbf486b31c0ce1618b67e4b29e17c16f3d2f0c71", 425 | "sha256:a8df6a828c3b27c4c81cfe66dc08e14333a7d06f7a82ae502a72892e1f32edac", 426 | "sha256:f6b67545a87ec1e276bd5bf06abcc84332c4eb9dfa2fd415113e07a908fe55bb" 427 | ], 428 | "version": "==1.24.0" 429 | }, 430 | "boto3": { 431 | "hashes": [ 432 | "sha256:2e58368d32171f42100353007fb48fe583ea9deafd0ec556aa2bc1ce1582d06d", 433 | "sha256:c774003dc13d6de74b5e19d2b84d625da4456e64bd97f44baa1fcf40d808d29a" 434 | ], 435 | "version": "==1.13.19" 436 | }, 437 | "botocore": { 438 | "hashes": [ 439 | "sha256:5cb537e7a4cf2d59a2a8dfbbc8e14ec3bc5b640eb81a1bf3bb0523c0a75e6b1b", 440 | "sha256:7b8b1f082665c8670b9aa70143ee527c5d04939fe027a63ac5958359be20ccb0" 441 | ], 442 | "version": "==1.16.19" 443 | }, 444 | "cfn-lint": { 445 | "hashes": [ 446 | "sha256:96dedfaa72b84aed2c93c82f0ad68e0d0385cb986a3cc3fa8b7491cc5f565292", 447 | "sha256:da8adba55060cb95118b15142fefb49b760ff0b4e83c4cff10151408a90c9a21" 448 | ], 449 | "index": "pypi", 450 | "version": "==0.32.1" 451 | }, 452 | "coverage": { 453 | "hashes": [ 454 | "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a", 455 | "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355", 456 | "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65", 457 | "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7", 458 | "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9", 459 | "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1", 460 | "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0", 461 | "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55", 462 | "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c", 463 | "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6", 464 | "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef", 465 | "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019", 466 | "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e", 467 | "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0", 468 | "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf", 469 | "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24", 470 | "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2", 471 | "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c", 472 | "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4", 473 | "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0", 474 | "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd", 475 | "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04", 476 | "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e", 477 | "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730", 478 | "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2", 479 | "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768", 480 | "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796", 481 | "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7", 482 | "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a", 483 | "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489", 484 | "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052" 485 | ], 486 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", 487 | "version": "==5.1" 488 | }, 489 | "decorator": { 490 | "hashes": [ 491 | "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", 492 | "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" 493 | ], 494 | "version": "==4.4.2" 495 | }, 496 | "docutils": { 497 | "hashes": [ 498 | "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", 499 | "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", 500 | "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" 501 | ], 502 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 503 | "version": "==0.15.2" 504 | }, 505 | "flake8": { 506 | "hashes": [ 507 | "sha256:c69ac1668e434d37a2d2880b3ca9aafd54b3a10a3ac1ab101d22f29e29cf8634", 508 | "sha256:ccaa799ef9893cebe69fdfefed76865aeaefbb94cb8545617b2298786a4de9a5" 509 | ], 510 | "index": "pypi", 511 | "version": "==3.8.2" 512 | }, 513 | "importlib-metadata": { 514 | "hashes": [ 515 | "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", 516 | "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" 517 | ], 518 | "markers": "python_version < '3.8'", 519 | "version": "==1.6.0" 520 | }, 521 | "jmespath": { 522 | "hashes": [ 523 | "sha256:695cb76fa78a10663425d5b73ddc5714eb711157e52704d69be03b1a02ba4fec", 524 | "sha256:cca55c8d153173e21baa59983015ad0daf603f9cb799904ff057bfb8ff8dc2d9" 525 | ], 526 | "version": "==0.9.5" 527 | }, 528 | "jsonpatch": { 529 | "hashes": [ 530 | "sha256:cc3a7241010a1fd3f50145a3b33be2c03c1e679faa19934b628bb07d0f64819e", 531 | "sha256:ddc0f7628b8bfdd62e3cbfbc24ca6671b0b6265b50d186c2cf3659dc0f78fd6a" 532 | ], 533 | "markers": "python_version != '3.4'", 534 | "version": "==1.25" 535 | }, 536 | "jsonpointer": { 537 | "hashes": [ 538 | "sha256:c192ba86648e05fdae4f08a17ec25180a9aef5008d973407b581798a83975362", 539 | "sha256:ff379fa021d1b81ab539f5ec467c7745beb1a5671463f9dcc2b2d458bd361c1e" 540 | ], 541 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 542 | "version": "==2.0" 543 | }, 544 | "jsonschema": { 545 | "hashes": [ 546 | "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", 547 | "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" 548 | ], 549 | "version": "==3.2.0" 550 | }, 551 | "junit-xml": { 552 | "hashes": [ 553 | "sha256:ec5ca1a55aefdd76d28fcc0b135251d156c7106fa979686a4b48d62b761b4732" 554 | ], 555 | "version": "==1.9" 556 | }, 557 | "mccabe": { 558 | "hashes": [ 559 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 560 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 561 | ], 562 | "version": "==0.6.1" 563 | }, 564 | "more-itertools": { 565 | "hashes": [ 566 | "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be", 567 | "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982" 568 | ], 569 | "markers": "python_version >= '3.5'", 570 | "version": "==8.3.0" 571 | }, 572 | "networkx": { 573 | "hashes": [ 574 | "sha256:cdfbf698749a5014bf2ed9db4a07a5295df1d3a53bf80bf3cbd61edf9df05fa1", 575 | "sha256:f8f4ff0b6f96e4f9b16af6b84622597b5334bf9cae8cf9b2e42e7985d5c95c64" 576 | ], 577 | "markers": "python_version >= '3.5'", 578 | "version": "==2.4" 579 | }, 580 | "packaging": { 581 | "hashes": [ 582 | "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", 583 | "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" 584 | ], 585 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 586 | "version": "==20.4" 587 | }, 588 | "pathspec": { 589 | "hashes": [ 590 | "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0", 591 | "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061" 592 | ], 593 | "version": "==0.8.0" 594 | }, 595 | "pluggy": { 596 | "hashes": [ 597 | "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", 598 | "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" 599 | ], 600 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 601 | "version": "==0.13.1" 602 | }, 603 | "py": { 604 | "hashes": [ 605 | "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", 606 | "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" 607 | ], 608 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 609 | "version": "==1.8.1" 610 | }, 611 | "pycodestyle": { 612 | "hashes": [ 613 | "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", 614 | "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" 615 | ], 616 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 617 | "version": "==2.6.0" 618 | }, 619 | "pyflakes": { 620 | "hashes": [ 621 | "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", 622 | "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" 623 | ], 624 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 625 | "version": "==2.2.0" 626 | }, 627 | "pyparsing": { 628 | "hashes": [ 629 | "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", 630 | "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" 631 | ], 632 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 633 | "version": "==2.4.7" 634 | }, 635 | "pyrsistent": { 636 | "hashes": [ 637 | "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3" 638 | ], 639 | "version": "==0.16.0" 640 | }, 641 | "pytest": { 642 | "hashes": [ 643 | "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3", 644 | "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698" 645 | ], 646 | "index": "pypi", 647 | "version": "==5.4.2" 648 | }, 649 | "pytest-cov": { 650 | "hashes": [ 651 | "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322", 652 | "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424" 653 | ], 654 | "index": "pypi", 655 | "version": "==2.9.0" 656 | }, 657 | "python-dateutil": { 658 | "hashes": [ 659 | "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", 660 | "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" 661 | ], 662 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 663 | "version": "==2.8.0" 664 | }, 665 | "pyyaml": { 666 | "hashes": [ 667 | "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", 668 | "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", 669 | "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", 670 | "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", 671 | "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", 672 | "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", 673 | "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", 674 | "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", 675 | "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", 676 | "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", 677 | "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" 678 | ], 679 | "version": "==5.3.1" 680 | }, 681 | "s3transfer": { 682 | "hashes": [ 683 | "sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13", 684 | "sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db" 685 | ], 686 | "version": "==0.3.3" 687 | }, 688 | "six": { 689 | "hashes": [ 690 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 691 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 692 | ], 693 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 694 | "version": "==1.15.0" 695 | }, 696 | "urllib3": { 697 | "hashes": [ 698 | "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", 699 | "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" 700 | ], 701 | "markers": "python_version != '3.4'", 702 | "version": "==1.25.9" 703 | }, 704 | "wcwidth": { 705 | "hashes": [ 706 | "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1", 707 | "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1" 708 | ], 709 | "version": "==0.1.9" 710 | }, 711 | "yamllint": { 712 | "hashes": [ 713 | "sha256:0fa69bf8a86182b7fe14918bdd3a30354c869966bbc7cbfff176af71bda9c806", 714 | "sha256:59f3ff77f44e7f46be6aecdb985830f73a1c51e290b7082a7d38c2ae1940f4a9" 715 | ], 716 | "index": "pypi", 717 | "version": "==1.23.0" 718 | }, 719 | "zipp": { 720 | "hashes": [ 721 | "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", 722 | "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" 723 | ], 724 | "markers": "python_version >= '3.6'", 725 | "version": "==3.1.0" 726 | } 727 | } 728 | } 729 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Writing and testing AWS Lambdas in Python 2 | 3 | Example code for 4 | * [10 Recommendations for writing pragmatic AWS Lambdas in Python](https://medium.com/@jan.groth.de/10-recommendations-for-writing-pragmatic-aws-lambdas-in-python-5f4b038caafe) 5 | 6 | ![Flow Diagram](media/example_flow.png) 7 | 8 | ## How to deploy 9 | 10 | Assumes: 11 | * General understanding of how to build and run AWS SAM applications 12 | 13 | Prerequisites: 14 | * Python 3.7 15 | * `pip3` (or `pip` aliased to `pip3`) 16 | * `make` 17 | * `aws-cli` 18 | 19 | Tested on: 20 | * Linux 21 | * MacOS 22 | 23 | ### Setup (via Python pipenv) 24 | 25 | * Install dependencies: 26 | ```shell script 27 | make install-dependencies 28 | ``` 29 | * Note: 30 | * Depending on your local setup you might want to change `pip3` to `pip`. This makefile assumes the Python3 version. 31 | * Running `make install-dependencies` is a one-off task, feel free to install the required Python packages with your preferred tool) 32 | 33 | * Change into `pipenv`-shell: 34 | ```shell script 35 | pipenv shell 36 | ``` 37 | 38 | * Configure AWS profile: 39 | ```shell script 40 | export AWS_PROFILE=[your profile name] 41 | ``` 42 | (or use the *default* profile if configured) 43 | 44 | * Create artifact bucket: 45 | * Edit `ARTIFACT_BUCKET` in `Makefile` to become globally unique 46 | * E.g. `default-sg-remediation-artifacts-[your account id]` 47 | ```shell script 48 | make create-artifact-bucket 49 | ``` 50 | 51 | ### Deploy application 52 | 53 | ```shell script 54 | make deploy 55 | ``` 56 | 57 | ## How to test 58 | 59 | * Change _egress_ or _ingress_ on the default security group 60 | * Lambda gets invoked 61 | * egress/ingress get revoked 62 | * security group gets tagged 63 | -------------------------------------------------------------------------------- /default-sg-remediation.sam.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Transform: AWS::Serverless-2016-10-31 4 | Description: > 5 | default sg ingress egress remediation 6 | [default sg] -> eventbridge -> eventsrule -> lambda -> revoke egress/ingress on sg 7 | 8 | Parameters: 9 | minimumLogLevel: 10 | Type: String 11 | Default: DEBUG 12 | 13 | Resources: 14 | 15 | RevokeDefaultSgLambda: 16 | Type: AWS::Serverless::Function 17 | Properties: 18 | CodeUri: ./src 19 | Handler: revokedefaultsg.app.handler 20 | Runtime: python3.7 21 | Role: !GetAtt RevokeDefaultSgLambdaRole.Arn 22 | Timeout: 900 23 | Environment: 24 | Variables: 25 | LOGGING: !Ref minimumLogLevel 26 | Events: 27 | RevokeDefaultSgPattern: 28 | Type: EventBridgeRule 29 | Properties: 30 | Pattern: 31 | detail-type: 32 | - "AWS API Call via CloudTrail" 33 | detail: 34 | eventSource: 35 | - ec2.amazonaws.com 36 | eventName: 37 | - AuthorizeSecurityGroupIngress 38 | - AuthorizeSecurityGroupEgress 39 | 40 | RevokeDefaultSgLambdaRole: 41 | Type: AWS::IAM::Role 42 | Properties: 43 | ManagedPolicyArns: 44 | - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 45 | AssumeRolePolicyDocument: 46 | Version: '2012-10-17' 47 | Statement: 48 | - Action: 49 | - sts:AssumeRole 50 | Effect: Allow 51 | Principal: 52 | Service: 53 | - lambda.amazonaws.com 54 | Policies: 55 | - PolicyName: revoke-sg-ingress-egress 56 | PolicyDocument: 57 | Version: 2012-10-17 58 | Statement: 59 | - Effect: Allow 60 | Action: 61 | - ec2:DescribeVpcs 62 | - ec2:DescribeSecurityGroups 63 | - ec2:RevokeSecurityGroupIngress 64 | - ec2:RevokeSecurityGroupEgress 65 | - ec2:CreateTags 66 | Resource: "*" 67 | -------------------------------------------------------------------------------- /media/WritingAwsLambdasInPython.xml: -------------------------------------------------------------------------------- 1 | 2 | 7Vxbd9o4EP41PCbHV9k81lx62Wy223S3233JMbYAtwZ5hQnQX7+SLRvLEuamACEh57RoJMvyfKNvRiOZltmZLN9jPxn/jkIYtwwtXLbMbssw2o5F/qWCVS7QTVPLJSMchUy2FjxEvyATFs3mUQhnXMMUoTiNEl4YoOkUBikn8zFGC77ZEMX8XRN/BAXBQ+DHovRbFKbjXOra2lr+AUajcXFnXWM1E79ozASzsR+iRaVXs9cyOxihNP82WXZgTJVX6CW/rr+hthwYhtN0lwsm/c5NMulNQTwfPgZ/PXn9J/1Gd9jg0lXxxDAkCmBFhNMxGqGpH/fWUg+j+TSEtFuNlNZt7hBKiFAnwh8wTVcMTX+eIiIap5OY1c5SjH6W+iSa8MSHKcD28QimTU/AGtJh15VLrBKiCUzxijTAMPbT6InH1WfmMSrblZd+RhEZi6ExUzba7fwSZskG0PguZmiOA8iuWuPwDmN/VWmW0Aaz/e/DuiNf8h6LUuUZ16IM+j3MoLTRSzWDXLlNT7CjuViqraUCzAF6z/t98uM5u1PLADEZvxdGTxwe4L85JQpviKbpzSzT6DvSQLeS5bqSfBux/+Oi8eG99J4ICh5hZaIq1iF5yLxP/j5EnI23kNZMCc3TOJrCTknRGhtCB8UIZ21M8tenivNG2A8jyNX1TctyjUpdN8KkowhNSf2UGh7tL4rjyjVeRzdtUBpZpWaYfaiK/dm4NN8niNOI0P6dP4DxZzSLWPcDlKZoUmnwLo5GtCKlRu75rBSQUUHMmzd9Qmb7ulGUmVboLf1ZkqtjGC3pODziIRJaOVmOqDO99Rcz6xbD3PY/BnQ8Hinm3/hWkGI1yLFqmEb0IeCy0fBZrVXQG6Oh0m8v1l7QcXPRuOIAC5n6qaILdvXCKEo59cgdCHBrDsSuOaqcI/dwVBxuCryNtZH1jiOsO38yCP2NXKWalRy3p1n7sVJXszsk5HotrBTngCghJGBfGCHppsSMj2AoXQVDmaLNhT50h4HM5kDgwgFnc7RDuIzSf+j3W80wWfk7NY5byyzK3SWzlqywqhQ+QxwR/VKry2SH06XzLHS5dzhumjybWga/yqq1d3TtqPa6Y28O97dfDeyduF4Vk+uuMAf+SOCUXjYdEVKYFWQ8wAUPE+MjoLLK2nQhVJDy1i0wWZ3wJlEY5hMJEvfgD0oeY8CSfm2vZXeJJKYk6vnBz1E26SoToQe6WrdXI0fAyn1/EsVUvx1isRGkw7+Hi7I/hEOIK31p2YdOt8QPiBbu4LBwLkziMfKuylhwwUpfs7kvnz7NXFSn0zIxwjTTYlmCjTSrkVnvAs6oblhPR8YlZJEJbh2tvf64/G1Mvkc0HM7gs1its524eVr2cVBYhVanS4knZwZQ5eEpmkJJGMHEtMPPfkosnJo0UUsWV+wRe+7uRt0aYbRFL6oXnrXqRkuhcj8qUoii5WvHeAsFLyYUhIEiA9Y1np2I4Jb5zPOFgu2jIr8LWJvqu6ZbDdVR2XGKL8xHNXl0YjQPv2I/ik/GIW9Jrm0cElBQ0gwUJVRiXFqOS5dlRurEEsdRMoMVTWZqIeXFOErhA4kgqXBB1MYjpUBhoLbccCTBg2NIggdNQfDw+Gvy6xu6Gdz89c/KtkePdwg/3ujgHMxLNIhXdI2s3dpF8TvrLius18dZqVggVyeLtStfy59bpGtpu7PRddOom8y7Tg+DZDK9ZcIqBFupbPNqr+TGHdkuqaQ02OyrZDk8KfEzIb2CZuY/Uny92WoyQPQBIMZ0MErCIdPmpqRtiLGQLYnn60lhZRhbZ5mQRc6Ky1fZzdmqAyfx4ZPW2HHSmqon7WGZL5vfRwB2Q2bqgKW59NHPchzhci0BXBR9GxuDbSW71ZHa7euOT/OQYgJy+162MJD6/vblPjRZMqAn6pb6Ch6znhMbo8lgPtseadZWKUOb/slWKSD7iOuk/KPIQeq8gyxj2IqDbEscZPu5HKRs24jZqFpb6DetXkvxYDPeXA50T8zdAAbSTaeBa1t2I3vusYAzLA5bV5LNBG0RWxW5TCm27ktyX4cETYe7PPCigx+nfYLgp8hmnSd2XkP/vVJzKbHzruZTsPuFhEzgRYVMD2M0j8O3kOktZOLJzz5zyFRM6mPT/BluNfi/MPh3TPMfExSFNnRDS4aqawzMEtVqhrCaTpec+N8fWoOHVhYxORJoVURMutM1UqIlazH++8Nv3+7+/GT1bs7i8g7cOZM+gCk6Jmm7sx08bxr128Hztz251mUdPHdcg2MoIGOoZ9qUkzPUDrsWJzjn2XzoqDzKuftx0HUMz0Xw64D+mBi+iQK3UqX+PGtAYZGna+3afqbR5vtQd3yySSFvB+GvnSVVHoRv285lEaT90kM4Z0deUk5LR6lddor17ejTlRKI6qNPQOM939lJRPo2jQpz/gKpVkh1F0fD9CTL/O0v3Chf1usCnm0BT1eyrq+/xK4Oz+O8wqUEyYc7FX3XaFf5e07H4fZcMWm2qL/S6WfZ55t9Sw19SjC8t7T3SYLmsfd4P5ac1DDF6VgeJN2iX0GPIRiAzPfWs9bDoZFt+vL6lWc1N75WpQAPu7auI/GFgIchwcN4LjxE5ybJIVwvHqZr1fAwzouHSHKSPPT14mHUfyIAANk7NCeFxBYguUMjAZTDoWhSadOuS/1NviqOKqAA/NQw2ob0daZTQiFuWvcRXvg4vH44zPorIY5zXizEtyPvURoNV9cPhQ3sy4JCFylKctj9er0GcC7Pa+giV5EFP1Hs9c8PYF+e59DFpOSrcR2gvX0ReFowXq/vcJ3zYfHnv19CPIffHn54D+/6w2WAUCp58+0eXT8MRu19USB5wdY6JQziOvw7FH+G5upwqB/ocyUH+k6Kg7j+fo3TwQHueWEQA9pXOR0cyStBinAgxfUvPOfHRta/k232/gc= -------------------------------------------------------------------------------- /media/debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangroth/writing-aws-lambdas-in-python/91c94af83e89102edc76158cdbf2cbfdfbe7e2e3/media/debugger.png -------------------------------------------------------------------------------- /media/example_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangroth/writing-aws-lambdas-in-python/91c94af83e89102edc76158cdbf2cbfdfbe7e2e3/media/example_flow.png -------------------------------------------------------------------------------- /media/exception_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangroth/writing-aws-lambdas-in-python/91c94af83e89102edc76158cdbf2cbfdfbe7e2e3/media/exception_flow.png -------------------------------------------------------------------------------- /media/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangroth/writing-aws-lambdas-in-python/91c94af83e89102edc76158cdbf2cbfdfbe7e2e3/media/github.png -------------------------------------------------------------------------------- /media/reactive_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangroth/writing-aws-lambdas-in-python/91c94af83e89102edc76158cdbf2cbfdfbe7e2e3/media/reactive_flow.png -------------------------------------------------------------------------------- /media/test_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangroth/writing-aws-lambdas-in-python/91c94af83e89102edc76158cdbf2cbfdfbe7e2e3/media/test_run.png -------------------------------------------------------------------------------- /media/versent_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangroth/writing-aws-lambdas-in-python/91c94af83e89102edc76158cdbf2cbfdfbe7e2e3/media/versent_logo.png -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangroth/writing-aws-lambdas-in-python/91c94af83e89102edc76158cdbf2cbfdfbe7e2e3/src/__init__.py -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangroth/writing-aws-lambdas-in-python/91c94af83e89102edc76158cdbf2cbfdfbe7e2e3/src/requirements.txt -------------------------------------------------------------------------------- /src/revokedefaultsg/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jangroth/writing-aws-lambdas-in-python/91c94af83e89102edc76158cdbf2cbfdfbe7e2e3/src/revokedefaultsg/__init__.py -------------------------------------------------------------------------------- /src/revokedefaultsg/app.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from functools import wraps 4 | 5 | import boto3 6 | 7 | logging.basicConfig(format="%(asctime)s %(name)s %(levelname)s %(message)s") 8 | logger = logging.getLogger("RevokeDefaultSgApplication") 9 | logger.setLevel(os.environ.get("LOGGING", logging.DEBUG)) 10 | 11 | 12 | def notify_cloudwatch(function): 13 | @wraps(function) 14 | def wrapper(event, context): 15 | logger.info(f"'{context.function_name}' - entry.\nIncoming event: '{event}'") 16 | result = function(event, context) 17 | logger.info(f"'{context.function_name}' - exit.\nResult: '{result}'") 18 | return result 19 | 20 | return wrapper 21 | 22 | 23 | class UnknownEventException(Exception): 24 | pass 25 | 26 | 27 | class RevokeDefaultSg: 28 | def __init__(self, region="ap-southeast-2"): 29 | self.logger = logging.getLogger(self.__class__.__name__) 30 | self.logger.setLevel(os.environ.get("LOGGING", logging.DEBUG)) 31 | self.ec2_client = boto3.client("ec2", region_name=region) 32 | self.ec2_resource = boto3.resource("ec2", region_name=region) 33 | 34 | def _extract_sg_id(self, event): 35 | is_correct_event_source = (event.get("detail", {}).get("eventSource", None) == "ec2.amazonaws.com") 36 | is_correct_event_type = (event.get("detail", {}).get("eventType", None) == "AwsApiCall") 37 | event_sg_id = (event.get("detail", {}).get("requestParameters", {}).get("groupId", None)) 38 | 39 | if not (is_correct_event_source and is_correct_event_type and event_sg_id): 40 | raise UnknownEventException(f"Cannot handle event: {event}") 41 | return event_sg_id 42 | 43 | def _is_default_sg(self, sg_id): 44 | sec_groups = self.ec2_client.describe_security_groups(GroupIds=[sg_id])[ 45 | "SecurityGroups" 46 | ] 47 | return sec_groups[0]["GroupName"] == "default" 48 | 49 | def _revoke_and_tag(self, sg_id): 50 | security_group = self.ec2_resource.SecurityGroup(sg_id) 51 | should_tag = False 52 | if security_group.ip_permissions: 53 | security_group.revoke_ingress(IpPermissions=security_group.ip_permissions) 54 | should_tag = True 55 | self.logger.debug("Revoking ingress rules") 56 | if security_group.ip_permissions_egress: 57 | should_tag = True 58 | security_group.revoke_egress( 59 | IpPermissions=security_group.ip_permissions_egress 60 | ) 61 | self.logger.debug("Revoking egress rules") 62 | if should_tag: 63 | security_group.create_tags( 64 | Tags=[ 65 | { 66 | "Key": "auto:remediation-reason", 67 | "Value": "AWS CIS Benchmark 4.4 - Default SG's rules are automatically revoked", 68 | } 69 | ] 70 | ) 71 | self.logger.debug("Adding tag.") 72 | else: 73 | self.logger.debug("No ingress/egress rules found to revoke") 74 | 75 | def process_event(self, event): 76 | sg_id = self._extract_sg_id(event) 77 | if self._is_default_sg(sg_id): 78 | self.logger.info(f"Revoking ingress/egress on default SG {sg_id}...") 79 | self._revoke_and_tag(sg_id) 80 | else: 81 | self.logger.info(f"SG {sg_id} is not a default SG, nothing to do...") 82 | return "SUCCESS" 83 | 84 | 85 | @notify_cloudwatch 86 | def handler(event, context): 87 | return RevokeDefaultSg().process_event(event) 88 | 89 | 90 | if __name__ == "__main__": 91 | pass 92 | -------------------------------------------------------------------------------- /tests/resources/incoming_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "12345678-b00a-ede7-937b-b4da1faf5b81", 4 | "detail-type": "AWS API Call via CloudTrail", 5 | "source": "aws.ec2", 6 | "account": "123456789012", 7 | "time": "2020-02-05T23:04:14Z", 8 | "region": "ap-southeast-2", 9 | "resources": [], 10 | "detail": { 11 | "eventVersion": "1.05", 12 | "userIdentity": { 13 | "type": "AssumedRole", 14 | "principalId": "AROAST26AMBD6UBEQ7CGZ:me@mail.com", 15 | "arn": "arn:aws:sts::123456789012:assumed-role/OrganizationAccountAccessRole/me@mail.com", 16 | "accountId": "123456789012", 17 | "accessKeyId": "ASIAST26AMBDWWMMWWMM", 18 | "sessionContext": { 19 | "sessionIssuer": { 20 | "type": "Role", 21 | "principalId": "AROAST26AMBD6UBEQ7777", 22 | "arn": "arn:aws:iam::123456789012:role/OrganizationAccountAccessRole", 23 | "accountId": "123456789012", 24 | "userName": "OrganizationAccountAccessRole" 25 | }, 26 | "webIdFederationData": {}, 27 | "attributes": { 28 | "mfaAuthenticated": "true", 29 | "creationDate": "2020-02-05T22:13:58Z" 30 | } 31 | } 32 | }, 33 | "eventTime": "2020-02-05T23:04:14Z", 34 | "eventSource": "ec2.amazonaws.com", 35 | "eventName": "AuthorizeSecurityGroupIngress", 36 | "awsRegion": "ap-southeast-2", 37 | "sourceIPAddress": "203.123.123.123", 38 | "userAgent": "console.ec2.amazonaws.com", 39 | "requestParameters": { 40 | "groupId": "sg-0414b25decc887df7", 41 | "ipPermissions": { 42 | "items": [ 43 | { 44 | "ipProtocol": "tcp", 45 | "fromPort": 22, 46 | "toPort": 22, 47 | "groups": {}, 48 | "ipRanges": { 49 | "items": [ 50 | { 51 | "cidrIp": "1.2.3.4/32" 52 | } 53 | ] 54 | }, 55 | "ipv6Ranges": {}, 56 | "prefixListIds": {} 57 | } 58 | ] 59 | } 60 | }, 61 | "responseElements": { 62 | "requestId": "12345678-eba3-4596-9413-686de15cf881", 63 | "_return": true 64 | }, 65 | "requestID": "12345678-eba3-4596-9413-686de15cf881", 66 | "eventID": "12345678-6466-4720-955e-e342e782d405", 67 | "eventType": "AwsApiCall" 68 | } 69 | } -------------------------------------------------------------------------------- /tests/unit/test_revoke_default_sg.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | from unittest.mock import MagicMock 3 | 4 | import pytest 5 | from pytest import fixture 6 | 7 | from revokedefaultsg.app import RevokeDefaultSg, UnknownEventException 8 | 9 | DEFAULT_GROUP = {"GroupName": "default"} 10 | NOT_A_DEFAULT_GROUP = {"GroupName": "not default"} 11 | TEST_SG = "sg-123" 12 | 13 | 14 | @fixture 15 | def good_event(): 16 | return { 17 | "id": "12345678-b00a-ede7-937b-b4da1faf5b81", 18 | "detail": { 19 | "eventVersion": "1.05", 20 | "eventTime": "2020-02-05T23:04:14Z", 21 | "eventSource": "ec2.amazonaws.com", 22 | "eventName": "AuthorizeSecurityGroupIngress", 23 | "eventID": "12345678-6466-4720-955e-e342e782d405", 24 | "eventType": "AwsApiCall", 25 | "requestParameters": {"groupId": "sg-123"}, 26 | }, 27 | } 28 | 29 | 30 | @fixture 31 | def bad_event(): 32 | return { 33 | "id": "12345678-b00a-ede7-937b-b4da1faf5b81", 34 | "detail": { 35 | "eventVersion": "1.05", 36 | "eventTime": "2020-02-05T23:04:14Z", 37 | "eventSource": "barrista.arround.the.corner", 38 | "eventName": "AuthorizeSecurityGroupIngress", 39 | "eventID": "12345678-6466-4720-955e-e342e782d405", 40 | "eventType": "DrinkMoreCoffee", 41 | }, 42 | } 43 | 44 | 45 | @fixture 46 | def unknown_event(): 47 | return {"foo": "bar"} 48 | 49 | 50 | @fixture() 51 | def obj(): 52 | obj = RevokeDefaultSg.__new__(RevokeDefaultSg) 53 | obj.logger = MagicMock() 54 | obj.ec2_client = MagicMock() 55 | obj.ec2_resource = MagicMock() 56 | return obj 57 | 58 | 59 | def test_should_process_event_and_revoke_if_default_sg(obj, good_event): 60 | obj._is_default_sg = MagicMock(return_value=True) 61 | obj._revoke_and_tag = MagicMock() 62 | 63 | obj.process_event(good_event) 64 | 65 | obj._is_default_sg.assert_called_once_with(TEST_SG) 66 | obj._revoke_and_tag.assert_called_once_with(TEST_SG) 67 | 68 | 69 | def test_should_process_event_and_do_nothing_if_non_default_sg(obj, good_event): 70 | obj._is_default_sg = MagicMock(return_value=False) 71 | obj._revoke_and_tag = MagicMock() 72 | 73 | obj.process_event(good_event) 74 | 75 | obj._is_default_sg.assert_called_once_with(TEST_SG) 76 | obj._revoke_and_tag.assert_not_called() 77 | 78 | 79 | def test_should_extract_sg_id_from_good_event(obj, good_event): 80 | assert obj._extract_sg_id(good_event) == TEST_SG 81 | 82 | 83 | def test_should_find_default_sg(obj): 84 | obj.ec2_client.describe_security_groups.return_value = { 85 | "SecurityGroups": [DEFAULT_GROUP] 86 | } 87 | 88 | assert obj._is_default_sg(TEST_SG) 89 | 90 | 91 | def test_should_find_non_default_sg(obj): 92 | obj.ec2_client.describe_security_groups.return_value = { 93 | "SecurityGroups": [NOT_A_DEFAULT_GROUP] 94 | } 95 | 96 | assert not obj._is_default_sg(TEST_SG) 97 | 98 | 99 | def test_should_tag_if_ingress_was_revoked(obj): 100 | mock_security_group = MagicMock() 101 | mock_security_group.ip_permissions = "ingress" 102 | mock_security_group.ip_permissions_egress = None 103 | obj.ec2_resource.SecurityGroup.return_value = mock_security_group 104 | 105 | obj._revoke_and_tag(TEST_SG) 106 | 107 | mock_security_group.create_tags.assert_called_once_with(Tags=mock.ANY) 108 | 109 | 110 | def test_should_tag_if_egress_was_revoked(obj): 111 | mock_security_group = MagicMock() 112 | mock_security_group.ip_permissions = None 113 | mock_security_group.ip_permissions_egress = "egress" 114 | obj.ec2_resource.SecurityGroup.return_value = mock_security_group 115 | 116 | obj._revoke_and_tag(TEST_SG) 117 | 118 | mock_security_group.create_tags.assert_called_once_with(Tags=mock.ANY) 119 | 120 | 121 | def test_should_not_tag_if_nothing_was_revoked(obj): 122 | mock_security_group = MagicMock() 123 | mock_security_group.ip_permissions = None 124 | mock_security_group.ip_permissions_egress = None 125 | obj.ec2_resource.SecurityGroup.return_value = mock_security_group 126 | 127 | obj._revoke_and_tag(TEST_SG) 128 | 129 | mock_security_group.create_tags.assert_not_called() 130 | 131 | 132 | def test_should_raise_exception_if_unknown_event(obj, unknown_event): 133 | with pytest.raises(UnknownEventException): 134 | obj._extract_sg_id(unknown_event) 135 | 136 | 137 | def test_should_raise_exception_if_bad_event(obj, bad_event): 138 | with pytest.raises(UnknownEventException): 139 | obj._extract_sg_id(bad_event) 140 | --------------------------------------------------------------------------------