├── .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
--------------------------------------------------------------------------------