├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── aws ├── README.md └── scripts │ ├── 00-variables.sh │ ├── 01-create-eks-cluster.sh │ ├── 02-install-aws-load-balancer-controller.sh │ ├── 03-deploy-sample-app.sh │ ├── 04-create-yelb-ingress.sh │ ├── 05-test-app-via-ingress.sh │ ├── 06-create-aws-waf-web-acl-with-no-rules.sh │ ├── 07-update-aws-waf-web-acl-with-rules.sh │ ├── 08-call-yelb-via-curl.sh │ ├── 99-aws-commands.sh │ ├── dry-run-cluster.yaml │ ├── iam-policy.json │ ├── icontentsmd │ ├── waf-rules.json │ ├── yelb-ingress-waf.yaml │ ├── yelb-ingress.yaml │ └── yelb_initial_deployment.yaml ├── azure ├── README.md ├── nginx-with-azure-waf │ ├── README.md │ ├── bicep │ │ ├── actionGroup.bicep │ │ ├── aksCluster.bicep │ │ ├── aksManagedIdentity.bicep │ │ ├── applicationGateway.bicep │ │ ├── applicationGatewayIdentity.bicep │ │ ├── azuredeploy.json │ │ ├── azuredeploy.parameters.json │ │ ├── containerRegistry.bicep │ │ ├── deploy.sh │ │ ├── deploymentScript.bicep │ │ ├── dnsZone.bicep │ │ ├── install-packages.sh │ │ ├── internalLoadBalancer.bicep │ │ ├── keyVault.bicep │ │ ├── kubeletManagedIdentity.bicep │ │ ├── logAnalytics.bicep │ │ ├── main.bicep │ │ ├── main.http.nginxviaaddon.bicepparam │ │ ├── main.http.nginxviahelm.bicepparam │ │ ├── main.https.nginxviaaddon.bicepparam │ │ ├── main.https.nginxviahelm.bicepparam │ │ ├── managedGrafana.bicep │ │ ├── managedPrometheus.bicep │ │ ├── metricAlerts.bicep │ │ ├── network.bicep │ │ ├── storageAccount.bicep │ │ └── virtualMachine.bicep │ └── scripts │ │ ├── http │ │ ├── 00-variables.sh │ │ ├── 01-install-tools.sh │ │ ├── 02-create-nginx-ingress-controller.sh │ │ ├── 03-deploy-yelb.sh │ │ ├── 04-configure-dns.sh │ │ ├── 05-call-yelb-ui.sh │ │ ├── cluster-issuer-nginx.yml │ │ ├── cluster-issuer-webapprouting.yml │ │ ├── ingress.yml │ │ └── yelb.yml │ │ └── https │ │ ├── 00-variables.sh │ │ ├── 01-install-tools.sh │ │ ├── 02-create-nginx-ingress-controller.sh │ │ ├── 03-deploy-yelb.sh │ │ ├── 04-configure-dns.sh │ │ ├── 05-call-yelb-ui.sh │ │ ├── cluster-issuer-nginx.yml │ │ ├── cluster-issuer-webapprouting.yml │ │ ├── ingress.yml │ │ └── yelb.yml └── nginx-with-modsecurity-waf │ ├── README.md │ ├── bicep │ ├── actionGroup.bicep │ ├── aksCluster.bicep │ ├── aksManagedIdentity.bicep │ ├── containerRegistry.bicep │ ├── deploy.sh │ ├── deploymentScript.bicep │ ├── dnsZone.bicep │ ├── install-nginx-ingress-controller-with-modsecurity.sh │ ├── keyVault.bicep │ ├── kubeletManagedIdentity.bicep │ ├── logAnalytics.bicep │ ├── main.bicep │ ├── main.bicepparam │ ├── managedGrafana.bicep │ ├── managedPrometheus.bicep │ ├── metricAlerts.bicep │ ├── network.bicep │ ├── storageAccount.bicep │ └── virtualMachine.bicep │ └── scripts │ ├── 00-variables.sh │ ├── 01-install-tools.sh │ ├── 02-create-nginx-ingress-controller.sh │ ├── 03-install-cert-manager.sh │ ├── 04-create-cluster-issuers.sh │ ├── 05-deploy-yelb.sh │ ├── 06-configure-dns.sh │ ├── 07-call-yelb-ui.sh │ ├── cluster-issuer-nginx.yml │ ├── cluster-issuer-webapprouting.yml │ ├── ingress.yml │ └── yelb.yml ├── images ├── application-gateway-aks-http-detail.png ├── application-gateway-aks-http.png ├── application-gateway-aks-https-detail.png ├── application-gateway-aks-https.png ├── application-gateway-for-containers-aks.png ├── application-gateway-ingress-controller-aks-http.png ├── architecture-on-aws.jpg ├── architecture-on-azure.jpg ├── front-door-aks-flow.png ├── front-door-aks.png ├── nginx-modsecurity-aks.png ├── yelb-architecture.png └── yelb-ui.png └── visio └── architecture.vsdx /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | > Please provide us with the following information: 5 | > --------------------------------------------------------------- 6 | 7 | ### This issue is for a: (mark with an `x`) 8 | ``` 9 | - [ ] bug report -> please search issues before submitting 10 | - [ ] feature request 11 | - [ ] documentation issue or request 12 | - [ ] regression (a behavior that used to work and stopped in a new release) 13 | ``` 14 | 15 | ### Minimal steps to reproduce 16 | > 17 | 18 | ### Any log messages given by the failure 19 | > 20 | 21 | ### Expected/desired behavior 22 | > 23 | 24 | ### OS and Version? 25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?) 26 | 27 | ### Versions 28 | > 29 | 30 | ### Mention any other details that might be useful 31 | 32 | > --------------------------------------------------------------- 33 | > Thanks! We'll be in touch soon. 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | * ... 4 | 5 | ## Does this introduce a breaking change? 6 | 7 | ``` 8 | [ ] Yes 9 | [ ] No 10 | ``` 11 | 12 | ## Pull Request Type 13 | What kind of change does this Pull Request introduce? 14 | 15 | 16 | ``` 17 | [ ] Bugfix 18 | [ ] Feature 19 | [ ] Code style update (formatting, local variables) 20 | [ ] Refactoring (no functional changes, no api changes) 21 | [ ] Documentation content changes 22 | [ ] Other... Please describe: 23 | ``` 24 | 25 | ## How to Test 26 | * Get the code 27 | 28 | ``` 29 | git clone [repo-address] 30 | cd [repo-name] 31 | git checkout [branch-name] 32 | npm install 33 | ``` 34 | 35 | * Test the code 36 | 37 | ``` 38 | ``` 39 | 40 | ## What to Check 41 | Verify that the following are valid 42 | * ... 43 | 44 | ## Other Information 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [project-title] Changelog 2 | 3 | 4 | # x.y.z (yyyy-mm-dd) 5 | 6 | *Features* 7 | * ... 8 | 9 | *Bug Fixes* 10 | * ... 11 | 12 | *Breaking Changes* 13 | * ... 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to [project-title] 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 6 | 7 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new]. 55 | 56 | ### Submitting a Pull Request (PR) 57 | Before you submit your Pull Request (PR) consider the following guidelines: 58 | 59 | * Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR 60 | that relates to your submission. You don't want to duplicate effort. 61 | 62 | * Make your changes in a new git fork: 63 | 64 | * Commit your changes using a descriptive commit message 65 | * Push your fork to GitHub: 66 | * In GitHub, create a pull request 67 | * If we suggest changes then: 68 | * Make the required updates. 69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 70 | 71 | ```shell 72 | git rebase master -i 73 | git push -f 74 | ``` 75 | 76 | That's it! Thank you for your contribution! 77 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /aws/scripts/00-variables.sh: -------------------------------------------------------------------------------- 1 | # For more information, see https://aws.amazon.com/it/blogs/containers/protecting-your-amazon-eks-web-apps-with-aws-waf/ 2 | 3 | WAF_AWS_REGION="us-east-2" 4 | WAF_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text) 5 | WAF_EKS_CLUSTER_NAME="waf-eks-sample" 6 | WAF_NAME="WAF-FOR-YELB" -------------------------------------------------------------------------------- /aws/scripts/01-create-eks-cluster.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # For more information, see https://aws.amazon.com/it/blogs/containers/protecting-your-amazon-eks-web-apps-with-aws-waf/ 4 | 5 | # Load environment variables 6 | source ./00-variables.sh 7 | 8 | # Check if the cluster already exists 9 | EXISTING_CLUSTER=$(eksctl get cluster -o json | jq -r ".[].Name" | grep -E "^${WAF_EKS_CLUSTER_NAME}$") 10 | 11 | if [ -n "$EXISTING_CLUSTER" ]; then 12 | echo "Cluster [$WAF_EKS_CLUSTER_NAME] already exists. Skipping cluster creation." 13 | else 14 | echo "Cluster [$WAF_EKS_CLUSTER_NAME] does not exist. Creating a new cluster..." 15 | 16 | # Create EKS cluster 17 | eksctl create cluster \ 18 | --name $WAF_EKS_CLUSTER_NAME \ 19 | --region $WAF_AWS_REGION \ 20 | --managed \ 21 | --nodegroup-name default \ 22 | --ssh-access=true \ 23 | --nodes-min 3 \ 24 | --nodes-max 5 \ 25 | --node-type t3.medium \ 26 | --node-labels "env=dev" \ 27 | --tags "env=dev" \ 28 | --with-oidc \ 29 | --zones $WAF_AWS_REGION"a,"$WAF_AWS_REGION"b,"$WAF_AWS_REGION"c" \ 30 | --asg-access \ 31 | --alb-ingress-access=true 32 | 33 | # Check if the kubectl config already exists for the cluster 34 | EXISTING_CONFIG=$(kubectl config get-contexts -o name | grep -E "^$WAF_EKS_CLUSTER_NAME$") 35 | 36 | if [ -n "$EXISTING_CONFIG" ]; then 37 | echo "Kubectl config for cluster [$WAF_EKS_CLUSTER_NAME] already exists. Removing existing config..." 38 | 39 | # Remove the existing kubectl config 40 | kubectl config delete-context "$WAF_EKS_CLUSTER_NAME" 41 | kubectl config delete-cluster "$WAF_EKS_CLUSTER_NAME" 42 | fi 43 | 44 | # Update the kubectl config for the cluster 45 | aws eks update-kubeconfig --name "$WAF_EKS_CLUSTER_NAME" --alias "$WAF_EKS_CLUSTER_NAME" 46 | fi 47 | -------------------------------------------------------------------------------- /aws/scripts/02-install-aws-load-balancer-controller.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # For more information, see https://aws.amazon.com/it/blogs/containers/protecting-your-amazon-eks-web-apps-with-aws-waf/ 4 | 5 | # Load environment variables 6 | source ./00-variables.sh 7 | 8 | # The AWS Load Balancer Controller is a Kubernetes controller that runs in your EKS cluster and handles the configuration of the Network Load Balancers and Application Load Balancers on your behalf. 9 | # It allows you to configure Load Balancers declaratively in the same manner as you handle the configuration of your application. 10 | 11 | # Get the VPC ID 12 | WAF_VPC_ID=$(aws eks describe-cluster \ 13 | --name $WAF_EKS_CLUSTER_NAME \ 14 | --region $WAF_AWS_REGION \ 15 | --query 'cluster.resourcesVpcConfig.vpcId' \ 16 | --output text) 17 | 18 | # Install the AWS Load Balancer Controller by running these commands: 19 | ## Associate OIDC provider 20 | eksctl utils associate-iam-oidc-provider \ 21 | --cluster $WAF_EKS_CLUSTER_NAME \ 22 | --region $WAF_AWS_REGION \ 23 | --approve 24 | 25 | # Download the IAM policy document 26 | curl -o iam-policy.json https://raw.githubusercontent.com/aws-samples/containers-blog-maelstrom/main/eks-waf-blog/iam-policy.json 27 | 28 | # Create an IAM policy 29 | WAF_LBC_IAM_POLICY=$(aws iam create-policy \ 30 | --policy-name AWSLoadBalancerControllerIAMPolicy-WAFDEMO \ 31 | --policy-document file://iam-policy.json) 32 | 33 | # Get IAM Policy ARN 34 | WAF_LBC_IAM_POLICY_ARN=$(aws iam list-policies \ 35 | --query "Policies[?PolicyName=='AWSLoadBalancerControllerIAMPolicy-WAFDEMO'].Arn" \ 36 | --output text) 37 | 38 | # Create a service account 39 | eksctl create iamserviceaccount \ 40 | --cluster=$WAF_EKS_CLUSTER_NAME \ 41 | --region $WAF_AWS_REGION \ 42 | --namespace=kube-system \ 43 | --name=aws-load-balancer-controller \ 44 | --override-existing-serviceaccounts \ 45 | --attach-policy-arn=${WAF_LBC_IAM_POLICY_ARN} \ 46 | --approve 47 | 48 | # Add the helm repo and install the AWS Load Balancer Controller 49 | helm repo add eks https://aws.github.io/eks-charts && helm repo update 50 | 51 | # Update the Helm repo 52 | helm repo update 53 | 54 | # Install the AWS Load Balancer Controller via Helm 55 | helm install aws-load-balancer-controller \ 56 | eks/aws-load-balancer-controller \ 57 | --namespace kube-system \ 58 | --set clusterName=$WAF_EKS_CLUSTER_NAME \ 59 | --set serviceAccount.create=false \ 60 | --set serviceAccount.name=aws-load-balancer-controller \ 61 | --set vpcId=$WAF_VPC_ID \ 62 | --set region=$WAF_AWS_REGION 63 | 64 | # Verify that the controller is installed 65 | kubectl get deployment -n kube-system aws-load-balancer-controller -------------------------------------------------------------------------------- /aws/scripts/03-deploy-sample-app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Download the Yelb YAML manifest from the official repository 4 | curl -o yelb_initial_deployment.yaml https://raw.githubusercontent.com/aws/aws-app-mesh-examples/main/walkthroughs/eks-getting-started/infrastructure/yelb_initial_deployment.yaml 5 | 6 | # Apply the YAML configuration 7 | kubectl apply -f yelb_initial_deployment.yaml 8 | 9 | # Check the deployed resources within the yelb namespace: 10 | kubectl get all -n yelb 11 | -------------------------------------------------------------------------------- /aws/scripts/04-create-yelb-ingress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Create Yelb Ingress 4 | kubectl apply -f yelb-ingress.yaml -------------------------------------------------------------------------------- /aws/scripts/05-test-app-via-ingress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Test the application by sending a request using curl or by using a web browser to navigate to the URL. 4 | # It may take some time for the load balancer to become available. 5 | kubectl wait -n yelb ingress yelb.app --for=jsonpath='{.status.loadBalancer.ingress}' 6 | 7 | # Get the Yelb UI app URL 8 | YELB_URL=$(kubectl get ingress yelb.app -n yelb -o jsonpath="{.status.loadBalancer.ingress[].hostname}") 9 | 10 | # Echo the Yelb URL 11 | echo $YELB_URL 12 | 13 | # Call the sample app using curl 14 | curl $YELB_URL -------------------------------------------------------------------------------- /aws/scripts/06-create-aws-waf-web-acl-with-no-rules.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # For more information, see https://aws.amazon.com/it/blogs/containers/protecting-your-amazon-eks-web-apps-with-aws-waf/ 4 | 5 | # Load environment variables 6 | source ./00-variables.sh 7 | 8 | # Check if the WAF Web ACL already exists 9 | EXISTING_WAF_WACL_ARN=$(aws wafv2 list-web-acls \ 10 | --region $WAF_AWS_REGION \ 11 | --scope REGIONAL \ 12 | --query "WebACLs[?Name=='$WAF_NAME'].ARN" \ 13 | --output text) 14 | 15 | if [ -z "$EXISTING_WAF_WACL_ARN" ]; then 16 | echo "[$WAF_NAME] WAF Web ACL does not exist. Creating a new WAF Web ACL..." 17 | # Create a WAF Web ACL if it does not exist 18 | WAF_WACL_ARN=$(aws wafv2 create-web-acl \ 19 | --name $WAF_NAME \ 20 | --region $WAF_AWS_REGION \ 21 | --default-action Allow={} \ 22 | --scope REGIONAL \ 23 | --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=YelbWAFAclMetrics \ 24 | --description 'WAF Web ACL for Yelb' \ 25 | --query 'Summary.ARN' \ 26 | --output text) 27 | else 28 | # Use the existing WAF Web ACL ARN 29 | echo "[$WAF_NAME] WAF Web ACL already exists. Using the existing WAF Web ACL ARN..." 30 | WAF_WACL_ARN=$EXISTING_WAF_WACL_ARN 31 | fi 32 | 33 | # Echo the WAF Web ACL Amazon Resource Name (ARN) 34 | echo $WAF_WACL_ARN 35 | 36 | # Store the AWS WAF web ACL’s Id in a variable 37 | WAF_WAF_ID=$(aws wafv2 list-web-acls \ 38 | --region $WAF_AWS_REGION \ 39 | --scope REGIONAL \ 40 | --query "WebACLs[?Name=='$WAF_NAME'].Id" \ 41 | --output text) 42 | 43 | # Update the ingress and associate this AWS WAF web ACL with the ALB that the ingress uses 44 | cat <yelb-ingress-waf.yaml 45 | apiVersion: networking.k8s.io/v1 46 | kind: Ingress 47 | metadata: 48 | name: yelb.app 49 | namespace: yelb 50 | annotations: 51 | alb.ingress.kubernetes.io/scheme: internet-facing 52 | alb.ingress.kubernetes.io/target-type: ip 53 | alb.ingress.kubernetes.io/wafv2-acl-arn: ${WAF_WACL_ARN} 54 | spec: 55 | ingressClassName: alb 56 | rules: 57 | - http: 58 | paths: 59 | - path: / 60 | pathType: Prefix 61 | backend: 62 | service: 63 | name: yelb-ui 64 | port: 65 | number: 80 66 | EOF 67 | kubectl apply -f yelb-ingress-waf.yaml 68 | 69 | # By adding alb.ingress.kubernetes.io/wafv2-acl-arn annotation to the ingress, AWS WAF is inspecting incoming traffic. 70 | # However, it’s not blocking any traffic yet. Before we send a request to our sample app using curl, 71 | # let's wait for the loadbalancer to become ready for traffic 72 | kubectl wait -n yelb ingress yelb.app --for=jsonpath='{.status.loadBalancer.ingress}' 73 | 74 | # Get the Yelb UI app URL 75 | YELB_URL=$(kubectl get ingress yelb.app -n yelb -o jsonpath="{.status.loadBalancer.ingress[].hostname}") 76 | echo $YELB_URL 77 | 78 | # Call the sample app using curl 79 | curl $YELB_URL 80 | -------------------------------------------------------------------------------- /aws/scripts/07-update-aws-waf-web-acl-with-rules.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # For more information, see: 4 | # https://aws.amazon.com/it/blogs/containers/protecting-your-amazon-eks-web-apps-with-aws-waf/ 5 | # https://docs.aws.amazon.com/waf/latest/APIReference/API_AWSManagedRulesBotControlRuleSet.html 6 | # https://docs.aws.amazon.com/waf/latest/developerguide/waf-bot-control-deploying.html 7 | 8 | # Load environment variables 9 | source ./00-variables.sh 10 | 11 | # Create a JSON file containing the AWSManagedRulesBotControlRuleSet rule group. 12 | # This rule group contains rules to block and manage requests from bots as described in AWS WAF documentation. 13 | # AWS WAF blocks the requests we send using curl because AWS WAF web ACL rules are configured to inspect and block requests 14 | # for user agent strings that don’t seem to be from a web browser. 15 | cat << EOF > waf-rules.json 16 | [ 17 | { 18 | "Name": "AWS-AWSManagedRulesBotControlRuleSet", 19 | "Priority": 0, 20 | "Statement": { 21 | "ManagedRuleGroupStatement": { 22 | "VendorName": "AWS", 23 | "Name": "AWSManagedRulesBotControlRuleSet" 24 | } 25 | }, 26 | "OverrideAction": { 27 | "None": {} 28 | }, 29 | "VisibilityConfig": { 30 | "SampledRequestsEnabled": true, 31 | "CloudWatchMetricsEnabled": true, 32 | "MetricName": "AWS-AWSManagedRulesBotControlRuleSet" 33 | } 34 | } 35 | ] 36 | EOF 37 | 38 | # Store the AWS WAF web ACL’s Id in a variable 39 | WAF_WAF_ID=$(aws wafv2 list-web-acls \ 40 | --region $WAF_AWS_REGION \ 41 | --scope REGIONAL \ 42 | --query "WebACLs[?Name=='$WAF_NAME'].Id" \ 43 | --output text) 44 | 45 | if [ -z "$WAF_WAF_ID" ]; then 46 | echo "[$WAF_NAME] WAF Web ACL does not exist." 47 | exit -1 48 | fi 49 | 50 | # Update the WAF Web ACL with the rules 51 | aws wafv2 update-web-acl \ 52 | --name $WAF_NAME \ 53 | --scope REGIONAL \ 54 | --id $WAF_WAF_ID \ 55 | --default-action Allow={} \ 56 | --lock-token $(aws wafv2 list-web-acls \ 57 | --region $WAF_AWS_REGION \ 58 | --scope REGIONAL \ 59 | --query "WebACLs[?Name=='$WAF_NAME'].LockToken" \ 60 | --output text) \ 61 | --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=YelbWAFAclMetrics \ 62 | --region $WAF_AWS_REGION \ 63 | --rules file://waf-rules.json -------------------------------------------------------------------------------- /aws/scripts/08-call-yelb-via-curl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # For more information, see: 4 | # https://aws.amazon.com/it/blogs/containers/protecting-your-amazon-eks-web-apps-with-aws-waf/ 5 | # https://docs.aws.amazon.com/waf/latest/APIReference/API_AWSManagedRulesBotControlRuleSet.html 6 | # https://docs.aws.amazon.com/waf/latest/developerguide/waf-bot-control-deploying.html 7 | 8 | # Load environment variables 9 | source ./00-variables.sh 10 | 11 | # Get the Yelb UI app URL 12 | YELB_URL=$(kubectl get ingress yelb.app -n yelb -o jsonpath="{.status.loadBalancer.ingress[].hostname}") 13 | echo $YELB_URL 14 | 15 | # Call the sample app using curl 16 | curl $YELB_URL 17 | 18 | # Print a separator 19 | printf '%.s-' {1..80}; echo 20 | 21 | # Call the sample app using curl with a user agent string that AWS WAF will block 22 | curl -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" $YELB_URL 23 | -------------------------------------------------------------------------------- /aws/scripts/99-aws-commands.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # List all stacks 4 | aws cloudformation list-stacks 5 | 6 | # List all EKS clusters 7 | aws eks list-clusters 8 | 9 | # List all login profiles 10 | aws configure list 11 | 12 | # Refresh sso token 13 | aws sso login --profile SSOAdminAccess-941377123301 -------------------------------------------------------------------------------- /aws/scripts/dry-run-cluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: eksctl.io/v1alpha5 2 | availabilityZones: 3 | - us-east-2a 4 | - us-east-2b 5 | - us-east-2c 6 | cloudWatch: 7 | clusterLogging: {} 8 | iam: 9 | vpcResourceControllerPolicy: true 10 | withOIDC: true 11 | kind: ClusterConfig 12 | kubernetesNetworkConfig: 13 | ipFamily: IPv4 14 | managedNodeGroups: 15 | - amiFamily: AmazonLinux2 16 | desiredCapacity: 3 17 | disableIMDSv1: true 18 | disablePodIMDS: false 19 | iam: 20 | withAddonPolicies: 21 | albIngress: false 22 | appMesh: false 23 | appMeshPreview: false 24 | autoScaler: true 25 | awsLoadBalancerController: true 26 | certManager: false 27 | cloudWatch: false 28 | ebs: false 29 | efs: false 30 | externalDNS: false 31 | fsx: false 32 | imageBuilder: false 33 | xRay: false 34 | instanceSelector: {} 35 | instanceType: t3.medium 36 | labels: 37 | alpha.eksctl.io/cluster-name: waf-eks-sample 38 | alpha.eksctl.io/nodegroup-name: default 39 | env: dev 40 | maxSize: 5 41 | minSize: 3 42 | name: default 43 | privateNetworking: false 44 | releaseVersion: "" 45 | securityGroups: 46 | withLocal: null 47 | withShared: null 48 | ssh: 49 | allow: true 50 | publicKeyPath: ~/.ssh/id_rsa.pub 51 | tags: 52 | alpha.eksctl.io/nodegroup-name: default 53 | alpha.eksctl.io/nodegroup-type: managed 54 | volumeIOPS: 3000 55 | volumeSize: 80 56 | volumeThroughput: 125 57 | volumeType: gp3 58 | metadata: 59 | name: waf-eks-sample 60 | region: us-east-2 61 | tags: 62 | env: dev 63 | version: "1.30" 64 | privateCluster: 65 | enabled: false 66 | skipEndpointCreation: false 67 | vpc: 68 | autoAllocateIPv6: false 69 | cidr: 192.168.0.0/16 70 | clusterEndpoints: 71 | privateAccess: false 72 | publicAccess: true 73 | manageSharedNodeSecurityGroupRules: true 74 | nat: 75 | gateway: Single -------------------------------------------------------------------------------- /aws/scripts/iam-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "iam:CreateServiceLinkedRole", 8 | "ec2:DescribeAccountAttributes", 9 | "ec2:DescribeAddresses", 10 | "ec2:DescribeAvailabilityZones", 11 | "ec2:DescribeInternetGateways", 12 | "ec2:DescribeVpcs", 13 | "ec2:DescribeSubnets", 14 | "ec2:DescribeSecurityGroups", 15 | "ec2:DescribeInstances", 16 | "ec2:DescribeNetworkInterfaces", 17 | "ec2:DescribeTags", 18 | "ec2:GetCoipPoolUsage", 19 | "ec2:DescribeCoipPools", 20 | "elasticloadbalancing:DescribeLoadBalancers", 21 | "elasticloadbalancing:DescribeLoadBalancerAttributes", 22 | "elasticloadbalancing:DescribeListeners", 23 | "elasticloadbalancing:DescribeListenerCertificates", 24 | "elasticloadbalancing:DescribeSSLPolicies", 25 | "elasticloadbalancing:DescribeRules", 26 | "elasticloadbalancing:DescribeTargetGroups", 27 | "elasticloadbalancing:DescribeTargetGroupAttributes", 28 | "elasticloadbalancing:DescribeTargetHealth", 29 | "elasticloadbalancing:DescribeTags" 30 | ], 31 | "Resource": "*" 32 | }, 33 | { 34 | "Effect": "Allow", 35 | "Action": [ 36 | "cognito-idp:DescribeUserPoolClient", 37 | "acm:ListCertificates", 38 | "acm:DescribeCertificate", 39 | "iam:ListServerCertificates", 40 | "iam:GetServerCertificate", 41 | "waf-regional:GetWebACL", 42 | "waf-regional:GetWebACLForResource", 43 | "waf-regional:AssociateWebACL", 44 | "waf-regional:DisassociateWebACL", 45 | "wafv2:GetWebACL", 46 | "wafv2:GetWebACLForResource", 47 | "wafv2:AssociateWebACL", 48 | "wafv2:DisassociateWebACL", 49 | "shield:GetSubscriptionState", 50 | "shield:DescribeProtection", 51 | "shield:CreateProtection", 52 | "shield:DeleteProtection" 53 | ], 54 | "Resource": "*" 55 | }, 56 | { 57 | "Effect": "Allow", 58 | "Action": [ 59 | "ec2:AuthorizeSecurityGroupIngress", 60 | "ec2:RevokeSecurityGroupIngress" 61 | ], 62 | "Resource": "*" 63 | }, 64 | { 65 | "Effect": "Allow", 66 | "Action": [ 67 | "ec2:CreateSecurityGroup" 68 | ], 69 | "Resource": "*" 70 | }, 71 | { 72 | "Effect": "Allow", 73 | "Action": [ 74 | "ec2:CreateTags" 75 | ], 76 | "Resource": "arn:aws:ec2:*:*:security-group/*", 77 | "Condition": { 78 | "StringEquals": { 79 | "ec2:CreateAction": "CreateSecurityGroup" 80 | }, 81 | "Null": { 82 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 83 | } 84 | } 85 | }, 86 | { 87 | "Effect": "Allow", 88 | "Action": [ 89 | "ec2:CreateTags", 90 | "ec2:DeleteTags" 91 | ], 92 | "Resource": "arn:aws:ec2:*:*:security-group/*", 93 | "Condition": { 94 | "Null": { 95 | "aws:RequestTag/elbv2.k8s.aws/cluster": "true", 96 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 97 | } 98 | } 99 | }, 100 | { 101 | "Effect": "Allow", 102 | "Action": [ 103 | "ec2:AuthorizeSecurityGroupIngress", 104 | "ec2:RevokeSecurityGroupIngress", 105 | "ec2:DeleteSecurityGroup" 106 | ], 107 | "Resource": "*", 108 | "Condition": { 109 | "Null": { 110 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 111 | } 112 | } 113 | }, 114 | { 115 | "Effect": "Allow", 116 | "Action": [ 117 | "elasticloadbalancing:CreateLoadBalancer", 118 | "elasticloadbalancing:CreateTargetGroup" 119 | ], 120 | "Resource": "*", 121 | "Condition": { 122 | "Null": { 123 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 124 | } 125 | } 126 | }, 127 | { 128 | "Effect": "Allow", 129 | "Action": [ 130 | "elasticloadbalancing:CreateListener", 131 | "elasticloadbalancing:DeleteListener", 132 | "elasticloadbalancing:CreateRule", 133 | "elasticloadbalancing:DeleteRule" 134 | ], 135 | "Resource": "*" 136 | }, 137 | { 138 | "Effect": "Allow", 139 | "Action": [ 140 | "elasticloadbalancing:AddTags", 141 | "elasticloadbalancing:RemoveTags" 142 | ], 143 | "Resource": [ 144 | "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*", 145 | "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*", 146 | "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*", 147 | "arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*", 148 | "arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*", 149 | "arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*", 150 | "arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*" 151 | ] 152 | }, 153 | { 154 | "Effect": "Allow", 155 | "Action": [ 156 | "elasticloadbalancing:ModifyLoadBalancerAttributes", 157 | "elasticloadbalancing:SetIpAddressType", 158 | "elasticloadbalancing:SetSecurityGroups", 159 | "elasticloadbalancing:SetSubnets", 160 | "elasticloadbalancing:DeleteLoadBalancer", 161 | "elasticloadbalancing:ModifyTargetGroup", 162 | "elasticloadbalancing:ModifyTargetGroupAttributes", 163 | "elasticloadbalancing:DeleteTargetGroup" 164 | ], 165 | "Resource": "*", 166 | "Condition": { 167 | "Null": { 168 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 169 | } 170 | } 171 | }, 172 | { 173 | "Effect": "Allow", 174 | "Action": [ 175 | "elasticloadbalancing:RegisterTargets", 176 | "elasticloadbalancing:DeregisterTargets" 177 | ], 178 | "Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*" 179 | }, 180 | { 181 | "Effect": "Allow", 182 | "Action": [ 183 | "elasticloadbalancing:SetWebAcl", 184 | "elasticloadbalancing:ModifyListener", 185 | "elasticloadbalancing:AddListenerCertificates", 186 | "elasticloadbalancing:RemoveListenerCertificates", 187 | "elasticloadbalancing:ModifyRule" 188 | ], 189 | "Resource": "*" 190 | } 191 | ] 192 | } -------------------------------------------------------------------------------- /aws/scripts/icontentsmd: -------------------------------------------------------------------------------- 1 | The count mode in AWS WAF refers to a feature that allows you to track the number of requests that match a particular rule or rule group, without taking any action. When count mode is enabled for a rule or rule group, AWS WAF will increment a counter each time a request matches the specified conditions, but it will not take any blocking or filtering action. 2 | 3 | This feature is useful for monitoring and gathering insights about traffic patterns and potential threats without actually blocking or allowing any requests. It can help you understand the types of requests that are being made to your applications and identify potential security vulnerabilities or attack patterns. 4 | 5 | By utilizing count mode in AWS WAF, you can gather data and use it to fine-tune your rules and policies, making more informed decisions about how to handle different types of requests. It provides a high-level view of the traffic patterns and helps you prioritize the rules that need immediate attention. 6 | 7 | Overall, count mode in AWS WAF is a valuable tool for monitoring and collecting data on request patterns, which can be utilized for improving the security and performance of your applications. 8 | 9 | In AWS WAF, labels are used to customize how Bot Control detects and handles requests from different types of bots. Bot Control is a feature that helps protect your applications from automated attacks, such as those carried out by malicious bots. 10 | 11 | By assigning labels to different types of bots, you can define specific rules and actions to be taken for each bot category. This allows you to have granular control over how Bot Control handles requests from different bots based on their behavior, intentions, or characteristics. 12 | 13 | Here's how it works: 14 | 15 | 1. Define Labels: You can create custom labels in AWS WAF to categorize different types of bots. For example, you can create labels like "GoodBot," "BadBot," or "WebScraper" to differentiate between legitimate bots, malicious bots, or web scraping bots. 16 | 17 | 2. Configure Bot Control Rules: After defining labels, you can associate them with rules in the Bot Control settings. Each rule can have one or more labels assigned to it. These rules define how requests from bots with specific labels should be treated. 18 | 19 | 3. Specify Actions: For each rule, you can specify different actions to be taken when requests from bots with matching labels are encountered. Actions can include allowing, blocking, redirecting, or logging requests. You can also configure custom responses to be sent back to the bots based on their labels. 20 | 21 | 4. Fine-Tune Bot Control Settings: By analyzing the data and monitoring the behavior of different bots, you can continuously fine-tune your Bot Control settings. You can update the labels, rules, and actions to improve the accuracy and effectiveness of bot detection and mitigation. 22 | 23 | By leveraging labels in AWS WAF Bot Control, you can customize the bot management rules based on your specific requirements. It enables you to have better control, security, and flexibility in dealing with different types of bots, protecting your applications from various automated threats. -------------------------------------------------------------------------------- /aws/scripts/waf-rules.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "AWS-AWSManagedRulesBotControlRuleSet", 4 | "Priority": 0, 5 | "Statement": { 6 | "ManagedRuleGroupStatement": { 7 | "VendorName": "AWS", 8 | "Name": "AWSManagedRulesBotControlRuleSet" 9 | } 10 | }, 11 | "OverrideAction": { 12 | "None": {} 13 | }, 14 | "VisibilityConfig": { 15 | "SampledRequestsEnabled": true, 16 | "CloudWatchMetricsEnabled": true, 17 | "MetricName": "AWS-AWSManagedRulesBotControlRuleSet" 18 | } 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /aws/scripts/yelb-ingress-waf.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: yelb.app 5 | namespace: yelb 6 | annotations: 7 | alb.ingress.kubernetes.io/scheme: internet-facing 8 | alb.ingress.kubernetes.io/target-type: ip 9 | alb.ingress.kubernetes.io/wafv2-acl-arn: arn:aws:wafv2:us-east-2:941377123301:regional/webacl/WAF-FOR-YELB/80221b19-dead-49a4-89e1-b07e70b2c0dc 10 | spec: 11 | ingressClassName: alb 12 | rules: 13 | - http: 14 | paths: 15 | - path: / 16 | pathType: Prefix 17 | backend: 18 | service: 19 | name: yelb-ui 20 | port: 21 | number: 80 22 | -------------------------------------------------------------------------------- /aws/scripts/yelb-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: yelb.app 5 | namespace: yelb 6 | annotations: 7 | alb.ingress.kubernetes.io/scheme: internet-facing 8 | alb.ingress.kubernetes.io/target-type: ip 9 | spec: 10 | ingressClassName: alb # Updated method to attach ingress class 11 | rules: 12 | - http: 13 | paths: 14 | - path: / 15 | pathType: Prefix 16 | backend: 17 | service: 18 | name: yelb-ui 19 | port: 20 | number: 80 21 | -------------------------------------------------------------------------------- /aws/scripts/yelb_initial_deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: yelb 5 | --- 6 | apiVersion: v1 7 | kind: Service 8 | metadata: 9 | namespace: yelb 10 | name: redis-server 11 | labels: 12 | app: redis-server 13 | tier: cache 14 | spec: 15 | type: ClusterIP 16 | ports: 17 | - port: 6379 18 | selector: 19 | app: redis-server 20 | tier: cache 21 | --- 22 | apiVersion: v1 23 | kind: Service 24 | metadata: 25 | namespace: yelb 26 | name: yelb-db 27 | labels: 28 | app: yelb-db 29 | tier: backenddb 30 | spec: 31 | type: ClusterIP 32 | ports: 33 | - port: 5432 34 | selector: 35 | app: yelb-db 36 | tier: backenddb 37 | --- 38 | apiVersion: v1 39 | kind: Service 40 | metadata: 41 | namespace: yelb 42 | name: yelb-appserver 43 | labels: 44 | app: yelb-appserver 45 | tier: middletier 46 | spec: 47 | type: ClusterIP 48 | ports: 49 | - port: 4567 50 | selector: 51 | app: yelb-appserver 52 | tier: middletier 53 | --- 54 | apiVersion: v1 55 | kind: Service 56 | metadata: 57 | namespace: yelb 58 | name: yelb-ui 59 | labels: 60 | app: yelb-ui 61 | tier: frontend 62 | spec: 63 | type: LoadBalancer 64 | ports: 65 | - port: 80 66 | protocol: TCP 67 | targetPort: 80 68 | selector: 69 | app: yelb-ui 70 | tier: frontend 71 | --- 72 | apiVersion: apps/v1 73 | kind: Deployment 74 | metadata: 75 | namespace: yelb 76 | name: yelb-ui 77 | spec: 78 | replicas: 1 79 | selector: 80 | matchLabels: 81 | app: yelb-ui 82 | tier: frontend 83 | template: 84 | metadata: 85 | labels: 86 | app: yelb-ui 87 | tier: frontend 88 | spec: 89 | containers: 90 | - name: yelb-ui 91 | image: mreferre/yelb-ui:0.7 92 | ports: 93 | - containerPort: 80 94 | --- 95 | apiVersion: apps/v1 96 | kind: Deployment 97 | metadata: 98 | namespace: yelb 99 | name: redis-server 100 | spec: 101 | selector: 102 | matchLabels: 103 | app: redis-server 104 | tier: cache 105 | replicas: 1 106 | template: 107 | metadata: 108 | labels: 109 | app: redis-server 110 | tier: cache 111 | spec: 112 | containers: 113 | - name: redis-server 114 | image: redis:4.0.2 115 | ports: 116 | - containerPort: 6379 117 | --- 118 | apiVersion: apps/v1 119 | kind: Deployment 120 | metadata: 121 | namespace: yelb 122 | name: yelb-db 123 | spec: 124 | replicas: 1 125 | selector: 126 | matchLabels: 127 | app: yelb-db 128 | tier: backenddb 129 | template: 130 | metadata: 131 | labels: 132 | app: yelb-db 133 | tier: backenddb 134 | spec: 135 | containers: 136 | - name: yelb-db 137 | image: mreferre/yelb-db:0.5 138 | ports: 139 | - containerPort: 5432 140 | --- 141 | apiVersion: apps/v1 142 | kind: Deployment 143 | metadata: 144 | namespace: yelb 145 | name: yelb-appserver 146 | spec: 147 | replicas: 1 148 | selector: 149 | matchLabels: 150 | app: yelb-appserver 151 | tier: middletier 152 | template: 153 | metadata: 154 | labels: 155 | app: yelb-appserver 156 | tier: middletier 157 | spec: 158 | containers: 159 | - name: yelb-appserver 160 | image: mreferre/yelb-appserver:0.5 161 | ports: 162 | - containerPort: 4567 163 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/actionGroup.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the Action Group resource.') 3 | param name string 4 | 5 | @description('Specifies the short name of the action group. This will be used in SMS messages..') 6 | param groupShortName string = 'AksAlerts' 7 | 8 | @description('Specifies whether this action group is enabled. If an action group is not enabled, then none of its receivers will receive communications.') 9 | param enabled bool = true 10 | 11 | @description('Specifies the email address of the receiver.') 12 | param emailAddress string 13 | 14 | @description('Specifies whether to use common alert schema..') 15 | param useCommonAlertSchema bool = false 16 | 17 | @description('Specifies the country code of the SMS receiver.') 18 | param countryCode string = '39' 19 | 20 | @description('Specifies the phone number of the SMS receiver.') 21 | param phoneNumber string = '' 22 | 23 | @description('Specifies the resource tags.') 24 | param tags object 25 | 26 | // Resources 27 | resource actionGroup 'Microsoft.Insights/actionGroups@2023-01-01' = { 28 | name: name 29 | location: 'Global' 30 | tags: tags 31 | properties: { 32 | groupShortName: groupShortName 33 | enabled: enabled 34 | emailReceivers: !empty(emailAddress) ? [ 35 | { 36 | name: 'EmailAndTextMessageOthers_-EmailAction-' 37 | emailAddress: emailAddress 38 | useCommonAlertSchema: useCommonAlertSchema 39 | } 40 | ] : [] 41 | smsReceivers: !empty(countryCode) && !empty(phoneNumber) ? [ 42 | { 43 | name: 'EmailAndTextMessageOthers_-SMSAction-' 44 | countryCode: countryCode 45 | phoneNumber: phoneNumber 46 | } 47 | ] : [] 48 | armRoleReceivers: [ 49 | { 50 | name: 'EmailOwner' 51 | roleId: '8e3af657-a8ff-443c-a75c-2fe8c4bcb635' 52 | useCommonAlertSchema: false 53 | } 54 | ] 55 | } 56 | } 57 | 58 | //Outputs 59 | output id string = actionGroup.id 60 | output name string = actionGroup.name 61 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/aksManagedIdentity.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the user-defined managed identity.') 3 | param managedIdentityName string 4 | 5 | @description('Specifies the name of the existing virtual network.') 6 | param virtualNetworkName string 7 | 8 | @description('Specifies the location of the user-defined managed identity.') 9 | param location string = resourceGroup().location 10 | 11 | @description('Specifies the resource tags.') 12 | param tags object 13 | 14 | // Variables 15 | var networkContributorRoleDefinitionId = resourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7') 16 | 17 | // Resources 18 | resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' = { 19 | name: managedIdentityName 20 | location: location 21 | tags: tags 22 | } 23 | 24 | resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-01-01' existing = { 25 | name: virtualNetworkName 26 | } 27 | 28 | 29 | resource virtualNetworkContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 30 | name: guid(managedIdentity.id, virtualNetwork.id, networkContributorRoleDefinitionId) 31 | scope: virtualNetwork 32 | properties: { 33 | roleDefinitionId: networkContributorRoleDefinitionId 34 | principalId: managedIdentity.properties.principalId 35 | principalType: 'ServicePrincipal' 36 | } 37 | } 38 | 39 | // Outputs 40 | output id string = managedIdentity.id 41 | output name string = managedIdentity.name 42 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/applicationGatewayIdentity.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the user-defined managed identity of the Application Gateway.') 3 | param managedIdentityName string 4 | 5 | @description('Specifies the location of the user-defined managed identity of the Application Gateway.') 6 | param location string = resourceGroup().location 7 | 8 | @description('Specifies the resource tags.') 9 | param tags object 10 | 11 | // Resources 12 | resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' = { 13 | name: managedIdentityName 14 | location: location 15 | tags: tags 16 | } 17 | 18 | // Outputs 19 | output id string = managedIdentity.id 20 | output name string = managedIdentity.name 21 | output principalId string = managedIdentity.properties.principalId 22 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "aksClusterNetworkMode": { 6 | "value": "transparent" 7 | }, 8 | "aksClusterNetworkDataplane": { 9 | "value": "cilium" 10 | }, 11 | "aksClusterNetworkPlugin": { 12 | "value": "azure" 13 | }, 14 | "aksClusterNetworkPluginMode": { 15 | "value": "overlay" 16 | }, 17 | "aksClusterNetworkPolicy": { 18 | "value": "cilium" 19 | }, 20 | "aksClusterWebAppRoutingEnabled": { 21 | "value": true 22 | }, 23 | "aksClusterSkuTier": { 24 | "value": "Standard" 25 | }, 26 | "aksClusterPodCidr": { 27 | "value": "192.168.0.0/16" 28 | }, 29 | "aksClusterServiceCidr": { 30 | "value": "172.16.0.0/16" 31 | }, 32 | "aksClusterDnsServiceIP": { 33 | "value": "172.16.0.10" 34 | }, 35 | "aksClusterOutboundType": { 36 | "value": "userAssignedNATGateway" 37 | }, 38 | "aksClusterKubernetesVersion": { 39 | "value": "1.30.4" 40 | }, 41 | "aksClusterAdminUsername": { 42 | "value": "azadmin" 43 | }, 44 | "aksClusterSshPublicKey": { 45 | "value": "" 46 | }, 47 | "loadBalancerBackendPoolType": { 48 | "value": "nodeIP" 49 | }, 50 | "aadProfileManaged": { 51 | "value": true 52 | }, 53 | "aadProfileEnableAzureRBAC": { 54 | "value": true 55 | }, 56 | "aadProfileAdminGroupObjectIDs": { 57 | "value": [ 58 | "" 59 | ] 60 | }, 61 | "systemAgentPoolName": { 62 | "value": "system" 63 | }, 64 | "systemAgentPoolVmSize": { 65 | "value": "Standard_F8s_v2" 66 | }, 67 | "systemAgentPoolOsDiskSizeGB": { 68 | "value": 80 69 | }, 70 | "systemAgentPoolAgentCount": { 71 | "value": 3 72 | }, 73 | "systemAgentPoolMaxCount": { 74 | "value": 5 75 | }, 76 | "systemAgentPoolMinCount": { 77 | "value": 3 78 | }, 79 | "systemAgentPoolNodeTaints": { 80 | "value": [ 81 | "CriticalAddonsOnly=true:NoSchedule" 82 | ] 83 | }, 84 | "userAgentPoolName": { 85 | "value": "user" 86 | }, 87 | "userAgentPoolVmSize": { 88 | "value": "Standard_F8s_v2" 89 | }, 90 | "userAgentPoolOsDiskSizeGB": { 91 | "value": 80 92 | }, 93 | "userAgentPoolAgentCount": { 94 | "value": 3 95 | }, 96 | "userAgentPoolMaxCount": { 97 | "value": 5 98 | }, 99 | "userAgentPoolMinCount": { 100 | "value": 3 101 | }, 102 | "enableVnetIntegration": { 103 | "value": true 104 | }, 105 | "virtualNetworkAddressPrefixes": { 106 | "value": "10.0.0.0/8" 107 | }, 108 | "systemAgentPoolSubnetName": { 109 | "value": "SystemSubnet" 110 | }, 111 | "systemAgentPoolSubnetAddressPrefix": { 112 | "value": "10.240.0.0/16" 113 | }, 114 | "userAgentPoolSubnetName": { 115 | "value": "UserSubnet" 116 | }, 117 | "userAgentPoolSubnetAddressPrefix": { 118 | "value": "10.241.0.0/16" 119 | }, 120 | "podSubnetName": { 121 | "value": "PodSubnet" 122 | }, 123 | "podSubnetAddressPrefix": { 124 | "value": "10.242.0.0/16" 125 | }, 126 | "apiServerSubnetName": { 127 | "value": "ApiServerSubnet" 128 | }, 129 | "apiServerSubnetAddressPrefix": { 130 | "value": "10.243.0.0/27" 131 | }, 132 | "vmSubnetName": { 133 | "value": "VmSubnet" 134 | }, 135 | "vmSubnetAddressPrefix": { 136 | "value": "10.243.1.0/24" 137 | }, 138 | "bastionSubnetAddressPrefix": { 139 | "value": "10.243.2.0/24" 140 | }, 141 | "logAnalyticsSku": { 142 | "value": "PerGB2018" 143 | }, 144 | "logAnalyticsRetentionInDays": { 145 | "value": 60 146 | }, 147 | "vmEnabled": { 148 | "value": true 149 | }, 150 | "vmName": { 151 | "value": "TestVm" 152 | }, 153 | "vmSize": { 154 | "value": "Standard_F8s_v2" 155 | }, 156 | "imagePublisher": { 157 | "value": "Canonical" 158 | }, 159 | "imageOffer": { 160 | "value": "0001-com-ubuntu-server-jammy" 161 | }, 162 | "imageSku": { 163 | "value": "22_04-lts-gen2" 164 | }, 165 | "authenticationType": { 166 | "value": "sshPublicKey" 167 | }, 168 | "vmAdminUsername": { 169 | "value": "azadmin" 170 | }, 171 | "vmAdminPasswordOrKey": { 172 | "value": "" 173 | }, 174 | "diskStorageAccountType": { 175 | "value": "Premium_LRS" 176 | }, 177 | "numDataDisks": { 178 | "value": 1 179 | }, 180 | "osDiskSize": { 181 | "value": 50 182 | }, 183 | "dataDiskSize": { 184 | "value": 50 185 | }, 186 | "dataDiskCaching": { 187 | "value": "ReadWrite" 188 | }, 189 | "aksClusterEnablePrivateCluster": { 190 | "value": false 191 | }, 192 | "aksEnablePrivateClusterPublicFQDN": { 193 | "value": false 194 | }, 195 | "podIdentityProfileEnabled": { 196 | "value": false 197 | }, 198 | "kedaEnabled": { 199 | "value": true 200 | }, 201 | "daprEnabled": { 202 | "value": true 203 | }, 204 | "fluxGitOpsEnabled": { 205 | "value": false 206 | }, 207 | "verticalPodAutoscalerEnabled": { 208 | "value": true 209 | }, 210 | "deploymentScriptUri": { 211 | "value": "https://raw.githubusercontent.com/paolosalvatori/scripts/refs/heads/main/install-packages.sh" 212 | }, 213 | "blobCSIDriverEnabled": { 214 | "value": true 215 | }, 216 | "diskCSIDriverEnabled": { 217 | "value": true 218 | }, 219 | "fileCSIDriverEnabled": { 220 | "value": true 221 | }, 222 | "snapshotControllerEnabled": { 223 | "value": true 224 | }, 225 | "defenderSecurityMonitoringEnabled": { 226 | "value": true 227 | }, 228 | "imageCleanerEnabled": { 229 | "value": true 230 | }, 231 | "imageCleanerIntervalHours": { 232 | "value": 24 233 | }, 234 | "nodeRestrictionEnabled": { 235 | "value": true 236 | }, 237 | "workloadIdentityEnabled": { 238 | "value": true 239 | }, 240 | "oidcIssuerProfileEnabled": { 241 | "value": true 242 | }, 243 | "dnsZoneName": { 244 | "value": "" 245 | }, 246 | "dnsZoneResourceGroupName": { 247 | "value": "" 248 | }, 249 | "actionGroupEmailAddress": { 250 | "value": "" 251 | }, 252 | "keyVaultName": { 253 | "value": "" 254 | }, 255 | "keyVaultResourceGroupName": { 256 | "value": "" 257 | }, 258 | "keyVaultCertificateName": { 259 | "value": "" 260 | }, 261 | "backendAddressPoolName": { 262 | "value": "DefaultBackendAddressPool" 263 | }, 264 | "frontendPorts": { 265 | "value": [ 266 | { 267 | "name": "HttpFrontendPort", 268 | "port": 443 269 | } 270 | ] 271 | }, 272 | "httpListeners": { 273 | "value": [ 274 | { 275 | "name": "DefaultHttpListener", 276 | "protocol": "Https", 277 | "frontendPort": "HttpFrontendPort", 278 | "sslCertificate": "", 279 | "hostNames": [ 280 | "your-yelb-hostname" 281 | ], 282 | "firewallPolicy": "Enabled" 283 | } 284 | ] 285 | }, 286 | "requestRoutingRules": { 287 | "value": [ 288 | { 289 | "name": "DefaultRequestRoutingRule", 290 | "ruleType": "Basic", 291 | "priority": 1000, 292 | "listener": "DefaultHttpListener", 293 | "backendPool": "DefaultBackendAddressPool", 294 | "backendHttpSettings": "DefaultBackendHttpSettings" 295 | } 296 | ] 297 | }, 298 | "backendHttpSettings": { 299 | "value": [ 300 | { 301 | "name": "DefaultBackendHttpSettings", 302 | "port": 443, 303 | "protocol": "Https", 304 | "cookieBasedAffinity": "Disabled", 305 | "probeName": "DefaultProbe", 306 | "probeEnabled": true, 307 | "pickHostNameFromBackendAddress": false, 308 | "requestTimeout": 300 309 | } 310 | ] 311 | }, 312 | "probes": { 313 | "value": [ 314 | { 315 | "name": "DefaultProbe", 316 | "protocol": "Https", 317 | "path": "/", 318 | "host": "your-yelb-hostname", 319 | "port": 443, 320 | "interval": 60, 321 | "timeout": 30, 322 | "unhealthyThreshold": 3, 323 | "pickHostNameFromBackendHttpSettings": false, 324 | "match": { 325 | "statusCodes": [ 326 | "200" 327 | ] 328 | } 329 | } 330 | ] 331 | }, 332 | "redirectConfigurations": { 333 | "value": [] 334 | } 335 | } 336 | } -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/containerRegistry.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Name of your Azure Container Registry') 3 | @minLength(5) 4 | @maxLength(50) 5 | param name string = 'acr${uniqueString(resourceGroup().id)}' 6 | 7 | @description('Enable admin user that have push / pull permission to the registry.') 8 | param adminUserEnabled bool = true 9 | 10 | @description('Specifies whether to allow public network access for the container registry.') 11 | @allowed([ 12 | 'Disabled' 13 | 'Enabled' 14 | ]) 15 | param publicNetworkAccess string = 'Enabled' 16 | 17 | @description('Tier of your Azure Container Registry.') 18 | @allowed([ 19 | 'Basic' 20 | 'Standard' 21 | 'Premium' 22 | ]) 23 | param sku string = 'Premium' 24 | 25 | @description('Specifies whether or not registry-wide pull is enabled from unauthenticated clients.') 26 | param anonymousPullEnabled bool = true 27 | 28 | @description('Specifies whether or not a single data endpoint is enabled per region for serving data.') 29 | param dataEndpointEnabled bool = true 30 | 31 | @description('Specifies the network rule set for the container registry.') 32 | param networkRuleSet object = { 33 | defaultAction: 'Allow' 34 | } 35 | 36 | @description('Specifies ehether to allow trusted Azure services to access a network restricted registry.') 37 | @allowed([ 38 | 'AzureServices' 39 | 'None' 40 | ]) 41 | param networkRuleBypassOptions string = 'AzureServices' 42 | 43 | @description('Specifies whether or not zone redundancy is enabled for this container registry.') 44 | @allowed([ 45 | 'Disabled' 46 | 'Enabled' 47 | ]) 48 | param zoneRedundancy string = 'Disabled' 49 | 50 | @description('Specifies the resource id of the Log Analytics workspace.') 51 | param workspaceId string 52 | 53 | @description('Specifies the location.') 54 | param location string = resourceGroup().location 55 | 56 | @description('Specifies the resource tags.') 57 | param tags object 58 | 59 | // Variables 60 | var diagnosticSettingsName = 'diagnosticSettings' 61 | var logCategories = [ 62 | 'ContainerRegistryRepositoryEvents' 63 | 'ContainerRegistryLoginEvents' 64 | ] 65 | var metricCategories = [ 66 | 'AllMetrics' 67 | ] 68 | var logs = [ 69 | for category in logCategories: { 70 | category: category 71 | enabled: true 72 | retentionPolicy: { 73 | enabled: true 74 | days: 0 75 | } 76 | } 77 | ] 78 | var metrics = [ 79 | for category in metricCategories: { 80 | category: category 81 | enabled: true 82 | retentionPolicy: { 83 | enabled: true 84 | days: 0 85 | } 86 | } 87 | ] 88 | 89 | // Resources 90 | resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' = { 91 | name: name 92 | location: location 93 | tags: tags 94 | sku: { 95 | name: sku 96 | } 97 | properties: { 98 | adminUserEnabled: adminUserEnabled 99 | anonymousPullEnabled: anonymousPullEnabled 100 | dataEndpointEnabled: dataEndpointEnabled 101 | networkRuleBypassOptions: networkRuleBypassOptions 102 | networkRuleSet: networkRuleSet 103 | policies: { 104 | quarantinePolicy: { 105 | status: 'disabled' 106 | } 107 | retentionPolicy: { 108 | status: 'enabled' 109 | days: 7 110 | } 111 | trustPolicy: { 112 | status: 'enabled' 113 | type: 'Notary' 114 | } 115 | } 116 | publicNetworkAccess: publicNetworkAccess 117 | zoneRedundancy: zoneRedundancy 118 | } 119 | } 120 | 121 | resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { 122 | name: diagnosticSettingsName 123 | scope: containerRegistry 124 | properties: { 125 | workspaceId: workspaceId 126 | logs: logs 127 | metrics: metrics 128 | } 129 | } 130 | 131 | // Outputs 132 | output id string = containerRegistry.id 133 | output name string = containerRegistry.name 134 | output sku string = containerRegistry.sku.name 135 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/deploymentScript.bicep: -------------------------------------------------------------------------------- 1 | // For more information, see https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-script-bicep 2 | @description('Specifies the name of the deployment script uri.') 3 | param name string = 'BashScript' 4 | 5 | @description('Specifies the Azure CLI module version.') 6 | param azCliVersion string = '2.61.0' 7 | 8 | @description('Specifies the maximum allowed script execution time specified in ISO 8601 format. Default value is P1D.') 9 | param timeout string = 'PT30M' 10 | 11 | @description('Specifies the clean up preference when the script execution gets in a terminal state. Default setting is Always.') 12 | @allowed([ 13 | 'Always' 14 | 'OnExpiration' 15 | 'OnSuccess' 16 | ]) 17 | param cleanupPreference string = 'OnSuccess' 18 | 19 | @description('Specifies the interval for which the service retains the script resource after it reaches a terminal state. Resource will be deleted when this duration expires.') 20 | param retentionInterval string = 'P1D' 21 | 22 | @description('Specifies the name of the user-assigned managed identity of the deployment script.') 23 | param managedIdentityName string 24 | 25 | @description('Specifies the primary script URI.') 26 | param primaryScriptUri string 27 | 28 | @description('Specifies the name of the AKS cluster.') 29 | param clusterName string 30 | 31 | @description('Specifies the resource group name') 32 | param resourceGroupName string = resourceGroup().name 33 | 34 | @description('Specifies the subscription id.') 35 | param subscriptionId string = subscription().subscriptionId 36 | 37 | @description('Specifies whether to deploy Prometheus and Grafana to the AKS cluster using a Helm chart.') 38 | param deployPrometheusAndGrafanaViaHelm bool = true 39 | 40 | @description('Specifies whether to whether to deploy the Certificate Manager to the AKS cluster using a Helm chart.') 41 | param deployCertificateManagerViaHelm bool = true 42 | 43 | @description('Specifies the list of ingress classes for which a cert-manager cluster issuer should be created.') 44 | param ingressClassNames array = ['nginx', 'webapprouting.kubernetes.azure.com'] 45 | 46 | @description('Specifies the list of the names for the cert-manager cluster issuers.') 47 | param clusterIssuerNames array = ['letsencrypt-nginx', 'letsencrypt-webapprouting'] 48 | 49 | @description('Specifies whether and how to deploy the NGINX Ingress Controller to the AKS cluster using a Helm chart. Possible values are None, Internal, and External.') 50 | @allowed([ 51 | 'None' 52 | 'Internal' 53 | 'External' 54 | ]) 55 | param deployNginxIngressControllerViaHelm string = 'Internal' 56 | 57 | @description('Specifies the email address for the cert-manager cluster issuer.') 58 | param email string = 'admin@contoso.com' 59 | 60 | @description('Specifies the current datetime') 61 | param utcValue string = utcNow() 62 | 63 | @description('Specifies the location.') 64 | param location string = resourceGroup().location 65 | 66 | @description('Specifies the resource tags.') 67 | param tags object 68 | 69 | // Variables 70 | var clusterAdminRoleDefinitionId = resourceId('Microsoft.Authorization/roleDefinitions', '0ab0b1a8-8aac-4efd-b8c2-3ee1fb270be8') 71 | 72 | // Resources 73 | resource aksCluster 'Microsoft.ContainerService/managedClusters@2022-11-02-preview' existing = { 74 | name: clusterName 75 | } 76 | 77 | resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' = { 78 | name: managedIdentityName 79 | location: location 80 | tags: tags 81 | } 82 | 83 | resource clusterAdminContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 84 | name: guid(managedIdentity.id, aksCluster.id, clusterAdminRoleDefinitionId) 85 | scope: aksCluster 86 | properties: { 87 | roleDefinitionId: clusterAdminRoleDefinitionId 88 | principalId: managedIdentity.properties.principalId 89 | principalType: 'ServicePrincipal' 90 | } 91 | } 92 | 93 | // Script 94 | resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = if (deployPrometheusAndGrafanaViaHelm || deployCertificateManagerViaHelm || deployNginxIngressControllerViaHelm != 'None') { 95 | name: name 96 | location: location 97 | kind: 'AzureCLI' 98 | identity: { 99 | type: 'UserAssigned' 100 | userAssignedIdentities: { 101 | '${managedIdentity.id}': {} 102 | } 103 | } 104 | properties: { 105 | forceUpdateTag: utcValue 106 | azCliVersion: azCliVersion 107 | timeout: timeout 108 | environmentVariables: [ 109 | { 110 | name: 'clusterName' 111 | value: clusterName 112 | } 113 | { 114 | name: 'resourceGroupName' 115 | value: resourceGroupName 116 | } 117 | { 118 | name: 'subscriptionId' 119 | value: subscriptionId 120 | } 121 | { 122 | name: 'deployPrometheusAndGrafanaViaHelm' 123 | value: deployPrometheusAndGrafanaViaHelm ? 'true' : 'false' 124 | } 125 | { 126 | name: 'ingressClassNames' 127 | value: join(ingressClassNames, ',') 128 | } 129 | { 130 | name: 'clusterIssuerNames' 131 | value: join(clusterIssuerNames, ',') 132 | } 133 | { 134 | name: 'deployCertificateManagerViaHelm' 135 | value: deployCertificateManagerViaHelm ? 'true' : 'false' 136 | } 137 | { 138 | name: 'deployNginxIngressControllerViaHelm' 139 | value: deployNginxIngressControllerViaHelm 140 | } 141 | { 142 | name: 'email' 143 | value: email 144 | } 145 | ] 146 | primaryScriptUri: primaryScriptUri 147 | cleanupPreference: cleanupPreference 148 | retentionInterval: retentionInterval 149 | } 150 | } 151 | 152 | // Outputs 153 | output result object = deploymentScript.properties.outputs 154 | output certManager string = deploymentScript.properties.outputs.certManager 155 | output nginxIngressController string = deploymentScript.properties.outputs.nginxIngressController 156 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/dnsZone.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of an existing public DNS zone.') 3 | param name string 4 | 5 | @description('Specifies the name of the CNAME record to create within the DNS zone. The record will be an alias to your Front Door endpoint.') 6 | param cnameRecordName string 7 | 8 | @description('Specifies the time-to-live (TTL) value for the CNAME record.') 9 | param ttl int = 3600 10 | 11 | @description('Specifies the Front Door endpoint to which the CNAME record will point.') 12 | param hostName string 13 | 14 | @description('Specifies the validation state of the custom domain.') 15 | param domainValidationState string 16 | 17 | @description('Specifies the validation token of the custom domain.') 18 | param validationToken string 19 | 20 | resource dnsZone 'Microsoft.Network/dnsZones@2023-07-01-preview' existing = { 21 | name: name 22 | } 23 | 24 | resource cnameRecord 'Microsoft.Network/dnsZones/CNAME@2023-07-01-preview' = { 25 | parent: dnsZone 26 | name: cnameRecordName 27 | properties: { 28 | TTL: ttl 29 | CNAMERecord: { 30 | cname: hostName 31 | } 32 | } 33 | } 34 | 35 | resource validationTxtRecord 'Microsoft.Network/dnsZones/TXT@2023-07-01-preview' = if (domainValidationState != 'Approved') { 36 | parent: dnsZone 37 | name: '_dnsauth.${cnameRecordName}' 38 | properties: { 39 | TTL: ttl 40 | TXTRecords: [ 41 | { 42 | value: [ 43 | validationToken 44 | ] 45 | } 46 | ] 47 | } 48 | } 49 | 50 | // Outputs 51 | output dnsZoneId string = dnsZone.id 52 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/install-packages.sh: -------------------------------------------------------------------------------- 1 | # Install kubectl 2 | az aks install-cli --only-show-errors 3 | 4 | # Get AKS credentials 5 | az aks get-credentials \ 6 | --admin \ 7 | --name $clusterName \ 8 | --resource-group $resourceGroupName \ 9 | --subscription $subscriptionId \ 10 | --only-show-errors 11 | 12 | # Check if the cluster is private or not 13 | private=$(az aks show --name $clusterName \ 14 | --resource-group $resourceGroupName \ 15 | --subscription $subscriptionId \ 16 | --query apiServerAccessProfile.enablePrivateCluster \ 17 | --output tsv) 18 | 19 | # Install openssl 20 | apk add --no-cache --quiet openssl 21 | 22 | # Install Helm 23 | wget -O get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 24 | chmod 700 get_helm.sh 25 | ./get_helm.sh 26 | 27 | # Add Helm repos 28 | if [[ $deployPrometheusAndGrafanaViaHelm == 'true' ]]; then 29 | echo "Adding Prometheus Helm repository..." 30 | helm repo add prometheus-community https://prometheus-community.github.io/helm-charts 31 | fi 32 | 33 | if [[ $deployCertificateManagerViaHelm == 'true' ]]; then 34 | echo "Adding cert-manager Helm repository..." 35 | helm repo add jetstack https://charts.jetstack.io 36 | fi 37 | 38 | if [[ $deployNginxIngressControllerViaHelm != 'None' ]]; then 39 | echo "Adding NGINX ingress controller Helm repository..." 40 | helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx 41 | fi 42 | 43 | # Update Helm repos 44 | echo "Updating Helm repositories..." 45 | helm repo update 46 | 47 | # Install Prometheus 48 | if [[ $deployPrometheusAndGrafanaViaHelm == 'true' ]]; then 49 | echo "Installing Prometheus and Grafana..." 50 | helm install prometheus prometheus-community/kube-prometheus-stack \ 51 | --create-namespace \ 52 | --namespace prometheus \ 53 | --set prometheus.prometheusSpec.podMonitorSelectorNilUsesHelmValues=false \ 54 | --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false 55 | fi 56 | 57 | # Install certificate manager 58 | if [[ $deployCertificateManagerViaHelm == 'true' ]]; then 59 | echo "Installing cert-manager..." 60 | helm install cert-manager jetstack/cert-manager \ 61 | --create-namespace \ 62 | --namespace cert-manager \ 63 | --set crds.enabled=true \ 64 | --set prometheus.enabled=true \ 65 | --set nodeSelector."kubernetes\.io/os"=linux 66 | 67 | # Create arrays from the comma-separated strings 68 | IFS=',' read -ra ingressClassArray <<<"$ingressClassNames" # Split the string into an array 69 | IFS=',' read -ra clusterIssuerArray <<<"$clusterIssuerNames" # Split the string into an array 70 | 71 | # Check if the two arrays have the same length and are not empty 72 | # Check if the two arrays have the same length and are not empty 73 | if [[ ${#ingressClassArray[@]} > 0 && ${#ingressClassArray[@]} == ${#clusterIssuerArray[@]} ]]; then 74 | for i in ${!ingressClassArray[@]}; do 75 | echo "Creating cluster issuer ${clusterIssuerArray[$i]} for the ${ingressClassArray[$i]} ingress class..." 76 | # Create cluster issuer 77 | cat <$AZ_SCRIPTS_OUTPUT_PATH 137 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/internalLoadBalancer.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the load balancer.') 3 | param name string 4 | 5 | @description('Specifies the name of the resource group containing the load balancer.') 6 | param resourceGroupName string 7 | 8 | // Resources 9 | resource loadBalancer 'Microsoft.Network/loadBalancers@2024-01-01' existing = { 10 | name: name 11 | scope: resourceGroup(resourceGroupName) 12 | } 13 | 14 | // Outputs 15 | output privateIpAddress string = loadBalancer.properties.frontendIPConfigurations[0].properties.privateIPAddress 16 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/keyVault.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of an existing Key Vault resource holding the TLS certificate.') 3 | param name string 4 | 5 | @description('Specifies the object id of the Key Vault CSI Driver user-assigned managed identity.') 6 | param aksManagedIdentityObjectId string 7 | 8 | @description('Specifies the principal id of the Application Gateway user-assigned managed identity.') 9 | param applicationGatewayManagedIdentityPrincipalId string 10 | 11 | @description('Specifies whether the Azure Key Vault Provider for Secrets Store CSI Driver addon is enabled or not.') 12 | param azureKeyvaultSecretsProviderEnabled bool = true 13 | 14 | // Resources 15 | resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = { 16 | name: name 17 | } 18 | 19 | // Role Definitions 20 | resource keyVaultAdministratorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { 21 | name: '00482a5a-887f-4fb3-b363-3b7fe8e74483' 22 | scope: subscription() 23 | } 24 | 25 | resource keyVaultSecretsUserRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { 26 | name: '4633458b-17de-408a-b874-0445c86b69e6' 27 | scope: subscription() 28 | } 29 | 30 | // Role Assignments 31 | resource keyVaultCSIdriverSecretsUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (azureKeyvaultSecretsProviderEnabled) { 32 | name: guid(keyVault.id, 'CSIDriver', keyVaultAdministratorRoleDefinition.id, aksManagedIdentityObjectId) 33 | scope: keyVault 34 | properties: { 35 | roleDefinitionId: keyVaultAdministratorRoleDefinition.id 36 | principalType: 'ServicePrincipal' 37 | principalId: aksManagedIdentityObjectId 38 | } 39 | } 40 | 41 | resource keyVaultSecretsUserApplicationGatewayIdentityRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 42 | scope: keyVault 43 | name: guid(keyVault.id, applicationGatewayManagedIdentityPrincipalId, keyVaultSecretsUserRoleDefinition.id) 44 | properties: { 45 | roleDefinitionId: keyVaultSecretsUserRoleDefinition.id 46 | principalType: 'ServicePrincipal' 47 | principalId: applicationGatewayManagedIdentityPrincipalId 48 | } 49 | } 50 | 51 | // Outputs 52 | output id string = keyVault.id 53 | output name string = keyVault.name 54 | output vaultUri string = keyVault.properties.vaultUri 55 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/kubeletManagedIdentity.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the existing AKS cluster.') 3 | param aksClusterName string 4 | 5 | @description('Specifies the name of the existing container registry.') 6 | param acrName string 7 | 8 | // Variables 9 | var acrPullRoleDefinitionId = resourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') 10 | 11 | // Resources 12 | resource aksCluster 'Microsoft.ContainerService/managedClusters@2024-07-01' existing = { 13 | name: aksClusterName 14 | } 15 | 16 | resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-11-01-preview' existing = { 17 | name: acrName 18 | } 19 | 20 | resource acrPullRoleAssignmentName 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 21 | name: guid(aksCluster.name, containerRegistry.id, acrPullRoleDefinitionId) 22 | scope: containerRegistry 23 | properties: { 24 | roleDefinitionId: acrPullRoleDefinitionId 25 | principalId: any(aksCluster.properties.identityProfile.kubeletidentity).objectId 26 | principalType: 'ServicePrincipal' 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/logAnalytics.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the Log Analytics workspace.') 3 | param name string 4 | 5 | @description('Specifies the service tier of the workspace: Free, Standalone, PerNode, Per-GB.') 6 | @allowed([ 7 | 'Free' 8 | 'Standalone' 9 | 'PerNode' 10 | 'PerGB2018' 11 | ]) 12 | param sku string = 'PerNode' 13 | 14 | @description('Specifies the workspace data retention in days. -1 means Unlimited retention for the Unlimited Sku. 730 days is the maximum allowed for all other Skus.') 15 | param retentionInDays int = 60 16 | 17 | @description('Specifies the location.') 18 | param location string = resourceGroup().location 19 | 20 | @description('Specifies the resource tags.') 21 | param tags object 22 | 23 | // Resources 24 | resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { 25 | name: name 26 | tags: tags 27 | location: location 28 | properties: { 29 | sku: { 30 | name: sku 31 | } 32 | retentionInDays: retentionInDays 33 | } 34 | } 35 | 36 | //Outputs 37 | output id string = logAnalyticsWorkspace.id 38 | output name string = logAnalyticsWorkspace.name 39 | output customerId string = logAnalyticsWorkspace.properties.customerId 40 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/main.http.nginxviaaddon.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | // Variables 4 | var httpFrontendPortName = 'HttpFrontendPort' 5 | var httpListenerName = 'DefaultHttpListener' 6 | var requestRoutingRuleName = 'DefaultRequestRoutingRule' 7 | var backendHttpSettingsName = 'DefaultBackendHttpSettings' 8 | var probeName = 'DefaultProbe' 9 | var hostnames = ['your-yelb-hostname'] 10 | 11 | // Parameters 12 | param aksClusterNetworkMode = 'transparent' 13 | param aksClusterNetworkDataplane = 'cilium' 14 | param aksClusterNetworkPlugin = 'azure' 15 | param aksClusterNetworkPluginMode = 'overlay' 16 | param aksClusterNetworkPolicy = 'cilium' 17 | param aksClusterWebAppRoutingEnabled = true 18 | param aksClusterSkuTier = 'Standard' 19 | param aksClusterPodCidr = '192.168.0.0/16' 20 | param aksClusterServiceCidr = '172.16.0.0/16' 21 | param aksClusterDnsServiceIP = '172.16.0.10' 22 | param aksClusterOutboundType = 'userAssignedNATGateway' 23 | param aksClusterKubernetesVersion = '1.30.4' 24 | param aksClusterAdminUsername = 'azadmin' 25 | param aksClusterSshPublicKey = '' 26 | param loadBalancerBackendPoolType = 'nodeIP' 27 | param aadProfileManaged = true 28 | param aadProfileEnableAzureRBAC = true 29 | param aadProfileAdminGroupObjectIDs = [ 30 | '' 31 | ] 32 | param systemAgentPoolName = 'system' 33 | param systemAgentPoolVmSize = 'Standard_F8s_v2' 34 | param systemAgentPoolOsDiskSizeGB = 80 35 | param systemAgentPoolAgentCount = 3 36 | param systemAgentPoolMaxCount = 5 37 | param systemAgentPoolMinCount = 3 38 | param systemAgentPoolNodeTaints = [ 39 | 'CriticalAddonsOnly=true:NoSchedule' 40 | ] 41 | param userAgentPoolName = 'user' 42 | param userAgentPoolVmSize = 'Standard_F8s_v2' 43 | param userAgentPoolOsDiskSizeGB = 80 44 | param userAgentPoolAgentCount = 3 45 | param userAgentPoolMaxCount = 5 46 | param userAgentPoolMinCount = 3 47 | param enableVnetIntegration = true 48 | param virtualNetworkAddressPrefixes = '10.0.0.0/8' 49 | param systemAgentPoolSubnetName = 'SystemSubnet' 50 | param systemAgentPoolSubnetAddressPrefix = '10.240.0.0/16' 51 | param userAgentPoolSubnetName = 'UserSubnet' 52 | param userAgentPoolSubnetAddressPrefix = '10.241.0.0/16' 53 | param podSubnetName = 'PodSubnet' 54 | param podSubnetAddressPrefix = '10.242.0.0/16' 55 | param apiServerSubnetName = 'ApiServerSubnet' 56 | param apiServerSubnetAddressPrefix = '10.243.0.0/27' 57 | param vmSubnetName = 'VmSubnet' 58 | param vmSubnetAddressPrefix = '10.243.1.0/24' 59 | param bastionSubnetAddressPrefix = '10.243.2.0/24' 60 | param logAnalyticsSku = 'PerGB2018' 61 | param logAnalyticsRetentionInDays = 60 62 | param vmEnabled = true 63 | param vmName = 'TestVm' 64 | param vmSize = 'Standard_F8s_v2' 65 | param imagePublisher = 'Canonical' 66 | param imageOffer = '0001-com-ubuntu-server-jammy' 67 | param imageSku = '22_04-lts-gen2' 68 | param authenticationType = 'sshPublicKey' 69 | param vmAdminUsername = 'azadmin' 70 | param vmAdminPasswordOrKey = '' 71 | param diskStorageAccountType = 'Premium_LRS' 72 | param numDataDisks = 1 73 | param osDiskSize = 50 74 | param dataDiskSize = 50 75 | param dataDiskCaching = 'ReadWrite' 76 | param aksClusterEnablePrivateCluster = false 77 | param aksEnablePrivateClusterPublicFQDN = false 78 | param podIdentityProfileEnabled = false 79 | param kedaEnabled = true 80 | param daprEnabled = true 81 | param fluxGitOpsEnabled = false 82 | param verticalPodAutoscalerEnabled = true 83 | param deploymentScriptUri = 'https://raw.githubusercontent.com/azure-samples/aks-web-application-replicate-from-aws/refs/heads/main/azure/nginx-with-azure-waf/bicep/install-packages.sh' 84 | param blobCSIDriverEnabled = true 85 | param diskCSIDriverEnabled = true 86 | param fileCSIDriverEnabled = true 87 | param snapshotControllerEnabled = true 88 | param defenderSecurityMonitoringEnabled = true 89 | param imageCleanerEnabled = true 90 | param imageCleanerIntervalHours = 24 91 | param nodeRestrictionEnabled = true 92 | param workloadIdentityEnabled = true 93 | param oidcIssuerProfileEnabled = true 94 | param dnsZoneName = '' 95 | param dnsZoneResourceGroupName = '' 96 | param actionGroupEmailAddress = '' 97 | param keyVaultName = '' 98 | param keyVaultResourceGroupName = '' 99 | param keyVaultCertificateName = '' 100 | param backendAddressPoolName = 'DefaultBackendAddressPool' 101 | param frontendPorts = [ 102 | { 103 | name: httpFrontendPortName 104 | port: 443 105 | } 106 | ] 107 | param httpListeners = [ 108 | { 109 | name: httpListenerName 110 | protocol: 'Https' 111 | frontendPort: httpFrontendPortName 112 | sslCertificate: keyVaultCertificateName 113 | hostNames: hostnames 114 | firewallPolicy: 'Enabled' 115 | } 116 | ] 117 | param requestRoutingRules = [ 118 | { 119 | name: requestRoutingRuleName 120 | ruleType: 'Basic' 121 | priority: 1000 122 | listener: httpListenerName 123 | backendPool: backendAddressPoolName 124 | backendHttpSettings: backendHttpSettingsName 125 | } 126 | ] 127 | param backendHttpSettings = [ 128 | { 129 | name: backendHttpSettingsName 130 | port: 80 131 | protocol: 'Http' 132 | cookieBasedAffinity: 'Disabled' 133 | probeName: probeName 134 | probeEnabled: true 135 | pickHostNameFromBackendAddress: false 136 | requestTimeout: 300 137 | } 138 | ] 139 | param probes = [ 140 | { 141 | name: probeName 142 | protocol: 'Http' 143 | path: '/' 144 | host: hostnames[0] 145 | port: 80 146 | interval: 60 147 | timeout: 30 148 | unhealthyThreshold: 3 149 | pickHostNameFromBackendHttpSettings: false 150 | match: { 151 | statusCodes: [ 152 | '200' 153 | ] 154 | } 155 | } 156 | ] 157 | param redirectConfigurations = [] 158 | param deployPrometheusAndGrafanaViaHelm = false 159 | param deployCertificateManagerViaHelm = true 160 | param ingressClassNames = ['webapprouting.kubernetes.azure.com'] 161 | param clusterIssuerNames = ['letsencrypt-nginx'] 162 | param deployNginxIngressControllerViaHelm = 'None' 163 | param email = '' 164 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/main.http.nginxviahelm.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | // Variables 4 | var httpFrontendPortName = 'HttpFrontendPort' 5 | var httpListenerName = 'DefaultHttpListener' 6 | var requestRoutingRuleName = 'DefaultRequestRoutingRule' 7 | var backendHttpSettingsName = 'DefaultBackendHttpSettings' 8 | var probeName = 'DefaultProbe' 9 | var hostnames = ['your-yelb-hostname'] 10 | 11 | // Parameters 12 | param aksClusterNetworkMode = 'transparent' 13 | param aksClusterNetworkDataplane = 'cilium' 14 | param aksClusterNetworkPlugin = 'azure' 15 | param aksClusterNetworkPluginMode = 'overlay' 16 | param aksClusterNetworkPolicy = 'cilium' 17 | param aksClusterWebAppRoutingEnabled = false 18 | param aksClusterSkuTier = 'Standard' 19 | param aksClusterPodCidr = '192.168.0.0/16' 20 | param aksClusterServiceCidr = '172.16.0.0/16' 21 | param aksClusterDnsServiceIP = '172.16.0.10' 22 | param aksClusterOutboundType = 'userAssignedNATGateway' 23 | param aksClusterKubernetesVersion = '1.30.4' 24 | param aksClusterAdminUsername = 'azadmin' 25 | param aksClusterSshPublicKey = '' 26 | param loadBalancerBackendPoolType = 'nodeIP' 27 | param aadProfileManaged = true 28 | param aadProfileEnableAzureRBAC = true 29 | param aadProfileAdminGroupObjectIDs = [ 30 | '' 31 | ] 32 | param systemAgentPoolName = 'system' 33 | param systemAgentPoolVmSize = 'Standard_F8s_v2' 34 | param systemAgentPoolOsDiskSizeGB = 80 35 | param systemAgentPoolAgentCount = 3 36 | param systemAgentPoolMaxCount = 5 37 | param systemAgentPoolMinCount = 3 38 | param systemAgentPoolNodeTaints = [ 39 | 'CriticalAddonsOnly=true:NoSchedule' 40 | ] 41 | param userAgentPoolName = 'user' 42 | param userAgentPoolVmSize = 'Standard_F8s_v2' 43 | param userAgentPoolOsDiskSizeGB = 80 44 | param userAgentPoolAgentCount = 3 45 | param userAgentPoolMaxCount = 5 46 | param userAgentPoolMinCount = 3 47 | param enableVnetIntegration = true 48 | param virtualNetworkAddressPrefixes = '10.0.0.0/8' 49 | param systemAgentPoolSubnetName = 'SystemSubnet' 50 | param systemAgentPoolSubnetAddressPrefix = '10.240.0.0/16' 51 | param userAgentPoolSubnetName = 'UserSubnet' 52 | param userAgentPoolSubnetAddressPrefix = '10.241.0.0/16' 53 | param podSubnetName = 'PodSubnet' 54 | param podSubnetAddressPrefix = '10.242.0.0/16' 55 | param apiServerSubnetName = 'ApiServerSubnet' 56 | param apiServerSubnetAddressPrefix = '10.243.0.0/27' 57 | param vmSubnetName = 'VmSubnet' 58 | param vmSubnetAddressPrefix = '10.243.1.0/24' 59 | param bastionSubnetAddressPrefix = '10.243.2.0/24' 60 | param logAnalyticsSku = 'PerGB2018' 61 | param logAnalyticsRetentionInDays = 60 62 | param vmEnabled = true 63 | param vmName = 'TestVm' 64 | param vmSize = 'Standard_F8s_v2' 65 | param imagePublisher = 'Canonical' 66 | param imageOffer = '0001-com-ubuntu-server-jammy' 67 | param imageSku = '22_04-lts-gen2' 68 | param authenticationType = 'sshPublicKey' 69 | param vmAdminUsername = 'azadmin' 70 | param vmAdminPasswordOrKey = '' 71 | param diskStorageAccountType = 'Premium_LRS' 72 | param numDataDisks = 1 73 | param osDiskSize = 50 74 | param dataDiskSize = 50 75 | param dataDiskCaching = 'ReadWrite' 76 | param aksClusterEnablePrivateCluster = false 77 | param aksEnablePrivateClusterPublicFQDN = false 78 | param podIdentityProfileEnabled = false 79 | param kedaEnabled = true 80 | param daprEnabled = true 81 | param fluxGitOpsEnabled = false 82 | param verticalPodAutoscalerEnabled = true 83 | param deploymentScriptUri = 'https://raw.githubusercontent.com/azure-samples/aks-web-application-replicate-from-aws/refs/heads/main/azure/nginx-with-azure-waf/bicep/install-packages.sh' 84 | param blobCSIDriverEnabled = true 85 | param diskCSIDriverEnabled = true 86 | param fileCSIDriverEnabled = true 87 | param snapshotControllerEnabled = true 88 | param defenderSecurityMonitoringEnabled = true 89 | param imageCleanerEnabled = true 90 | param imageCleanerIntervalHours = 24 91 | param nodeRestrictionEnabled = true 92 | param workloadIdentityEnabled = true 93 | param oidcIssuerProfileEnabled = true 94 | param dnsZoneName = '' 95 | param dnsZoneResourceGroupName = '' 96 | param actionGroupEmailAddress = '' 97 | param keyVaultName = '' 98 | param keyVaultResourceGroupName = '' 99 | param keyVaultCertificateName = '' 100 | param backendAddressPoolName = 'DefaultBackendAddressPool' 101 | param frontendPorts = [ 102 | { 103 | name: httpFrontendPortName 104 | port: 443 105 | } 106 | ] 107 | param httpListeners = [ 108 | { 109 | name: httpListenerName 110 | protocol: 'Https' 111 | frontendPort: httpFrontendPortName 112 | sslCertificate: keyVaultCertificateName 113 | hostNames: hostnames 114 | firewallPolicy: 'Enabled' 115 | } 116 | ] 117 | param requestRoutingRules = [ 118 | { 119 | name: requestRoutingRuleName 120 | ruleType: 'Basic' 121 | priority: 1000 122 | listener: httpListenerName 123 | backendPool: backendAddressPoolName 124 | backendHttpSettings: backendHttpSettingsName 125 | } 126 | ] 127 | param backendHttpSettings = [ 128 | { 129 | name: backendHttpSettingsName 130 | port: 80 131 | protocol: 'Http' 132 | cookieBasedAffinity: 'Disabled' 133 | probeName: probeName 134 | probeEnabled: true 135 | pickHostNameFromBackendAddress: false 136 | requestTimeout: 300 137 | } 138 | ] 139 | param probes = [ 140 | { 141 | name: probeName 142 | protocol: 'Http' 143 | path: '/' 144 | host: hostnames[0] 145 | port: 80 146 | interval: 60 147 | timeout: 30 148 | unhealthyThreshold: 3 149 | pickHostNameFromBackendHttpSettings: false 150 | match: { 151 | statusCodes: [ 152 | '200' 153 | ] 154 | } 155 | } 156 | ] 157 | param redirectConfigurations = [] 158 | param deployPrometheusAndGrafanaViaHelm = true 159 | param deployCertificateManagerViaHelm = true 160 | param ingressClassNames = ['nginx'] 161 | param clusterIssuerNames = ['letsencrypt-nginx'] 162 | param deployNginxIngressControllerViaHelm = 'Internal' 163 | param email = '' 164 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/main.https.nginxviaaddon.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | // Variables 4 | var httpFrontendPortName = 'HttpFrontendPort' 5 | var httpListenerName = 'DefaultHttpListener' 6 | var requestRoutingRuleName = 'DefaultRequestRoutingRule' 7 | var backendHttpSettingsName = 'DefaultBackendHttpSettings' 8 | var probeName = 'DefaultProbe' 9 | var hostnames = ['your-yelb-hostname'] 10 | 11 | // Parameters 12 | param aksClusterNetworkMode = 'transparent' 13 | param aksClusterNetworkDataplane = 'cilium' 14 | param aksClusterNetworkPlugin = 'azure' 15 | param aksClusterNetworkPluginMode = 'overlay' 16 | param aksClusterNetworkPolicy = 'cilium' 17 | param aksClusterWebAppRoutingEnabled = true 18 | param aksClusterSkuTier = 'Standard' 19 | param aksClusterPodCidr = '192.168.0.0/16' 20 | param aksClusterServiceCidr = '172.16.0.0/16' 21 | param aksClusterDnsServiceIP = '172.16.0.10' 22 | param aksClusterOutboundType = 'userAssignedNATGateway' 23 | param aksClusterKubernetesVersion = '1.30.4' 24 | param aksClusterAdminUsername = 'azadmin' 25 | param aksClusterSshPublicKey = '' 26 | param loadBalancerBackendPoolType = 'nodeIP' 27 | param aadProfileManaged = true 28 | param aadProfileEnableAzureRBAC = true 29 | param aadProfileAdminGroupObjectIDs = [ 30 | '' 31 | ] 32 | param systemAgentPoolName = 'system' 33 | param systemAgentPoolVmSize = 'Standard_F8s_v2' 34 | param systemAgentPoolOsDiskSizeGB = 80 35 | param systemAgentPoolAgentCount = 3 36 | param systemAgentPoolMaxCount = 5 37 | param systemAgentPoolMinCount = 3 38 | param systemAgentPoolNodeTaints = [ 39 | 'CriticalAddonsOnly=true:NoSchedule' 40 | ] 41 | param userAgentPoolName = 'user' 42 | param userAgentPoolVmSize = 'Standard_F8s_v2' 43 | param userAgentPoolOsDiskSizeGB = 80 44 | param userAgentPoolAgentCount = 3 45 | param userAgentPoolMaxCount = 5 46 | param userAgentPoolMinCount = 3 47 | param enableVnetIntegration = true 48 | param virtualNetworkAddressPrefixes = '10.0.0.0/8' 49 | param systemAgentPoolSubnetName = 'SystemSubnet' 50 | param systemAgentPoolSubnetAddressPrefix = '10.240.0.0/16' 51 | param userAgentPoolSubnetName = 'UserSubnet' 52 | param userAgentPoolSubnetAddressPrefix = '10.241.0.0/16' 53 | param podSubnetName = 'PodSubnet' 54 | param podSubnetAddressPrefix = '10.242.0.0/16' 55 | param apiServerSubnetName = 'ApiServerSubnet' 56 | param apiServerSubnetAddressPrefix = '10.243.0.0/27' 57 | param vmSubnetName = 'VmSubnet' 58 | param vmSubnetAddressPrefix = '10.243.1.0/24' 59 | param bastionSubnetAddressPrefix = '10.243.2.0/24' 60 | param logAnalyticsSku = 'PerGB2018' 61 | param logAnalyticsRetentionInDays = 60 62 | param vmEnabled = true 63 | param vmName = 'TestVm' 64 | param vmSize = 'Standard_F8s_v2' 65 | param imagePublisher = 'Canonical' 66 | param imageOffer = '0001-com-ubuntu-server-jammy' 67 | param imageSku = '22_04-lts-gen2' 68 | param authenticationType = 'sshPublicKey' 69 | param vmAdminUsername = 'azadmin' 70 | param vmAdminPasswordOrKey = '' 71 | param diskStorageAccountType = 'Premium_LRS' 72 | param numDataDisks = 1 73 | param osDiskSize = 50 74 | param dataDiskSize = 50 75 | param dataDiskCaching = 'ReadWrite' 76 | param aksClusterEnablePrivateCluster = false 77 | param aksEnablePrivateClusterPublicFQDN = false 78 | param podIdentityProfileEnabled = false 79 | param kedaEnabled = true 80 | param daprEnabled = true 81 | param fluxGitOpsEnabled = false 82 | param verticalPodAutoscalerEnabled = true 83 | param deploymentScriptUri = 'https://raw.githubusercontent.com/paolosalvatori/scripts/refs/heads/main/install-packages.sh' 84 | param blobCSIDriverEnabled = true 85 | param diskCSIDriverEnabled = true 86 | param fileCSIDriverEnabled = true 87 | param snapshotControllerEnabled = true 88 | param defenderSecurityMonitoringEnabled = true 89 | param imageCleanerEnabled = true 90 | param imageCleanerIntervalHours = 24 91 | param nodeRestrictionEnabled = true 92 | param workloadIdentityEnabled = true 93 | param oidcIssuerProfileEnabled = true 94 | param dnsZoneName = '' 95 | param dnsZoneResourceGroupName = '' 96 | param actionGroupEmailAddress = '' 97 | param keyVaultName = '' 98 | param keyVaultResourceGroupName = '' 99 | param keyVaultCertificateName = '' 100 | param backendAddressPoolName = 'DefaultBackendAddressPool' 101 | param frontendPorts = [ 102 | { 103 | name: httpFrontendPortName 104 | port: 443 105 | } 106 | ] 107 | param httpListeners = [ 108 | { 109 | name: httpListenerName 110 | protocol: 'Https' 111 | frontendPort: httpFrontendPortName 112 | sslCertificate: keyVaultCertificateName 113 | hostNames: hostnames 114 | firewallPolicy: 'Enabled' 115 | } 116 | ] 117 | param requestRoutingRules = [ 118 | { 119 | name: requestRoutingRuleName 120 | ruleType: 'Basic' 121 | priority: 1000 122 | listener: httpListenerName 123 | backendPool: backendAddressPoolName 124 | backendHttpSettings: backendHttpSettingsName 125 | } 126 | ] 127 | param backendHttpSettings = [ 128 | { 129 | name: backendHttpSettingsName 130 | port: 443 131 | protocol: 'Https' 132 | cookieBasedAffinity: 'Disabled' 133 | probeName: probeName 134 | probeEnabled: true 135 | pickHostNameFromBackendAddress: false 136 | requestTimeout: 300 137 | } 138 | ] 139 | param probes = [ 140 | { 141 | name: probeName 142 | protocol: 'Https' 143 | path: '/' 144 | host: hostnames[0] 145 | port: 443 146 | interval: 60 147 | timeout: 30 148 | unhealthyThreshold: 3 149 | pickHostNameFromBackendHttpSettings: false 150 | match: { 151 | statusCodes: [ 152 | '200' 153 | ] 154 | } 155 | } 156 | ] 157 | param redirectConfigurations = [] 158 | param deployPrometheusAndGrafanaViaHelm = false 159 | param deployCertificateManagerViaHelm = true 160 | param ingressClassNames = ['webapprouting.kubernetes.azure.com'] 161 | param clusterIssuerNames = ['letsencrypt-nginx'] 162 | param deployNginxIngressControllerViaHelm = 'None' 163 | param email = '' 164 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/main.https.nginxviahelm.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | // Variables 4 | var httpFrontendPortName = 'HttpFrontendPort' 5 | var httpListenerName = 'DefaultHttpListener' 6 | var requestRoutingRuleName = 'DefaultRequestRoutingRule' 7 | var backendHttpSettingsName = 'DefaultBackendHttpSettings' 8 | var probeName = 'DefaultProbe' 9 | var hostnames = ['your-yelb-hostname'] 10 | 11 | // Parameters 12 | param aksClusterNetworkMode = 'transparent' 13 | param aksClusterNetworkDataplane = 'cilium' 14 | param aksClusterNetworkPlugin = 'azure' 15 | param aksClusterNetworkPluginMode = 'overlay' 16 | param aksClusterNetworkPolicy = 'cilium' 17 | param aksClusterWebAppRoutingEnabled = false 18 | param aksClusterSkuTier = 'Standard' 19 | param aksClusterPodCidr = '192.168.0.0/16' 20 | param aksClusterServiceCidr = '172.16.0.0/16' 21 | param aksClusterDnsServiceIP = '172.16.0.10' 22 | param aksClusterOutboundType = 'userAssignedNATGateway' 23 | param aksClusterKubernetesVersion = '1.30.4' 24 | param aksClusterAdminUsername = 'azadmin' 25 | param aksClusterSshPublicKey = '' 26 | param loadBalancerBackendPoolType = 'nodeIP' 27 | param aadProfileManaged = true 28 | param aadProfileEnableAzureRBAC = true 29 | param aadProfileAdminGroupObjectIDs = [ 30 | '' 31 | ] 32 | param systemAgentPoolName = 'system' 33 | param systemAgentPoolVmSize = 'Standard_F8s_v2' 34 | param systemAgentPoolOsDiskSizeGB = 80 35 | param systemAgentPoolAgentCount = 3 36 | param systemAgentPoolMaxCount = 5 37 | param systemAgentPoolMinCount = 3 38 | param systemAgentPoolNodeTaints = [ 39 | 'CriticalAddonsOnly=true:NoSchedule' 40 | ] 41 | param userAgentPoolName = 'user' 42 | param userAgentPoolVmSize = 'Standard_F8s_v2' 43 | param userAgentPoolOsDiskSizeGB = 80 44 | param userAgentPoolAgentCount = 3 45 | param userAgentPoolMaxCount = 5 46 | param userAgentPoolMinCount = 3 47 | param enableVnetIntegration = true 48 | param virtualNetworkAddressPrefixes = '10.0.0.0/8' 49 | param systemAgentPoolSubnetName = 'SystemSubnet' 50 | param systemAgentPoolSubnetAddressPrefix = '10.240.0.0/16' 51 | param userAgentPoolSubnetName = 'UserSubnet' 52 | param userAgentPoolSubnetAddressPrefix = '10.241.0.0/16' 53 | param podSubnetName = 'PodSubnet' 54 | param podSubnetAddressPrefix = '10.242.0.0/16' 55 | param apiServerSubnetName = 'ApiServerSubnet' 56 | param apiServerSubnetAddressPrefix = '10.243.0.0/27' 57 | param vmSubnetName = 'VmSubnet' 58 | param vmSubnetAddressPrefix = '10.243.1.0/24' 59 | param bastionSubnetAddressPrefix = '10.243.2.0/24' 60 | param logAnalyticsSku = 'PerGB2018' 61 | param logAnalyticsRetentionInDays = 60 62 | param vmEnabled = true 63 | param vmName = 'TestVm' 64 | param vmSize = 'Standard_F8s_v2' 65 | param imagePublisher = 'Canonical' 66 | param imageOffer = '0001-com-ubuntu-server-jammy' 67 | param imageSku = '22_04-lts-gen2' 68 | param authenticationType = 'sshPublicKey' 69 | param vmAdminUsername = 'azadmin' 70 | param vmAdminPasswordOrKey = '' 71 | param diskStorageAccountType = 'Premium_LRS' 72 | param numDataDisks = 1 73 | param osDiskSize = 50 74 | param dataDiskSize = 50 75 | param dataDiskCaching = 'ReadWrite' 76 | param aksClusterEnablePrivateCluster = false 77 | param aksEnablePrivateClusterPublicFQDN = false 78 | param podIdentityProfileEnabled = false 79 | param kedaEnabled = true 80 | param daprEnabled = true 81 | param fluxGitOpsEnabled = false 82 | param verticalPodAutoscalerEnabled = true 83 | param deploymentScriptUri = 'https://raw.githubusercontent.com/paolosalvatori/scripts/refs/heads/main/install-packages.sh' 84 | param blobCSIDriverEnabled = true 85 | param diskCSIDriverEnabled = true 86 | param fileCSIDriverEnabled = true 87 | param snapshotControllerEnabled = true 88 | param defenderSecurityMonitoringEnabled = true 89 | param imageCleanerEnabled = true 90 | param imageCleanerIntervalHours = 24 91 | param nodeRestrictionEnabled = true 92 | param workloadIdentityEnabled = true 93 | param oidcIssuerProfileEnabled = true 94 | param dnsZoneName = '' 95 | param dnsZoneResourceGroupName = '' 96 | param actionGroupEmailAddress = '' 97 | param keyVaultName = '' 98 | param keyVaultResourceGroupName = '' 99 | param keyVaultCertificateName = '' 100 | param backendAddressPoolName = 'DefaultBackendAddressPool' 101 | param frontendPorts = [ 102 | { 103 | name: httpFrontendPortName 104 | port: 443 105 | } 106 | ] 107 | param httpListeners = [ 108 | { 109 | name: httpListenerName 110 | protocol: 'Https' 111 | frontendPort: httpFrontendPortName 112 | sslCertificate: keyVaultCertificateName 113 | hostNames: hostnames 114 | firewallPolicy: 'Enabled' 115 | } 116 | ] 117 | param requestRoutingRules = [ 118 | { 119 | name: requestRoutingRuleName 120 | ruleType: 'Basic' 121 | priority: 1000 122 | listener: httpListenerName 123 | backendPool: backendAddressPoolName 124 | backendHttpSettings: backendHttpSettingsName 125 | } 126 | ] 127 | param backendHttpSettings = [ 128 | { 129 | name: backendHttpSettingsName 130 | port: 443 131 | protocol: 'Https' 132 | cookieBasedAffinity: 'Disabled' 133 | probeName: probeName 134 | probeEnabled: true 135 | pickHostNameFromBackendAddress: false 136 | requestTimeout: 300 137 | } 138 | ] 139 | param probes = [ 140 | { 141 | name: probeName 142 | protocol: 'Https' 143 | path: '/' 144 | host: hostnames[0] 145 | port: 443 146 | interval: 60 147 | timeout: 30 148 | unhealthyThreshold: 3 149 | pickHostNameFromBackendHttpSettings: false 150 | match: { 151 | statusCodes: [ 152 | '200' 153 | ] 154 | } 155 | } 156 | ] 157 | param redirectConfigurations = [] 158 | param deployPrometheusAndGrafanaViaHelm = true 159 | param deployCertificateManagerViaHelm = true 160 | param ingressClassNames = ['nginx'] 161 | param clusterIssuerNames = ['letsencrypt-nginx'] 162 | param deployNginxIngressControllerViaHelm = 'Internal' 163 | param email = '' 164 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/managedGrafana.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the Azure Monitor managed service for Prometheus resource.') 3 | param prometheusName string 4 | 5 | @description('Specifies the name of the Azure Managed Grafana resource.') 6 | param name string 7 | 8 | @description('Specifies the location of the Azure Managed Grafana resource.') 9 | param location string = resourceGroup().location 10 | 11 | @description('Specifies the sku of the Azure Managed Grafana resource.') 12 | param skuName string = 'Standard' 13 | 14 | @description('Specifies the api key setting of the Azure Managed Grafana resource.') 15 | @allowed([ 16 | 'Disabled' 17 | 'Enabled' 18 | ]) 19 | param apiKey string = 'Enabled' 20 | 21 | @description('Specifies the scope for dns deterministic name hash calculation.') 22 | @allowed([ 23 | 'TenantReuse' 24 | ]) 25 | param autoGeneratedDomainNameLabelScope string = 'TenantReuse' 26 | 27 | @description('Specifies whether the Azure Managed Grafana resource uses deterministic outbound IPs.') 28 | @allowed([ 29 | 'Disabled' 30 | 'Enabled' 31 | ]) 32 | param deterministicOutboundIP string = 'Disabled' 33 | 34 | @description('Specifies the the state for enable or disable traffic over the public interface for the the Azure Managed Grafana resource.') 35 | @allowed([ 36 | 'Disabled' 37 | 'Enabled' 38 | ]) 39 | param publicNetworkAccess string = 'Enabled' 40 | 41 | @description('The zone redundancy setting of the Azure Managed Grafana resource.') 42 | @allowed([ 43 | 'Disabled' 44 | 'Enabled' 45 | ]) 46 | param zoneRedundancy string = 'Disabled' 47 | 48 | @description('Specifies the object id of an Azure Active Directory user. In general, this the object id of the system administrator who deploys the Azure resources.') 49 | param userId string = '' 50 | 51 | @description('Specifies the resource tags for the Azure Monitor managed service for Prometheus resource.') 52 | param tags object 53 | 54 | // Resources 55 | resource mmonitoringReaderRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { 56 | name: '43d0d8ad-25c7-4714-9337-8ba259a9fe05' 57 | scope: subscription() 58 | } 59 | 60 | resource monitoringDataReaderRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { 61 | name: 'b0d8363b-8ddd-447d-831f-62ca05bff136' 62 | scope: subscription() 63 | } 64 | 65 | resource grafanaAdminRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { 66 | name: '22926164-76b3-42b3-bc55-97df8dab3e41' 67 | scope: subscription() 68 | } 69 | 70 | resource managedPrometheus 'Microsoft.Monitor/accounts@2023-10-01-preview' existing = { 71 | name: prometheusName 72 | } 73 | 74 | resource managedGrafana 'Microsoft.Dashboard/grafana@2023-09-01' = { 75 | name: name 76 | location: location 77 | tags: tags 78 | sku: { 79 | name: skuName 80 | } 81 | identity: { 82 | type: 'SystemAssigned' 83 | } 84 | properties: { 85 | apiKey: apiKey 86 | autoGeneratedDomainNameLabelScope: autoGeneratedDomainNameLabelScope 87 | deterministicOutboundIP: deterministicOutboundIP 88 | grafanaIntegrations: { 89 | azureMonitorWorkspaceIntegrations: [{ 90 | azureMonitorWorkspaceResourceId: managedPrometheus.id 91 | }] 92 | } 93 | publicNetworkAccess: publicNetworkAccess 94 | zoneRedundancy: zoneRedundancy 95 | } 96 | } 97 | 98 | // Assign the Monitoring Reader role to the Azure Managed Grafana system-assigned managed identity at the workspace scope 99 | resource monitoringReaderRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 100 | name: guid(name, managedPrometheus.name, mmonitoringReaderRole.id) 101 | scope: managedPrometheus 102 | properties: { 103 | roleDefinitionId: mmonitoringReaderRole.id 104 | principalId: managedGrafana.identity.principalId 105 | principalType: 'ServicePrincipal' 106 | } 107 | } 108 | 109 | // Assign the Monitoring Data Reader role to the Azure Managed Grafana system-assigned managed identity at the workspace scope 110 | resource monitoringDataReaderRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 111 | name: guid(name, managedPrometheus.name, monitoringDataReaderRole.id) 112 | scope: managedPrometheus 113 | properties: { 114 | roleDefinitionId: monitoringDataReaderRole.id 115 | principalId: managedGrafana.identity.principalId 116 | principalType: 'ServicePrincipal' 117 | } 118 | } 119 | 120 | // Assign the Grafana Admin role to the Microsoft Entra ID user at the Azure Managed Grafana resource scope 121 | resource grafanaAdminRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(userId)) { 122 | name: guid(name, userId, grafanaAdminRole.id) 123 | scope: managedGrafana 124 | properties: { 125 | roleDefinitionId: grafanaAdminRole.id 126 | principalId: userId 127 | principalType: 'User' 128 | } 129 | } 130 | 131 | // Outputs 132 | output id string = managedGrafana.id 133 | output name string = managedGrafana.name 134 | output location string = managedGrafana.location 135 | output principalId string = managedGrafana.identity.principalId 136 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/storageAccount.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the globally unique name for the storage account used to store the boot diagnostics logs of the virtual machine.') 3 | param name string = 'boot${uniqueString(resourceGroup().id)}' 4 | 5 | @description('Specifies whether to create containers.') 6 | param createContainers bool = true 7 | 8 | @description('Specifies an array of containers to create.') 9 | param containerNames array 10 | 11 | @description('Specifies the resource id of the Log Analytics workspace.') 12 | param workspaceId string 13 | 14 | @description('Specifies the location.') 15 | param location string = resourceGroup().location 16 | 17 | @description('Specifies the resource tags.') 18 | param tags object 19 | 20 | // Variables 21 | var diagnosticSettingsName = 'diagnosticSettings' 22 | var logCategories = [ 23 | 'StorageRead' 24 | 'StorageWrite' 25 | 'StorageDelete' 26 | ] 27 | var metricCategories = [ 28 | 'Transaction' 29 | ] 30 | var logs = [for category in logCategories: { 31 | category: category 32 | enabled: true 33 | retentionPolicy: { 34 | enabled: true 35 | days: 0 36 | } 37 | }] 38 | var metrics = [for category in metricCategories: { 39 | category: category 40 | enabled: true 41 | retentionPolicy: { 42 | enabled: true 43 | days: 0 44 | } 45 | }] 46 | 47 | // Resources 48 | resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = { 49 | name: name 50 | location: location 51 | tags: tags 52 | sku: { 53 | name: 'Standard_LRS' 54 | } 55 | kind: 'StorageV2' 56 | 57 | // Containers live inside of a blob service 58 | resource blobService 'blobServices' = { 59 | name: 'default' 60 | 61 | // Creating containers with provided names if contition is true 62 | resource containers 'containers' = [for containerName in containerNames: if(createContainers) { 63 | name: containerName 64 | properties: { 65 | publicAccess: 'None' 66 | } 67 | }] 68 | } 69 | } 70 | 71 | resource blobServiceDiagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { 72 | name: diagnosticSettingsName 73 | scope: storageAccount::blobService 74 | properties: { 75 | workspaceId: workspaceId 76 | logs: logs 77 | metrics: metrics 78 | } 79 | } 80 | 81 | // Outputs 82 | output id string = storageAccount.id 83 | output name string = storageAccount.name 84 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/bicep/virtualMachine.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the virtual machine.') 3 | param vmName string = 'TestVm' 4 | 5 | @description('Specifies the size of the virtual machine.') 6 | param vmSize string = 'Standard_DS3_v2' 7 | 8 | @description('Specifies the resource id of the subnet hosting the virtual machine.') 9 | param vmSubnetId string 10 | 11 | @description('Specifies the name of the storage account where the bootstrap diagnostic logs of the virtual machine are stored.') 12 | param storageAccountName string 13 | 14 | @description('Specifies the image publisher of the disk image used to create the virtual machine.') 15 | param imagePublisher string = 'Canonical' 16 | 17 | @description('Specifies the offer of the platform image or marketplace image used to create the virtual machine.') 18 | param imageOffer string = '0001-com-ubuntu-server-jammy' 19 | 20 | @description('Specifies the Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version.') 21 | param imageSku string = '22_04-lts-gen2' 22 | 23 | @description('Specifies the type of authentication when accessing the Virtual Machine. SSH key is recommended.') 24 | @allowed([ 25 | 'sshPublicKey' 26 | 'password' 27 | ]) 28 | param authenticationType string = 'password' 29 | 30 | @description('Specifies the name of the administrator account of the virtual machine.') 31 | param vmAdminUsername string 32 | 33 | @description('Specifies the SSH Key or password for the virtual machine. SSH key is recommended.') 34 | @secure() 35 | param vmAdminPasswordOrKey string 36 | 37 | @description('Specifies the storage account type for OS and data disk.') 38 | @allowed([ 39 | 'Premium_LRS' 40 | 'StandardSSD_LRS' 41 | 'Standard_LRS' 42 | 'UltraSSD_LRS' 43 | ]) 44 | param diskStorageAccountType string = 'Premium_LRS' 45 | 46 | @description('Specifies the number of data disks of the virtual machine.') 47 | @minValue(0) 48 | @maxValue(64) 49 | param numDataDisks int = 1 50 | 51 | @description('Specifies the size in GB of the OS disk of the VM.') 52 | param osDiskSize int = 50 53 | 54 | @description('Specifies the size in GB of the OS disk of the virtual machine.') 55 | param dataDiskSize int = 50 56 | 57 | @description('Specifies the caching requirements for the data disks.') 58 | param dataDiskCaching string = 'ReadWrite' 59 | 60 | @description('Specifies the name of the user-defined managed identity used by the Azure Monitor Agent.') 61 | param managedIdentityName string 62 | 63 | @description('Specifies the location.') 64 | param location string = resourceGroup().location 65 | 66 | @description('Specifies the resource tags.') 67 | param tags object 68 | 69 | // Variables 70 | var vmNicName = '${vmName}Nic' 71 | var linuxConfiguration = { 72 | disablePasswordAuthentication: true 73 | ssh: { 74 | publicKeys: [ 75 | { 76 | path: '/home/${vmAdminUsername}/.ssh/authorized_keys' 77 | keyData: vmAdminPasswordOrKey 78 | } 79 | ] 80 | } 81 | provisionVMAgent: true 82 | } 83 | 84 | // Resources 85 | resource virtualMachineNic 'Microsoft.Network/networkInterfaces@2021-08-01' = { 86 | name: vmNicName 87 | location: location 88 | tags: tags 89 | properties: { 90 | ipConfigurations: [ 91 | { 92 | name: 'ipconfig1' 93 | properties: { 94 | privateIPAllocationMethod: 'Dynamic' 95 | subnet: { 96 | id: vmSubnetId 97 | } 98 | } 99 | } 100 | ] 101 | } 102 | } 103 | 104 | resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = { 105 | name: storageAccountName 106 | } 107 | 108 | resource virtualMachine 'Microsoft.Compute/virtualMachines@2024-07-01' = { 109 | name: vmName 110 | location: location 111 | tags: tags 112 | properties: { 113 | hardwareProfile: { 114 | vmSize: vmSize 115 | } 116 | osProfile: { 117 | computerName: vmName 118 | adminUsername: vmAdminUsername 119 | adminPassword: vmAdminPasswordOrKey 120 | linuxConfiguration: (authenticationType == 'password') ? null : linuxConfiguration 121 | } 122 | storageProfile: { 123 | imageReference: { 124 | publisher: imagePublisher 125 | offer: imageOffer 126 | sku: imageSku 127 | version: 'latest' 128 | } 129 | osDisk: { 130 | name: '${vmName}_OSDisk' 131 | caching: 'ReadWrite' 132 | createOption: 'FromImage' 133 | diskSizeGB: osDiskSize 134 | managedDisk: { 135 | storageAccountType: diskStorageAccountType 136 | } 137 | } 138 | dataDisks: [for j in range(0, numDataDisks): { 139 | caching: dataDiskCaching 140 | diskSizeGB: dataDiskSize 141 | lun: j 142 | name: '${vmName}-DataDisk${j}' 143 | createOption: 'Empty' 144 | managedDisk: { 145 | storageAccountType: diskStorageAccountType 146 | } 147 | }] 148 | } 149 | networkProfile: { 150 | networkInterfaces: [ 151 | { 152 | id: virtualMachineNic.id 153 | } 154 | ] 155 | } 156 | diagnosticsProfile: { 157 | bootDiagnostics: { 158 | enabled: true 159 | storageUri: storageAccount.properties.primaryEndpoints.blob 160 | } 161 | } 162 | } 163 | } 164 | 165 | resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' = { 166 | name: managedIdentityName 167 | location: location 168 | tags: tags 169 | } 170 | 171 | resource linuxAgent 'Microsoft.Compute/virtualMachines/extensions@2024-07-01' = { 172 | name: 'AzureMonitorLinuxAgent' 173 | parent: virtualMachine 174 | location: location 175 | properties: { 176 | publisher: 'Microsoft.Azure.Monitor' 177 | type: 'AzureMonitorLinuxAgent' 178 | typeHandlerVersion: '1.21' 179 | autoUpgradeMinorVersion: true 180 | enableAutomaticUpgrade: true 181 | settings: { 182 | authentication: { 183 | managedIdentity: { 184 | 'identifier-name': 'mi_res_id' 185 | 'identifier-value': managedIdentity.id 186 | } 187 | } 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/http/00-variables.sh: -------------------------------------------------------------------------------- 1 | # Azure Resources 2 | RESOURCE_GROUP_NAME="" 3 | SUBSCRIPTION_ID=$(az account show --query id --output tsv) 4 | SUBSCRIPTION_NAME=$(az account show --query name --output tsv) 5 | TENANT_ID=$(az account show --query tenantId --output tsv) 6 | AKS_CLUSTER_NAME="" 7 | AGW_NAME="" 8 | AGW_PUBLIC_IP_NAME="" 9 | DNS_ZONE_NAME="" 10 | DNS_ZONE_RESOURCE_GROUP_NAME="" 11 | DNS_ZONE_SUBSCRIPTION_ID='' 12 | 13 | # NGINX Ingress Controller installed via Helm 14 | NGINX_NAMESPACE="ingress-basic" 15 | NGINX_REPO_NAME="ingress-nginx" 16 | NGINX_REPO_URL="https://kubernetes.github.io/ingress-nginx" 17 | NGINX_CHART_NAME="ingress-nginx" 18 | NGINX_RELEASE_NAME="ingress-nginx" 19 | NGINX_REPLICA_COUNT=3 20 | 21 | # Specify the ingress class name for the ingress controller. 22 | # - nginx: unmanaged NGINX ingress controller installed via Helm 23 | # - webapprouting.kubernetes.azure.com: managed NGINX ingress controller installed via AKS application routing add-on 24 | INGRESS_CLASS_NAME="webapprouting.kubernetes.azure.com" 25 | 26 | # Subdomain of the Yelb UI service 27 | SUBDOMAIN="" 28 | 29 | # URL of the Yelb UI service 30 | URL="https://$SUBDOMAIN.$DNS_ZONE_NAME" 31 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/http/01-install-tools.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Install jq if not installed 7 | path=$(which jq) 8 | 9 | if [[ -z $path ]]; then 10 | echo 'Installing jq...' 11 | sudo apt install -y jq 12 | fi 13 | 14 | # Install yq if not installed 15 | path=$(which yq) 16 | 17 | if [[ -z $path ]]; then 18 | echo 'Installing wq...' 19 | sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq 20 | sudo chmod +x /usr/bin/yq 21 | fi 22 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/http/02-create-nginx-ingress-controller.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Check if the NGINX ingress controller Helm chart is already installed 7 | result=$(helm list -n $NGINX_NAMESPACE | grep $NGINX_RELEASE_NAME | awk '{print $1}') 8 | 9 | if [[ -n $result ]]; then 10 | echo "[$NGINX_RELEASE_NAME] NGINX ingress controller release already exists in the [$NGINX_NAMESPACE] namespace" 11 | else 12 | # Check if the NGINX ingress controller repository is not already added 13 | result=$(helm repo list | grep $NGINX_REPO_NAME | awk '{print $1}') 14 | 15 | if [[ -n $result ]]; then 16 | echo "[$NGINX_REPO_NAME] Helm repo already exists" 17 | else 18 | # Add the NGINX ingress controller repository 19 | echo "Adding [$NGINX_REPO_NAME] Helm repo..." 20 | helm repo add $NGINX_REPO_NAME $NGINX_REPO_URL 21 | fi 22 | 23 | # Update your local Helm chart repository cache 24 | echo 'Updating Helm repos...' 25 | helm repo update 26 | 27 | # Deploy NGINX ingress controller 28 | echo "Deploying [$NGINX_RELEASE_NAME] NGINX ingress controller to the [$NGINX_NAMESPACE] namespace..." 29 | helm install $NGINX_RELEASE_NAME $NGINX_REPO_NAME/$nginxChartName \ 30 | --create-namespace \ 31 | --namespace $NGINX_NAMESPACE \ 32 | --set controller.nodeSelector."kubernetes\.io/os"=linux \ 33 | --set controller.replicaCount=$NGINX_REPLICA_COUNT \ 34 | --set defaultBackend.nodeSelector."kubernetes\.io/os"=linux \ 35 | --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz 36 | fi 37 | 38 | # Get values 39 | helm get values $NGINX_RELEASE_NAME --namespace $NGINX_NAMESPACE 40 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/http/03-deploy-yelb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Apply the YAML configuration 7 | kubectl apply -f yelb.yml 8 | 9 | # Create chat-ingress 10 | cat ingress.yml | 11 | yq "(.spec.ingressClassName)|="\""$INGRESS_CLASS_NAME"\" | 12 | yq "(.spec.rules[0].host)|="\""$SUBDOMAIN.$DNS_ZONE_NAME"\" | 13 | kubectl apply -f - 14 | 15 | # Check the deployed resources within the yelb namespace: 16 | kubectl get all -n yelb 17 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/http/04-configure-dns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Get the address of the Application Gateway Public IP 7 | echo "Retrieving the address of the [$AGW_PUBLIC_IP_NAME] public IP address of the [$AGW_NAME] Application Gateway..." 8 | 9 | PUBLIC_IP_ADDRESS=$(az network public-ip show \ 10 | --resource-group $RESOURCE_GROUP_NAME \ 11 | --name $AGW_PUBLIC_IP_NAME \ 12 | --query ipAddress \ 13 | --output tsv \ 14 | --only-show-errors) 15 | 16 | if [[ -n $PUBLIC_IP_ADDRESS ]]; then 17 | echo "[$PUBLIC_IP_ADDRESS] public IP address successfully retrieved for the [$AGW_NAME] Application Gateway" 18 | else 19 | echo "Failed to retrieve the public IP address of the [$AGW_NAME] Application Gateway" 20 | exit 21 | fi 22 | 23 | # Check if an A record for todolist subdomain exists in the DNS Zone 24 | echo "Retrieving the A record for the [$SUBDOMAIN] subdomain from the [$DNS_ZONE_NAME] DNS zone..." 25 | IPV4_ADDRESS=$(az network dns record-set a list \ 26 | --zone-name $DNS_ZONE_NAME \ 27 | --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \ 28 | --subscription $DNS_ZONE_SUBSCRIPTION_ID \ 29 | --query "[?name=='$SUBDOMAIN'].ARecords[].IPV4_ADDRESS" \ 30 | --output tsv \ 31 | --only-show-errors) 32 | 33 | if [[ -n $IPV4_ADDRESS ]]; then 34 | echo "An A record already exists in [$DNS_ZONE_NAME] DNS zone for the [$SUBDOMAIN] subdomain with [$IPV4_ADDRESS] IP address" 35 | 36 | if [[ $IPV4_ADDRESS == $PUBLIC_IP_ADDRESS ]]; then 37 | echo "The [$IPV4_ADDRESS] ip address of the existing A record is equal to the ip address of the ingress" 38 | echo "No additional step is required" 39 | continue 40 | else 41 | echo "The [$IPV4_ADDRESS] ip address of the existing A record is different than the ip address of the ingress" 42 | fi 43 | # Retrieving name of the record set relative to the zone 44 | echo "Retrieving the name of the record set relative to the [$DNS_ZONE_NAME] zone..." 45 | 46 | RECORDSET_NAME=$(az network dns record-set a list \ 47 | --zone-name $DNS_ZONE_NAME \ 48 | --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \ 49 | --subscription $DNS_ZONE_SUBSCRIPTION_ID \ 50 | --query "[?name=='$SUBDOMAIN'].name" \ 51 | --output tsv \ 52 | --only-show-errors 2>/dev/null) 53 | 54 | if [[ -n $RECORDSET_NAME ]]; then 55 | echo "[$RECORDSET_NAME] record set name successfully retrieved" 56 | else 57 | echo "Failed to retrieve the name of the record set relative to the [$DNS_ZONE_NAME] zone" 58 | exit 59 | fi 60 | 61 | # Remove the A record 62 | echo "Removing the A record from the record set relative to the [$DNS_ZONE_NAME] zone..." 63 | 64 | az network dns record-set a remove-record \ 65 | --ipv4-address $IPV4_ADDRESS \ 66 | --record-set-name $RECORDSET_NAME \ 67 | --zone-name $DNS_ZONE_NAME \ 68 | --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \ 69 | --subscription $DNS_ZONE_SUBSCRIPTION_ID \ 70 | --only-show-errors 1>/dev/null 71 | 72 | if [[ $? == 0 ]]; then 73 | echo "[$IPV4_ADDRESS] ip address successfully removed from the [$RECORDSET_NAME] record set" 74 | else 75 | echo "Failed to remove the [$IPV4_ADDRESS] ip address from the [$RECORDSET_NAME] record set" 76 | exit 77 | fi 78 | fi 79 | 80 | # Create the A record 81 | echo "Creating an A record in [$DNS_ZONE_NAME] DNS zone for the [$SUBDOMAIN] subdomain with [$PUBLIC_IP_ADDRESS] IP address..." 82 | az network dns record-set a add-record \ 83 | --zone-name $DNS_ZONE_NAME \ 84 | --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \ 85 | --subscription $DNS_ZONE_SUBSCRIPTION_ID \ 86 | --record-set-name $SUBDOMAIN \ 87 | --ipv4-address $PUBLIC_IP_ADDRESS \ 88 | --only-show-errors 1>/dev/null 89 | 90 | if [[ $? == 0 ]]; then 91 | echo "A record for the [$SUBDOMAIN] subdomain with [$PUBLIC_IP_ADDRESS] IP address successfully created in [$DNS_ZONE_NAME] DNS zone" 92 | else 93 | echo "Failed to create an A record for the $SUBDOMAIN subdomain with [$PUBLIC_IP_ADDRESS] IP address in [$DNS_ZONE_NAME] DNS zone" 94 | fi 95 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/http/05-call-yelb-ui.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Call REST API 7 | echo "Calling Yelb UI service at $URL..." 8 | curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL 9 | 10 | # Simulate SQL injection 11 | echo "Simulating SQL injection when calling $URL..." 12 | curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL/?users=ExampleSQLInjection%27%20-- 13 | 14 | # Simulate XSS 15 | echo "Simulating XSS when calling $URL..." 16 | curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL/?users=ExampleXSS%3Cscript%3Ealert%28%27XSS%27%29%3C%2Fscript%3E 17 | 18 | # A custom rule blocks any request with the word blockme in the querystring. 19 | echo "Simulating query string manipulation with the 'blockme' word in the query string..." 20 | curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL/?users?task=blockme -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/http/cluster-issuer-nginx.yml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: letsencrypt-nginx 5 | spec: 6 | acme: 7 | server: https://acme-v02.api.letsencrypt.org/directory 8 | email: 9 | privateKeySecretRef: 10 | name: letsencrypt-nginx 11 | solvers: 12 | - http01: 13 | ingress: 14 | class: nginx 15 | podTemplate: 16 | spec: 17 | nodeSelector: 18 | "kubernetes.io/os": linux -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/http/cluster-issuer-webapprouting.yml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: letsencrypt-webapprouting 5 | spec: 6 | acme: 7 | server: https://acme-v02.api.letsencrypt.org/directory 8 | email: 9 | privateKeySecretRef: 10 | name: letsencrypt-webapprouting 11 | solvers: 12 | - http01: 13 | ingress: 14 | class: webapprouting.kubernetes.azure.com 15 | podTemplate: 16 | spec: 17 | nodeSelector: 18 | "kubernetes.io/os": linux -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/http/ingress.yml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: yelb.app 5 | namespace: yelb 6 | annotations: 7 | nginx.ingress.kubernetes.io/proxy-connect-timeout: "360" 8 | nginx.ingress.kubernetes.io/proxy-send-timeout: "360" 9 | nginx.ingress.kubernetes.io/proxy-read-timeout: "360" 10 | spec: 11 | ingressClassName: nginx 12 | rules: 13 | - host: yelb.contoso.com 14 | http: 15 | paths: 16 | - path: / 17 | pathType: Prefix 18 | backend: 19 | service: 20 | name: yelb-ui 21 | port: 22 | number: 80 -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/http/yelb.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: yelb 5 | --- 6 | apiVersion: v1 7 | kind: Service 8 | metadata: 9 | namespace: yelb 10 | name: redis-server 11 | labels: 12 | app: redis-server 13 | tier: cache 14 | spec: 15 | type: ClusterIP 16 | ports: 17 | - port: 6379 18 | selector: 19 | app: redis-server 20 | tier: cache 21 | --- 22 | apiVersion: v1 23 | kind: Service 24 | metadata: 25 | namespace: yelb 26 | name: yelb-db 27 | labels: 28 | app: yelb-db 29 | tier: backenddb 30 | spec: 31 | type: ClusterIP 32 | ports: 33 | - port: 5432 34 | selector: 35 | app: yelb-db 36 | tier: backenddb 37 | --- 38 | apiVersion: v1 39 | kind: Service 40 | metadata: 41 | namespace: yelb 42 | name: yelb-appserver 43 | labels: 44 | app: yelb-appserver 45 | tier: middletier 46 | spec: 47 | type: ClusterIP 48 | ports: 49 | - port: 4567 50 | selector: 51 | app: yelb-appserver 52 | tier: middletier 53 | --- 54 | apiVersion: v1 55 | kind: Service 56 | metadata: 57 | namespace: yelb 58 | name: yelb-ui 59 | labels: 60 | app: yelb-ui 61 | tier: frontend 62 | spec: 63 | type: ClusterIP 64 | ports: 65 | - port: 80 66 | protocol: TCP 67 | targetPort: 80 68 | selector: 69 | app: yelb-ui 70 | tier: frontend 71 | --- 72 | apiVersion: apps/v1 73 | kind: Deployment 74 | metadata: 75 | namespace: yelb 76 | name: yelb-ui 77 | spec: 78 | replicas: 1 79 | selector: 80 | matchLabels: 81 | app: yelb-ui 82 | tier: frontend 83 | template: 84 | metadata: 85 | labels: 86 | app: yelb-ui 87 | tier: frontend 88 | spec: 89 | containers: 90 | - name: yelb-ui 91 | image: mreferre/yelb-ui:0.7 92 | ports: 93 | - containerPort: 80 94 | --- 95 | apiVersion: apps/v1 96 | kind: Deployment 97 | metadata: 98 | namespace: yelb 99 | name: redis-server 100 | spec: 101 | selector: 102 | matchLabels: 103 | app: redis-server 104 | tier: cache 105 | replicas: 1 106 | template: 107 | metadata: 108 | labels: 109 | app: redis-server 110 | tier: cache 111 | spec: 112 | containers: 113 | - name: redis-server 114 | image: redis:4.0.2 115 | ports: 116 | - containerPort: 6379 117 | --- 118 | apiVersion: apps/v1 119 | kind: Deployment 120 | metadata: 121 | namespace: yelb 122 | name: yelb-db 123 | spec: 124 | replicas: 1 125 | selector: 126 | matchLabels: 127 | app: yelb-db 128 | tier: backenddb 129 | template: 130 | metadata: 131 | labels: 132 | app: yelb-db 133 | tier: backenddb 134 | spec: 135 | containers: 136 | - name: yelb-db 137 | image: mreferre/yelb-db:0.5 138 | ports: 139 | - containerPort: 5432 140 | --- 141 | apiVersion: apps/v1 142 | kind: Deployment 143 | metadata: 144 | namespace: yelb 145 | name: yelb-appserver 146 | spec: 147 | replicas: 1 148 | selector: 149 | matchLabels: 150 | app: yelb-appserver 151 | tier: middletier 152 | template: 153 | metadata: 154 | labels: 155 | app: yelb-appserver 156 | tier: middletier 157 | spec: 158 | containers: 159 | - name: yelb-appserver 160 | image: mreferre/yelb-appserver:0.5 161 | ports: 162 | - containerPort: 4567 163 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/https/00-variables.sh: -------------------------------------------------------------------------------- 1 | # Azure Resources 2 | RESOURCE_GROUP_NAME="" 3 | SUBSCRIPTION_ID=$(az account show --query id --output tsv) 4 | SUBSCRIPTION_NAME=$(az account show --query name --output tsv) 5 | TENANT_ID=$(az account show --query tenantId --output tsv) 6 | AKS_CLUSTER_NAME="" 7 | AGW_NAME="" 8 | AGW_PUBLIC_IP_NAME="" 9 | DNS_ZONE_NAME="" 10 | DNS_ZONE_RESOURCE_GROUP_NAME="" 11 | DNS_ZONE_SUBSCRIPTION_ID='' 12 | 13 | # NGINX Ingress Controller installed via Helm 14 | NGINX_NAMESPACE="ingress-basic" 15 | NGINX_REPO_NAME="ingress-nginx" 16 | NGINX_REPO_URL="https://kubernetes.github.io/ingress-nginx" 17 | NGINX_CHART_NAME="ingress-nginx" 18 | NGINX_RELEASE_NAME="ingress-nginx" 19 | NGINX_REPLICA_COUNT=3 20 | 21 | # Specify the ingress class name for the ingress controller. 22 | # - nginx: unmanaged NGINX ingress controller installed via Helm 23 | # - webapprouting.kubernetes.azure.com: managed NGINX ingress controller installed via AKS application routing add-on 24 | INGRESS_CLASS_NAME="webapprouting.kubernetes.azure.com" 25 | 26 | # Subdomain of the Yelb UI service 27 | SUBDOMAIN="" 28 | 29 | # URL of the Yelb UI service 30 | URL="https://$SUBDOMAIN.$DNS_ZONE_NAME" 31 | 32 | # Secret Provider Class 33 | KEY_VAULT_NAME="" 34 | KEY_VAULT_CERTIFICATE_NAME="" 35 | KEY_VAULT_SECRET_PROVIDER_IDENTITY_CLIENT_ID="16639445-6a46-4f62-90b0-2f1de3f779e7" 36 | TLS_SECRET_NAME="yelb-tls-secret" 37 | NAMESPACE="yelb" -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/https/01-install-tools.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Install jq if not installed 7 | path=$(which jq) 8 | 9 | if [[ -z $path ]]; then 10 | echo 'Installing jq...' 11 | sudo apt install -y jq 12 | fi 13 | 14 | # Install yq if not installed 15 | path=$(which yq) 16 | 17 | if [[ -z $path ]]; then 18 | echo 'Installing wq...' 19 | sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq 20 | sudo chmod +x /usr/bin/yq 21 | fi 22 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/https/02-create-nginx-ingress-controller.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Check if the NGINX ingress controller Helm chart is already installed 7 | result=$(helm list -n $NGINX_NAMESPACE | grep $NGINX_RELEASE_NAME | awk '{print $1}') 8 | 9 | if [[ -n $result ]]; then 10 | echo "[$NGINX_RELEASE_NAME] NGINX ingress controller release already exists in the [$NGINX_NAMESPACE] namespace" 11 | else 12 | # Check if the NGINX ingress controller repository is not already added 13 | result=$(helm repo list | grep $NGINX_REPO_NAME | awk '{print $1}') 14 | 15 | if [[ -n $result ]]; then 16 | echo "[$NGINX_REPO_NAME] Helm repo already exists" 17 | else 18 | # Add the NGINX ingress controller repository 19 | echo "Adding [$NGINX_REPO_NAME] Helm repo..." 20 | helm repo add $NGINX_REPO_NAME $NGINX_REPO_URL 21 | fi 22 | 23 | # Update your local Helm chart repository cache 24 | echo 'Updating Helm repos...' 25 | helm repo update 26 | 27 | # Deploy NGINX ingress controller 28 | echo "Deploying [$NGINX_RELEASE_NAME] NGINX ingress controller to the [$NGINX_NAMESPACE] namespace..." 29 | helm install $NGINX_RELEASE_NAME $NGINX_REPO_NAME/$nginxChartName \ 30 | --create-namespace \ 31 | --namespace $NGINX_NAMESPACE \ 32 | --set controller.nodeSelector."kubernetes\.io/os"=linux \ 33 | --set controller.replicaCount=$NGINX_REPLICA_COUNT \ 34 | --set defaultBackend.nodeSelector."kubernetes\.io/os"=linux \ 35 | --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz 36 | fi 37 | 38 | # Get values 39 | helm get values $NGINX_RELEASE_NAME --namespace $NGINX_NAMESPACE 40 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/https/03-deploy-yelb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Check if namespace exists in the cluster 7 | result=$(kubectl get namespace -o jsonpath="{.items[?(@.metadata.name=='$NAMESPACE')].metadata.name}") 8 | 9 | if [[ -n $result ]]; then 10 | echo "$NAMESPACE namespace already exists in the cluster" 11 | else 12 | echo "$NAMESPACE namespace does not exist in the cluster" 13 | echo "creating $NAMESPACE namespace in the cluster..." 14 | kubectl create namespace $NAMESPACE 15 | fi 16 | 17 | # Create the Secret Provider Class object 18 | echo "Creating the secret provider class object..." 19 | cat </dev/null 2>&1; then 55 | echo "secret $TLS_SECRET_NAME found!" 56 | break 57 | else 58 | printf "." 59 | sleep 3 60 | fi 61 | done 62 | 63 | # Create chat-ingress 64 | cat ingress.yml | 65 | yq "(.spec.ingressClassName)|="\""$INGRESS_CLASS_NAME"\" | 66 | yq "(.spec.tls[0].hosts[0])|="\""$SUBDOMAIN.$DNS_ZONE_NAME"\" | 67 | yq "(.spec.tls[0].secretName)|="\""$TLS_SECRET_NAME"\" | 68 | yq "(.spec.rules[0].host)|="\""$SUBDOMAIN.$DNS_ZONE_NAME"\" | 69 | kubectl apply -f - 70 | 71 | # Check the deployed resources within the yelb namespace: 72 | kubectl get all -n yelb 73 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/https/04-configure-dns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Get the address of the Application Gateway Public IP 7 | echo "Retrieving the address of the [$AGW_PUBLIC_IP_NAME] public IP address of the [$AGW_NAME] Application Gateway..." 8 | 9 | PUBLIC_IP_ADDRESS=$(az network public-ip show \ 10 | --resource-group $RESOURCE_GROUP_NAME \ 11 | --name $AGW_PUBLIC_IP_NAME \ 12 | --query ipAddress \ 13 | --output tsv \ 14 | --only-show-errors) 15 | 16 | if [[ -n $PUBLIC_IP_ADDRESS ]]; then 17 | echo "[$PUBLIC_IP_ADDRESS] public IP address successfully retrieved for the [$AGW_NAME] Application Gateway" 18 | else 19 | echo "Failed to retrieve the public IP address of the [$AGW_NAME] Application Gateway" 20 | exit 21 | fi 22 | 23 | # Check if an A record for todolist subdomain exists in the DNS Zone 24 | echo "Retrieving the A record for the [$SUBDOMAIN] subdomain from the [$DNS_ZONE_NAME] DNS zone..." 25 | IPV4_ADDRESS=$(az network dns record-set a list \ 26 | --zone-name $DNS_ZONE_NAME \ 27 | --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \ 28 | --subscription $DNS_ZONE_SUBSCRIPTION_ID \ 29 | --query "[?name=='$SUBDOMAIN'].ARecords[].IPV4_ADDRESS" \ 30 | --output tsv \ 31 | --only-show-errors) 32 | 33 | if [[ -n $IPV4_ADDRESS ]]; then 34 | echo "An A record already exists in [$DNS_ZONE_NAME] DNS zone for the [$SUBDOMAIN] subdomain with [$IPV4_ADDRESS] IP address" 35 | 36 | if [[ $IPV4_ADDRESS == $PUBLIC_IP_ADDRESS ]]; then 37 | echo "The [$IPV4_ADDRESS] ip address of the existing A record is equal to the ip address of the ingress" 38 | echo "No additional step is required" 39 | continue 40 | else 41 | echo "The [$IPV4_ADDRESS] ip address of the existing A record is different than the ip address of the ingress" 42 | fi 43 | # Retrieving name of the record set relative to the zone 44 | echo "Retrieving the name of the record set relative to the [$DNS_ZONE_NAME] zone..." 45 | 46 | RECORDSET_NAME=$(az network dns record-set a list \ 47 | --zone-name $DNS_ZONE_NAME \ 48 | --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \ 49 | --subscription $DNS_ZONE_SUBSCRIPTION_ID \ 50 | --query "[?name=='$SUBDOMAIN'].name" \ 51 | --output tsv \ 52 | --only-show-errors 2>/dev/null) 53 | 54 | if [[ -n $RECORDSET_NAME ]]; then 55 | echo "[$RECORDSET_NAME] record set name successfully retrieved" 56 | else 57 | echo "Failed to retrieve the name of the record set relative to the [$DNS_ZONE_NAME] zone" 58 | exit 59 | fi 60 | 61 | # Remove the A record 62 | echo "Removing the A record from the record set relative to the [$DNS_ZONE_NAME] zone..." 63 | 64 | az network dns record-set a remove-record \ 65 | --ipv4-address $IPV4_ADDRESS \ 66 | --record-set-name $RECORDSET_NAME \ 67 | --zone-name $DNS_ZONE_NAME \ 68 | --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \ 69 | --subscription $DNS_ZONE_SUBSCRIPTION_ID \ 70 | --only-show-errors 1>/dev/null 71 | 72 | if [[ $? == 0 ]]; then 73 | echo "[$IPV4_ADDRESS] ip address successfully removed from the [$RECORDSET_NAME] record set" 74 | else 75 | echo "Failed to remove the [$IPV4_ADDRESS] ip address from the [$RECORDSET_NAME] record set" 76 | exit 77 | fi 78 | fi 79 | 80 | # Create the A record 81 | echo "Creating an A record in [$DNS_ZONE_NAME] DNS zone for the [$SUBDOMAIN] subdomain with [$PUBLIC_IP_ADDRESS] IP address..." 82 | az network dns record-set a add-record \ 83 | --zone-name $DNS_ZONE_NAME \ 84 | --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \ 85 | --subscription $DNS_ZONE_SUBSCRIPTION_ID \ 86 | --record-set-name $SUBDOMAIN \ 87 | --ipv4-address $PUBLIC_IP_ADDRESS \ 88 | --only-show-errors 1>/dev/null 89 | 90 | if [[ $? == 0 ]]; then 91 | echo "A record for the [$SUBDOMAIN] subdomain with [$PUBLIC_IP_ADDRESS] IP address successfully created in [$DNS_ZONE_NAME] DNS zone" 92 | else 93 | echo "Failed to create an A record for the $SUBDOMAIN subdomain with [$PUBLIC_IP_ADDRESS] IP address in [$DNS_ZONE_NAME] DNS zone" 94 | fi 95 | -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/https/05-call-yelb-ui.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Call REST API 7 | echo "Calling Yelb UI service at $URL..." 8 | curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL 9 | 10 | # Simulate SQL injection 11 | echo "Simulating SQL injection when calling $URL..." 12 | curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL/?users=ExampleSQLInjection%27%20-- 13 | 14 | # Simulate XSS 15 | echo "Simulating XSS when calling $URL..." 16 | curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL/?users=ExampleXSS%3Cscript%3Ealert%28%27XSS%27%29%3C%2Fscript%3E 17 | 18 | # A custom rule blocks any request with the word blockme in the querystring. 19 | echo "Simulating query string manipulation with the 'blockme' word in the query string..." 20 | curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL/?users?task=blockme -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/https/cluster-issuer-nginx.yml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: letsencrypt-nginx 5 | spec: 6 | acme: 7 | server: https://acme-v02.api.letsencrypt.org/directory 8 | email: 9 | privateKeySecretRef: 10 | name: letsencrypt-nginx 11 | solvers: 12 | - http01: 13 | ingress: 14 | class: nginx 15 | podTemplate: 16 | spec: 17 | nodeSelector: 18 | "kubernetes.io/os": linux -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/https/cluster-issuer-webapprouting.yml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: letsencrypt-webapprouting 5 | spec: 6 | acme: 7 | server: https://acme-v02.api.letsencrypt.org/directory 8 | email: 9 | privateKeySecretRef: 10 | name: letsencrypt-webapprouting 11 | solvers: 12 | - http01: 13 | ingress: 14 | class: webapprouting.kubernetes.azure.com 15 | podTemplate: 16 | spec: 17 | nodeSelector: 18 | "kubernetes.io/os": linux -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/https/ingress.yml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: yelb.app 5 | namespace: yelb 6 | annotations: 7 | nginx.ingress.kubernetes.io/proxy-connect-timeout: "360" 8 | nginx.ingress.kubernetes.io/proxy-send-timeout: "360" 9 | nginx.ingress.kubernetes.io/proxy-read-timeout: "360" 10 | spec: 11 | ingressClassName: nginx 12 | tls: 13 | - hosts: 14 | - yelb.contoso.com 15 | secretName: yelb-tls-secret 16 | rules: 17 | - host: yelb.contoso.com 18 | http: 19 | paths: 20 | - path: / 21 | pathType: Prefix 22 | backend: 23 | service: 24 | name: yelb-ui 25 | port: 26 | number: 80 -------------------------------------------------------------------------------- /azure/nginx-with-azure-waf/scripts/https/yelb.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | namespace: yelb 5 | name: redis-server 6 | labels: 7 | app: redis-server 8 | tier: cache 9 | spec: 10 | type: ClusterIP 11 | ports: 12 | - port: 6379 13 | selector: 14 | app: redis-server 15 | tier: cache 16 | --- 17 | apiVersion: v1 18 | kind: Service 19 | metadata: 20 | namespace: yelb 21 | name: yelb-db 22 | labels: 23 | app: yelb-db 24 | tier: backenddb 25 | spec: 26 | type: ClusterIP 27 | ports: 28 | - port: 5432 29 | selector: 30 | app: yelb-db 31 | tier: backenddb 32 | --- 33 | apiVersion: v1 34 | kind: Service 35 | metadata: 36 | namespace: yelb 37 | name: yelb-appserver 38 | labels: 39 | app: yelb-appserver 40 | tier: middletier 41 | spec: 42 | type: ClusterIP 43 | ports: 44 | - port: 4567 45 | selector: 46 | app: yelb-appserver 47 | tier: middletier 48 | --- 49 | apiVersion: v1 50 | kind: Service 51 | metadata: 52 | namespace: yelb 53 | name: yelb-ui 54 | labels: 55 | app: yelb-ui 56 | tier: frontend 57 | spec: 58 | type: ClusterIP 59 | ports: 60 | - port: 80 61 | protocol: TCP 62 | targetPort: 80 63 | selector: 64 | app: yelb-ui 65 | tier: frontend 66 | --- 67 | apiVersion: apps/v1 68 | kind: Deployment 69 | metadata: 70 | namespace: yelb 71 | name: yelb-ui 72 | spec: 73 | replicas: 1 74 | selector: 75 | matchLabels: 76 | app: yelb-ui 77 | tier: frontend 78 | template: 79 | metadata: 80 | labels: 81 | app: yelb-ui 82 | tier: frontend 83 | spec: 84 | containers: 85 | - name: yelb-ui 86 | image: mreferre/yelb-ui:0.7 87 | ports: 88 | - containerPort: 80 89 | volumeMounts: 90 | - name: secrets-store-inline 91 | mountPath: "/mnt/secrets-store" 92 | readOnly: true 93 | volumes: 94 | - name: secrets-store-inline 95 | csi: 96 | driver: secrets-store.csi.k8s.io 97 | readOnly: true 98 | volumeAttributes: 99 | secretProviderClass: yelb 100 | --- 101 | apiVersion: apps/v1 102 | kind: Deployment 103 | metadata: 104 | namespace: yelb 105 | name: redis-server 106 | spec: 107 | selector: 108 | matchLabels: 109 | app: redis-server 110 | tier: cache 111 | replicas: 1 112 | template: 113 | metadata: 114 | labels: 115 | app: redis-server 116 | tier: cache 117 | spec: 118 | containers: 119 | - name: redis-server 120 | image: redis:4.0.2 121 | ports: 122 | - containerPort: 6379 123 | --- 124 | apiVersion: apps/v1 125 | kind: Deployment 126 | metadata: 127 | namespace: yelb 128 | name: yelb-db 129 | spec: 130 | replicas: 1 131 | selector: 132 | matchLabels: 133 | app: yelb-db 134 | tier: backenddb 135 | template: 136 | metadata: 137 | labels: 138 | app: yelb-db 139 | tier: backenddb 140 | spec: 141 | containers: 142 | - name: yelb-db 143 | image: mreferre/yelb-db:0.5 144 | ports: 145 | - containerPort: 5432 146 | --- 147 | apiVersion: apps/v1 148 | kind: Deployment 149 | metadata: 150 | namespace: yelb 151 | name: yelb-appserver 152 | spec: 153 | replicas: 1 154 | selector: 155 | matchLabels: 156 | app: yelb-appserver 157 | tier: middletier 158 | template: 159 | metadata: 160 | labels: 161 | app: yelb-appserver 162 | tier: middletier 163 | spec: 164 | containers: 165 | - name: yelb-appserver 166 | image: mreferre/yelb-appserver:0.5 167 | ports: 168 | - containerPort: 4567 169 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/bicep/actionGroup.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the Action Group resource.') 3 | param name string 4 | 5 | @description('Specifies the short name of the action group. This will be used in SMS messages..') 6 | param groupShortName string = 'AksAlerts' 7 | 8 | @description('Specifies whether this action group is enabled. If an action group is not enabled, then none of its receivers will receive communications.') 9 | param enabled bool = true 10 | 11 | @description('Specifies the email address of the receiver.') 12 | param emailAddress string 13 | 14 | @description('Specifies whether to use common alert schema..') 15 | param useCommonAlertSchema bool = false 16 | 17 | @description('Specifies the country code of the SMS receiver.') 18 | param countryCode string = '39' 19 | 20 | @description('Specifies the phone number of the SMS receiver.') 21 | param phoneNumber string = '' 22 | 23 | @description('Specifies the resource tags.') 24 | param tags object 25 | 26 | // Resources 27 | resource actionGroup 'Microsoft.Insights/actionGroups@2023-01-01' = { 28 | name: name 29 | location: 'Global' 30 | tags: tags 31 | properties: { 32 | groupShortName: groupShortName 33 | enabled: enabled 34 | emailReceivers: !empty(emailAddress) ? [ 35 | { 36 | name: 'EmailAndTextMessageOthers_-EmailAction-' 37 | emailAddress: emailAddress 38 | useCommonAlertSchema: useCommonAlertSchema 39 | } 40 | ] : [] 41 | smsReceivers: !empty(countryCode) && !empty(phoneNumber) ? [ 42 | { 43 | name: 'EmailAndTextMessageOthers_-SMSAction-' 44 | countryCode: countryCode 45 | phoneNumber: phoneNumber 46 | } 47 | ] : [] 48 | armRoleReceivers: [ 49 | { 50 | name: 'EmailOwner' 51 | roleId: '8e3af657-a8ff-443c-a75c-2fe8c4bcb635' 52 | useCommonAlertSchema: false 53 | } 54 | ] 55 | } 56 | } 57 | 58 | //Outputs 59 | output id string = actionGroup.id 60 | output name string = actionGroup.name 61 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/bicep/aksManagedIdentity.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the user-defined managed identity.') 3 | param managedIdentityName string 4 | 5 | @description('Specifies the name of the existing virtual network.') 6 | param virtualNetworkName string 7 | 8 | @description('Specifies the location of the user-defined managed identity.') 9 | param location string = resourceGroup().location 10 | 11 | @description('Specifies the resource tags.') 12 | param tags object 13 | 14 | // Variables 15 | var networkContributorRoleDefinitionId = resourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7') 16 | 17 | // Resources 18 | resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' = { 19 | name: managedIdentityName 20 | location: location 21 | tags: tags 22 | } 23 | 24 | resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-01-01' existing = { 25 | name: virtualNetworkName 26 | } 27 | 28 | 29 | resource virtualNetworkContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 30 | name: guid(managedIdentity.id, virtualNetwork.id, networkContributorRoleDefinitionId) 31 | scope: virtualNetwork 32 | properties: { 33 | roleDefinitionId: networkContributorRoleDefinitionId 34 | principalId: managedIdentity.properties.principalId 35 | principalType: 'ServicePrincipal' 36 | } 37 | } 38 | 39 | // Outputs 40 | output id string = managedIdentity.id 41 | output name string = managedIdentity.name 42 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/bicep/containerRegistry.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Name of your Azure Container Registry') 3 | @minLength(5) 4 | @maxLength(50) 5 | param name string = 'acr${uniqueString(resourceGroup().id)}' 6 | 7 | @description('Enable admin user that have push / pull permission to the registry.') 8 | param adminUserEnabled bool = true 9 | 10 | @description('Specifies whether to allow public network access for the container registry.') 11 | @allowed([ 12 | 'Disabled' 13 | 'Enabled' 14 | ]) 15 | param publicNetworkAccess string = 'Enabled' 16 | 17 | @description('Tier of your Azure Container Registry.') 18 | @allowed([ 19 | 'Basic' 20 | 'Standard' 21 | 'Premium' 22 | ]) 23 | param sku string = 'Premium' 24 | 25 | @description('Specifies whether or not registry-wide pull is enabled from unauthenticated clients.') 26 | param anonymousPullEnabled bool = true 27 | 28 | @description('Specifies whether or not a single data endpoint is enabled per region for serving data.') 29 | param dataEndpointEnabled bool = true 30 | 31 | @description('Specifies the network rule set for the container registry.') 32 | param networkRuleSet object = { 33 | defaultAction: 'Allow' 34 | } 35 | 36 | @description('Specifies ehether to allow trusted Azure services to access a network restricted registry.') 37 | @allowed([ 38 | 'AzureServices' 39 | 'None' 40 | ]) 41 | param networkRuleBypassOptions string = 'AzureServices' 42 | 43 | @description('Specifies whether or not zone redundancy is enabled for this container registry.') 44 | @allowed([ 45 | 'Disabled' 46 | 'Enabled' 47 | ]) 48 | param zoneRedundancy string = 'Disabled' 49 | 50 | @description('Specifies the resource id of the Log Analytics workspace.') 51 | param workspaceId string 52 | 53 | @description('Specifies the location.') 54 | param location string = resourceGroup().location 55 | 56 | @description('Specifies the resource tags.') 57 | param tags object 58 | 59 | // Variables 60 | var diagnosticSettingsName = 'diagnosticSettings' 61 | var logCategories = [ 62 | 'ContainerRegistryRepositoryEvents' 63 | 'ContainerRegistryLoginEvents' 64 | ] 65 | var metricCategories = [ 66 | 'AllMetrics' 67 | ] 68 | var logs = [ 69 | for category in logCategories: { 70 | category: category 71 | enabled: true 72 | retentionPolicy: { 73 | enabled: true 74 | days: 0 75 | } 76 | } 77 | ] 78 | var metrics = [ 79 | for category in metricCategories: { 80 | category: category 81 | enabled: true 82 | retentionPolicy: { 83 | enabled: true 84 | days: 0 85 | } 86 | } 87 | ] 88 | 89 | // Resources 90 | resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' = { 91 | name: name 92 | location: location 93 | tags: tags 94 | sku: { 95 | name: sku 96 | } 97 | properties: { 98 | adminUserEnabled: adminUserEnabled 99 | anonymousPullEnabled: anonymousPullEnabled 100 | dataEndpointEnabled: dataEndpointEnabled 101 | networkRuleBypassOptions: networkRuleBypassOptions 102 | networkRuleSet: networkRuleSet 103 | policies: { 104 | quarantinePolicy: { 105 | status: 'disabled' 106 | } 107 | retentionPolicy: { 108 | status: 'enabled' 109 | days: 7 110 | } 111 | trustPolicy: { 112 | status: 'enabled' 113 | type: 'Notary' 114 | } 115 | } 116 | publicNetworkAccess: publicNetworkAccess 117 | zoneRedundancy: zoneRedundancy 118 | } 119 | } 120 | 121 | resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { 122 | name: diagnosticSettingsName 123 | scope: containerRegistry 124 | properties: { 125 | workspaceId: workspaceId 126 | logs: logs 127 | metrics: metrics 128 | } 129 | } 130 | 131 | // Outputs 132 | output id string = containerRegistry.id 133 | output name string = containerRegistry.name 134 | output sku string = containerRegistry.sku.name 135 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/bicep/deploymentScript.bicep: -------------------------------------------------------------------------------- 1 | // For more information, see https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-script-bicep 2 | @description('Specifies the name of the deployment script uri.') 3 | param name string = 'BashScript' 4 | 5 | @description('Specifies the Azure CLI module version.') 6 | param azCliVersion string = '2.61.0' 7 | 8 | @description('Specifies the maximum allowed script execution time specified in ISO 8601 format. Default value is P1D.') 9 | param timeout string = 'PT30M' 10 | 11 | @description('Specifies the clean up preference when the script execution gets in a terminal state. Default setting is Always.') 12 | @allowed([ 13 | 'Always' 14 | 'OnExpiration' 15 | 'OnSuccess' 16 | ]) 17 | param cleanupPreference string = 'OnSuccess' 18 | 19 | @description('Specifies the interval for which the service retains the script resource after it reaches a terminal state. Resource will be deleted when this duration expires.') 20 | param retentionInterval string = 'P1D' 21 | 22 | @description('Specifies the name of the user-assigned managed identity of the deployment script.') 23 | param managedIdentityName string 24 | 25 | @description('Specifies the primary script URI.') 26 | param primaryScriptUri string 27 | 28 | @description('Specifies the name of the AKS cluster.') 29 | param clusterName string 30 | 31 | @description('Specifies the resource group name') 32 | param resourceGroupName string = resourceGroup().name 33 | 34 | @description('Specifies the subscription id.') 35 | param subscriptionId string = subscription().subscriptionId 36 | 37 | @description('Specifies whether to deploy Prometheus and Grafana to the AKS cluster using a Helm chart.') 38 | param deployPrometheusAndGrafanaViaHelm bool = true 39 | 40 | @description('Specifies whether to whether to deploy the Certificate Manager to the AKS cluster using a Helm chart.') 41 | param deployCertificateManagerViaHelm bool = true 42 | 43 | @description('Specifies the list of ingress classes for which a cert-manager cluster issuer should be created.') 44 | param ingressClassNames array = ['nginx', 'webapprouting.kubernetes.azure.com'] 45 | 46 | @description('Specifies the list of the names for the cert-manager cluster issuers.') 47 | param clusterIssuerNames array = ['letsencrypt-nginx', 'letsencrypt-webapprouting'] 48 | 49 | @description('Specifies whether and how to deploy the NGINX Ingress Controller to the AKS cluster using a Helm chart. Possible values are None, Internal, and External.') 50 | @allowed([ 51 | 'None' 52 | 'Internal' 53 | 'External' 54 | ]) 55 | param deployNginxIngressControllerViaHelm string = 'Internal' 56 | 57 | @description('Specifies the email address for the cert-manager cluster issuer.') 58 | param email string = 'admin@contoso.com' 59 | 60 | @description('Specifies the current datetime') 61 | param utcValue string = utcNow() 62 | 63 | @description('Specifies the location.') 64 | param location string = resourceGroup().location 65 | 66 | @description('Specifies the resource tags.') 67 | param tags object 68 | 69 | // Variables 70 | var clusterAdminRoleDefinitionId = resourceId('Microsoft.Authorization/roleDefinitions', '0ab0b1a8-8aac-4efd-b8c2-3ee1fb270be8') 71 | 72 | // Resources 73 | resource aksCluster 'Microsoft.ContainerService/managedClusters@2022-11-02-preview' existing = { 74 | name: clusterName 75 | } 76 | 77 | resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' = { 78 | name: managedIdentityName 79 | location: location 80 | tags: tags 81 | } 82 | 83 | resource clusterAdminContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 84 | name: guid(managedIdentity.id, aksCluster.id, clusterAdminRoleDefinitionId) 85 | scope: aksCluster 86 | properties: { 87 | roleDefinitionId: clusterAdminRoleDefinitionId 88 | principalId: managedIdentity.properties.principalId 89 | principalType: 'ServicePrincipal' 90 | } 91 | } 92 | 93 | // Script 94 | resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = if (deployPrometheusAndGrafanaViaHelm || deployCertificateManagerViaHelm || deployNginxIngressControllerViaHelm != 'None') { 95 | name: name 96 | location: location 97 | kind: 'AzureCLI' 98 | identity: { 99 | type: 'UserAssigned' 100 | userAssignedIdentities: { 101 | '${managedIdentity.id}': {} 102 | } 103 | } 104 | properties: { 105 | forceUpdateTag: utcValue 106 | azCliVersion: azCliVersion 107 | timeout: timeout 108 | environmentVariables: [ 109 | { 110 | name: 'clusterName' 111 | value: clusterName 112 | } 113 | { 114 | name: 'resourceGroupName' 115 | value: resourceGroupName 116 | } 117 | { 118 | name: 'subscriptionId' 119 | value: subscriptionId 120 | } 121 | { 122 | name: 'deployPrometheusAndGrafanaViaHelm' 123 | value: deployPrometheusAndGrafanaViaHelm ? 'true' : 'false' 124 | } 125 | { 126 | name: 'ingressClassNames' 127 | value: join(ingressClassNames, ',') 128 | } 129 | { 130 | name: 'clusterIssuerNames' 131 | value: join(clusterIssuerNames, ',') 132 | } 133 | { 134 | name: 'deployCertificateManagerViaHelm' 135 | value: deployCertificateManagerViaHelm ? 'true' : 'false' 136 | } 137 | { 138 | name: 'deployNginxIngressControllerViaHelm' 139 | value: deployNginxIngressControllerViaHelm 140 | } 141 | { 142 | name: 'email' 143 | value: email 144 | } 145 | ] 146 | primaryScriptUri: primaryScriptUri 147 | cleanupPreference: cleanupPreference 148 | retentionInterval: retentionInterval 149 | } 150 | } 151 | 152 | // Outputs 153 | output result object = deploymentScript.properties.outputs 154 | output certManager string = deploymentScript.properties.outputs.certManager 155 | output nginxIngressController string = deploymentScript.properties.outputs.nginxIngressController 156 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/bicep/dnsZone.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of an existing public DNS zone.') 3 | param name string 4 | 5 | @description('Specifies the name of the CNAME record to create within the DNS zone. The record will be an alias to your Front Door endpoint.') 6 | param cnameRecordName string 7 | 8 | @description('Specifies the time-to-live (TTL) value for the CNAME record.') 9 | param ttl int = 3600 10 | 11 | @description('Specifies the Front Door endpoint to which the CNAME record will point.') 12 | param hostName string 13 | 14 | @description('Specifies the validation state of the custom domain.') 15 | param domainValidationState string 16 | 17 | @description('Specifies the validation token of the custom domain.') 18 | param validationToken string 19 | 20 | resource dnsZone 'Microsoft.Network/dnsZones@2023-07-01-preview' existing = { 21 | name: name 22 | } 23 | 24 | resource cnameRecord 'Microsoft.Network/dnsZones/CNAME@2023-07-01-preview' = { 25 | parent: dnsZone 26 | name: cnameRecordName 27 | properties: { 28 | TTL: ttl 29 | CNAMERecord: { 30 | cname: hostName 31 | } 32 | } 33 | } 34 | 35 | resource validationTxtRecord 'Microsoft.Network/dnsZones/TXT@2023-07-01-preview' = if (domainValidationState != 'Approved') { 36 | parent: dnsZone 37 | name: '_dnsauth.${cnameRecordName}' 38 | properties: { 39 | TTL: ttl 40 | TXTRecords: [ 41 | { 42 | value: [ 43 | validationToken 44 | ] 45 | } 46 | ] 47 | } 48 | } 49 | 50 | // Outputs 51 | output dnsZoneId string = dnsZone.id 52 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/bicep/install-nginx-ingress-controller-with-modsecurity.sh: -------------------------------------------------------------------------------- 1 | # Install kubectl 2 | az aks install-cli --only-show-errors 3 | 4 | # Get AKS credentials 5 | az aks get-credentials \ 6 | --admin \ 7 | --name $clusterName \ 8 | --resource-group $resourceGroupName \ 9 | --subscription $subscriptionId \ 10 | --only-show-errors 11 | 12 | # Check if the cluster is private or not 13 | private=$(az aks show --name $clusterName \ 14 | --resource-group $resourceGroupName \ 15 | --subscription $subscriptionId \ 16 | --query apiServerAccessProfile.enablePrivateCluster \ 17 | --output tsv) 18 | 19 | # Install openssl 20 | apk add --no-cache --quiet openssl 21 | 22 | # Install Helm 23 | wget -O get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 24 | chmod 700 get_helm.sh 25 | ./get_helm.sh 26 | 27 | # Add Helm repos 28 | helm repo add prometheus-community https://prometheus-community.github.io/helm-charts 29 | helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx 30 | helm repo add jetstack https://charts.jetstack.io 31 | 32 | # Update Helm repos 33 | helm repo update 34 | 35 | # Install Prometheus 36 | helm install prometheus prometheus-community/kube-prometheus-stack \ 37 | --create-namespace \ 38 | --namespace prometheus \ 39 | --set prometheus.prometheusSpec.podMonitorSelectorNilUsesHelmValues=false \ 40 | --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false 41 | 42 | # Install NGINX ingress controller with ModSecurity WAF 43 | helm install nginx-ingress ingress-nginx/ingress-nginx \ 44 | --create-namespace \ 45 | --namespace ingress-basic \ 46 | --set controller.replicaCount=3 \ 47 | --set controller.nodeSelector."kubernetes\.io/os"=linux \ 48 | --set defaultBackend.nodeSelector."kubernetes\.io/os"=linux \ 49 | --set controller.metrics.enabled=true \ 50 | --set controller.metrics.serviceMonitor.enabled=true \ 51 | --set controller.metrics.serviceMonitor.additionalLabels.release="prometheus" \ 52 | --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz \ 53 | --set controller.config.modsecurity-snippet=\ 54 | 'SecRuleEngine On 55 | SecRequestBodyAccess On 56 | SecAuditLog /dev/stdout 57 | SecAuditLogFormat JSON 58 | SecAuditEngine RelevantOnly 59 | SecRule REMOTE_ADDR "@ipMatch 127.0.0.1" "id:87,phase:1,pass,nolog,ctl:ruleEngine=Off"' 60 | 61 | # Install certificate manager 62 | helm install cert-manager jetstack/cert-manager \ 63 | --create-namespace \ 64 | --namespace cert-manager \ 65 | --set crds.enabled=true \ 66 | --set prometheus.enabled=true \ 67 | --set nodeSelector."kubernetes\.io/os"=linux 68 | 69 | # Create cluster issuer 70 | cat <$AZ_SCRIPTS_OUTPUT_PATH 96 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/bicep/keyVault.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the Key Vault resource.') 3 | param name string 4 | 5 | @description('Specifies the location.') 6 | param location string = resourceGroup().location 7 | 8 | @description('Specifies the sku name of the Key Vault resource.') 9 | @allowed([ 10 | 'premium' 11 | 'standard' 12 | ]) 13 | param skuName string = 'standard' 14 | 15 | @description('Specifies the Azure Active Directory tenant ID that should be used for authenticating requests to the key vault.') 16 | param tenantId string = subscription().tenantId 17 | 18 | @description('Specifies whether to allow public network access for Key Vault.') 19 | @allowed([ 20 | 'Disabled' 21 | 'Enabled' 22 | ]) 23 | param publicNetworkAccess string = 'Disabled' 24 | 25 | @description('The default action of allow or deny when no other rules match. Allowed values: Allow or Deny') 26 | @allowed([ 27 | 'Allow' 28 | 'Deny' 29 | ]) 30 | param networkAclsDefaultAction string = 'Deny' 31 | 32 | @description('Specifies whether the Azure Key Vault resource is enabled for deployments.') 33 | param enabledForDeployment bool = true 34 | 35 | @description('Specifies whether the Azure Key Vault resource is enabled for disk encryption.') 36 | param enabledForDiskEncryption bool = true 37 | 38 | @description('Specifies whether the Azure Key Vault resource is enabled for template deployment.') 39 | param enabledForTemplateDeployment bool = true 40 | 41 | @description('Specifies whether purge protection is enabled for this Azure Key Vault resource.') 42 | param enablePurgeProtection bool = true 43 | 44 | @description('Specifies whether enable the RBAC authorization for the Azure Key Vault resource.') 45 | param enableRbacAuthorization bool = true 46 | 47 | @description('Specifies whether the soft deelete is enabled for this Azure Key Vault resource.') 48 | param enableSoftDelete bool = true 49 | 50 | @description('Specifies the soft delete retention in days.') 51 | param softDeleteRetentionInDays int = 7 52 | 53 | @description('Specifies the resource id of the Log Analytics workspace.') 54 | param workspaceId string 55 | 56 | @description('Specifies the object id of a Miccrosoft Entra ID user. In general, this the object id of the system administrator who deploys the Azure resources.') 57 | param userId string = '' 58 | 59 | @description('Specifies the resource tags.') 60 | param tags object 61 | 62 | // Variables 63 | var diagnosticSettingsName = 'diagnosticSettings' 64 | var logCategories = [ 65 | 'AuditEvent' 66 | 'AzurePolicyEvaluationDetails' 67 | ] 68 | var metricCategories = [ 69 | 'AllMetrics' 70 | ] 71 | var logs = [ 72 | for category in logCategories: { 73 | category: category 74 | enabled: true 75 | retentionPolicy: { 76 | enabled: true 77 | days: 0 78 | } 79 | } 80 | ] 81 | var metrics = [ 82 | for category in metricCategories: { 83 | category: category 84 | enabled: true 85 | retentionPolicy: { 86 | enabled: true 87 | days: 0 88 | } 89 | } 90 | ] 91 | 92 | // Resources 93 | resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = { 94 | name: name 95 | location: location 96 | tags: tags 97 | properties: { 98 | createMode: 'default' 99 | sku: { 100 | family: 'A' 101 | name: skuName 102 | } 103 | tenantId: tenantId 104 | networkAcls: { 105 | bypass: 'AzureServices' 106 | defaultAction: networkAclsDefaultAction 107 | } 108 | enabledForDeployment: enabledForDeployment 109 | enabledForDiskEncryption: enabledForDiskEncryption 110 | enabledForTemplateDeployment: enabledForTemplateDeployment 111 | enablePurgeProtection: enablePurgeProtection ? enablePurgeProtection : null 112 | enableRbacAuthorization: enableRbacAuthorization 113 | enableSoftDelete: enableSoftDelete 114 | softDeleteRetentionInDays: softDeleteRetentionInDays 115 | publicNetworkAccess: publicNetworkAccess 116 | } 117 | } 118 | 119 | // Role Definitions 120 | resource keyVaultAdministratorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { 121 | name: '00482a5a-887f-4fb3-b363-3b7fe8e74483' 122 | scope: subscription() 123 | } 124 | 125 | // Role Assignments 126 | 127 | // This role assignment grants the user the required permissions to perform all data plane operations Key Vault and all objects in it, including certificates, keys, and secrets. 128 | resource keyVaultAdministratorUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(userId)) { 129 | name: guid(keyVault.id, keyVaultAdministratorRoleDefinition.id, userId) 130 | scope: keyVault 131 | properties: { 132 | roleDefinitionId: keyVaultAdministratorRoleDefinition.id 133 | principalType: 'User' 134 | principalId: userId 135 | } 136 | } 137 | 138 | resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { 139 | name: diagnosticSettingsName 140 | scope: keyVault 141 | properties: { 142 | workspaceId: workspaceId 143 | logs: logs 144 | metrics: metrics 145 | } 146 | } 147 | 148 | // Outputs 149 | output id string = keyVault.id 150 | output name string = keyVault.name 151 | output vaultUri string = keyVault.properties.vaultUri 152 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/bicep/kubeletManagedIdentity.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the existing AKS cluster.') 3 | param aksClusterName string 4 | 5 | @description('Specifies the name of the existing container registry.') 6 | param acrName string 7 | 8 | // Variables 9 | var acrPullRoleDefinitionId = resourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') 10 | 11 | // Resources 12 | resource aksCluster 'Microsoft.ContainerService/managedClusters@2024-07-01' existing = { 13 | name: aksClusterName 14 | } 15 | 16 | resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-11-01-preview' existing = { 17 | name: acrName 18 | } 19 | 20 | resource acrPullRoleAssignmentName 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 21 | name: guid(aksCluster.name, containerRegistry.id, acrPullRoleDefinitionId) 22 | scope: containerRegistry 23 | properties: { 24 | roleDefinitionId: acrPullRoleDefinitionId 25 | principalId: any(aksCluster.properties.identityProfile.kubeletidentity).objectId 26 | principalType: 'ServicePrincipal' 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/bicep/logAnalytics.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the Log Analytics workspace.') 3 | param name string 4 | 5 | @description('Specifies the service tier of the workspace: Free, Standalone, PerNode, Per-GB.') 6 | @allowed([ 7 | 'Free' 8 | 'Standalone' 9 | 'PerNode' 10 | 'PerGB2018' 11 | ]) 12 | param sku string = 'PerNode' 13 | 14 | @description('Specifies the workspace data retention in days. -1 means Unlimited retention for the Unlimited Sku. 730 days is the maximum allowed for all other Skus.') 15 | param retentionInDays int = 60 16 | 17 | @description('Specifies the location.') 18 | param location string = resourceGroup().location 19 | 20 | @description('Specifies the resource tags.') 21 | param tags object 22 | 23 | // Resources 24 | resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { 25 | name: name 26 | tags: tags 27 | location: location 28 | properties: { 29 | sku: { 30 | name: sku 31 | } 32 | retentionInDays: retentionInDays 33 | } 34 | } 35 | 36 | //Outputs 37 | output id string = logAnalyticsWorkspace.id 38 | output name string = logAnalyticsWorkspace.name 39 | output customerId string = logAnalyticsWorkspace.properties.customerId 40 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/bicep/main.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | // Parameters 4 | param aksClusterNetworkMode = 'transparent' 5 | param aksClusterNetworkDataplane = 'cilium' 6 | param aksClusterNetworkPlugin = 'azure' 7 | param aksClusterNetworkPluginMode = 'overlay' 8 | param aksClusterNetworkPolicy = 'cilium' 9 | param aksClusterWebAppRoutingEnabled = true 10 | param aksClusterNginxDefaultIngressControllerType = 'Internal' 11 | param aksClusterSkuTier = 'Standard' 12 | param aksClusterPodCidr = '192.168.0.0/16' 13 | param aksClusterServiceCidr = '172.16.0.0/16' 14 | param aksClusterDnsServiceIP = '172.16.0.10' 15 | param aksClusterOutboundType = 'userAssignedNATGateway' 16 | param aksClusterKubernetesVersion = '1.30.4' 17 | param aksClusterAdminUsername = 'azadmin' 18 | param aksClusterSshPublicKey = getSecret( 19 | '5b88bb04-3d20-4aac-bfef-603c1e311ef2', 20 | 'HorusRG', 21 | 'HorusKeyVault', 22 | 'aksClusterSshPublicKey' 23 | ) 24 | param loadBalancerBackendPoolType = 'nodeIP' 25 | param aadProfileManaged = true 26 | param aadProfileEnableAzureRBAC = true 27 | param aadProfileAdminGroupObjectIDs = [ 28 | '4e4d0501-e693-4f3e-965b-5bec6c410c03' 29 | ] 30 | param systemAgentPoolName = 'system' 31 | param systemAgentPoolVmSize = 'Standard_F8s_v2' 32 | param systemAgentPoolOsDiskSizeGB = 80 33 | param systemAgentPoolAgentCount = 3 34 | param systemAgentPoolMaxCount = 5 35 | param systemAgentPoolMinCount = 3 36 | param systemAgentPoolNodeTaints = [ 37 | 'CriticalAddonsOnly=true:NoSchedule' 38 | ] 39 | param userAgentPoolName = 'user' 40 | param userAgentPoolVmSize = 'Standard_F8s_v2' 41 | param userAgentPoolOsDiskSizeGB = 80 42 | param userAgentPoolAgentCount = 3 43 | param userAgentPoolMaxCount = 5 44 | param userAgentPoolMinCount = 3 45 | param enableVnetIntegration = true 46 | param virtualNetworkAddressPrefixes = '10.0.0.0/8' 47 | param systemAgentPoolSubnetName = 'SystemSubnet' 48 | param systemAgentPoolSubnetAddressPrefix = '10.240.0.0/16' 49 | param userAgentPoolSubnetName = 'UserSubnet' 50 | param userAgentPoolSubnetAddressPrefix = '10.241.0.0/16' 51 | param podSubnetName = 'PodSubnet' 52 | param podSubnetAddressPrefix = '10.242.0.0/16' 53 | param apiServerSubnetName = 'ApiServerSubnet' 54 | param apiServerSubnetAddressPrefix = '10.243.0.0/27' 55 | param vmSubnetName = 'VmSubnet' 56 | param vmSubnetAddressPrefix = '10.243.1.0/24' 57 | param bastionSubnetAddressPrefix = '10.243.2.0/24' 58 | param logAnalyticsSku = 'PerGB2018' 59 | param logAnalyticsRetentionInDays = 60 60 | param vmEnabled = true 61 | param vmName = 'TestVm' 62 | param vmSize = 'Standard_F8s_v2' 63 | param imagePublisher = 'Canonical' 64 | param imageOffer = '0001-com-ubuntu-server-jammy' 65 | param imageSku = '22_04-lts-gen2' 66 | param authenticationType = 'sshPublicKey' 67 | param vmAdminUsername = 'azadmin' 68 | param vmAdminPasswordOrKey = getSecret( 69 | '5b88bb04-3d20-4aac-bfef-603c1e311ef2', 70 | 'HorusRG', 71 | 'HorusKeyVault', 72 | 'vmAdminSshPublicKey' 73 | ) 74 | param diskStorageAccountType = 'Premium_LRS' 75 | param numDataDisks = 1 76 | param osDiskSize = 50 77 | param dataDiskSize = 50 78 | param dataDiskCaching = 'ReadWrite' 79 | param aksClusterEnablePrivateCluster = false 80 | param aksEnablePrivateClusterPublicFQDN = false 81 | param podIdentityProfileEnabled = false 82 | param kedaEnabled = true 83 | param daprEnabled = true 84 | param fluxGitOpsEnabled = false 85 | param verticalPodAutoscalerEnabled = true 86 | param deploymentScriptUri = 'https://raw.githubusercontent.com/paolosalvatori/yelb/refs/heads/main/azure/nginx-with-modsecurity-waf/bicep/install-nginx-ingress-controller-with-modsecurity.sh' 87 | param blobCSIDriverEnabled = true 88 | param diskCSIDriverEnabled = true 89 | param fileCSIDriverEnabled = true 90 | param snapshotControllerEnabled = true 91 | param defenderSecurityMonitoringEnabled = true 92 | param imageCleanerEnabled = true 93 | param imageCleanerIntervalHours = 24 94 | param nodeRestrictionEnabled = true 95 | param workloadIdentityEnabled = true 96 | param oidcIssuerProfileEnabled = true 97 | param dnsZoneName = 'babosbird.com' 98 | param dnsZoneResourceGroupName = 'DnsResourceGroup' 99 | param actionGroupEmailAddress = 'paolos@microsoft.com' 100 | param deployPrometheusAndGrafanaViaHelm = true 101 | param deployCertificateManagerViaHelm = true 102 | param ingressClassNames = ['nginx','webapprouting.kubernetes.azure.com'] 103 | param clusterIssuerNames = ['letsencrypt-nginx','letsencrypt-webapprouting'] 104 | param deployNginxIngressControllerViaHelm = 'External' 105 | param email = 'paolos@microsoft.com' 106 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/bicep/managedGrafana.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the Azure Monitor managed service for Prometheus resource.') 3 | param prometheusName string 4 | 5 | @description('Specifies the name of the Azure Managed Grafana resource.') 6 | param name string 7 | 8 | @description('Specifies the location of the Azure Managed Grafana resource.') 9 | param location string = resourceGroup().location 10 | 11 | @description('Specifies the sku of the Azure Managed Grafana resource.') 12 | param skuName string = 'Standard' 13 | 14 | @description('Specifies the api key setting of the Azure Managed Grafana resource.') 15 | @allowed([ 16 | 'Disabled' 17 | 'Enabled' 18 | ]) 19 | param apiKey string = 'Enabled' 20 | 21 | @description('Specifies the scope for dns deterministic name hash calculation.') 22 | @allowed([ 23 | 'TenantReuse' 24 | ]) 25 | param autoGeneratedDomainNameLabelScope string = 'TenantReuse' 26 | 27 | @description('Specifies whether the Azure Managed Grafana resource uses deterministic outbound IPs.') 28 | @allowed([ 29 | 'Disabled' 30 | 'Enabled' 31 | ]) 32 | param deterministicOutboundIP string = 'Disabled' 33 | 34 | @description('Specifies the the state for enable or disable traffic over the public interface for the the Azure Managed Grafana resource.') 35 | @allowed([ 36 | 'Disabled' 37 | 'Enabled' 38 | ]) 39 | param publicNetworkAccess string = 'Enabled' 40 | 41 | @description('The zone redundancy setting of the Azure Managed Grafana resource.') 42 | @allowed([ 43 | 'Disabled' 44 | 'Enabled' 45 | ]) 46 | param zoneRedundancy string = 'Disabled' 47 | 48 | @description('Specifies the object id of an Azure Active Directory user. In general, this the object id of the system administrator who deploys the Azure resources.') 49 | param userId string = '' 50 | 51 | @description('Specifies the resource tags for the Azure Monitor managed service for Prometheus resource.') 52 | param tags object 53 | 54 | // Resources 55 | resource mmonitoringReaderRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { 56 | name: '43d0d8ad-25c7-4714-9337-8ba259a9fe05' 57 | scope: subscription() 58 | } 59 | 60 | resource monitoringDataReaderRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { 61 | name: 'b0d8363b-8ddd-447d-831f-62ca05bff136' 62 | scope: subscription() 63 | } 64 | 65 | resource grafanaAdminRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { 66 | name: '22926164-76b3-42b3-bc55-97df8dab3e41' 67 | scope: subscription() 68 | } 69 | 70 | resource managedPrometheus 'Microsoft.Monitor/accounts@2023-10-01-preview' existing = { 71 | name: prometheusName 72 | } 73 | 74 | resource managedGrafana 'Microsoft.Dashboard/grafana@2023-09-01' = { 75 | name: name 76 | location: location 77 | tags: tags 78 | sku: { 79 | name: skuName 80 | } 81 | identity: { 82 | type: 'SystemAssigned' 83 | } 84 | properties: { 85 | apiKey: apiKey 86 | autoGeneratedDomainNameLabelScope: autoGeneratedDomainNameLabelScope 87 | deterministicOutboundIP: deterministicOutboundIP 88 | grafanaIntegrations: { 89 | azureMonitorWorkspaceIntegrations: [{ 90 | azureMonitorWorkspaceResourceId: managedPrometheus.id 91 | }] 92 | } 93 | publicNetworkAccess: publicNetworkAccess 94 | zoneRedundancy: zoneRedundancy 95 | } 96 | } 97 | 98 | // Assign the Monitoring Reader role to the Azure Managed Grafana system-assigned managed identity at the workspace scope 99 | resource monitoringReaderRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 100 | name: guid(name, managedPrometheus.name, mmonitoringReaderRole.id) 101 | scope: managedPrometheus 102 | properties: { 103 | roleDefinitionId: mmonitoringReaderRole.id 104 | principalId: managedGrafana.identity.principalId 105 | principalType: 'ServicePrincipal' 106 | } 107 | } 108 | 109 | // Assign the Monitoring Data Reader role to the Azure Managed Grafana system-assigned managed identity at the workspace scope 110 | resource monitoringDataReaderRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 111 | name: guid(name, managedPrometheus.name, monitoringDataReaderRole.id) 112 | scope: managedPrometheus 113 | properties: { 114 | roleDefinitionId: monitoringDataReaderRole.id 115 | principalId: managedGrafana.identity.principalId 116 | principalType: 'ServicePrincipal' 117 | } 118 | } 119 | 120 | // Assign the Grafana Admin role to the Microsoft Entra ID user at the Azure Managed Grafana resource scope 121 | resource grafanaAdminRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(userId)) { 122 | name: guid(name, userId, grafanaAdminRole.id) 123 | scope: managedGrafana 124 | properties: { 125 | roleDefinitionId: grafanaAdminRole.id 126 | principalId: userId 127 | principalType: 'User' 128 | } 129 | } 130 | 131 | // Outputs 132 | output id string = managedGrafana.id 133 | output name string = managedGrafana.name 134 | output location string = managedGrafana.location 135 | output principalId string = managedGrafana.identity.principalId 136 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/bicep/storageAccount.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the globally unique name for the storage account used to store the boot diagnostics logs of the virtual machine.') 3 | param name string = 'boot${uniqueString(resourceGroup().id)}' 4 | 5 | @description('Specifies whether to create containers.') 6 | param createContainers bool = true 7 | 8 | @description('Specifies an array of containers to create.') 9 | param containerNames array 10 | 11 | @description('Specifies the resource id of the Log Analytics workspace.') 12 | param workspaceId string 13 | 14 | @description('Specifies the location.') 15 | param location string = resourceGroup().location 16 | 17 | @description('Specifies the resource tags.') 18 | param tags object 19 | 20 | // Variables 21 | var diagnosticSettingsName = 'diagnosticSettings' 22 | var logCategories = [ 23 | 'StorageRead' 24 | 'StorageWrite' 25 | 'StorageDelete' 26 | ] 27 | var metricCategories = [ 28 | 'Transaction' 29 | ] 30 | var logs = [for category in logCategories: { 31 | category: category 32 | enabled: true 33 | retentionPolicy: { 34 | enabled: true 35 | days: 0 36 | } 37 | }] 38 | var metrics = [for category in metricCategories: { 39 | category: category 40 | enabled: true 41 | retentionPolicy: { 42 | enabled: true 43 | days: 0 44 | } 45 | }] 46 | 47 | // Resources 48 | resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = { 49 | name: name 50 | location: location 51 | tags: tags 52 | sku: { 53 | name: 'Standard_LRS' 54 | } 55 | kind: 'StorageV2' 56 | 57 | // Containers live inside of a blob service 58 | resource blobService 'blobServices' = { 59 | name: 'default' 60 | 61 | // Creating containers with provided names if contition is true 62 | resource containers 'containers' = [for containerName in containerNames: if(createContainers) { 63 | name: containerName 64 | properties: { 65 | publicAccess: 'None' 66 | } 67 | }] 68 | } 69 | } 70 | 71 | resource blobServiceDiagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { 72 | name: diagnosticSettingsName 73 | scope: storageAccount::blobService 74 | properties: { 75 | workspaceId: workspaceId 76 | logs: logs 77 | metrics: metrics 78 | } 79 | } 80 | 81 | // Outputs 82 | output id string = storageAccount.id 83 | output name string = storageAccount.name 84 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/bicep/virtualMachine.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the virtual machine.') 3 | param vmName string = 'TestVm' 4 | 5 | @description('Specifies the size of the virtual machine.') 6 | param vmSize string = 'Standard_DS3_v2' 7 | 8 | @description('Specifies the resource id of the subnet hosting the virtual machine.') 9 | param vmSubnetId string 10 | 11 | @description('Specifies the name of the storage account where the bootstrap diagnostic logs of the virtual machine are stored.') 12 | param storageAccountName string 13 | 14 | @description('Specifies the image publisher of the disk image used to create the virtual machine.') 15 | param imagePublisher string = 'Canonical' 16 | 17 | @description('Specifies the offer of the platform image or marketplace image used to create the virtual machine.') 18 | param imageOffer string = '0001-com-ubuntu-server-jammy' 19 | 20 | @description('Specifies the Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version.') 21 | param imageSku string = '22_04-lts-gen2' 22 | 23 | @description('Specifies the type of authentication when accessing the Virtual Machine. SSH key is recommended.') 24 | @allowed([ 25 | 'sshPublicKey' 26 | 'password' 27 | ]) 28 | param authenticationType string = 'password' 29 | 30 | @description('Specifies the name of the administrator account of the virtual machine.') 31 | param vmAdminUsername string 32 | 33 | @description('Specifies the SSH Key or password for the virtual machine. SSH key is recommended.') 34 | @secure() 35 | param vmAdminPasswordOrKey string 36 | 37 | @description('Specifies the storage account type for OS and data disk.') 38 | @allowed([ 39 | 'Premium_LRS' 40 | 'StandardSSD_LRS' 41 | 'Standard_LRS' 42 | 'UltraSSD_LRS' 43 | ]) 44 | param diskStorageAccountType string = 'Premium_LRS' 45 | 46 | @description('Specifies the number of data disks of the virtual machine.') 47 | @minValue(0) 48 | @maxValue(64) 49 | param numDataDisks int = 1 50 | 51 | @description('Specifies the size in GB of the OS disk of the VM.') 52 | param osDiskSize int = 50 53 | 54 | @description('Specifies the size in GB of the OS disk of the virtual machine.') 55 | param dataDiskSize int = 50 56 | 57 | @description('Specifies the caching requirements for the data disks.') 58 | param dataDiskCaching string = 'ReadWrite' 59 | 60 | @description('Specifies the name of the user-defined managed identity used by the Azure Monitor Agent.') 61 | param managedIdentityName string 62 | 63 | @description('Specifies the location.') 64 | param location string = resourceGroup().location 65 | 66 | @description('Specifies the resource tags.') 67 | param tags object 68 | 69 | // Variables 70 | var vmNicName = '${vmName}Nic' 71 | var linuxConfiguration = { 72 | disablePasswordAuthentication: true 73 | ssh: { 74 | publicKeys: [ 75 | { 76 | path: '/home/${vmAdminUsername}/.ssh/authorized_keys' 77 | keyData: vmAdminPasswordOrKey 78 | } 79 | ] 80 | } 81 | provisionVMAgent: true 82 | } 83 | 84 | // Resources 85 | resource virtualMachineNic 'Microsoft.Network/networkInterfaces@2021-08-01' = { 86 | name: vmNicName 87 | location: location 88 | tags: tags 89 | properties: { 90 | ipConfigurations: [ 91 | { 92 | name: 'ipconfig1' 93 | properties: { 94 | privateIPAllocationMethod: 'Dynamic' 95 | subnet: { 96 | id: vmSubnetId 97 | } 98 | } 99 | } 100 | ] 101 | } 102 | } 103 | 104 | resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = { 105 | name: storageAccountName 106 | } 107 | 108 | resource virtualMachine 'Microsoft.Compute/virtualMachines@2024-07-01' = { 109 | name: vmName 110 | location: location 111 | tags: tags 112 | properties: { 113 | hardwareProfile: { 114 | vmSize: vmSize 115 | } 116 | osProfile: { 117 | computerName: vmName 118 | adminUsername: vmAdminUsername 119 | adminPassword: vmAdminPasswordOrKey 120 | linuxConfiguration: (authenticationType == 'password') ? null : linuxConfiguration 121 | } 122 | storageProfile: { 123 | imageReference: { 124 | publisher: imagePublisher 125 | offer: imageOffer 126 | sku: imageSku 127 | version: 'latest' 128 | } 129 | osDisk: { 130 | name: '${vmName}_OSDisk' 131 | caching: 'ReadWrite' 132 | createOption: 'FromImage' 133 | diskSizeGB: osDiskSize 134 | managedDisk: { 135 | storageAccountType: diskStorageAccountType 136 | } 137 | } 138 | dataDisks: [for j in range(0, numDataDisks): { 139 | caching: dataDiskCaching 140 | diskSizeGB: dataDiskSize 141 | lun: j 142 | name: '${vmName}-DataDisk${j}' 143 | createOption: 'Empty' 144 | managedDisk: { 145 | storageAccountType: diskStorageAccountType 146 | } 147 | }] 148 | } 149 | networkProfile: { 150 | networkInterfaces: [ 151 | { 152 | id: virtualMachineNic.id 153 | } 154 | ] 155 | } 156 | diagnosticsProfile: { 157 | bootDiagnostics: { 158 | enabled: true 159 | storageUri: storageAccount.properties.primaryEndpoints.blob 160 | } 161 | } 162 | } 163 | } 164 | 165 | resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' = { 166 | name: managedIdentityName 167 | location: location 168 | tags: tags 169 | } 170 | 171 | resource linuxAgent 'Microsoft.Compute/virtualMachines/extensions@2024-07-01' = { 172 | name: 'AzureMonitorLinuxAgent' 173 | parent: virtualMachine 174 | location: location 175 | properties: { 176 | publisher: 'Microsoft.Azure.Monitor' 177 | type: 'AzureMonitorLinuxAgent' 178 | typeHandlerVersion: '1.21' 179 | autoUpgradeMinorVersion: true 180 | enableAutomaticUpgrade: true 181 | settings: { 182 | authentication: { 183 | managedIdentity: { 184 | 'identifier-name': 'mi_res_id' 185 | 'identifier-value': managedIdentity.id 186 | } 187 | } 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/scripts/00-variables.sh: -------------------------------------------------------------------------------- 1 | # Azure Subscription and Tenant 2 | RESOURCE_GROUP_NAME="" 3 | SUBSCRIPTION_ID=$(az account show --query id --output tsv) 4 | SUBSCRIPTION_NAME=$(az account show --query name --output tsv) 5 | TENANT_ID=$(az account show --query tenantId --output tsv) 6 | DNS_ZONE_NAME="" 7 | DNS_ZONE_RESOURCE_GROUP_NAME="" 8 | DNS_ZONE_SUBSCRIPTION_ID='' 9 | SUBDOMAIN="" 10 | URL="https://$SUBDOMAIN.$DNS_ZONE_NAME" 11 | 12 | # NGINX Ingress Controller installed via Helm 13 | NGINX_NAMESPACE="ingress-basic" 14 | NGINX_REPO_NAME="ingress-nginx" 15 | NGINX_REPO_URL="https://kubernetes.github.io/ingress-nginx" 16 | NGINX_CHART_NAME="ingress-nginx" 17 | NGINX_RELEASE_NAME="ingress-nginx" 18 | NGINX_REPLICA_COUNT=3 19 | 20 | # NGINX Ingress Controller installed via AKS application routing add-on 21 | WEB_APP_ROUTING_NAMESPACE="app-routing-system" 22 | WEB_APP_ROUTING_SERVICE_NAME="nginx" 23 | 24 | # Certificate Manager 25 | CM_NAMESPACE="cert-manager" 26 | CM_REPO_NAME="jetstack" 27 | CM_REPO_URL="https://charts.jetstack.io" 28 | CM_CHART_NAME="cert-manager" 29 | CM_RELEASE_NAME="cert-manager" 30 | 31 | # Cluster Issuer 32 | EMAIL="" 33 | CLUSTER_ISSUER_NAMES=("letsencrypt-nginx" "letsencrypt-webapprouting") 34 | CLUSTER_ISSUER_TEMPLATES=("cluster-issuer-nginx.yml" "cluster-issuer-webapprouting.yml") 35 | 36 | # Specify the ingress class name for the ingress controller. 37 | # - nginx: unmanaged NGINX ingress controller installed vuia Helm 38 | # - webapprouting.kubernetes.azure.com: managed NGINX ingress controller installed via AKS application routing add-on 39 | INGRESS_CLASS_NAME="nginx" 40 | 41 | if [[ $INGRESS_CLASS_NAME == "nginx" ]]; then 42 | # Specify the name of the ingress objects. 43 | INGRESS_NAME="chat-ingress-nginx" 44 | 45 | # Specify the cluster issuer name for the ingress controller. 46 | CLUSTER_ISSUER="letsencrypt-nginx" 47 | 48 | # Specify the name of the secret that contains the TLS certificate for the ingress controller. 49 | INGRESS_SECRET_NAME="chat-tls-secret-nginx" 50 | else 51 | # Specify the name of the ingress objects. 52 | INGRESS_NAME="chat-ingress-webapprouting" 53 | 54 | # Specify the cluster issuer name for the ingress controller. 55 | CLUSTER_ISSUER="letsencrypt-webapprouting" 56 | 57 | # Specify the name of the secret that contains the TLS certificate for the ingress controller. 58 | INGRESS_SECRET_NAME="chat-tls-secret-webapprouting" 59 | fi -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/scripts/01-install-tools.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Install jq if not installed 7 | path=$(which jq) 8 | 9 | if [[ -z $path ]]; then 10 | echo 'Installing jq...' 11 | sudo apt install -y jq 12 | fi 13 | 14 | # Install yq if not installed 15 | path=$(which yq) 16 | 17 | if [[ -z $path ]]; then 18 | echo 'Installing wq...' 19 | sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq 20 | sudo chmod +x /usr/bin/yq 21 | fi 22 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/scripts/02-create-nginx-ingress-controller.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Check if the NGINX ingress controller Helm chart is already installed 7 | result=$(helm list -n $NGINX_NAMESPACE | grep $NGINX_RELEASE_NAME | awk '{print $1}') 8 | 9 | if [[ -n $result ]]; then 10 | echo "[$NGINX_RELEASE_NAME] NGINX ingress controller release already exists in the [$NGINX_NAMESPACE] namespace" 11 | else 12 | # Check if the NGINX ingress controller repository is not already added 13 | result=$(helm repo list | grep $NGINX_REPO_NAME | awk '{print $1}') 14 | 15 | if [[ -n $result ]]; then 16 | echo "[$NGINX_REPO_NAME] Helm repo already exists" 17 | else 18 | # Add the NGINX ingress controller repository 19 | echo "Adding [$NGINX_REPO_NAME] Helm repo..." 20 | helm repo add $NGINX_REPO_NAME $NGINX_REPO_URL 21 | fi 22 | 23 | # Update your local Helm chart repository cache 24 | echo 'Updating Helm repos...' 25 | helm repo update 26 | 27 | # Deploy NGINX ingress controller 28 | echo "Deploying [$NGINX_RELEASE_NAME] NGINX ingress controller to the [$NGINX_NAMESPACE] namespace..." 29 | helm install $NGINX_RELEASE_NAME $NGINX_REPO_NAME/$nginxChartName \ 30 | --create-namespace \ 31 | --namespace $NGINX_NAMESPACE \ 32 | --set controller.replicaCount=3 \ 33 | --set controller.nodeSelector."kubernetes\.io/os"=linux \ 34 | --set controller.replicaCount=$NGINX_REPLICA_COUNT \ 35 | --set defaultBackend.nodeSelector."kubernetes\.io/os"=linux \ 36 | --set controller.metrics.enabled=true \ 37 | --set controller.metrics.serviceMonitor.enabled=true \ 38 | --set controller.metrics.serviceMonitor.additionalLabels.release="prometheus" \ 39 | --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz \ 40 | --set controller.config.modsecurity-snippet=\ 41 | 'SecRuleEngine On 42 | SecRequestBodyAccess On 43 | SecAuditLog /dev/stdout 44 | SecAuditLogFormat JSON 45 | SecAuditEngine RelevantOnly 46 | SecRule REMOTE_ADDR "@ipMatch 127.0.0.1" "id:87,phase:1,pass,nolog,ctl:ruleEngine=Off"' 47 | fi 48 | 49 | # Get values 50 | helm get values $NGINX_RELEASE_NAME --namespace $NGINX_NAMESPACE 51 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/scripts/03-install-cert-manager.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Check if the certificate manager Helm chart is already installed 7 | result=$(helm list -n $CM_NAMESPACE | grep $CM_RELEASE_NAME | awk '{print $1}') 8 | 9 | if [[ -n $result ]]; then 10 | echo "[$CM_RELEASE_NAME] certificate manager release already exists in the [$CM_NAMESPACE] namespace" 11 | else 12 | # Check if the certificate manager repository is not already added 13 | result=$(helm repo list | grep $CM_REPO_NAME | awk '{print $1}') 14 | 15 | if [[ -n $result ]]; then 16 | echo "[$CM_REPO_NAME] Helm repo already exists" 17 | else 18 | # Add the certificate manager repository 19 | echo "Adding [$CM_REPO_NAME] Helm repo..." 20 | helm repo add $CM_REPO_NAME $CM_REPO_URL 21 | fi 22 | 23 | # Update your local Helm chart repository cache 24 | echo 'Updating Helm repos...' 25 | helm repo update 26 | 27 | # Install the cert-manager Helm chart 28 | echo "Deploying [$CM_RELEASE_NAME] cert-manager to the $CM_NAMESPACE namespace..." 29 | helm install $CM_RELEASE_NAME $CM_REPO_NAME/$cmChartName \ 30 | --create-namespace \ 31 | --namespace $CM_NAMESPACE \ 32 | --set installCRDs=true \ 33 | --set nodeSelector."kubernetes\.io/os"=linux 34 | fi -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/scripts/04-create-cluster-issuers.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Use a for loop to tag and push the local docker images to the Azure Container Registry 7 | for INDEX in ${!CLUSTER_ISSUER_NAMES[@]}; do 8 | CLUSTER_ISSUER_NAME=${CLUSTER_ISSUER_NAMES[$INDEX]} 9 | 10 | # Check if the cluster issuer already exists 11 | RESULT=$(kubectl get clusterissuer -o jsonpath='{.items[?(@.metadata.name=="'"$CLUSTER_ISSUER_NAME"'")].metadata.name}') 12 | 13 | if [[ -n $RESULT ]]; then 14 | echo "[$CLUSTER_ISSUER_NAME] cluster issuer already exists" 15 | continue 16 | else 17 | # Create the cluster issuer 18 | echo "[$CLUSTER_ISSUER_NAME] cluster issuer does not exist" 19 | echo "Creating [$CLUSTER_ISSUER_NAME] cluster issuer..." 20 | 21 | TEMPLATE=${CLUSTER_ISSUER_TEMPLATES[$INDEX]} 22 | cat $TEMPLATE | 23 | yq "(.spec.acme.email)|="\""$EMAIL"\" | 24 | kubectl apply -f - 25 | fi 26 | done 27 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/scripts/05-deploy-yelb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Apply the YAML configuration 7 | kubectl apply -f yelb.yml 8 | 9 | # Create chat-ingress 10 | cat ingress.yml | 11 | yq "(.metadata.annotations.\"cert-manager.io/cluster-issuer\")|="\""$CLUSTER_ISSUER"\" | 12 | yq "(.spec.ingressClassName)|="\""$INGRESS_CLASS_NAME"\" | 13 | yq "(.spec.tls[0].hosts[0])|="\""$SUBDOMAIN.$DNS_ZONE_NAME"\" | 14 | yq "(.spec.tls[0].secretName)|="\""$INGRESS_SECRET_NAME"\" | 15 | yq "(.spec.rules[0].host)|="\""$SUBDOMAIN.$DNS_ZONE_NAME"\" | 16 | kubectl apply -f - 17 | 18 | # Check the deployed resources within the yelb namespace: 19 | kubectl get all -n yelb 20 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/scripts/06-configure-dns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source ./00-variables.sh 5 | 6 | # Choose the ingress controller to use 7 | if [[ $INGRESS_CLASS_NAME == "nginx" ]]; then 8 | INGRESS_NAMESPACE=$NGINX_NAMESPACE 9 | INGRESS_SERVICE_NAME="${NGINX_RELEASE_NAME}-controller" 10 | else 11 | INGRESS_NAMESPACE=$WEB_APP_ROUTING_NAMESPACE 12 | INGRESS_SERVICE_NAME=$WEB_APP_ROUTING_SERVICE_NAME 13 | fi 14 | 15 | # Retrieve the public IP address of the NGINX ingress controller 16 | echo "Retrieving the external IP address of the [$INGRESS_CLASS_NAME] NGINX ingress controller..." 17 | PUBLIC_IP_ADDRESS=$(kubectl get service -o json -n $INGRESS_NAMESPACE | 18 | jq -r '.items[] | 19 | select(.spec.type == "LoadBalancer") | 20 | .status.loadBalancer.ingress[0].ip') 21 | 22 | if [ -n "$PUBLIC_IP_ADDRESS" ]; then 23 | echo "[$PUBLIC_IP_ADDRESS] external IP address of the [$INGRESS_CLASS_NAME] NGINX ingress controller successfully retrieved" 24 | else 25 | echo "Failed to retrieve the external IP address of the [$INGRESS_CLASS_NAME] NGINX ingress controller" 26 | exit 27 | fi 28 | 29 | # Check if an A record for todolist subdomain exists in the DNS Zone 30 | echo "Retrieving the A record for the [$SUBDOMAIN] subdomain from the [$DNS_ZONE_NAME] DNS zone..." 31 | IPV4_ADDRESS=$(az network dns record-set a list \ 32 | --zone-name $DNS_ZONE_NAME \ 33 | --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \ 34 | --subscription $DNS_ZONE_SUBSCRIPTION_ID \ 35 | --query "[?name=='$SUBDOMAIN'].ARecords[].IPV4_ADDRESS" \ 36 | --output tsv \ 37 | --only-show-errors) 38 | 39 | if [[ -n $IPV4_ADDRESS ]]; then 40 | echo "An A record already exists in [$DNS_ZONE_NAME] DNS zone for the [$SUBDOMAIN] subdomain with [$IPV4_ADDRESS] IP address" 41 | 42 | if [[ $IPV4_ADDRESS == $PUBLIC_IP_ADDRESS ]]; then 43 | echo "The [$IPV4_ADDRESS] ip address of the existing A record is equal to the ip address of the ingress" 44 | echo "No additional step is required" 45 | continue 46 | else 47 | echo "The [$IPV4_ADDRESS] ip address of the existing A record is different than the ip address of the ingress" 48 | fi 49 | # Retrieving name of the record set relative to the zone 50 | echo "Retrieving the name of the record set relative to the [$DNS_ZONE_NAME] zone..." 51 | 52 | RECORDSET_NAME=$(az network dns record-set a list \ 53 | --zone-name $DNS_ZONE_NAME \ 54 | --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \ 55 | --subscription $DNS_ZONE_SUBSCRIPTION_ID \ 56 | --query "[?name=='$SUBDOMAIN'].name" \ 57 | --output tsv \ 58 | --only-show-errors 2>/dev/null) 59 | 60 | if [[ -n $RECORDSET_NAME ]]; then 61 | echo "[$RECORDSET_NAME] record set name successfully retrieved" 62 | else 63 | echo "Failed to retrieve the name of the record set relative to the [$DNS_ZONE_NAME] zone" 64 | exit 65 | fi 66 | 67 | # Remove the A record 68 | echo "Removing the A record from the record set relative to the [$DNS_ZONE_NAME] zone..." 69 | 70 | az network dns record-set a remove-record \ 71 | --ipv4-address $IPV4_ADDRESS \ 72 | --record-set-name $RECORDSET_NAME \ 73 | --zone-name $DNS_ZONE_NAME \ 74 | --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \ 75 | --subscription $DNS_ZONE_SUBSCRIPTION_ID \ 76 | --only-show-errors 1>/dev/null 77 | 78 | if [[ $? == 0 ]]; then 79 | echo "[$IPV4_ADDRESS] ip address successfully removed from the [$RECORDSET_NAME] record set" 80 | else 81 | echo "Failed to remove the [$IPV4_ADDRESS] ip address from the [$RECORDSET_NAME] record set" 82 | exit 83 | fi 84 | fi 85 | 86 | # Create the A record 87 | echo "Creating an A record in [$DNS_ZONE_NAME] DNS zone for the [$SUBDOMAIN] subdomain with [$PUBLIC_IP_ADDRESS] IP address..." 88 | az network dns record-set a add-record \ 89 | --zone-name $DNS_ZONE_NAME \ 90 | --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \ 91 | --subscription $DNS_ZONE_SUBSCRIPTION_ID \ 92 | --record-set-name $SUBDOMAIN \ 93 | --ipv4-address $PUBLIC_IP_ADDRESS \ 94 | --only-show-errors 1>/dev/null 95 | 96 | if [[ $? == 0 ]]; then 97 | echo "A record for the [$SUBDOMAIN] subdomain with [$PUBLIC_IP_ADDRESS] IP address successfully created in [$DNS_ZONE_NAME] DNS zone" 98 | else 99 | echo "Failed to create an A record for the $SUBDOMAIN subdomain with [$PUBLIC_IP_ADDRESS] IP address in [$DNS_ZONE_NAME] DNS zone" 100 | fi 101 | -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/scripts/07-call-yelb-ui.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | source 00-variables.sh 5 | 6 | # Call REST API 7 | echo "Calling Yelb UI service at $URL..." 8 | curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL 9 | 10 | # Simulate SQL injection 11 | echo "Simulating SQL injection when calling $URL..." 12 | curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL/?users=ExampleSQLInjection%27%20-- 13 | 14 | # Simulate XSS 15 | echo "Simulating XSS when calling $URL..." 16 | curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL/?users=ExampleXSS%3Cscript%3Ealert%28%27XSS%27%29%3C%2Fscript%3E -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/scripts/cluster-issuer-nginx.yml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: letsencrypt-nginx 5 | spec: 6 | acme: 7 | server: https://acme-v02.api.letsencrypt.org/directory 8 | email: 9 | privateKeySecretRef: 10 | name: letsencrypt-nginx 11 | solvers: 12 | - http01: 13 | ingress: 14 | class: nginx 15 | podTemplate: 16 | spec: 17 | nodeSelector: 18 | "kubernetes.io/os": linux -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/scripts/cluster-issuer-webapprouting.yml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: letsencrypt-webapprouting 5 | spec: 6 | acme: 7 | server: https://acme-v02.api.letsencrypt.org/directory 8 | email: 9 | privateKeySecretRef: 10 | name: letsencrypt-webapprouting 11 | solvers: 12 | - http01: 13 | ingress: 14 | class: webapprouting.kubernetes.azure.com 15 | podTemplate: 16 | spec: 17 | nodeSelector: 18 | "kubernetes.io/os": linux -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/scripts/ingress.yml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: yelb.app 5 | namespace: yelb 6 | annotations: 7 | cert-manager.io/cluster-issuer: letsencrypt-nginx 8 | cert-manager.io/acme-challenge-type: http01 9 | spec: 10 | ingressClassName: nginx 11 | tls: 12 | - hosts: 13 | - yelb. 14 | secretName: yelb-tls-secret 15 | rules: 16 | - host: yelb. 17 | http: 18 | paths: 19 | - path: / 20 | pathType: Prefix 21 | backend: 22 | service: 23 | name: yelb-ui 24 | port: 25 | number: 80 -------------------------------------------------------------------------------- /azure/nginx-with-modsecurity-waf/scripts/yelb.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: yelb 5 | --- 6 | apiVersion: v1 7 | kind: Service 8 | metadata: 9 | namespace: yelb 10 | name: redis-server 11 | labels: 12 | app: redis-server 13 | tier: cache 14 | spec: 15 | type: ClusterIP 16 | ports: 17 | - port: 6379 18 | selector: 19 | app: redis-server 20 | tier: cache 21 | --- 22 | apiVersion: v1 23 | kind: Service 24 | metadata: 25 | namespace: yelb 26 | name: yelb-db 27 | labels: 28 | app: yelb-db 29 | tier: backenddb 30 | spec: 31 | type: ClusterIP 32 | ports: 33 | - port: 5432 34 | selector: 35 | app: yelb-db 36 | tier: backenddb 37 | --- 38 | apiVersion: v1 39 | kind: Service 40 | metadata: 41 | namespace: yelb 42 | name: yelb-appserver 43 | labels: 44 | app: yelb-appserver 45 | tier: middletier 46 | spec: 47 | type: ClusterIP 48 | ports: 49 | - port: 4567 50 | selector: 51 | app: yelb-appserver 52 | tier: middletier 53 | --- 54 | apiVersion: v1 55 | kind: Service 56 | metadata: 57 | namespace: yelb 58 | name: yelb-ui 59 | labels: 60 | app: yelb-ui 61 | tier: frontend 62 | spec: 63 | type: LoadBalancer 64 | ports: 65 | - port: 80 66 | protocol: TCP 67 | targetPort: 80 68 | selector: 69 | app: yelb-ui 70 | tier: frontend 71 | --- 72 | apiVersion: apps/v1 73 | kind: Deployment 74 | metadata: 75 | namespace: yelb 76 | name: yelb-ui 77 | spec: 78 | replicas: 1 79 | selector: 80 | matchLabels: 81 | app: yelb-ui 82 | tier: frontend 83 | template: 84 | metadata: 85 | labels: 86 | app: yelb-ui 87 | tier: frontend 88 | spec: 89 | containers: 90 | - name: yelb-ui 91 | image: mreferre/yelb-ui:0.7 92 | ports: 93 | - containerPort: 80 94 | --- 95 | apiVersion: apps/v1 96 | kind: Deployment 97 | metadata: 98 | namespace: yelb 99 | name: redis-server 100 | spec: 101 | selector: 102 | matchLabels: 103 | app: redis-server 104 | tier: cache 105 | replicas: 1 106 | template: 107 | metadata: 108 | labels: 109 | app: redis-server 110 | tier: cache 111 | spec: 112 | containers: 113 | - name: redis-server 114 | image: redis:4.0.2 115 | ports: 116 | - containerPort: 6379 117 | --- 118 | apiVersion: apps/v1 119 | kind: Deployment 120 | metadata: 121 | namespace: yelb 122 | name: yelb-db 123 | spec: 124 | replicas: 1 125 | selector: 126 | matchLabels: 127 | app: yelb-db 128 | tier: backenddb 129 | template: 130 | metadata: 131 | labels: 132 | app: yelb-db 133 | tier: backenddb 134 | spec: 135 | containers: 136 | - name: yelb-db 137 | image: mreferre/yelb-db:0.5 138 | ports: 139 | - containerPort: 5432 140 | --- 141 | apiVersion: apps/v1 142 | kind: Deployment 143 | metadata: 144 | namespace: yelb 145 | name: yelb-appserver 146 | spec: 147 | replicas: 1 148 | selector: 149 | matchLabels: 150 | app: yelb-appserver 151 | tier: middletier 152 | template: 153 | metadata: 154 | labels: 155 | app: yelb-appserver 156 | tier: middletier 157 | spec: 158 | containers: 159 | - name: yelb-appserver 160 | image: mreferre/yelb-appserver:0.5 161 | ports: 162 | - containerPort: 4567 163 | -------------------------------------------------------------------------------- /images/application-gateway-aks-http-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/aks-web-application-replicate-from-aws/cb22c02f68e97ddcc8091f1ab3a7ba66df193b13/images/application-gateway-aks-http-detail.png -------------------------------------------------------------------------------- /images/application-gateway-aks-http.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/aks-web-application-replicate-from-aws/cb22c02f68e97ddcc8091f1ab3a7ba66df193b13/images/application-gateway-aks-http.png -------------------------------------------------------------------------------- /images/application-gateway-aks-https-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/aks-web-application-replicate-from-aws/cb22c02f68e97ddcc8091f1ab3a7ba66df193b13/images/application-gateway-aks-https-detail.png -------------------------------------------------------------------------------- /images/application-gateway-aks-https.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/aks-web-application-replicate-from-aws/cb22c02f68e97ddcc8091f1ab3a7ba66df193b13/images/application-gateway-aks-https.png -------------------------------------------------------------------------------- /images/application-gateway-for-containers-aks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/aks-web-application-replicate-from-aws/cb22c02f68e97ddcc8091f1ab3a7ba66df193b13/images/application-gateway-for-containers-aks.png -------------------------------------------------------------------------------- /images/application-gateway-ingress-controller-aks-http.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/aks-web-application-replicate-from-aws/cb22c02f68e97ddcc8091f1ab3a7ba66df193b13/images/application-gateway-ingress-controller-aks-http.png -------------------------------------------------------------------------------- /images/architecture-on-aws.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/aks-web-application-replicate-from-aws/cb22c02f68e97ddcc8091f1ab3a7ba66df193b13/images/architecture-on-aws.jpg -------------------------------------------------------------------------------- /images/architecture-on-azure.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/aks-web-application-replicate-from-aws/cb22c02f68e97ddcc8091f1ab3a7ba66df193b13/images/architecture-on-azure.jpg -------------------------------------------------------------------------------- /images/front-door-aks-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/aks-web-application-replicate-from-aws/cb22c02f68e97ddcc8091f1ab3a7ba66df193b13/images/front-door-aks-flow.png -------------------------------------------------------------------------------- /images/front-door-aks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/aks-web-application-replicate-from-aws/cb22c02f68e97ddcc8091f1ab3a7ba66df193b13/images/front-door-aks.png -------------------------------------------------------------------------------- /images/nginx-modsecurity-aks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/aks-web-application-replicate-from-aws/cb22c02f68e97ddcc8091f1ab3a7ba66df193b13/images/nginx-modsecurity-aks.png -------------------------------------------------------------------------------- /images/yelb-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/aks-web-application-replicate-from-aws/cb22c02f68e97ddcc8091f1ab3a7ba66df193b13/images/yelb-architecture.png -------------------------------------------------------------------------------- /images/yelb-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/aks-web-application-replicate-from-aws/cb22c02f68e97ddcc8091f1ab3a7ba66df193b13/images/yelb-ui.png -------------------------------------------------------------------------------- /visio/architecture.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/aks-web-application-replicate-from-aws/cb22c02f68e97ddcc8091f1ab3a7ba66df193b13/visio/architecture.vsdx --------------------------------------------------------------------------------