├── .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 | High Level Architecture Diagram 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 | IaC Network Tester State Machine 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 | IaC Network Tester Output 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 --------------------------------------------------------------------------------