├── .DS_Store
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── LICENSE.txt
├── README.md
├── check_network_test
├── __init__.py
├── app.py
└── requirements.txt
├── delete_test_resources
├── __init__.py
├── app.py
└── requirements.txt
├── images
├── Fig10v2.png
├── Fig15v2.png
├── IaCStateMachine.png
└── hl_architecture.png
├── retrieve_path_to_test
├── __init__.py
├── app.py
└── requirements.txt
├── samconfig.toml
├── sample_resources
├── sample-pipeline.yml
├── sample-stack-3-tier-app.yml
└── sample-stack-multi-vpc.yml
├── start_network_test
├── __init__.py
├── app.py
└── requirements.txt
├── statemachine
└── network-test.json
├── template.yaml
└── test_duration_iterator
├── __init__.py
├── app.py
└── requirements.txt
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-iac-network-tester/f787ad4ccc51d1f939f12506e9aabc7031b6bec0/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode
3 |
4 | .aws-sam
5 |
6 | .LICENSE.txt
7 | ### Linux ###
8 | *~
9 |
10 | # temporary files which can be created if a process still has a handle open of a deleted file
11 | .fuse_hidden*
12 |
13 | # KDE directory preferences
14 | .directory
15 |
16 | # Linux trash folder which might appear on any partition or disk
17 | .Trash-*
18 |
19 | # .nfs files are created when an open file is removed but is still being accessed
20 | .nfs*
21 |
22 | ### OSX ###
23 | *.DS_Store
24 | .AppleDouble
25 | .LSOverride
26 |
27 | # Icon must end with two \r
28 | Icon
29 |
30 | # Thumbnails
31 | ._*
32 |
33 | # Files that might appear in the root of a volume
34 | .DocumentRevisions-V100
35 | .fseventsd
36 | .Spotlight-V100
37 | .TemporaryItems
38 | .Trashes
39 | .VolumeIcon.icns
40 | .com.apple.timemachine.donotpresent
41 |
42 | # Directories potentially created on remote AFP share
43 | .AppleDB
44 | .AppleDesktop
45 | Network Trash Folder
46 | Temporary Items
47 | .apdisk
48 |
49 | ### PyCharm ###
50 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
51 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
52 |
53 | # User-specific stuff:
54 | .idea/**/workspace.xml
55 | .idea/**/tasks.xml
56 | .idea/dictionaries
57 |
58 | # Sensitive or high-churn files:
59 | .idea/**/dataSources/
60 | .idea/**/dataSources.ids
61 | .idea/**/dataSources.xml
62 | .idea/**/dataSources.local.xml
63 | .idea/**/sqlDataSources.xml
64 | .idea/**/dynamic.xml
65 | .idea/**/uiDesigner.xml
66 |
67 | # Gradle:
68 | .idea/**/gradle.xml
69 | .idea/**/libraries
70 |
71 | # CMake
72 | cmake-build-debug/
73 |
74 | # Mongo Explorer plugin:
75 | .idea/**/mongoSettings.xml
76 |
77 | ## File-based project format:
78 | *.iws
79 |
80 | ## Plugin-specific files:
81 |
82 | # IntelliJ
83 | /out/
84 |
85 | # mpeltonen/sbt-idea plugin
86 | .idea_modules/
87 |
88 | # JIRA plugin
89 | atlassian-ide-plugin.xml
90 |
91 | # Cursive Clojure plugin
92 | .idea/replstate.xml
93 |
94 | # Ruby plugin and RubyMine
95 | /.rakeTasks
96 |
97 | # Crashlytics plugin (for Android Studio and IntelliJ)
98 | com_crashlytics_export_strings.xml
99 | crashlytics.properties
100 | crashlytics-build.properties
101 | fabric.properties
102 |
103 | ### PyCharm Patch ###
104 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
105 |
106 | # *.iml
107 | # modules.xml
108 | # .idea/misc.xml
109 | # *.ipr
110 |
111 | # Sonarlint plugin
112 | .idea/sonarlint
113 |
114 | ### Python ###
115 | # Byte-compiled / optimized / DLL files
116 | __pycache__/
117 | *.py[cod]
118 | *$py.class
119 |
120 | # C extensions
121 | *.so
122 |
123 | # Distribution / packaging
124 | .Python
125 | build/
126 | develop-eggs/
127 | dist/
128 | downloads/
129 | eggs/
130 | .eggs/
131 | lib/
132 | lib64/
133 | parts/
134 | sdist/
135 | var/
136 | wheels/
137 | *.egg-info/
138 | .installed.cfg
139 | *.egg
140 |
141 | # PyInstaller
142 | # Usually these files are written by a python script from a template
143 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
144 | *.manifest
145 | *.spec
146 |
147 | # Installer logs
148 | pip-log.txt
149 | pip-delete-this-directory.txt
150 |
151 | # Unit test / coverage reports
152 | htmlcov/
153 | .tox/
154 | .coverage
155 | .coverage.*
156 | .cache
157 | .pytest_cache/
158 | nosetests.xml
159 | coverage.xml
160 | *.cover
161 | .hypothesis/
162 |
163 | # Translations
164 | *.mo
165 | *.pot
166 |
167 | # Flask stuff:
168 | instance/
169 | .webassets-cache
170 |
171 | # Scrapy stuff:
172 | .scrapy
173 |
174 | # Sphinx documentation
175 | docs/_build/
176 |
177 | # PyBuilder
178 | target/
179 |
180 | # Jupyter Notebook
181 | .ipynb_checkpoints
182 |
183 | # pyenv
184 | .python-version
185 |
186 | # celery beat schedule file
187 | celerybeat-schedule.*
188 |
189 | # SageMath parsed files
190 | *.sage.py
191 |
192 | # Environments
193 | .env
194 | .venv
195 | env/
196 | venv/
197 | ENV/
198 | env.bak/
199 | venv.bak/
200 |
201 | # Spyder project settings
202 | .spyderproject
203 | .spyproject
204 |
205 | # Rope project settings
206 | .ropeproject
207 |
208 | # mkdocs documentation
209 | /site
210 |
211 | # mypy
212 | .mypy_cache/
213 |
214 | ### VisualStudioCode ###
215 | .vscode/*
216 | !.vscode/settings.json
217 | !.vscode/tasks.json
218 | !.vscode/launch.json
219 | !.vscode/extensions.json
220 | .history
221 |
222 | ### Windows ###
223 | # Windows thumbnail cache files
224 | Thumbs.db
225 | ehthumbs.db
226 | ehthumbs_vista.db
227 |
228 | # Folder config file
229 | Desktop.ini
230 |
231 | # Recycle Bin used on file shares
232 | $RECYCLE.BIN/
233 |
234 | # Windows Installer files
235 | *.cab
236 | *.msi
237 | *.msm
238 | *.msp
239 |
240 | # Windows shortcuts
241 | *.lnk
242 |
243 | # Build folder
244 |
245 | */build/*
246 |
247 | # End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *main* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT No Attribution
2 |
3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Infrastructure as Code Network Tester (IaC Network Tester)
2 |
3 | This package provides a a tool that helps you run connectivity testing against a set of source and destination resources to ensure configuration matches intent.
4 |
5 | For infrastructure deployed via AWS Console, SDK or CLI, the tester can be run post infrastructure deployment by specifying the stack name and the logical identifier of the stack output.
6 |
7 | For infrastructure is deployed via a CI/CD pipeline, the tester can be integrated into the testing phase of the pipeline prior to deploying to production.
8 |
9 | A detailed walkthrough of this tool can be found in this [blog post](https://aws.amazon.com/blogs/networking-and-content-delivery/integrating-network-connectivity-testing-with-infrastructure-deployment/)
10 |
11 |
12 |
13 |
14 |
15 | ## What does the state machine look like?
16 |
17 | The state machine is shown in the figure below. The high level logic implemented on the state machine to carry out the test include:
18 |
19 | 1. Identify the routes to be tested by retrieving the JSON formatted output from the stack output
20 | 2. Start the network test by invoking VPC Reachability Analyzer concurrently for the routes
21 | 3. Wait for the test to run and retrieve the test results
22 | 4. Clean up the VPC Reachability Analyzer resources used to carry out the test
23 | 5. Repeat the process from Step 2 if more than five routes are to be tested (the tool tests in batches of 5 due to the quota for concurrent analyses for VPC Reachability Analyzer)
24 |
25 |
26 |
27 |
28 |
29 | ## How to deploy the state machine
30 |
31 | IaC Network Tester is a SAM application with the lambda functions developed in Python. To deploy the SAM application you can use AWS CloudShell (which comes with AWS CLI, SAM CLI and Python pre-installed). You can also use any other CLI tool but you will need to install the dependencies. See below for instructions
32 |
33 | - Install Python and its package manager, pip, if they are not already installed. To download and install the latest version of Python, [visit the Python website](https://www.python.org/).
34 |
35 | - Install the latest version of the AWS CLI on your Linux, macOS, Windows, or Unix computer. You can find instructions [here](https://docs.aws.amazon.com/cli/latest/userguide/installing.html).
36 |
37 | - [Install SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)
38 |
39 | Deploy the IaC Network Tester application on your AWS account:
40 |
41 | ```bash
42 | sam build
43 | sam deploy --guided
44 | ```
45 |
46 | ## How to do I use IaC Network Tester for my Infrastructure as Code Template
47 |
48 | The IaC Network Tester can be used for infrastructure deployed directly via the AWS Console, SDK or CLI. It can also be used for infrastructure deployed via a CI/CD Pipeline. To use the tool for your IaC tempalte, one of the OUTPUTS from the stack must be a JSON formatted array with each item on the array containing the following keys:
49 |
50 | - **Source** - The source resource where the traffic will originate
51 | - **Destination** - The destination resource where the traffic will terminate
52 | - **RouteTag** - An identifier for the source and destination route
53 |
54 | See sample JSON formatted array below. More routes can be added following the same pattern.
55 |
56 | ```yaml
57 | Outputs:
58 | NetworkReachabilityTestPaths:
59 | Value: !Sub |
60 | [
61 | {"Source":"${AppServerInstance}", "Destination":"${InternetGateway}","RouteTag":"AppToInternet"},
62 | {"Source":"${WebServerInstance}", "Destination":"${InternetGateway}","RouteTag":"WebToInternet"},
63 | {"Source":"${InternetGateway}", "Destination":"${DBServerInstance}","RouteTag":"InternetToDB"},
64 | {"Source":"${InternetGateway}", "Destination":"${AppServerInstance}","RouteTag":"InternetToApp"},
65 | {"Source":"${InternetGateway}", "Destination":"${WebServerInstance}","RouteTag":"InternetToWeb"}
66 | ]
67 | ```
68 |
69 | For a detailed walkthrough of how the tool can be used on a sample template, refer to this [blog post](https://aws.amazon.com/blogs/networking-and-content-delivery/integrating-network-connectivity-testing-with-infrastructure-deployment/)
70 |
71 | ## How to execute the state machine
72 |
73 | Run the IaC Network Tester state machine by starting a new execution using the command below. The `` parameter is the arn of the IaC Network Tester State Machine.
74 |
75 | ```bash
76 | aws stepfunctions start-execution \
77 | --state-machine-arn \
78 | --input "{\"stackName\": \"\", \"routeToTestOutputKey\": \"\", \"analysisDuration\": 15, \"analysisWaitCount\": 3}"
79 | ```
80 |
81 | - **stackName** - the name of the CloudFormation stack to test
82 | - **routeToTestOutputKey** - the output key of the CloudFormation stack that contains the JSON formatted string for the route to test
83 | - **analysisDuration** - the duration in seconds which specifies the time to wait for the VPC Reachability Analysis to run after initiating the analysis.
84 | - **analysisWaitCount** - the number of times to wait for the analysis to run if after the `analysisDuration` the test is still running. Each wait is the duration specified in `analysisDuration`.
85 |
86 | ## What results can I expect from IaC Network Tester?
87 |
88 | A sample output from the IaC Network Tester state machine is shown below. It is a JSON formatted string that shows the tests that succeeded, timed out (if the analysis did not complete within the configured time) or failed. For each route tested, the results show the Source, Destination and the RouteTag which were specified in the input. Apart from these, additional parameters such as NetworkInsightsPathId, NetworkInsightsAnalysisId, are identifiers of internal analysis objects created. NetworkPathFound and Explanations are details from the VPC Reachability Analysis of each route. NetworkPathFound indicates if the route is reachable and if not the Explanations field provides details of why the route is not reachable
89 |
90 |
91 |
92 |
93 |
94 | ## Security
95 |
96 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
97 |
98 | ## License
99 |
100 | This library is licensed under the MIT-0 License. See the LICENSE file.
101 | Testing
102 |
--------------------------------------------------------------------------------
/check_network_test/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-iac-network-tester/f787ad4ccc51d1f939f12506e9aabc7031b6bec0/check_network_test/__init__.py
--------------------------------------------------------------------------------
/check_network_test/app.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import botocore
3 | import boto3
4 |
5 | ec2Client = boto3.client('ec2')
6 |
7 |
8 | def lambda_handler(event, context):
9 | inflight_network_test_details = event['globalvars']['routedetails']['inflightroutetotest']['inflightroutes']
10 |
11 | logging.info("inflight network tests >> " +
12 | str(inflight_network_test_details))
13 |
14 | updated_next_route_to_test = []
15 | runningtestscount = 0
16 |
17 | # Populating Test Results
18 | if inflight_network_test_details:
19 | for test_detail in inflight_network_test_details:
20 | logging.info("test detail >> " + str(test_detail))
21 | try:
22 | response = ec2Client.describe_network_insights_analyses(
23 | NetworkInsightsAnalysisIds=[
24 | test_detail['NetworkInsightsAnalysisId']],
25 | NetworkInsightsPathId=test_detail['NetworkInsightsPathId']
26 | )
27 | except botocore.exceptions.ClientError as error:
28 | logging.error(
29 | "Call to describe_network_insights_analyses failed")
30 | raise error
31 |
32 | test_status = response['NetworkInsightsAnalyses'][0]['Status']
33 |
34 | updated_route_details = {
35 | 'Source': test_detail['Source'],
36 | 'Destination': test_detail['Destination'],
37 | 'RouteTag': test_detail['RouteTag'],
38 | 'NetworkInsightsPathId': test_detail['NetworkInsightsPathId'],
39 | 'NetworkInsightsAnalysisId': test_detail['NetworkInsightsAnalysisId'],
40 | 'Status': test_status
41 | }
42 |
43 | if (test_status == 'succeeded'):
44 | updated_route_details['NetworkPathFound'] = response['NetworkInsightsAnalyses'][0]['NetworkPathFound']
45 | if not updated_route_details['NetworkPathFound']:
46 | updated_route_details['Explanations'] = response['NetworkInsightsAnalyses'][0]['Explanations']
47 |
48 | if(test_status == 'running'):
49 | runningtestscount += 1
50 |
51 | updated_next_route_to_test.append(updated_route_details)
52 |
53 | return {
54 | "inflightroutes": updated_next_route_to_test,
55 | "runningtestscount": runningtestscount
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/check_network_test/requirements.txt:
--------------------------------------------------------------------------------
1 | requests
2 | boto3
--------------------------------------------------------------------------------
/delete_test_resources/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-iac-network-tester/f787ad4ccc51d1f939f12506e9aabc7031b6bec0/delete_test_resources/__init__.py
--------------------------------------------------------------------------------
/delete_test_resources/app.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import botocore
3 | import boto3
4 |
5 | cloudFormationClient = boto3.client('cloudformation')
6 | ec2Client = boto3.client('ec2')
7 |
8 |
9 | def lambda_handler(event, context):
10 | inflight_network_test_details = event['globalvars']['routedetails']['inflightroutetotest']['inflightroutes']
11 | logging.info("event >> " + str(event))
12 |
13 | delete_network_insights_resources(inflight_network_test_details)
14 | updatedTestResult = format_test_output(
15 | inflight_network_test_details, event)
16 |
17 | return updatedTestResult
18 |
19 |
20 | def delete_network_insights_resources(inflight_network_test_details):
21 |
22 | if inflight_network_test_details:
23 | for test_detail in inflight_network_test_details:
24 | try:
25 | ec2Client.delete_network_insights_analysis(
26 | NetworkInsightsAnalysisId=test_detail['NetworkInsightsAnalysisId']
27 | )
28 | except botocore.exceptions.ClientError as error:
29 | logging.error(
30 | "Call to delete_network_insights_analysis failed")
31 | raise error
32 |
33 | try:
34 | ec2Client.delete_network_insights_path(
35 | NetworkInsightsPathId=test_detail['NetworkInsightsPathId']
36 | )
37 | except botocore.exceptions.ClientError as error:
38 | logging.error("Call to delete_network_insights_path failed")
39 | raise error
40 |
41 |
42 | def format_test_output(inflight_network_test_details, event):
43 | testResult = {}
44 |
45 | successful_tests = []
46 | timedout_tests = []
47 | failed_tests = []
48 |
49 | for test_detail in inflight_network_test_details:
50 | if (test_detail['Status'] == 'succeeded'):
51 | successful_tests.append(test_detail)
52 | elif(test_detail['Status'] == 'running'):
53 | timedout_tests.append(test_detail)
54 | elif(test_detail['Status'] == 'running'):
55 | failed_tests.append(test_detail)
56 |
57 | try:
58 | testResult = event['testresult']
59 | successful_tests = successful_tests + \
60 | testResult['succeeded']['testdetail']
61 | timedout_tests = timedout_tests + testResult['running']['testdetail']
62 | failed_tests = failed_tests + testResult['failed']['testdetail']
63 |
64 | except KeyError:
65 | logging.info("first time processing test results")
66 |
67 | updatedTestResult = {
68 | "succeeded": {
69 | "testdetail": successful_tests,
70 | "count": len(successful_tests)
71 | },
72 | "timedout": {
73 | "testdetail": timedout_tests,
74 | "count": len(timedout_tests)
75 | },
76 | "failed": {
77 | "testdetail": failed_tests,
78 | "count": len(failed_tests)
79 | },
80 | }
81 |
82 | return updatedTestResult
83 |
--------------------------------------------------------------------------------
/delete_test_resources/requirements.txt:
--------------------------------------------------------------------------------
1 | requests
2 | boto3
--------------------------------------------------------------------------------
/images/Fig10v2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-iac-network-tester/f787ad4ccc51d1f939f12506e9aabc7031b6bec0/images/Fig10v2.png
--------------------------------------------------------------------------------
/images/Fig15v2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-iac-network-tester/f787ad4ccc51d1f939f12506e9aabc7031b6bec0/images/Fig15v2.png
--------------------------------------------------------------------------------
/images/IaCStateMachine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-iac-network-tester/f787ad4ccc51d1f939f12506e9aabc7031b6bec0/images/IaCStateMachine.png
--------------------------------------------------------------------------------
/images/hl_architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-iac-network-tester/f787ad4ccc51d1f939f12506e9aabc7031b6bec0/images/hl_architecture.png
--------------------------------------------------------------------------------
/retrieve_path_to_test/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-iac-network-tester/f787ad4ccc51d1f939f12506e9aabc7031b6bec0/retrieve_path_to_test/__init__.py
--------------------------------------------------------------------------------
/retrieve_path_to_test/app.py:
--------------------------------------------------------------------------------
1 | import json
2 | import logging
3 | import botocore
4 | import boto3
5 |
6 | cloudFormationClient = boto3.client('cloudformation')
7 | ec2Client = boto3.client('ec2')
8 |
9 |
10 | def lambda_handler(event, context):
11 |
12 | currentRouteIndex = event['globalvars']['routedetails']['currentrouteindex']
13 | testBatchSize = event['globalvars']['routedetails']['testbatchsize']
14 | allRoutes = {}
15 |
16 | if(currentRouteIndex == 0):
17 | cloudFormationStackName = event['stackName']
18 | routeToTestOutputKey = event['routeToTestOutputKey']
19 | logging.info("stack name>> " + str(cloudFormationStackName))
20 | logging.info("route to test output key>> " + str(routeToTestOutputKey))
21 |
22 | if cloudFormationStackName and routeToTestOutputKey:
23 | try:
24 | cfnStack = cloudFormationClient.describe_stacks(
25 | StackName=cloudFormationStackName
26 | )
27 | except botocore.exceptions.ClientError as error:
28 | logging.error("Call to describe_stacks failed")
29 | raise error
30 |
31 | stackDetails = cfnStack['Stacks'][0]
32 | try:
33 | allRoutes = json.loads(get_network_path_from_cfnoutput(
34 | stackDetails,
35 | routeToTestOutputKey
36 | ))
37 | except botocore.exceptions.ClientError as error:
38 | logging.error("Call to get_network_path_from_cfoutput failed")
39 | raise error
40 |
41 | else:
42 | logging.error(
43 | "The two parameters stackNames and routeToTestOutputKey are required")
44 | raise botocore.exceptions.ClientError
45 |
46 | else:
47 | allRoutes = event['globalvars']['routedetails']['allroutes']
48 |
49 | routeDetails = inflight_routes_to_test(
50 | allRoutes, currentRouteIndex, testBatchSize)
51 |
52 | return routeDetails
53 |
54 |
55 | def get_network_path_from_cfnoutput(stackDetails, outputName):
56 | completeStatus = ['CREATE_COMPLETE', 'IMPORT_COMPLETE', 'UPDATE_COMPLETE']
57 | if stackDetails['StackStatus'] in completeStatus:
58 | stackOuputs = stackDetails['Outputs']
59 | logging.info("cf outputs >> " + str(stackOuputs))
60 |
61 | for output in stackOuputs:
62 | if output['OutputKey'] == outputName:
63 | networkTestDetails = output['OutputValue']
64 | logging.info("cf output details >> " + str(networkTestDetails))
65 | return networkTestDetails
66 | pass
67 |
68 |
69 | def inflight_routes_to_test(allRoutes, currentRouteIndex, testBatchSize):
70 | if((currentRouteIndex + testBatchSize) < len(allRoutes)):
71 | inflightRoutesToTest = allRoutes[currentRouteIndex:(
72 | currentRouteIndex + testBatchSize)]
73 | currentRouteIndex += testBatchSize
74 | isAllRoutesTested = False
75 | else:
76 | inflightRoutesToTest = allRoutes[currentRouteIndex:]
77 | isAllRoutesTested = True
78 |
79 | return {
80 | "inflightroutetotest": {
81 | "inflightroutes": inflightRoutesToTest,
82 | "runningtestscount": 0
83 | },
84 | "currentrouteindex": currentRouteIndex,
85 | "testbatchsize": testBatchSize,
86 | "isallroutestested": isAllRoutesTested,
87 | "allroutes": allRoutes,
88 | }
89 |
--------------------------------------------------------------------------------
/retrieve_path_to_test/requirements.txt:
--------------------------------------------------------------------------------
1 | requests
2 | boto3
--------------------------------------------------------------------------------
/samconfig.toml:
--------------------------------------------------------------------------------
1 | version = 0.1
2 | [default]
3 | [default.deploy]
4 | [default.deploy.parameters]
5 | stack_name = "iac-network-tester"
6 | s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-z47vr6vdnys2"
7 | s3_prefix = "iac-network-tester"
8 | region = "eu-central-1"
9 | capabilities = "CAPABILITY_IAM"
10 |
--------------------------------------------------------------------------------
/sample_resources/sample-pipeline.yml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: "2010-09-09"
2 |
3 | Description: >
4 | AWS CloudFormation Sample Template Continuous Delievery: This template
5 | builds an AWS CodePipeline pipeline that implements a continuous delivery release
6 | process for AWS CloudFormation stacks. Submit a CloudFormation source artifact
7 | to an Amazon S3 location before building the pipeline. The pipeline uses the
8 | artifact to automatically create stacks and change sets.
9 |
10 | Parameters:
11 | PipelineName:
12 | Default: iac-network-tester-sample-pipeline
13 | Description: Pipeline Name
14 | Type: String
15 | SourceS3Key:
16 | Default: iacnetworktestersamplestack.zip
17 | Description: The file name of the source artifact, such as myfolder/myartifact.zip
18 | Type: String
19 | TemplateFileName:
20 | Default: sample-stack-multi-vpc.yml
21 | Description: The file name of the network deployment
22 | Type: String
23 | TestStackName:
24 | Default: Test-IaCNetworkTesterSampleInfra
25 | Description: A name for the test env
26 | Type: String
27 | ProdStackName:
28 | Default: Prod-IaCNetworkTesterSampleInfra
29 | Description: A name for the production env
30 | Type: String
31 | ChangeSetName:
32 | Default: UpdatePreview-IaCNetworkTesterChangeSet
33 | Description: A name for the production stack change set
34 | Type: String
35 | Email:
36 | Description: The email address where CodePipeline sends pipeline notifications
37 | Type: String
38 | IaCNetworkTesterStateMachineArn:
39 | Description: Network test state machine Step Functions ARN
40 | Type: String
41 | IaCNetworkTesterRouteToTestOuputKey:
42 | Default: NetworkReachabilityTestPaths
43 | Description: The output key of the CloudFormation stack that contains the JSON formatted string for the route to test
44 | Type: String
45 | IaCNetworkTesterAnalysisDuration:
46 | Default: 15
47 | Description: The duration in seconds which specifies the time to wait for the VPC Reachability Analysis to run after initiating the analysis
48 | Type: Number
49 | IaCNetworkTesterAnalysisWaitCount:
50 | Default: 3
51 | Description: The number of times to wait for the analysis to run if after the “analysisDuration“ the test is still running. Each wait is the duration specified in “analysisDuration”.
52 | Type: Number
53 |
54 | Metadata:
55 | AWS::CloudFormation::Interface:
56 | ParameterGroups:
57 | - Label:
58 | default: "CodePipeline Settings"
59 | Parameters:
60 | - PipelineName
61 | - SourceS3Key
62 | - Email
63 | - Label:
64 | default: "Test Stack Settings"
65 | Parameters:
66 | - TestStackName
67 | - TemplateFileName
68 | - Label:
69 | default: "Production Stack Settings"
70 | Parameters:
71 | - ChangeSetName
72 | - ProdStackName
73 | - Label:
74 | default: "IaC Network Tester Settings"
75 | Parameters:
76 | - IaCNetworkTesterStateMachineArn
77 | - IaCNetworkTesterRouteToTestOuputKey
78 | - IaCNetworkTesterAnalysisDuration
79 | - IaCNetworkTesterAnalysisWaitCount
80 |
81 | Resources:
82 | ArtifactStoreBucket:
83 | Type: AWS::S3::Bucket
84 | Properties:
85 | VersioningConfiguration:
86 | Status: Enabled
87 |
88 | SourceBucket:
89 | Type: AWS::S3::Bucket
90 | Properties:
91 | BucketName: !Sub
92 | - ${AccountID}-iac-nt-bucket
93 | - AccountID: !Ref AWS::AccountId
94 | VersioningConfiguration:
95 | Status: Enabled
96 |
97 | CodePipelineSNSTopic:
98 | Type: AWS::SNS::Topic
99 | Properties:
100 | Subscription:
101 | - Endpoint: !Ref Email
102 | Protocol: email
103 |
104 | Pipeline:
105 | Type: AWS::CodePipeline::Pipeline
106 | Properties:
107 | ArtifactStore:
108 | Location: !Ref "ArtifactStoreBucket"
109 | Type: S3
110 | DisableInboundStageTransitions: []
111 | Name: !Ref "PipelineName"
112 | RoleArn: !GetAtt [PipelineRole, Arn]
113 | Stages:
114 | - Name: S3Source
115 | Actions:
116 | - Name: TemplateSource
117 | ActionTypeId:
118 | Category: Source
119 | Owner: AWS
120 | Provider: S3
121 | Version: "1"
122 | Configuration:
123 | S3Bucket: !Ref "SourceBucket"
124 | S3ObjectKey: !Ref "SourceS3Key"
125 | OutputArtifacts:
126 | - Name: TemplateSource
127 | RunOrder: "1"
128 | - Name: TestStage
129 | Actions:
130 | - Name: CreateStack
131 | ActionTypeId:
132 | Category: Deploy
133 | Owner: AWS
134 | Provider: CloudFormation
135 | Version: "1"
136 | InputArtifacts:
137 | - Name: TemplateSource
138 | Configuration:
139 | ActionMode: REPLACE_ON_FAILURE
140 | RoleArn: !GetAtt [CFNRole, Arn]
141 | StackName: !Ref TestStackName
142 | TemplatePath: !Sub "TemplateSource::${TemplateFileName}"
143 | RunOrder: "1"
144 | - Name: ExecuteNetworkTest
145 | ActionTypeId:
146 | Category: Invoke
147 | Owner: AWS
148 | Provider: StepFunctions
149 | Version: 1
150 | Configuration:
151 | StateMachineArn: !Ref IaCNetworkTesterStateMachineArn
152 | ExecutionNamePrefix: iac-network-tester
153 | Input: !Join
154 | - ""
155 | - - '{"stackName":"'
156 | - !Ref TestStackName
157 | - '",'
158 | - '"routeToTestOutputKey": "'
159 | - !Ref IaCNetworkTesterRouteToTestOuputKey
160 | - '",'
161 | - '"analysisDuration": '
162 | - !Ref IaCNetworkTesterAnalysisDuration
163 | - ","
164 | - '"analysisWaitCount": '
165 | - !Ref IaCNetworkTesterAnalysisWaitCount
166 | - "}"
167 | OutputArtifacts:
168 | - Name: networkTestOutput
169 | RunOrder: "2"
170 | - Name: ApproveTestStack
171 | ActionTypeId:
172 | Category: Approval
173 | Owner: AWS
174 | Provider: Manual
175 | Version: "1"
176 | Configuration:
177 | NotificationArn: !Ref CodePipelineSNSTopic
178 | CustomData: !Sub "Do you want to create a change set against the production stack and delete the ${TestStackName} stack?"
179 | RunOrder: "3"
180 | - Name: DeleteTestStack
181 | ActionTypeId:
182 | Category: Deploy
183 | Owner: AWS
184 | Provider: CloudFormation
185 | Version: "1"
186 | Configuration:
187 | ActionMode: DELETE_ONLY
188 | RoleArn: !GetAtt [CFNRole, Arn]
189 | StackName: !Ref TestStackName
190 | RunOrder: "4"
191 | - Name: ProdStage
192 | Actions:
193 | - Name: CreateChangeSet
194 | ActionTypeId:
195 | Category: Deploy
196 | Owner: AWS
197 | Provider: CloudFormation
198 | Version: "1"
199 | InputArtifacts:
200 | - Name: TemplateSource
201 | Configuration:
202 | ActionMode: CHANGE_SET_REPLACE
203 | RoleArn: !GetAtt [CFNRole, Arn]
204 | StackName: !Ref ProdStackName
205 | ChangeSetName: !Ref ChangeSetName
206 | TemplatePath: !Sub "TemplateSource::${TemplateFileName}"
207 | RunOrder: "1"
208 | - Name: ApproveChangeSet
209 | ActionTypeId:
210 | Category: Approval
211 | Owner: AWS
212 | Provider: Manual
213 | Version: "1"
214 | Configuration:
215 | NotificationArn: !Ref CodePipelineSNSTopic
216 | CustomData: !Sub "A new change set was created for the ${ProdStackName} stack. Do you want to implement the changes?"
217 | RunOrder: "2"
218 | - Name: ExecuteChangeSet
219 | ActionTypeId:
220 | Category: Deploy
221 | Owner: AWS
222 | Provider: CloudFormation
223 | Version: "1"
224 | Configuration:
225 | ActionMode: CHANGE_SET_EXECUTE
226 | ChangeSetName: !Ref ChangeSetName
227 | RoleArn: !GetAtt [CFNRole, Arn]
228 | StackName: !Ref ProdStackName
229 | RunOrder: "3"
230 |
231 | CFNRole:
232 | Type: AWS::IAM::Role
233 | Properties:
234 | AssumeRolePolicyDocument:
235 | Statement:
236 | - Action: ["sts:AssumeRole"]
237 | Effect: Allow
238 | Principal:
239 | Service: [cloudformation.amazonaws.com]
240 | Version: "2012-10-17"
241 | Path: /
242 | Policies:
243 | - PolicyName: CloudFormationRole
244 | PolicyDocument:
245 | Version: "2012-10-17"
246 | Statement:
247 | - Action:
248 | - "ec2:*"
249 | - "ssm:*"
250 | Effect: Allow
251 | Resource: "*"
252 |
253 | PipelineRole:
254 | Type: AWS::IAM::Role
255 | Properties:
256 | AssumeRolePolicyDocument:
257 | Statement:
258 | - Action: ["sts:AssumeRole"]
259 | Effect: Allow
260 | Principal:
261 | Service: [codepipeline.amazonaws.com]
262 | Version: "2012-10-17"
263 | Path: /
264 | Policies:
265 | - PolicyName: NetworkTestStepFunctionAccess
266 | PolicyDocument:
267 | Version: "2012-10-17"
268 | Statement:
269 | - Action:
270 | - "states:DescribeExecution"
271 | - "states:StartExecution"
272 | - "states:DescribeStateMachine"
273 | - "states:ListExecutions"
274 | Effect: Allow
275 | Resource: "*"
276 | - PolicyName: CodePipelineAccess
277 | PolicyDocument:
278 | Version: "2012-10-17"
279 | Statement:
280 | - Action:
281 | - "s3:*"
282 | - "cloudformation:CreateStack"
283 | - "cloudformation:DescribeStacks"
284 | - "cloudformation:DeleteStack"
285 | - "cloudformation:UpdateStack"
286 | - "cloudformation:CreateChangeSet"
287 | - "cloudformation:ExecuteChangeSet"
288 | - "cloudformation:DeleteChangeSet"
289 | - "cloudformation:DescribeChangeSet"
290 | - "cloudformation:SetStackPolicy"
291 | - "iam:PassRole"
292 | - "sns:Publish"
293 | Effect: Allow
294 | Resource: "*"
295 |
--------------------------------------------------------------------------------
/sample_resources/sample-stack-3-tier-app.yml:
--------------------------------------------------------------------------------
1 | ---
2 | Description: An AWS VPC configuration with 1 subnet, 2 security groups and 3 instances. When testing ReachabilityAnalyzer, this provides both a path found and path not found scenario.
3 | AWSTemplateFormatVersion: 2010-09-09
4 |
5 | Parameters:
6 | LatestAmiId:
7 | Type: "AWS::SSM::Parameter::Value"
8 | Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
9 |
10 | Resources:
11 | # VPC
12 | VPC:
13 | Type: AWS::EC2::VPC
14 | Properties:
15 | CidrBlock: 172.0.0.0/16
16 | EnableDnsSupport: true
17 | EnableDnsHostnames: true
18 | InstanceTenancy: default
19 |
20 | # Subnets
21 | WebServerSubnet:
22 | Type: AWS::EC2::Subnet
23 | Properties:
24 | VpcId: !Ref VPC
25 | CidrBlock: 172.0.0.0/21
26 | MapPublicIpOnLaunch: true
27 |
28 | WebServerSubnetRoutTableAss:
29 | Type: AWS::EC2::SubnetRouteTableAssociation
30 | Properties:
31 | RouteTableId: !Ref PublicRouteTable
32 | SubnetId: !Ref WebServerSubnet
33 |
34 | # Subnets
35 | AppServerSubnet:
36 | Type: AWS::EC2::Subnet
37 | Properties:
38 | VpcId: !Ref VPC
39 | CidrBlock: 172.0.8.0/21
40 | MapPublicIpOnLaunch: false
41 |
42 | AppServerSubnetRoutTableAss:
43 | Type: AWS::EC2::SubnetRouteTableAssociation
44 | Properties:
45 | RouteTableId: !Ref PrivateRouteTable
46 | SubnetId: !Ref AppServerSubnet
47 |
48 | # Subnets
49 | DBServerSubnet:
50 | Type: AWS::EC2::Subnet
51 | Properties:
52 | VpcId: !Ref VPC
53 | CidrBlock: 172.0.16.0/21
54 | MapPublicIpOnLaunch: false
55 |
56 | DBServerSubnetRoutTableAss:
57 | Type: AWS::EC2::SubnetRouteTableAssociation
58 | Properties:
59 | RouteTableId: !Ref PrivateRouteTable
60 | SubnetId: !Ref DBServerSubnet
61 |
62 | # SGs
63 | WebServerSG:
64 | Type: AWS::EC2::SecurityGroup
65 | Properties:
66 | GroupDescription: Allow http(s) and egress traffic
67 | VpcId: !Ref VPC
68 | SecurityGroupIngress:
69 | - IpProtocol: tcp
70 | FromPort: 80
71 | ToPort: 80
72 | CidrIp: 0.0.0.0/0
73 | SecurityGroupEgress:
74 | - CidrIp: 172.0.8.0/21
75 | FromPort: 80
76 | ToPort: 80
77 | IpProtocol: tcp
78 | - IpProtocol: tcp
79 | FromPort: 80
80 | ToPort: 80
81 | CidrIp: 0.0.0.0/0
82 |
83 | AppServerSG:
84 | Type: AWS::EC2::SecurityGroup
85 | Properties:
86 | GroupDescription: Allow all ingress from WebServerSG and all egress to DBServerSG and to download update from the internet
87 | VpcId: !Ref VPC
88 | SecurityGroupIngress:
89 | - CidrIp: 172.0.0.0/21
90 | FromPort: 80
91 | ToPort: 80
92 | IpProtocol: tcp
93 | SecurityGroupEgress:
94 | - CidrIp: 172.0.16.0/21
95 | FromPort: 3306
96 | ToPort: 3306
97 | IpProtocol: tcp
98 | - IpProtocol: tcp
99 | FromPort: 80
100 | ToPort: 80
101 | CidrIp: 0.0.0.0/0
102 |
103 | DBServerSG:
104 | Type: AWS::EC2::SecurityGroup
105 | Properties:
106 | GroupDescription: Allow all ingress from AppServer and egress to download update from the internet
107 | VpcId: !Ref VPC
108 | SecurityGroupIngress:
109 | - CidrIp: 172.0.8.0/21
110 | FromPort: 3306
111 | ToPort: 3306
112 | IpProtocol: tcp
113 | SecurityGroupEgress:
114 | - IpProtocol: tcp
115 | FromPort: 80
116 | ToPort: 80
117 | CidrIp: 0.0.0.0/0
118 |
119 | # NAT Gateway
120 | NATGateway:
121 | Type: AWS::EC2::NatGateway
122 | Properties:
123 | AllocationId:
124 | Fn::GetAtt:
125 | - EIP
126 | - AllocationId
127 | SubnetId:
128 | Ref: WebServerSubnet
129 |
130 | # NAT Gateway EIP
131 | EIP:
132 | DependsOn: GatewayToInternet
133 | Type: AWS::EC2::EIP
134 | Properties:
135 | Domain: vpc
136 |
137 | # Internet Gateway
138 | InternetGateway:
139 | Type: AWS::EC2::InternetGateway
140 |
141 | # Internet Gateway Attachment
142 | GatewayToInternet:
143 | Type: AWS::EC2::VPCGatewayAttachment
144 | Properties:
145 | VpcId:
146 | Ref: VPC
147 | InternetGatewayId:
148 | Ref: InternetGateway
149 |
150 | # Route Tables
151 | PrivateRouteTable:
152 | Type: AWS::EC2::RouteTable
153 | Properties:
154 | VpcId:
155 | Ref: VPC
156 |
157 | PublicRouteTable:
158 | Type: AWS::EC2::RouteTable
159 | Properties:
160 | VpcId:
161 | Ref: VPC
162 |
163 | # Routes
164 | RouteToNATGateway:
165 | Type: AWS::EC2::Route
166 | Properties:
167 | DestinationCidrBlock: 0.0.0.0/0
168 | NatGatewayId:
169 | Ref: NATGateway
170 | RouteTableId:
171 | Ref: PrivateRouteTable
172 |
173 | RouteToInternetGateway:
174 | DependsOn: GatewayToInternet
175 | Type: AWS::EC2::Route
176 | Properties:
177 | DestinationCidrBlock: 0.0.0.0/0
178 | GatewayId:
179 | Ref: InternetGateway
180 | RouteTableId:
181 | Ref: PublicRouteTable
182 |
183 | # Instances
184 | WebServerInstance:
185 | DependsOn: GatewayToInternet
186 | Type: AWS::EC2::Instance
187 | Properties:
188 | ImageId: !Ref LatestAmiId
189 | InstanceType: "t3.nano"
190 | SubnetId:
191 | Ref: WebServerSubnet
192 | SecurityGroupIds:
193 | - Ref: WebServerSG
194 |
195 | AppServerInstance:
196 | Type: AWS::EC2::Instance
197 | Properties:
198 | ImageId: !Ref LatestAmiId
199 | InstanceType: "t3.nano"
200 | SubnetId:
201 | Ref: AppServerSubnet
202 | SecurityGroupIds:
203 | - Ref: AppServerSG
204 |
205 | DBServerInstance:
206 | Type: AWS::EC2::Instance
207 | Properties:
208 | ImageId:
209 | ImageId: !Ref LatestAmiId
210 | InstanceType: "t3.nano"
211 | SubnetId:
212 | Ref: DBServerSubnet
213 | SecurityGroupIds:
214 | - Ref: DBServerSG
215 |
216 | # Output VPC Reachability Analyzer Tests
217 | Outputs:
218 | NetworkReachabilityTestPaths:
219 | Value: !Sub |
220 | [
221 | {"Source":"${AppServerInstance}", "Destination":"${InternetGateway}","RouteTag":"AppToInternet"},
222 | {"Source":"${WebServerInstance}", "Destination":"${InternetGateway}","RouteTag":"WebToInternet"},
223 | {"Source":"${InternetGateway}", "Destination":"${DBServerInstance}","RouteTag":"InternetToDB"},
224 | {"Source":"${InternetGateway}", "Destination":"${AppServerInstance}","RouteTag":"InternetToApp"},
225 | {"Source":"${InternetGateway}", "Destination":"${WebServerInstance}","RouteTag":"InternetToWeb"}
226 | ]
227 |
--------------------------------------------------------------------------------
/sample_resources/sample-stack-multi-vpc.yml:
--------------------------------------------------------------------------------
1 | ---
2 | Description: Deploys 3 VPCs, 1 ActiveDirectory VPC and peering connection to specific subnets within the other 2 VPCs. When testing ReachabilityAnalyzer, this provides both a path found and path not found scenario.
3 | AWSTemplateFormatVersion: 2010-09-09
4 |
5 | Parameters:
6 | LatestAmiId:
7 | Type: "AWS::SSM::Parameter::Value"
8 | Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
9 |
10 | Resources:
11 | # VPCs
12 | ActiveDirectoryVPC:
13 | Type: AWS::EC2::VPC
14 | Properties:
15 | CidrBlock: 172.16.0.0/16
16 | EnableDnsSupport: true
17 | EnableDnsHostnames: true
18 | InstanceTenancy: default
19 |
20 | RemoteDesktopVPC:
21 | Type: AWS::EC2::VPC
22 | Properties:
23 | CidrBlock: 10.0.0.0/16
24 | EnableDnsSupport: true
25 | EnableDnsHostnames: true
26 | InstanceTenancy: default
27 |
28 | BusinessAppVPC:
29 | Type: AWS::EC2::VPC
30 | Properties:
31 | CidrBlock: 192.168.0.0/20
32 | EnableDnsSupport: true
33 | EnableDnsHostnames: true
34 | InstanceTenancy: default
35 |
36 | # Subnets
37 | ActiveDirectoryVPCSubnet:
38 | Type: AWS::EC2::Subnet
39 | Properties:
40 | VpcId: !Ref ActiveDirectoryVPC
41 | CidrBlock: 172.16.0.0/24
42 |
43 | RemoteDesktopVPCSubnet:
44 | Type: AWS::EC2::Subnet
45 | Properties:
46 | VpcId: !Ref RemoteDesktopVPC
47 | CidrBlock: 10.0.0.0/24
48 | MapPublicIpOnLaunch: true
49 |
50 | BusinessAppVPCSubnet:
51 | Type: AWS::EC2::Subnet
52 | Properties:
53 | VpcId: !Ref BusinessAppVPC
54 | CidrBlock: 192.168.14.0/24
55 | MapPublicIpOnLaunch: true
56 |
57 | # VPC Peering Connection
58 | ActiveDirectoryVPCToRemoteDesktopVPC:
59 | Type: AWS::EC2::VPCPeeringConnection
60 | Properties:
61 | VpcId: !Ref ActiveDirectoryVPC
62 | PeerVpcId: !Ref RemoteDesktopVPC
63 |
64 | ActiveDirectoryVPCToBusinessAppVPC:
65 | Type: AWS::EC2::VPCPeeringConnection
66 | Properties:
67 | VpcId: !Ref ActiveDirectoryVPC
68 | PeerVpcId: !Ref BusinessAppVPC
69 |
70 | # Internet Gateway
71 | RemoteDesktopVPCIG:
72 | Type: AWS::EC2::InternetGateway
73 |
74 | BusinessAppVPCIG:
75 | Type: AWS::EC2::InternetGateway
76 |
77 | # Internet Gateway Attachment
78 | RemoteDesktopIGAttach:
79 | Type: AWS::EC2::VPCGatewayAttachment
80 | Properties:
81 | VpcId:
82 | Ref: RemoteDesktopVPC
83 | InternetGatewayId:
84 | Ref: RemoteDesktopVPCIG
85 |
86 | BusinessAppVPCIGAttach:
87 | Type: AWS::EC2::VPCGatewayAttachment
88 | Properties:
89 | VpcId:
90 | Ref: BusinessAppVPC
91 | InternetGatewayId:
92 | Ref: BusinessAppVPCIG
93 |
94 | # Routes
95 | ActiveDirectoryVPCSubnetToRemoteDesktopVPCSubnet:
96 | Type: AWS::EC2::Route
97 | Properties:
98 | DestinationCidrBlock: 10.0.0.0/24
99 | VpcPeeringConnectionId:
100 | Ref: ActiveDirectoryVPCToRemoteDesktopVPC
101 | RouteTableId:
102 | Ref: ActiveDirectoryVPCSubnetARouteTable
103 |
104 | ActiveDirectoryVPCSubnetToBusinessAppVPCSubnet:
105 | Type: AWS::EC2::Route
106 | Properties:
107 | DestinationCidrBlock: 192.168.14.0/24
108 | VpcPeeringConnectionId:
109 | Ref: ActiveDirectoryVPCToBusinessAppVPC
110 | RouteTableId:
111 | Ref: ActiveDirectoryVPCSubnetARouteTable
112 |
113 | RemoteDesktopVPCSubnetToActiveDirectoryVPCSubnet:
114 | Type: AWS::EC2::Route
115 | Properties:
116 | DestinationCidrBlock: 172.16.0.0/24
117 | VpcPeeringConnectionId:
118 | Ref: ActiveDirectoryVPCToRemoteDesktopVPC
119 | RouteTableId:
120 | Ref: RemoteDesktopVPCSubnetRouteTable
121 |
122 | BusinessAppVPCSubnetToActiveDirectoryVPCSubnet:
123 | Type: AWS::EC2::Route
124 | Properties:
125 | DestinationCidrBlock: 172.16.0.0/24
126 | VpcPeeringConnectionId:
127 | Ref: ActiveDirectoryVPCToBusinessAppVPC
128 | RouteTableId:
129 | Ref: BusinessAppVPCSubnetRouteTable
130 |
131 | RemoteDesktopRouteToInternetGateway:
132 | DependsOn: RemoteDesktopIGAttach
133 | Type: AWS::EC2::Route
134 | Properties:
135 | DestinationCidrBlock: 0.0.0.0/0
136 | GatewayId:
137 | Ref: RemoteDesktopVPCIG
138 | RouteTableId:
139 | Ref: RemoteDesktopVPCSubnetRouteTable
140 |
141 | BusinessAppRouteToInternetGateway:
142 | DependsOn: BusinessAppVPCIGAttach
143 | Type: AWS::EC2::Route
144 | Properties:
145 | DestinationCidrBlock: 0.0.0.0/0
146 | GatewayId:
147 | Ref: BusinessAppVPCIG
148 | RouteTableId:
149 | Ref: BusinessAppVPCSubnetRouteTable
150 |
151 | # Route Tables
152 | ActiveDirectoryVPCSubnetARouteTable:
153 | Type: AWS::EC2::RouteTable
154 | Properties:
155 | VpcId:
156 | Ref: ActiveDirectoryVPC
157 |
158 | RemoteDesktopVPCSubnetRouteTable:
159 | Type: AWS::EC2::RouteTable
160 | Properties:
161 | VpcId:
162 | Ref: RemoteDesktopVPC
163 |
164 | BusinessAppVPCSubnetRouteTable:
165 | Type: AWS::EC2::RouteTable
166 | Properties:
167 | VpcId:
168 | Ref: BusinessAppVPC
169 |
170 | # Route Table Subnet Association
171 | ActiveDirectorySubnetRTAss:
172 | Type: AWS::EC2::SubnetRouteTableAssociation
173 | Properties:
174 | RouteTableId: !Ref ActiveDirectoryVPCSubnetARouteTable
175 | SubnetId: !Ref ActiveDirectoryVPCSubnet
176 |
177 | RemoteDesktopSubnetRTAss:
178 | Type: AWS::EC2::SubnetRouteTableAssociation
179 | Properties:
180 | RouteTableId: !Ref RemoteDesktopVPCSubnetRouteTable
181 | SubnetId: !Ref RemoteDesktopVPCSubnet
182 |
183 | BusinessAppSubnetRTAss:
184 | Type: AWS::EC2::SubnetRouteTableAssociation
185 | Properties:
186 | RouteTableId: !Ref BusinessAppVPCSubnetRouteTable
187 | SubnetId: !Ref BusinessAppVPCSubnet
188 |
189 | # Security Groups
190 | ActiveDirectoryServerSG:
191 | Type: AWS::EC2::SecurityGroup
192 | Properties:
193 | GroupDescription: Allow http(s) and egress traffic
194 | VpcId: !Ref ActiveDirectoryVPC
195 | SecurityGroupIngress:
196 | - IpProtocol: tcp
197 | FromPort: 53
198 | ToPort: 53
199 | CidrIp: 10.0.0.0/24
200 | - IpProtocol: tcp
201 | FromPort: 53
202 | ToPort: 53
203 | CidrIp: 192.168.14.0/24
204 | SecurityGroupEgress:
205 | - CidrIp: 172.16.0.0/24
206 | IpProtocol: "-1"
207 |
208 | RemoteDesktopServerSG:
209 | Type: AWS::EC2::SecurityGroup
210 | Properties:
211 | GroupDescription: Allow http(s) and egress traffic
212 | VpcId: !Ref RemoteDesktopVPC
213 | SecurityGroupIngress:
214 | - IpProtocol: tcp
215 | FromPort: 3389
216 | ToPort: 3389
217 | CidrIp: 0.0.0.0/0
218 | SecurityGroupEgress:
219 | - CidrIp: 172.16.0.0/16
220 | FromPort: 53
221 | ToPort: 53
222 | IpProtocol: tcp
223 |
224 | BusinessAppServerSG:
225 | Type: AWS::EC2::SecurityGroup
226 | Properties:
227 | GroupDescription: Allow http(s) and egress traffic
228 | VpcId: !Ref BusinessAppVPC
229 | SecurityGroupIngress:
230 | - IpProtocol: tcp
231 | FromPort: 80
232 | ToPort: 80
233 | CidrIp: 0.0.0.0/0
234 | SecurityGroupEgress:
235 | - CidrIp: 172.16.0.0/16
236 | FromPort: 53
237 | ToPort: 53
238 | IpProtocol: tcp
239 |
240 | # EC2 Instances
241 | ActiveDirectoryInstance:
242 | Type: AWS::EC2::Instance
243 | Properties:
244 | ImageId: !Ref LatestAmiId
245 | InstanceType: t2.micro
246 | SubnetId:
247 | Ref: ActiveDirectoryVPCSubnet
248 | SecurityGroupIds:
249 | - Ref: ActiveDirectoryServerSG
250 |
251 | RemoteDesktopInstance:
252 | Type: AWS::EC2::Instance
253 | Properties:
254 | ImageId: !Ref LatestAmiId
255 | InstanceType: t2.micro
256 | SubnetId:
257 | Ref: RemoteDesktopVPCSubnet
258 | SecurityGroupIds:
259 | - Ref: RemoteDesktopServerSG
260 |
261 | BusinessAppInstance:
262 | Type: AWS::EC2::Instance
263 | Properties:
264 | ImageId: !Ref LatestAmiId
265 | InstanceType: t2.micro
266 | SubnetId:
267 | Ref: BusinessAppVPCSubnet
268 | SecurityGroupIds:
269 | - Ref: BusinessAppServerSG
270 |
271 | # Routes for VPC Reachability Analyzer Tests
272 | Outputs:
273 | NetworkReachabilityTestPaths:
274 | Value: !Sub |
275 | [
276 | {"Source":"${RemoteDesktopInstance}", "Destination":"${ActiveDirectoryInstance}","RouteTag":"RemoteDesktopToActiveDirectory"},
277 | {"Source":"${BusinessAppInstance}", "Destination":"${ActiveDirectoryInstance}","RouteTag":"BusinessAppToActiveDirectory"},
278 | {"Source":"${BusinessAppVPCIG}", "Destination":"${BusinessAppInstance}","RouteTag":"InternetToBusinessApp"},
279 | {"Source":"${BusinessAppInstance}", "Destination":"${RemoteDesktopInstance}","RouteTag":"BusinessAppToRemoteDesktop"},
280 | {"Source":"${ActiveDirectoryInstance}", "Destination":"${BusinessAppInstance}","RouteTag":"ActiveDirectoryToBusinessApp"},
281 | {"Source":"${ActiveDirectoryInstance}", "Destination":"${RemoteDesktopInstance}","RouteTag":"ActiveDirectoryToRemoteDesktopApp"},
282 | {"Source":"${RemoteDesktopInstance}", "Destination":"${RemoteDesktopVPCIG}","RouteTag":"RemoteDesktopToInternet"},
283 | {"Source":"${RemoteDesktopVPCIG}", "Destination":"${RemoteDesktopInstance}","RouteTag":"InternetToRemoteDesktopApp"}
284 | ]
285 |
--------------------------------------------------------------------------------
/start_network_test/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-iac-network-tester/f787ad4ccc51d1f939f12506e9aabc7031b6bec0/start_network_test/__init__.py
--------------------------------------------------------------------------------
/start_network_test/app.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import botocore
3 | import boto3
4 |
5 | cloudFormationClient = boto3.client('cloudformation')
6 | ec2Client = boto3.client('ec2')
7 |
8 |
9 | def lambda_handler(event, context):
10 | try:
11 | response = ec2Client.create_network_insights_path(
12 | Source=event['Source'],
13 | Destination=event['Destination'],
14 | Protocol='tcp'
15 | )
16 | except botocore.exceptions.ClientError as error:
17 | logging.error("Call to create_network_insights_path failed")
18 | raise error
19 |
20 | networkInsightPath = response['NetworkInsightsPath']
21 | networkInsightPathId = networkInsightPath['NetworkInsightsPathId']
22 | logging.info("networkInsight Path Id >> " + str(networkInsightPathId))
23 |
24 | try:
25 | response = ec2Client.start_network_insights_analysis(
26 | NetworkInsightsPathId=networkInsightPathId
27 | )
28 | except botocore.exceptions.ClientError as error:
29 | logging.error("Call to start_network_insights_analysis failed")
30 | raise error
31 |
32 | networkInsightAnalysis = response['NetworkInsightsAnalysis']
33 | networkInsightAnalysisId = networkInsightAnalysis['NetworkInsightsAnalysisId']
34 | logging.info("networkInsight Analysis Id >> " + str(networkInsightAnalysisId))
35 |
36 | inflight_network_test_detail = {
37 | 'Source': event['Source'],
38 | 'Destination': event['Destination'],
39 | 'RouteTag': event['RouteTag'],
40 | 'NetworkInsightsPathId': networkInsightPathId,
41 | 'NetworkInsightsAnalysisId': networkInsightAnalysisId
42 | }
43 | return inflight_network_test_detail
44 |
--------------------------------------------------------------------------------
/start_network_test/requirements.txt:
--------------------------------------------------------------------------------
1 | requests
2 | boto3
--------------------------------------------------------------------------------
/statemachine/network-test.json:
--------------------------------------------------------------------------------
1 | {
2 | "Comment": "A state machine to carry out network testing on CloudFormation template",
3 | "StartAt": "InitializeTest",
4 | "States": {
5 | "InitializeTest": {
6 | "Type": "Pass",
7 | "Result": {
8 | "timerdetails":{
9 | "testtimerindex": 0
10 | },
11 | "routedetails":{
12 | "currentrouteindex": 0,
13 | "testbatchsize": 5,
14 | "isallroutestested": false
15 | }
16 | },
17 | "ResultPath": "$.globalvars",
18 | "Next": "RetrievePathToTest"
19 | },
20 | "RetrievePathToTest": {
21 | "Type": "Task",
22 | "Resource": "${RetrievePathToTestFunctionArn}",
23 | "ResultPath": "$.globalvars.routedetails",
24 | "Next": "StartAllNetworkTestState"
25 | },
26 | "StartAllNetworkTestState": {
27 | "Type": "Map",
28 | "ItemsPath": "$.globalvars.routedetails.inflightroutetotest.inflightroutes",
29 | "MaxConcurrency": 0,
30 | "Iterator":{
31 | "StartAt": "StartNetworkTest",
32 | "States":{
33 | "StartNetworkTest": {
34 | "Type": "Task",
35 | "Resource":"${StartNetworkTestFunctionArn}",
36 | "End": true
37 | }
38 | }
39 | },
40 | "ResultPath": "$.globalvars.routedetails.inflightroutetotest.inflightroutes",
41 | "Next": "WaitForTestToRun"
42 | },
43 | "WaitForTestToRun": {
44 | "Type": "Wait",
45 | "SecondsPath": "$.analysisDuration",
46 | "Next": "CheckTestStatus"
47 | },
48 | "CheckTestStatus": {
49 | "Type": "Task",
50 | "Resource": "${CheckNetworkTestStatusFunctionArn}",
51 | "ResultPath": "$.globalvars.routedetails.inflightroutetotest",
52 | "Next": "IsTestCompleted"
53 | },
54 | "IsTestCompleted": {
55 | "Type": "Choice",
56 | "Choices": [
57 | {
58 | "Variable": "$.globalvars.routedetails.inflightroutetotest.runningtestscount",
59 | "NumericGreaterThan": 0,
60 | "Next": "IncrementTestTimer"
61 | }
62 | ],
63 | "Default": "CleanUp"
64 | },
65 | "IncrementTestTimer": {
66 | "Type": "Task",
67 | "Resource": "${TestDurationIteratorFunctionArn}",
68 | "ResultPath": "$.globalvars.timerdetails",
69 | "Next": "IsTestDurationReached"
70 | },
71 | "IsTestDurationReached": {
72 | "Type": "Choice",
73 | "Choices": [
74 | {
75 | "Variable": "$.globalvars.timerdetails.continue",
76 | "BooleanEquals": true,
77 | "Next": "WaitForTestToRun"
78 | }
79 | ],
80 | "Default": "CleanUp"
81 | },
82 | "CleanUp":{
83 | "Type": "Task",
84 | "Resource": "${DeleteTestResourcesFunctionArn}",
85 | "ResultPath": "$.testresult",
86 | "Next": "IsAllRoutesTested"
87 | },
88 | "IsAllRoutesTested":{
89 | "Type": "Choice",
90 | "Choices":[
91 | {
92 | "Variable": "$.globalvars.routedetails.isallroutestested",
93 | "BooleanEquals": false,
94 | "Next": "RetrievePathToTest"
95 | }
96 | ],
97 | "Default": "Done"
98 | },
99 | "Done": {
100 | "Type": "Pass",
101 | "OutputPath": "$.testresult",
102 | "End": true
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/template.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: "2010-09-09"
2 | Transform: AWS::Serverless-2016-10-31
3 | Description: >
4 | IaC Network Tester Application
5 |
6 | Metadata:
7 | AWS::ServerlessRepo::Application:
8 | Name: aws-iac-network-tester
9 | Description: AWS IaC Network Tester is an opensource tool that can help you test the network reachability of resources deployed via Infrastructure as Code. It can be used for infrastructure deployed directly via AWS Console, CLI or SDK or via a Continuous Integration and Continuous Deployment pipeline. It will be deployed on your AWS Account and is powered by Step Functions that identify the routes to test and check for network reachability using the VPC Rechability Analyzer.
10 | Author: Ozioma Uzoegwu
11 | SpdxLicenseId: Apache-2.0
12 | LicenseUrl: LICENSE.txt
13 | ReadmeUrl: README.md
14 | Labels: ["IaC", "Network Tester", "VPC Rechability Analyzer"]
15 | HomePageUrl: https://github.com/aws-samples/aws-iac-network-tester
16 | SemanticVersion: 0.0.1
17 | SourceCodeUrl: https://github.com/aws-samples/aws-iac-network-tester
18 |
19 | Globals:
20 | Function:
21 | Timeout: 300
22 |
23 | Resources:
24 | NetworkTestStateMachine:
25 | Type: AWS::Serverless::StateMachine
26 | Properties:
27 | DefinitionUri: statemachine/network-test.json
28 | DefinitionSubstitutions:
29 | RetrievePathToTestFunctionArn: !GetAtt RetrievePathToTestFunction.Arn
30 | CheckNetworkTestStatusFunctionArn: !GetAtt CheckNetworkTestStatusFunction.Arn
31 | TestDurationIteratorFunctionArn: !GetAtt TestDurationIteratorFunction.Arn
32 | StartNetworkTestFunctionArn: !GetAtt StartNetworkTestFunction.Arn
33 | DeleteTestResourcesFunctionArn: !GetAtt DeleteTestResourcesFunction.Arn
34 | Tracing:
35 | Enabled: True
36 | Policies:
37 | - LambdaInvokePolicy:
38 | FunctionName: !Ref RetrievePathToTestFunction
39 | - LambdaInvokePolicy:
40 | FunctionName: !Ref CheckNetworkTestStatusFunction
41 | - LambdaInvokePolicy:
42 | FunctionName: !Ref TestDurationIteratorFunction
43 | - LambdaInvokePolicy:
44 | FunctionName: !Ref StartNetworkTestFunction
45 | - LambdaInvokePolicy:
46 | FunctionName: !Ref DeleteTestResourcesFunction
47 |
48 | RetrievePathToTestFunction:
49 | Type: AWS::Serverless::Function
50 | Properties:
51 | CodeUri: retrieve_path_to_test/
52 | Handler: app.lambda_handler
53 | Runtime: python3.7
54 | Policies:
55 | - CloudFormationDescribeStacksPolicy: {}
56 | - Statement:
57 | - Sid: CloudFormationDescribeStackResourcePolicy
58 | Effect: Allow
59 | Action:
60 | - cloudformation:DescribeStackResource
61 | Resource: "*"
62 |
63 | StartNetworkTestFunction:
64 | Type: AWS::Serverless::Function
65 | Properties:
66 | CodeUri: start_network_test/
67 | Handler: app.lambda_handler
68 | Runtime: python3.7
69 | Policies:
70 | - CloudFormationDescribeStacksPolicy: {}
71 | - Statement:
72 | - Sid: StartNetworkInsightAnalysisPolicy
73 | Effect: Allow
74 | Action:
75 | - "*"
76 | Resource: "*"
77 |
78 | CheckNetworkTestStatusFunction:
79 | Type: AWS::Serverless::Function
80 | Properties:
81 | CodeUri: check_network_test/
82 | Handler: app.lambda_handler
83 | Runtime: python3.7
84 | Policies:
85 | - Statement:
86 | - Sid: DescribeNetworkInsightsAnalysesPolicy
87 | Effect: Allow
88 | Action:
89 | - ec2:DescribeNetworkInsightsAnalyses
90 | Resource: "*"
91 |
92 | TestDurationIteratorFunction:
93 | Type: AWS::Serverless::Function
94 | Properties:
95 | CodeUri: test_duration_iterator/
96 | Handler: app.lambda_handler
97 | Runtime: python3.7
98 |
99 | DeleteTestResourcesFunction:
100 | Type: AWS::Serverless::Function
101 | Properties:
102 | CodeUri: delete_test_resources/
103 | Handler: app.lambda_handler
104 | Runtime: python3.7
105 | Policies:
106 | - Statement:
107 | - Sid: DeleteNetworkAnalysisPolicy
108 | Effect: Allow
109 | Action:
110 | - "*"
111 | Resource: "*"
112 | Outputs:
113 | IaCNetworkTesterStateMachineArn:
114 | Value: !GetAtt NetworkTestStateMachine.Arn
115 |
--------------------------------------------------------------------------------
/test_duration_iterator/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-iac-network-tester/f787ad4ccc51d1f939f12506e9aabc7031b6bec0/test_duration_iterator/__init__.py
--------------------------------------------------------------------------------
/test_duration_iterator/app.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 |
4 | def lambda_handler(event, context):
5 | logging.info("event>>> " + str(event))
6 |
7 | numberOfTimesToWait = event['analysisWaitCount']
8 | index = event['globalvars']['timerdetails']['testtimerindex']
9 | step = event['analysisDuration']
10 |
11 | totalWait = numberOfTimesToWait * step
12 | index += step
13 |
14 | return {
15 | "testtimerindex": index,
16 | "continue": index < totalWait,
17 | }
18 |
--------------------------------------------------------------------------------
/test_duration_iterator/requirements.txt:
--------------------------------------------------------------------------------
1 | requests
--------------------------------------------------------------------------------