From 348e7f08703eb646b9cc18102b141b34586a6855 Mon Sep 17 00:00:00 2001 From: Wanpeng Yang <97911035+wanpengyang@users.noreply.github.com> Date: Fri, 8 Mar 2024 20:00:40 +0000 Subject: [PATCH] Squashed commit of the following: commit fb13f563518bfb9efb7d0011eb6993c5b38b5634 Author: aporodnov <41848211+aporodnov@users.noreply.github.com> Date: Thu Nov 16 19:00:37 2023 -0800 Removed #Requires -Modules from the PS scripts (#393) Co-authored-by: @skeeler commit e5fad99585a6aff1962f6262e2f23c599e7bcd1e Author: Barrington Willis <51492255+tredell@users.noreply.github.com> Date: Thu Sep 14 09:01:14 2023 -0700 Removed the Diagnostic Logs Audit requirement for EventGrid/eventSubscriptions (#390) * Removed Diagnostic logging auditing for EventGrid/eventSubscriptions from the PBMM and Log Analytics policy initiatives. commit aa697c32b2be380c14877ea03b85e2366569d308 Author: Barrington Willis <51492255+tredell@users.noreply.github.com> Date: Wed Sep 13 13:01:26 2023 -0700 PrivateLink support for MySql Flexible Databases (#388) commit db45632283e6982fb095f6be33540c28ad54960a Author: Steve Keeler Date: Sun Jul 9 23:14:55 2023 -0400 Scripts to generate config from template, support JSON config intellisense in editors, fix bugs in deployment scripts (#379) Fixes path normalization bug in deployment scripts #374 Fixes subscription filtering bug in deployment scripts #375 Adds CanadaPubSecALZ configuration JSON schema support for editors #376 Adds Scripts to generate CanadaPubSecALZ configuration files using existing environments as template #377 Adds Deploy landing zones to new Azure subscriptions in new primary tenant #378 commit 5830bcb63193565ab291076b54765f2d8986f64b Author: David Christiansen Date: Tue Apr 25 21:12:23 2023 +0100 Update identity.md (#365) Updated page title to reflect content commit 674f6cb1e7ee407765eeb9d99a8163ef0a461b32 Author: Yanick Lepine <65724245+ylepine@users.noreply.github.com> Date: Thu Mar 16 13:13:38 2023 -0400 Update DDoS.bicep (#363) Change policySetDefinitions to policyDefinitions for the policyScopedId variable. commit 5680e6582a6c28907898da2026ef3c1f0e56a332 Author: Barrington Willis <51492255+tredell@users.noreply.github.com> Date: Mon Mar 13 06:31:54 2023 -0700 Bug fixes - network routing & ADO Identity Pipelines (#362) * Fixed Bug: missing identityPathFromRoot variable missing * Fixed Bug: Allow Network transit thru the hub * renamed the Subscriptions Yaml commit f13f6ec24f5b8c0f318cf66f6cd1a2f3c7a01534 Author: Barrington Willis <51492255+tredell@users.noreply.github.com> Date: Fri Mar 3 07:00:06 2023 -0800 Identity Archetype (#359) * Squashed commit of the following: commit 6d6b3e49855c365f49a4674534b985bacf9cd74c Author: Barry Willis Date: Mon Feb 27 08:07:45 2023 -0800 changed the areacode on the logging service health alerts architype commit 86b4505c2ffd5127978883c0bc6a1f9b0e7d3268 Author: Barry Willis Date: Fri Feb 24 16:39:08 2023 -0800 prepping for testing in ESLZ test environment commit 0f92b6bf70aee1377b4d49db436fa7024f1bfd25 Merge: 2a3584a 7749e7b Author: Barry Willis Date: Fri Feb 24 16:10:37 2023 -0800 Merge remote-tracking branch 'origin/main' into IdentityLZ commit 7749e7bf7a8756e3b2ffd09016e3e9d9954407db Merge: f6555a4 5337654 Author: Barry Willis Date: Fri Feb 24 16:08:54 2023 -0800 Merge remote-tracking branch 'github-CanadaPubSecALZ/main' commit f6555a41227fdbe47a6981798e2cb2bb97bd7cd6 Author: Barry Willis Date: Mon Feb 13 12:30:20 2023 -0800 Added the patch version to the AKS versions in the Data Archetypes commit 8edcb63d833fd177ede60c9a51b6228f448c0c33 Author: Barry Willis Date: Mon Feb 13 11:32:54 2023 -0800 Changed hte AKS version to only have the Major.Minor commit 37123d71623b7c6ed288a5ba32c7cab5f8e75e6f Author: Barry Willis Date: Mon Feb 13 11:17:38 2023 -0800 updated AKS version in the Data Archetypes commit 459b3c62751cb6bfedf2ddc5800ad39137417d38 Author: Barry Willis Date: Mon Feb 13 08:55:13 2023 -0800 changed the servcie health number prefix to 604 commit cccf88662c3a0e0d7b2f625a13ec191053017985 Author: Barry Willis Date: Mon Feb 13 07:42:52 2023 -0800 changed the invalid dummy service alert phone number to a valid phone number commit 8e9628d26e1285c437a6ec8a3ebd479299f3cb5f Author: Barry Willis Date: Mon Feb 13 07:01:36 2023 -0800 fixed linter warnings in policy files commit 6c2b2f7d2d53b97d0014306656406cf564189779 Author: Barry Willis Date: Sat Feb 11 15:36:36 2023 -0800 Commit 95556ddd: changed the extensionResourceId function to tenantResourceId for all built-in polify definitions commit c58ba48f5073c0b86b41c54fddca9cab0368b59a Author: Barry Willis Date: Sat Feb 11 15:09:56 2023 -0800 Fixed the AKS policy deployment commit f9e8418b7e1faf8cc8122acc9414e12c5bfbd22e Author: Barry Willis Date: Sat Feb 11 14:04:22 2023 -0800 Fixed Bug on policy defnition commit 1a3c82e446072db49d927343a4792e30bdb31f05 Author: Barry Willis Date: Fri Feb 10 19:09:02 2023 -0800 updated the linter rules commit 20e188051a8999d7a5e6ee925ec193f6e1d2dea6 Author: Barry Willis Date: Fri Feb 10 18:52:18 2023 -0800 fixed the remaining linter errors in the policy definitions commit 1610a28e355af15a86d8a555a97ca9912cc11aeb Author: Barry Willis Date: Fri Feb 10 18:27:14 2023 -0800 fixed the remaining linter warnings commit 9f0e049fa09e19f0cf312f4826520e1005e58434 Author: Barry Willis Date: Fri Feb 10 17:31:21 2023 -0800 fixed BCP321 warning commit 466d7b0c070f4bb4fef94b1fb9bac2f3da754c4a Author: Barry Willis Date: Fri Feb 10 17:22:46 2023 -0800 changed the pOlicyScopedId var to be set by using the MGResourceID Function commit 9362967e5006d9ec3882cdc5bec5aae5b872bf29 Author: Barry Willis Date: Fri Feb 10 16:48:26 2023 -0800 Fixed Role Definition Id References to use the ResourceId function commit 4bcbc28212ecac9bff2a8e3c720a9a364479733c Author: Barry Willis Date: Fri Feb 10 16:07:33 2023 -0800 Fixed BCP321 Linter warning in networking files commit 2a3584a7cac9c5822c7a226bc8a5d44f52d69a65 Author: Barry Willis Date: Fri Feb 10 15:07:43 2023 -0800 Removed Linter exception BCP321 - will fix in the linter PR commit a0b48ec7710a5ee8023a066e4cb5394074002c1e Author: Barry Willis Date: Fri Feb 10 10:39:36 2023 -0800 Fixed the bugs with conditionally deploying DNS Resolver commit 4f24be78f48465b404c529b276db66496c9958db Author: Barry Willis Date: Wed Feb 8 15:29:38 2023 -0800 Updated documentation and made the DNS Resolver subnets optional commit 03fcb5e50b0670c67d1850063dd828ffa6945cf8 Merge: dfe0d9a 0fa01e8 Author: Barry Willis Date: Mon Feb 6 16:58:41 2023 -0800 Merge remote-tracking branch 'origin/main' into IdentityLZ commit dfe0d9acab086df1d9dfbfbdae5770fbf5da999a Author: Barry Willis Date: Wed Jan 11 15:52:06 2023 -0800 added Schema validation to the identity config file commit fb88630b5d707db6b7f4ab1aa2455ff79920d5b3 Author: Barry Willis Date: Mon Jan 9 10:28:13 2023 -0800 changed the DNS Resolver ruleset to be an object-array commit 78aaf4d6cdeff8d9832d8a309f26c10cefe97a22 Author: Barry Willis Date: Sat Jan 7 13:57:37 2023 -0800 first pass at creating conditional forwarding rulesets in the Identity LZ commit e7b554d04daee83a55a985073ec0c59084c7f3c2 Author: Barry Willis Date: Fri Jan 6 08:54:27 2023 -0800 Configured Subnet Delegation for Az DNS Resolver commit 978ab9925f876945ba02280493f7deba1c07e7ee Author: Barry Willis Date: Thu Jan 5 19:52:24 2023 -0800 added Private DNS Resolver to the Identity LZ commit 9735d58fc04d7a587a76a5387deb112c466390fe Author: Barry Willis Date: Thu Jan 5 13:19:05 2023 -0800 Removed the optional Subnet commit 4cd57ed41a09672b3cfbc1792c2edbdc3569a060 Author: Barry Willis Date: Thu Jan 5 13:09:36 2023 -0800 first cut at the identity LZ framework commit a119eea02fca28a2028362f484aa2835c9313c1d Author: Barry Willis Date: Wed Dec 21 11:54:58 2022 -0800 added identitypathfromroot in the branch config file commit 75b6ccc2ab6efd55037e0a5a938d49f2eef32de4 Author: Barry Willis Date: Wed Dec 21 11:35:12 2022 -0800 Added: identity vars display Changed: location reference to identity param file commit e0cfc41b5a83c4c331689fcafa5edc9928e93d39 Author: Barry Willis Date: Wed Dec 21 11:22:35 2022 -0800 fixed misconfigured working directory commit fb58b16999aeb9cc6b6b81647c76e95024e1267c Author: Barry Willis Date: Wed Dec 21 11:18:46 2022 -0800 removed schema validation to test deployment commit 240189de7e30fa57654c3ec76ec37c762ff80133 Author: Barry Willis Date: Wed Dec 21 11:15:43 2022 -0800 fixed bug - neworking region is now identity region commit 89e63b5976cb5cdc4e85d0b25c01234ffe4853d7 Author: Barry Willis Date: Wed Dec 21 11:11:48 2022 -0800 initial identity lz deployment commit d4b40b26b893b78d7a9250dffe24c3e9ce06d690 Author: Barry Willis Date: Wed Dec 21 11:03:29 2022 -0800 Added default region for Identity Subscription commit 41e611818d09181b1a455f612425cae20f0683f7 Author: Barry Willis Date: Wed Dec 21 08:29:33 2022 -0800 Changed bastion subnet range in identity subnet commit f5a43f2d44803e80db8a043d31e5c9f72fc51675 Author: Barry Willis Date: Wed Dec 21 07:33:03 2022 -0800 Param file for Identity LZ commit 13d084b0fe74f39ca1423b2eb9f333a2b760b1f2 Author: Barry Willis Date: Tue Dec 20 15:19:23 2022 +0000 Deleted identity.parameteres.json commit 5ba9a12fa8e8e02f60f3f2afea43681cc84d7446 Merge: 002b2be e395307 Author: Barry Willis Date: Tue Dec 20 07:18:40 2022 -0800 Merge branch 'IdentityLZ' of https://dev.azure.com/Tredell/CanadaALZ/_git/CanadaALZ into IdentityLZ commit 002b2be1bb5b555a334f35cbb505e7a68f321649 Author: Barry Willis Date: Tue Dec 20 07:18:32 2022 -0800 id-lz - created param section for id lz commit e395307b1c12786cc28cf3d4b00586dde69739d5 Author: Barry Willis Date: Tue Dec 20 07:13:54 2022 -0800 id-lz - created param section for id lz commit 7f4a43eb4fdc7f6f37ebab8e661981cccbee9f50 Author: Barry Willis Date: Mon Dec 19 14:54:57 2022 -0800 disabled privatelink infrastructure to be deployed in hub lz commit db85049ac94b5c394d586b6960343bc1286997f1 Author: Barry Willis Date: Mon Dec 19 14:46:36 2022 -0800 Configured hub networking parameter files commit 8d772e868803d1b712013f7db21044d48ab730d2 Author: Barry Willis Date: Mon Dec 19 14:07:43 2022 -0800 removed comment from json - not supported commit 89cde8d92704f1a41a123af46da6dd90568d99cb Author: Barry Willis Date: Mon Dec 19 12:56:47 2022 -0800 Configuring Policies for deployment to Test enviornment commit ba781ee844a4abd403071e072645988b63ada494 Author: Barry Willis Date: Mon Dec 19 12:40:53 2022 -0800 added a default security Group commit 1269da21e08fdf4c29a53b38a4d18722c64461e0 Author: Barry Willis Date: Mon Dec 19 12:26:14 2022 -0800 setting up logging for my test environment commit 4d6a41f4133380223f5895dba270cbce4ae5a39b Author: Barry Willis Date: Mon Dec 19 12:13:08 2022 -0800 testing the path to the logging configuraiton file commit 75d0b99caf6aed5f809c28566cad35569d78be58 Author: Barry Willis Date: Mon Dec 19 12:00:14 2022 -0800 added the full path to the logging parameters file commit 32e8382bcb8deaaaab0c7bc1c2791483ef439971 Author: Barry Willis Date: Mon Dec 19 11:55:00 2022 -0800 path to logging parameters file was incorrect commit 5757d36a486e7f3b707f00848d19cfe64de83358 Author: Barry Willis Date: Mon Dec 19 11:37:20 2022 -0800 Changed MG Root to match test enviornment commit 1fdd02db1638420decf5ab021fb617b95920aada Author: Barry Willis Date: Mon Dec 19 11:09:46 2022 -0800 Adding config file for IdentityLZ branch * PowerShell Deployment Files created * GitHub Action Pipelines modified to add the Identity Archetype * made the Identity GitHub Action optional * put the boolean option in single quotes * fixed a few bugs (BCP321 & references to the wrong tenant) * changed the sub id for the logging subscription * Removed the hardcoded reference to the LAW in the identity param file * updated the param file with the LAW ID * disabled private dns zone deployment in the identity sub * removed the config files from my custom branch * uncommented the validation in the Identity ADO Pipeline * removed commented trigger code from ADO Identity Pipeline * renenabled the dployment of the DNSPrivateEndPoints policyset * removed the provider registration for containerservices in the deploy-identity-pipeline yaml * added an explanation comment to the dnsforwardingruleset file * Added telemetry tracking for the identity subscription * fixed cut and paste errors * Updated test cases & documentation * added the consistency check & pull request checks for github actions * fixed spelling error commit 533765439f98250eccbbccc194f82309ff4be9ec Author: Barrington Willis <51492255+tredell@users.noreply.github.com> Date: Fri Feb 24 12:57:36 2023 -0800 Fixed Linter warnings & build errors (#354) * Fixed BCP321 Linter warning in networking files * Fixed Role Definition Id References to use the ResourceId function * changed the pOlicyScopedId var to be set by using the MGResourceID Function * fixed BCP321 warning * fixed the remaining linter warnings * fixed the remaining linter errors in the policy definitions * updated the linter rules * Fixed Bug on policy defnition * Fixed the AKS policy deployment * Commit 95556ddd: changed the extensionResourceId function to tenantResourceId for all built-in polify definitions * fixed linter warnings in policy files * changed the invalid dummy service alert phone number to a valid phone number * changed the servcie health number prefix to 604 * updated AKS version in the Data Archetypes * Changed hte AKS version to only have the Major.Minor * Added the patch version to the AKS versions in the Data Archetypes commit 0fa01e8b7b4320d3d9d50a38d044cdff5da1a3c6 Author: Luke Murray <24467442+lukemurraynz@users.noreply.github.com> Date: Tue Feb 7 12:26:03 2023 +1300 Updated documents, from docs.microsoft.com - to Learn. (#350) Updated documents, from docs.microsoft.com - to Learn. commit e44c7eabf85bb4d5ec526c8f4229dbc31b282ed3 Author: Obay Date: Wed Nov 30 19:14:57 2022 -0800 Update hubnetwork-azfw.md (#345) Having domain controllers under the "Connectivity" subscription is an anti-pattern that causes confusion to users. Co-authored-by: Barrington Willis <51492255+tredell@users.noreply.github.com> commit 12cd557bc479041ee6fca7f76c7fe1e4c17c7e74 Author: Steve Keeler Date: Wed Nov 30 21:27:08 2022 -0500 Add Barry to code owners list (#346) commit c714e65b81d4bf5048bcf56351534a8be26c5c0c Author: Steve Keeler Date: Fri Oct 14 15:48:33 2022 -0400 Update CODEOWNERS (#344) Adding Barry Willis and Kevin Evans to the CODEOWNERS file for the entire repo commit b8a9bc91168f5afe9cb4c6ea35148714c11b4761 Author: Steve Keeler Date: Thu Sep 1 15:31:28 2022 -0400 Version August 2022 schema changes (#342) commit 5851a09acff454df0bb8bbb2d6406fcd9a8efb6d Author: Senthuran Sivananthan Date: Wed Aug 17 18:50:15 2022 -0400 Revised Event Hub Diagnostic Settings policy (#339) commit e5fe39930e55ae9cb62745499d1a520a098693df Author: Senthuran Sivananthan Date: Wed Aug 17 18:37:43 2022 -0400 Update diagnostic settings profile name (#337) commit db52627fe3769b7430c99be757f9761238b27adc Author: Senthuran Sivananthan Date: Wed Aug 17 18:17:12 2022 -0400 Suppress false positive linter warning: secure-secrets-in-params (#335) commit 2a6042d38ccd04844d9cc445e0a95ead182e5a6b Author: Senthuran Sivananthan Date: Wed Aug 17 17:59:13 2022 -0400 Network security group support for private endpoints subnet (#333) commit e069a4b6ac4f5be8d7614eeb5a67d0cfb3534e52 Author: Senthuran Sivananthan Date: Wed Aug 17 17:28:39 2022 -0400 Support data collection rule (#331) commit c2afa0d99717c56bacc211cfb5ed13234880d9a1 Author: Senthuran Sivananthan Date: Mon Aug 8 15:42:22 2022 -0400 Support azkms.core.windows.net and IPs in firewall allow list (#329) commit a7f521dcf919114a9441296407fc4dd06be46927 Author: Senthuran Sivananthan Date: Tue Jul 19 23:31:56 2022 -0400 Add missing log categories in diagnostic settings for Azure Firewall (#324) commit 60198bc19eb4d87d0bbebc24d4c2fe240d2297ab Author: Senthuran Sivananthan Date: Tue Jul 19 23:11:10 2022 -0400 Resolve linter warning: prefer-unquoted-property-names (#322) commit a4e53fffe4b1f2a2fdbf25ec92a181ef625dd240 Author: Sabyasachi Dasgupta Date: Mon Jul 18 16:44:01 2022 -0400 Update machinelearning.md (#327) commit 8fc587a6bf2e53e516ded633d96c652874ab5875 Author: Ifyagolu <55541295+Ifyagolu@users.noreply.github.com> Date: Fri Jun 24 17:05:28 2022 -0400 Fix typo in onboarding guidance (#320) commit e9a0962b7db12c5438782d2597afd494de5354b2 Author: Islam Gomaa Date: Fri May 27 16:13:52 2022 -0400 Reference the Guardrails Solution Accelerator for 30-day guardrail assessment (#313) commit 2b11801386654f6b3f68bd63c887d74ec7a4fdb8 Author: Senthuran Sivananthan Date: Thu May 19 10:38:55 2022 -0400 Add service health notification info (#310) commit bce747c9fdc96c2be78881a4dc9276351ff40b64 Author: Senthuran Sivananthan Date: Wed May 18 09:29:03 2022 -0400 Update resource group names for Logging & Networking (#309) Remove `-rg` suffix commit 6765c48680e47ccc380ab0df929e3cd1af4f8a5b Author: Senthuran Sivananthan Date: Tue May 17 15:14:33 2022 -0400 Serial defender plan deployments & revised resource/resource group names (#307) commit 62adb00d6a8561030b39272f1d710c2a4e0cfcba Author: Senthuran Sivananthan Date: Mon May 16 13:53:37 2022 -0400 Log Analytics solutions for SQL servers on machines (#303) commit c1a3b99c969f802d8325245387b617f21bc0c921 Author: Senthuran Sivananthan Date: Mon May 16 09:26:47 2022 -0400 Flexible policy deployment using PowerShell & GitHub Actions (#300) commit 0ce5c1ac9ef8ff728a19e608bf8bd3654b453cbb Author: Senthuran Sivananthan Date: Sun May 15 12:19:01 2022 -0400 Disable fail fast for matrix deployments (#297) commit c078a797d9be10bf1b2dc7bed01957637ddb73ea Author: Senthuran Sivananthan Date: Sun May 15 11:19:43 2022 -0400 Concurrent role deployment with PowerShell & GitHub Actions (#299) commit 31a214abbf65c10b106962b1493a1830e37f9702 Author: Senthuran Sivananthan Date: Sun May 15 10:39:08 2022 -0400 Disable metrics in diagnostic settings for AKS through Policy (#295) commit 6a90a2fe9d881730a32303fe6a10d1bbcc22f943 Author: Senthuran Sivananthan Date: Wed May 11 10:56:26 2022 -0400 Separate Azure Firewall Policy deployment switch & unique telemetry tracking for policy assignments (#289) commit c4133077e1d97a6beaa6e4811588236912d5c768 Author: Senthuran Sivananthan Date: Tue May 10 16:46:06 2022 -0400 Ensure multiple subscriptions can be moved to a management in parallel (#288) Ensure deployment name for moving subscription is unique commit 93d2f13847d56c195e2c170d314a3bbc5cfe5c63 Author: Senthuran Sivananthan Date: Tue May 10 14:53:18 2022 -0400 Support jobs in GitHub Actions (#286) commit 31e8d0ab602bfcf856c9134666eb4814817d6964 Author: Steve Keeler Date: Tue May 10 12:30:36 2022 -0400 Correct wiring of the subscriptions-ci pipeline and prompt for NVA firewall username & password (#285) commit 229b14466384252ba034546095f5c21a932cb6fc Author: Steve Keeler Date: Mon May 9 20:41:06 2022 -0400 Fix DeploySubscriptionIds parameter type casting (#282) commit 799ad52d778ebbc4fc4ed53d56c872d56ab2fc29 Author: Senthuran Sivananthan Date: Mon May 9 20:10:33 2022 -0400 Pass-thru secure strings as-is until ready for use (#281) commit a9c941948d51c59c758d07bce702bcb36aee70ec Author: Steve Keeler Date: Mon May 9 17:11:12 2022 -0400 Add environment configuration override and protect sensitive parameters (#280) commit ce6c27f4e02cf194b3b13574c2caf4b60f8e8205 Author: Senthuran Sivananthan Date: Mon May 9 11:23:57 2022 -0400 Support schema validation (#277) commit 1d8dbd7bafc62b402719fb187698cfd950e8e3df Author: Steve Keeler Date: Mon May 9 08:07:26 2022 -0400 GitHub workflow implementation (#276) Implement GitHub workflows to deploy the Azure Landing Zones for Canadian Public Sector commit 08d8f9256aaf3236a6920abe67e7d58b95887a0c Author: Senthuran Sivananthan Date: Mon May 2 16:03:02 2022 -0400 Deployment flow diagram (#274) commit db098e17a13f111c18aa3af33c81f1cb54979cd1 Author: Senthuran Sivananthan Date: Fri Apr 29 22:37:58 2022 -0400 Powershell deployment script for archetypes (#273) Support for deploying subscriptions commit 15c2847a4255108680937da0192d54ccc2d7f16c Author: Senthuran Sivananthan Date: Fri Apr 29 16:29:22 2022 -0400 PowerShell deployment scripts (#271) commit 352257187e7d03bf5abade4a18302bdd310ab82c Author: Senthuran Sivananthan Date: Wed Apr 27 18:10:23 2022 -0400 Snapshot ARM parameters JSON schemas (#268) commit 60f3b59013e27c549e2d57bd16fba2ea26bf12b5 Author: Senthuran Sivananthan Date: Wed Apr 27 17:29:58 2022 -0400 Organize deployment parameters for Hub Networking with NVA (#266) commit 926521a1c01ab420ccaa319d47516a2870cf3a15 Author: ghostme Date: Wed Apr 27 15:20:08 2022 -0400 Updated documentation (#267) commit d68824a2eed32c62cc199f374ba15ea732025241 Author: Senthuran Sivananthan Date: Mon Apr 25 14:32:25 2022 -0400 Organize deployment parameters for Hub Networking with Azure Firewall (#265) commit 2bc196a0960bfecb9c545226000c5c34dbbabec8 Author: Senthuran Sivananthan Date: Mon Apr 25 14:03:31 2022 -0400 Support for optional subnets in Machine Learning & Healthcare archetypes (#264) commit b33cd36261fd797834cdcbeebe53ce1262ef21ac Author: Senthuran Sivananthan Date: Thu Apr 21 09:32:43 2022 -0400 Update common.yml example (#262) commit 300835322afd2d85f34aa8b8ff5921d3839c2e6c Author: Senthuran Sivananthan Date: Wed Apr 20 12:44:45 2022 -0400 Removed extra configuration files (#260) commit 1ee5b9e736feca7270c4ad62d27c4366751f1cab Author: Senthuran Sivananthan Date: Wed Apr 20 11:56:14 2022 -0400 Revise subnet configuration for Healthcare archetype (#256) commit 72fe50db665710eabc8e6edffae5d658d0497822 Author: Senthuran Sivananthan Date: Wed Apr 20 11:43:09 2022 -0400 Revise subnet configuration for Machine Learning archetype (#254) commit 70833771ac433d5de7950423dd8085777bfb03be Author: Senthuran Sivananthan Date: Wed Apr 20 11:38:07 2022 -0400 Revise subnet configuration for Generic Subscription archetype (#252) commit 3d9c60d251a98b2ebc400aadb2c452f3f6262712 Author: Senthuran Sivananthan Date: Wed Apr 20 11:30:10 2022 -0400 Migrate Networking configuration to JSON parameters file (#250) commit 38fc344508cd6b4707aac0fca2e0cf3e8609a882 Author: Mohamed Sharaf Date: Wed Apr 20 10:29:52 2022 -0400 Azure Active Directory support for Synapse (#259) commit 89613dbc876831f543f2749cbe6f804278a65612 Author: Senthuran Sivananthan Date: Tue Apr 12 21:31:06 2022 -0400 Include new Databricks' log categories for diagnostic settings (#248) Add new databricks' log categories for diagnostic settings commit 700eb9645cbde1435bdda80b28faa03a52dee671 Author: Senthuran Sivananthan Date: Tue Apr 12 17:33:12 2022 -0400 Support multiple private dns zone configuration when updating private DNS Zones through Azure Policy (#246) Update Private DNS Zone policy to support multiple dnsZoneConfigs commit 1c3727990cc12a401c0ecebdbf31234d71c472ab Author: Senthuran Sivananthan Date: Mon Apr 11 11:24:00 2022 -0400 Support logging infrastructure for multiple regions in same subscription (#244) Ensure subscription scoped deployments are unique per region commit 0e258f96cd99c622665d382d73aeba1e78f52319 Author: Steve Keeler Date: Sat Apr 9 13:50:50 2022 -0400 Update azure-devops-pipelines.md (#242) commit bfe1f588adc59922145fcf9a47c19173130cf321 Author: Senthuran Sivananthan Date: Fri Apr 8 11:31:52 2022 -0400 Migrate Logging configuration to JSON parameters file (#236) commit cc5f017b01e06331d4246d5fc0286cf50d525470 Author: Senthuran Sivananthan Date: Fri Apr 8 10:26:12 2022 -0400 PBMM & HITRUST/HIPAA policy update (#238) commit 3259994f47c482153368a9fb115ce60b9e3488fb Author: Steve Keeler Date: Tue Apr 5 14:41:17 2022 -0400 Fix order of `platform-connectivity-hub-azfw-policy` pipeline listed in run-pipelines.bat script #233 (#234) commit cb96311bf94224c1cf94470320c9c8fec029e165 Author: ccmsft <98336965+ccmsft@users.noreply.github.com> Date: Mon Apr 4 09:39:17 2022 -0400 Updating recommendations to reflect licensing reqs (#229) commit 3ce2cf875b5d6c9464a0262f183a37f40399f8dd Author: Senthuran Sivananthan Date: Fri Apr 1 22:49:44 2022 -0400 Use built-in policy for Cosmos DB for Defender Plan (#232) * Use built-in policy for Cosmos DB for Defender Plan * Add branch config * Remove branch config commit d2f959a2550b694d79fb0aa6d1a9d2b8166090c8 Author: ghostme Date: Fri Apr 1 10:05:21 2022 -0400 Update networking documentation for generic subscription archetype (#230) commit 575440e4c629b1c00686ba62e5911749375832ff Author: ccmsft <98336965+ccmsft@users.noreply.github.com> Date: Wed Mar 30 23:36:35 2022 -0400 Initial GC 30-day cloud guardrails compliance/guidance (#226) Initial GC 30-day cloud guardrails doc commit 6b36096f2356255a967a7d9cd14dd04a5dc3b6ce Author: Senthuran Sivananthan Date: Wed Mar 30 22:40:17 2022 -0400 Externalize Log Analytics Workspace parameters when loading pipeline variables (#220) Externalize the log analytics parameters to load arbitary LAW variables commit 0210df4fd3a11dfcaee3a82f2da1e2315bf70400 Author: Senthuran Sivananthan Date: Wed Mar 30 21:51:30 2022 -0400 Flexible policy assignment parameters JSON files (#222) commit f25f95781d6f9f3c2169bbe4b148c3b748a6ac93 Author: Senthuran Sivananthan Date: Wed Mar 30 20:57:07 2022 -0400 Private DNS Policy - Change Cosmos DB namespace to Microsoft.DocumentDB (#228) * Change Cosmos DB namespace to Microsoft.DocumentDB * Add branch config * Remove branch config commit 453a0f8bc78dbf7a78c46d01f0cde28b3ab2bbaa Author: Steve Keeler Date: Wed Mar 30 19:00:07 2022 -0400 Improve `delete-management-groups.bat` script (#224) commit 2e5a56b04fd25149da78e77f396073945ba785f5 Author: Senthuran Sivananthan Date: Thu Mar 24 09:02:36 2022 -0400 Fix formatting (#218) commit bf5e94bcdee854db8fde7a8eb60d7886bc2c2191 Author: Senthuran Sivananthan Date: Wed Mar 23 23:01:02 2022 -0400 Add instructions for customizing policy set assignments (#215) commit 0538d4d7d8765fcd558c99fdbf7aa7d6655c8b95 Author: Senthuran Sivananthan Date: Wed Mar 23 22:57:00 2022 -0400 Document delete lock usage (#216) Document when and where delete locks are used commit 789b18a888290ada72d8fe2328097429ee9823d6 Author: Senthuran Sivananthan Date: Wed Mar 23 22:49:24 2022 -0400 Update OZ subnet name to App Management Zone (#217) commit 97c2904a773f94adf26cd52924f0dfccab985cdf Author: Senthuran Sivananthan Date: Fri Mar 11 21:59:40 2022 -0500 Backward compatibility when setting pipeline variables from management group hierarchy (#213) commit 30b9cc2060e96dd99b12743bb4c959181a403e91 Author: Adil Ha Date: Fri Mar 11 11:26:31 2022 -0500 fixing doc typo in hubnetwork-azfw (#211) Co-authored-by: Adil Ha commit 27363b730f34536fbf7f9994e08da7aa5af3c58e Author: Senthuran Sivananthan Date: Sat Mar 5 13:04:13 2022 -0500 Support Defender Plan for Cosmos DB (#200) Add CosmosDB Defender Plan and custom policy to deploy Defender Plan for Cosmos DB commit 81eccd1d54956f7c7addb2a969ebb3e62e99b588 Author: Senthuran Sivananthan Date: Sat Mar 5 12:48:45 2022 -0500 Delete Lock for Log Analytics Workspace resource group (#205) Add delete lock for LAW RG commit 678355f149698ecfdab6d10669e631702f1d9d49 Author: Steve Keeler Date: Sat Mar 5 11:03:46 2022 -0500 Fix pipeline scripts reference to `subscription-ci` (#207) commit 5753cf0e35a9f921c4cb59ec90db787e26d6d400 Author: Senthuran Sivananthan Date: Thu Mar 3 14:44:31 2022 -0500 Ensure values from multiline variables are properly logged (#202) Print multi-line environment variables (typically JSON objects) in Show Variables step commit d6b1c08fec1a96c332cf5abb758b16cd8bfede87 Author: Senthuran Sivananthan Date: Thu Mar 3 14:09:47 2022 -0500 Revise subscription deployment instructions (#201) * Redirect subscriptoin configuration guidance to archetype authoring guide doc * Revise instructions for creating ARM parameter files & management group id selection commit 5e7322ee0b64ffa379e1ac546972796a76407db7 Author: Senthuran Sivananthan Date: Wed Mar 2 08:22:35 2022 -0500 Instructions for backfilling management group hierarchy (#197) * Add instructions for backfilling management group hierarchy * Update section titles, links and reference backfill instruction as part of MG setup * Instructions for installing AzCLI and jq * Clearfy that Tenant Root Group could have been renamed in the organization * Windows Shell example * Update instructions to delete pipeline variables that will be automatically created when MG heirarchy is used * Note on YAML indentation commit 5d33909d70f821039df0deab2d26a5d180d7a16c Author: Preston K. Parsard Date: Tue Mar 1 10:46:04 2022 -0500 subscription(generic): add instructions for configuring parameters (#193) commit 17846c4959c5156dee905736e3631fa56193d9e7 Author: Steve Keeler Date: Sun Feb 27 20:30:20 2022 -0500 Show Variables fix (#191) commit c62dcfcd5862ae15196000e0fd481d214081c817 Author: Steve Keeler Date: Sun Feb 27 16:50:20 2022 -0500 Configurable management group hierarchy (#186) Implement configurable management group hierarchy commit 9a141f7e5bf238f21838898ff908b6fc7f6d8fcc Author: Preston K. Parsard Date: Sat Feb 26 19:45:35 2022 -0500 Update onboarding document Co-authored-by: Preston K. Parsard commit 6b6ef29fd266fe0b2c23fed5f1bf6cc3fdb5e4a8 Author: Senthuran Sivananthan Date: Sat Feb 26 18:22:48 2022 -0500 Snapshot JSON schemas to v0.4.0 (#182) commit 4dd1f4a901fbd44c54a32fdf9ac23f5ca5bed736 Author: Senthuran Sivananthan Date: Wed Feb 23 15:39:43 2022 -0500 Update onboarding doc for logging & networking management group settings (#177) * Fix markdown linter warnings * Add instruction for logging and networking MGs commit 5d7eec3a319524b5ded5f32e6db951566c365ffc Author: Steve Keeler Date: Wed Feb 23 12:51:20 2022 -0500 Update `create-pipelines.bat` onboarding script to auto-provision environment (#178) commit 488fc6e767639f3acd00a2dea11a8f2a6476379e Author: Senthuran Sivananthan Date: Tue Feb 22 09:05:20 2022 -0500 Instructions for Azure DevOps Environments (#175) * Instructions for creating ADO pipeline environments * Fix formatting commit edabd873d42a622fc5d1503c099c514bb4f2bd7f Author: Senthuran Sivananthan Date: Thu Feb 17 23:29:42 2022 -0500 Support for Tag inheritance from Subscription to Resource Group (#161) * Add policy and policy set to inherit tags from subscription to resource group * Add branch config for testing * Remove policy type as it's not built in * Updated resource type for resource group * Update policy assignment * Ensure assignment name is <= 24 chars * Revert resource group type * Setting mode to all * Update documentation * Add branch config * Add explicit dependsOn for subscription scaffolding to complete * Update test deployment parameters * Remove explicit dependsOn for subscription scaffolding to complete * Update doc to describe approaches for adding tags to RGs * Reduce the options for tagging resources given subscripton to RG tagging is available * Add example scenarios for tag inheritence * Fix typo * Remove branch configs * Resolve linter error: no-loc-expr-outside-params commit e71ed265f2267d35cd36d30bab217f9ecbb6891c Author: Senthuran Sivananthan Date: Wed Feb 16 20:09:19 2022 -0500 Linter: no-loc-expr-outside-params - ensure compliance (#169) * Update linter rules for location parameter * Add location parameter with default value based on resourceGroup() or deployment() * Update archetype schema and docs for location * Add branch config for testing * Update AKS version * Update branch config * Remove branch configs commit 6061fa0b930200d73e906e0bedefafeb35e43296 Author: Senthuran Sivananthan Date: Thu Feb 10 16:49:42 2022 -0500 Repository clean up (#165) * Remove obsolete directory * Rotate resource group names for E2E deployments * Fix typo * Add branch config for testing * Fix typo * Remove branch configs * Remove timestamp from sample JSON templates. Timestamps are kept for E2E testing. * Remove date stamp commit 5104f393a618a0f0f7072100fd810df4534a3210 Author: Steve Keeler Date: Thu Feb 10 09:08:17 2022 -0500 Update DevOps Onboarding section of main readme (#162) commit 209f61cf72ac91555f8b2171dcf84c6daae6a7cc Author: Senthuran Sivananthan Date: Thu Feb 10 09:06:31 2022 -0500 Update Deployment Script's Azure CLI version to 2.32.0 (#164) Update Azure CLI version to 2.32.0 commit d7d52570c8dce3ed8bcc3b809191d1cd2ddf5e3f Author: Steve Keeler Date: Mon Feb 7 13:51:17 2022 -0500 Issue #157 - Update scripts documentation (#158) Update scripts documentation (Issue #157) Update docs/onboarding/azure-devops-scripts.md Co-authored-by: Senthuran Sivananthan commit b628c68ff84bb5b8796d6821161450010d19ce3b Author: Senthuran Sivananthan Date: Fri Feb 4 12:42:31 2022 -0500 Enhance PBMM policy assignment to disable diagnostic settings metrics (#156) Ensure diagnostic settings policy only checks for logs commit 61afd59bb6d7f6c2a37518d41c64ced985cafd92 Author: Senthuran Sivananthan Date: Mon Jan 31 12:52:09 2022 -0500 Snapshot landing zone schema to v0.3.0 (#152) commit 09f09ede5613cf600441616831f762595aecdbed Author: Steve Keeler Date: Mon Jan 31 09:20:20 2022 -0500 Automation scripts for Azure DevOps onboarding (#151) Implement #150, scripts and documentation commit 82dd82606059a6643d7de294cb1f15afab41cd94 Author: SlavaRoikhman <52217047+SlavaRoikhman@users.noreply.github.com> Date: Thu Jan 27 13:32:41 2022 -0500 Removed 'privatelink.monitor.azure.com' from Private DNS Zones (#149) commit 73ce2eb316175f1bf86135010d5f35ce9bbc6da7 Author: Senthuran Sivananthan Date: Fri Jan 21 23:23:45 2022 -0500 Flexible policy assignment scope (#147) * Add deployment scope for policy assignment * Add branch test config * Set new parameter for policy assignment scope: var-policyAssignmentManagementGroupId * Update pipeline for new var * Add separate scope for testing * Update pipeline parameter name * Ensure new temp file is created to populate the parameters. * Remove test job * Remove branch config * Update readme * Update authoring guide with new parameter commit c71051b21804f0b069acc02718ced57840863e86 Author: hudua <40040433+hudua@users.noreply.github.com> Date: Fri Jan 21 14:21:08 2022 -0500 Private Endpoint for App Service (#144) commit fff245db0c7f94221ce73404a2c5fb1a9ad44207 Author: Senthuran Sivananthan Date: Fri Jan 21 10:51:43 2022 -0500 Diagnostic Settings Policies for PaaS services (#143) * Add diagnostic settings policies for data services * Add branch config for testing * Add missing types for auditing * Add diagnostic setting policies for compute services * Add diagnostic setting policies for integration services * Add diagnostic setting policies for network services * Remove policy for ACI since it doesn't have logs to collect * Remove extra resource type * Set region to 'global' for edge services * Remove branch config. used for testing * Updated App Service log categories * Add branch config * Remove branch config --- .github/CODEOWNERS | 2 +- .github/workflows/0-everything.yml | 35 +- .github/workflows/1-management-groups.yml | 2 +- .github/workflows/2-roles.yml | 2 +- .github/workflows/3-logging.yml | 2 +- .github/workflows/4-policy.yml | 2 +- .github/workflows/5-azure-firewall-policy.yml | 2 +- .../5-hub-network-with-azure-firewall.yml | 2 +- .github/workflows/5-hub-network-with-nva.yml | 2 +- .github/workflows/6-identity.yml | 46 ++ .github/workflows/7-subscriptions.yml | 84 +++ .github/workflows/README.md | 3 +- .github/workflows/consistency-check.yml | 9 + .github/workflows/pull-request-check.yml | 9 + .gitignore | 1 + .pipelines/platform-identity.yml | 64 ++ .pipelines/policy.yml | 2 +- .../templates/jobs/trigger-subscriptions.yml | 6 +- .../steps/deploy-platform-identity.yml | 83 +++ .pipelines/templates/steps/show-variables.yml | 6 + README.md | 9 +- SECURITY.md | 2 +- .../web/appservice-linux-container.bicep | 2 +- .../compute/web/appservice-linux.bicep | 2 +- azresources/containers/acr/acr-with-cmk.bicep | 2 +- azresources/containers/aks/main.bicep | 4 +- azresources/data/sqldb/sqldb-with-cmk.bicep | 4 +- .../data/sqldb/sqldb-without-cmk.bicep | 4 +- azresources/data/sqlmi/sqlmi-with-cmk.bicep | 4 +- .../data/sqlmi/sqlmi-without-cmk.bicep | 4 +- .../network/dns-forwarding-ruleset.bicep | 57 ++ .../network/dnsresolver-vnet-link.bicep | 26 + azresources/network/dnsresolver.bicep | 73 ++ azresources/network/vnet-peering.bicep | 4 + .../security/key-vault-key-rsa2048.bicep | 2 +- .../service-health-caller-params.json | 4 +- ...r-usage-attribution-management-group.bicep | 2 +- ...mer-usage-attribution-resource-group.bicep | 2 +- ...tomer-usage-attribution-subscription.bicep | 2 +- .../customer-usage-attribution-tenant.bicep | 2 +- bicepconfig.json | 120 +++- .../identity.parameters.json | 187 +++++ .../logging.parameters.json | 149 ++++ .../azure-firewall-policy.parameters.json | 22 + .../hub-azfw/hub-network.parameters.json | 231 ++++++ .../hub-nva/hub-network.parameters.json | 318 +++++++++ ...0d7a360_machinelearning_canadacentral.json | 298 ++++++++ ...10cc2b79b6c7_healthcare_canadacentral.json | 200 ++++++ ...05_generic-subscription_canadacentral.json | 192 +++++ ...d9ea82c_machinelearning_canadacentral.json | 0 ...f3f1a61_machinelearning_canadacentral.json | 207 ++++++ ...5e16f12_machinelearning_canadacentral.json | 235 +++++++ config/telemetry.json | 1 + config/variables/CanadaPubSecALZ-main.yml | 78 +++ config/variables/common.yml | 1 + docs/archetypes/authoring-guide.md | 34 +- docs/archetypes/generic-subscription.md | 12 +- docs/archetypes/healthcare.md | 38 +- docs/archetypes/hubnetwork-azfw.md | 32 +- docs/archetypes/hubnetwork-nva-fortigate.md | 22 +- docs/archetypes/identity.md | 342 +++++++++ docs/archetypes/logging.md | 28 +- docs/archetypes/machinelearning.md | 36 +- docs/architecture.md | 92 +-- docs/gc-30-day-cloud-guardrails.md | 364 +++++----- .../media/architecture/archetype-identity.jpg | Bin 0 -> 140799 bytes docs/onboarding/azure-devops-pipelines.md | 267 +++++-- docs/onboarding/azure-devops-scripts.md | 22 +- docs/onboarding/azure-devops-setup.md | 40 +- docs/onboarding/configuration-scripts.md | 663 ++++++++++++++++++ docs/policy/authoring-guide.md | 32 +- docs/policy/readme.md | 28 +- .../lz-generic-subscription/main.bicep | 2 +- .../lz-generic-subscription/networking.bicep | 3 +- landingzones/lz-healthcare/main.bicep | 2 +- landingzones/lz-healthcare/networking.bicep | 7 +- landingzones/lz-machinelearning/main.bicep | 2 +- .../lz-machinelearning/networking.bicep | 3 +- .../azfw-policy/azure-firewall-policy.bicep | 12 +- .../main-azfw-policy.bicep | 2 +- .../main.bicep | 6 +- .../mrz/mrz.bicep | 1 + .../main.bicep | 6 +- .../mrz/mrz.bicep | 1 + .../lz-platform-identity/dnsResolver.bicep | 112 +++ landingzones/lz-platform-identity/main.bicep | 340 +++++++++ .../lz-platform-identity/networking.bicep | 404 +++++++++++ landingzones/lz-platform-logging/main.bicep | 6 +- landingzones/scaffold-subscription.bicep | 4 +- management-groups/structure-v2.bicep | 2 +- management-groups/structure.bicep | 2 +- policy/builtin/assignments/asb.bicep | 2 +- policy/builtin/assignments/cis-msft-130.bicep | 2 +- .../assignments/fedramp-moderate.bicep | 4 +- .../builtin/assignments/hitrust-hipaa.bicep | 12 +- policy/builtin/assignments/location.bicep | 6 +- policy/builtin/assignments/nist80053r4.bicep | 4 +- policy/builtin/assignments/nist80053r5.bicep | 4 +- policy/builtin/assignments/pbmm.bicep | 6 +- policy/custom/assignments/AKS.bicep | 16 +- policy/custom/assignments/DDoS.bicep | 8 +- .../assignments/DNSPrivateEndpoints.bicep | 8 +- .../custom/assignments/DefenderForCloud.bicep | 9 +- policy/custom/assignments/LogAnalytics.bicep | 12 +- policy/custom/assignments/Network.bicep | 6 +- policy/custom/assignments/Tags.bicep | 16 +- policy/custom/definitions/policyset/AKS.bicep | 6 +- .../definitions/policyset/AKS.parameters.json | 4 +- .../DNSPrivateEndpoints.parameters.json | 9 + .../policyset/DefenderForCloud.bicep | 38 +- .../DefenderForCloud.parameters.json | 4 +- .../definitions/policyset/LogAnalytics.bicep | 48 +- .../definitions/policyset/Network.bicep | 2 +- roles/la-vminsights-readonly.bicep | 2 +- roles/lz-appowner.bicep | 4 +- roles/lz-netops.bicep | 4 +- roles/lz-secops.bicep | 4 +- roles/lz-subowner.bicep | 4 +- .../landingzones/lz-platform-identity.json | 455 ++++++++++++ schemas/latest/readme.md | 12 +- schemas/v0.5.0/readme.md | 12 +- schemas/v0.6.0/readme.md | 12 +- .../configuration/Connect-AlzCredential.ps1 | 70 ++ .../configuration/Get-AlzConfiguration.ps1 | 53 ++ .../configuration/Get-AlzSubscriptions.ps1 | 55 ++ .../configuration/Install-Prerequisites.ps1 | 14 + .../configuration/New-AlzConfiguration.ps1 | 473 +++++++++++++ scripts/configuration/New-AlzCredential.ps1 | 130 ++++ scripts/configuration/New-AlzDeployment.ps1 | 215 ++++++ .../configuration/Remove-AlzConfiguration.ps1 | 150 ++++ .../configuration/Remove-AlzCredential.ps1 | 121 ++++ scripts/configuration/Test-AlzCredential.ps1 | 136 ++++ .../Functions/EnvironmentContext.ps1 | 27 +- .../Functions/HubNetworkWithAzureFirewall.ps1 | 46 +- .../Functions/HubNetworkWithNVA.ps1 | 44 +- scripts/deployments/Functions/Identity.ps1 | 81 +++ .../deployments/Functions/Subscriptions.ps1 | 30 +- scripts/deployments/RunWorkflows.ps1 | 42 +- scripts/onboarding/create-pipelines.bat | 4 +- .../set-variables.CanadaPubSecALZ.bat | 54 ++ .../BackupRecoveryVaultIsFalse.json | 6 +- .../BackupRecoveryVaultIsTrue.json | 6 +- .../BudgetIsFalse.json | 6 +- .../lz-generic-subscription/BudgetIsTrue.json | 6 +- .../EmptyResourceTags.json | 6 +- .../EmptySubscriptionTags.json | 6 +- .../FullDeployment-With-Hub.json | 6 +- .../FullDeployment-With-Location.json | 8 +- .../FullDeployment-Without-Hub.json | 6 +- .../WithoutCustomDNS.json | 6 +- .../WithoutSubnets.json | 6 +- .../schemas/lz-healthcare/BudgetIsFalse.json | 6 +- tests/schemas/lz-healthcare/BudgetIsTrue.json | 6 +- .../lz-healthcare/EmptyResourceTags.json | 6 +- .../lz-healthcare/EmptySubscriptionTags.json | 6 +- .../FullDeployment-With-Hub.json | 6 +- .../FullDeployment-With-Location.json | 6 +- .../FullDeployment-With-OptionalSubnets.json | 6 +- .../FullDeployment-Without-Hub.json | 6 +- .../lz-healthcare/SQLDB-aadAuthOnly.json | 6 +- .../lz-healthcare/SQLDB-mixedAuth.json | 6 +- .../schemas/lz-healthcare/SQLDB-sqlAuth.json | 6 +- tests/schemas/lz-healthcare/SQLDBIsFalse.json | 6 +- .../lz-healthcare/Synapse-aadAuthOnly.json | 6 +- .../lz-healthcare/Synapse-mixedAuth.json | 6 +- .../lz-healthcare/Synapse-sqlAuth.json | 6 +- tests/schemas/lz-healthcare/WithoutCMK.json | 6 +- .../AKS-AzureCNI-AzureNP.json | 6 +- .../AKS-AzureCNI-Calico.json | 6 +- .../AKS-Kubenet-Calico.json | 6 +- .../lz-machinelearning/AKSIsFalse.json | 6 +- .../AppServiceLinuxContainerIsFalse.json | 6 +- ...eLinuxContainerPrivateEndpointIsFalse.json | 29 +- .../lz-machinelearning/BudgetIsFalse.json | 6 +- .../lz-machinelearning/BudgetIsTrue.json | 6 +- .../lz-machinelearning/EmptyResourceTags.json | 6 +- .../EmptySubscriptionTags.json | 6 +- .../FullDeployment-With-Hub.json | 6 +- .../FullDeployment-With-Location.json | 24 +- .../FullDeployment-With-OptionalSubnets.json | 8 +- .../FullDeployment-Without-Hub.json | 6 +- .../lz-machinelearning/SQLDB-aadAuthOnly.json | 6 +- .../lz-machinelearning/SQLDB-mixedAuth.json | 6 +- .../lz-machinelearning/SQLDB-sqlAuth.json | 6 +- .../lz-machinelearning/SQLDBIsFalse.json | 6 +- .../lz-machinelearning/SQLMIIsFalse.json | 6 +- .../lz-machinelearning/WithoutCMK.json | 6 +- .../BudgetIsFalse.json | 6 +- ...ullDeployment-With-OptionalHubSubnets.json | 6 +- ...ullDeployment-WithAzureFirewallPolicy.json | 6 +- ...lDeployment-WithLogAnalyticsWorkspace.json | 6 +- ...ment-Without-ManagementRestrictedZone.json | 6 +- .../FullDeployment.json | 6 +- .../BudgetIsFalse.json | 6 +- .../FullDeployment-With-FortigateBYOL.json | 6 +- ...ullDeployment-With-OptionalHubSubnets.json | 6 +- .../FullDeployment-With-Ubuntu.json | 6 +- ...lDeployment-WithLogAnalyticsWorkspace.json | 6 +- .../lz-Identity-With-DNS-Resolver.json | 170 +++++ ...th-Private-DNS-Zones-And-DNS-Resolver.json | 170 +++++ .../lz-Identity-With-Private-DNS-Zones.json | 170 +++++ .../lz-Identity-Without-DNS-Resolver.json | 170 +++++ .../lz-platform-logging/BudgetIsFalse.json | 6 +- .../EmptyResourceTags.json | 6 +- .../EmptySubscriptionTags.json | 6 +- .../FullDeployment-With-Location.json | 6 +- .../lz-platform-logging/FullDeployment.json | 6 +- .../WithoutSubscriptionRoleAssignments.json | 6 +- tests/schemas/run-tests.sh | 2 + 209 files changed, 8475 insertions(+), 899 deletions(-) create mode 100644 .github/workflows/6-identity.yml create mode 100644 .github/workflows/7-subscriptions.yml create mode 100644 .pipelines/platform-identity.yml create mode 100644 .pipelines/templates/steps/deploy-platform-identity.yml create mode 100644 azresources/network/dns-forwarding-ruleset.bicep create mode 100644 azresources/network/dnsresolver-vnet-link.bicep create mode 100644 azresources/network/dnsresolver.bicep create mode 100644 config/identity/CanadaPubSecALZ-main/identity.parameters.json create mode 100644 config/logging/CanadaPubSecALZ-main/logging.parameters.json create mode 100644 config/networking/CanadaPubSecALZ-main/hub-azfw-policy/azure-firewall-policy.parameters.json create mode 100644 config/networking/CanadaPubSecALZ-main/hub-azfw/hub-network.parameters.json create mode 100644 config/networking/CanadaPubSecALZ-main/hub-nva/hub-network.parameters.json create mode 100644 config/subscriptions/CanadaPubSecALZ-main/DevTest/0a5970ed-b9d5-464a-acef-8a06f0d7a360_machinelearning_canadacentral.json create mode 100644 config/subscriptions/CanadaPubSecALZ-main/DevTest/1f519216-5e39-4b51-a9b6-10cc2b79b6c7_healthcare_canadacentral.json create mode 100644 config/subscriptions/CanadaPubSecALZ-main/DevTest/8422552f-3840-4934-a971-6ee349ffbb05_generic-subscription_canadacentral.json create mode 100644 config/subscriptions/CanadaPubSecALZ-main/DevTest/9d9817f5-c218-4553-b686-58be8d9ea82c_machinelearning_canadacentral.json create mode 100644 config/subscriptions/CanadaPubSecALZ-main/DevTest/bbe30f1a-fda5-4873-b51b-c838cf3f1a61_machinelearning_canadacentral.json create mode 100644 config/subscriptions/CanadaPubSecALZ-main/DevTest/f881fccb-2598-4b9c-b87c-b392f5e16f12_machinelearning_canadacentral.json create mode 100644 config/variables/CanadaPubSecALZ-main.yml create mode 100644 docs/archetypes/identity.md create mode 100644 docs/media/architecture/archetype-identity.jpg create mode 100644 docs/onboarding/configuration-scripts.md create mode 100644 landingzones/lz-platform-identity/dnsResolver.bicep create mode 100644 landingzones/lz-platform-identity/main.bicep create mode 100644 landingzones/lz-platform-identity/networking.bicep create mode 100644 schemas/latest/landingzones/lz-platform-identity.json create mode 100644 scripts/configuration/Connect-AlzCredential.ps1 create mode 100644 scripts/configuration/Get-AlzConfiguration.ps1 create mode 100644 scripts/configuration/Get-AlzSubscriptions.ps1 create mode 100644 scripts/configuration/Install-Prerequisites.ps1 create mode 100644 scripts/configuration/New-AlzConfiguration.ps1 create mode 100644 scripts/configuration/New-AlzCredential.ps1 create mode 100644 scripts/configuration/New-AlzDeployment.ps1 create mode 100644 scripts/configuration/Remove-AlzConfiguration.ps1 create mode 100644 scripts/configuration/Remove-AlzCredential.ps1 create mode 100644 scripts/configuration/Test-AlzCredential.ps1 create mode 100644 scripts/deployments/Functions/Identity.ps1 create mode 100644 scripts/onboarding/set-variables.CanadaPubSecALZ.bat create mode 100644 tests/schemas/lz-platform-identity/lz-Identity-With-DNS-Resolver.json create mode 100644 tests/schemas/lz-platform-identity/lz-Identity-With-Private-DNS-Zones-And-DNS-Resolver.json create mode 100644 tests/schemas/lz-platform-identity/lz-Identity-With-Private-DNS-Zones.json create mode 100644 tests/schemas/lz-platform-identity/lz-Identity-Without-DNS-Resolver.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ad259099..d732fb68 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # These owners will be the default owners for everything in the repo. -* @hudua @SenthuranSivananthan @skeeler @kevinevans @bawillis +* @hudua @SenthuranSivananthan @skeeler @Tredell diff --git a/.github/workflows/0-everything.yml b/.github/workflows/0-everything.yml index 8130c32a..6f58d224 100644 --- a/.github/workflows/0-everything.yml +++ b/.github/workflows/0-everything.yml @@ -20,13 +20,18 @@ on: - "HubNetworkWithNVA" - "HubNetworkWithAzureFirewall" default: "HubNetworkWithAzureFirewall" + deployIdentity: + type: boolean + description: "Deploy Identity Subscription" + required: true + default: false subscriptionIds: type: string description: Subscription ID(s) (optional), e.g. "abcd", "1234" required: false environmentName: type: string - description: Environment name (optional), e.g. CanadaESLZ-main + description: Environment name (optional), e.g. CanadaPubSecALZ-main required: false defaults: @@ -306,6 +311,34 @@ jobs: -NvaUsername (ConvertTo-SecureString -String '${{secrets.NVA_USERNAME}}' -AsPlainText -Force) ` -NvaPassword (ConvertTo-SecureString -String '${{secrets.NVA_PASSWORD}} '-AsPlainText -Force) + identity: + name: Identity + if: github.event.inputs.deployIdentity == 'true' + + needs: + - Logging + - HubNetworking + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Configure PowerShell modules + run: | + Install-Module Az -Force + Install-Module powershell-yaml -Force + + - name: Deploy Identity + run: | + ./RunWorkflows.ps1 ` + -DeployIdentity ` + -EnvironmentName '${{github.event.inputs.environmentName}}' ` + -LoginServicePrincipalJson (ConvertTo-SecureString -String '${{secrets.ALZ_CREDENTIALS}}' -AsPlainText -Force) ` + -GitHubRepo ${env:GITHUB_REPOSITORY} ` + -GitHubRef ${env:GITHUB_REF} + SubscriptionMatrix: if: github.event.inputs.subscriptionIds != '' diff --git a/.github/workflows/1-management-groups.yml b/.github/workflows/1-management-groups.yml index 17ffebe1..f042e3a6 100644 --- a/.github/workflows/1-management-groups.yml +++ b/.github/workflows/1-management-groups.yml @@ -14,7 +14,7 @@ on: inputs: environmentName: type: string - description: Environment name (optional), e.g. CanadaESLZ-main + description: Environment name (optional), e.g. CanadaPubSecALZ-main required: false defaults: diff --git a/.github/workflows/2-roles.yml b/.github/workflows/2-roles.yml index 115cb15c..f034d6e4 100644 --- a/.github/workflows/2-roles.yml +++ b/.github/workflows/2-roles.yml @@ -14,7 +14,7 @@ on: inputs: environmentName: type: string - description: Environment name (optional), e.g. CanadaESLZ-main + description: Environment name (optional), e.g. CanadaPubSecALZ-main required: false defaults: diff --git a/.github/workflows/3-logging.yml b/.github/workflows/3-logging.yml index a90b118b..dd883305 100644 --- a/.github/workflows/3-logging.yml +++ b/.github/workflows/3-logging.yml @@ -14,7 +14,7 @@ on: inputs: environmentName: type: string - description: Environment name (optional), e.g. CanadaESLZ-main + description: Environment name (optional), e.g. CanadaPubSecALZ-main required: false defaults: diff --git a/.github/workflows/4-policy.yml b/.github/workflows/4-policy.yml index 6ecf8b9d..004218b2 100644 --- a/.github/workflows/4-policy.yml +++ b/.github/workflows/4-policy.yml @@ -14,7 +14,7 @@ on: inputs: environmentName: type: string - description: Environment name (optional), e.g. CanadaESLZ-main + description: Environment name (optional), e.g. CanadaPubSecALZ-main required: false defaults: diff --git a/.github/workflows/5-azure-firewall-policy.yml b/.github/workflows/5-azure-firewall-policy.yml index b2240abf..a6d73f72 100644 --- a/.github/workflows/5-azure-firewall-policy.yml +++ b/.github/workflows/5-azure-firewall-policy.yml @@ -14,7 +14,7 @@ on: inputs: environmentName: type: string - description: Environment name (optional), e.g. CanadaESLZ-main + description: Environment name (optional), e.g. CanadaPubSecALZ-main required: false defaults: diff --git a/.github/workflows/5-hub-network-with-azure-firewall.yml b/.github/workflows/5-hub-network-with-azure-firewall.yml index ae131650..61579e5c 100644 --- a/.github/workflows/5-hub-network-with-azure-firewall.yml +++ b/.github/workflows/5-hub-network-with-azure-firewall.yml @@ -14,7 +14,7 @@ on: inputs: environmentName: type: string - description: Environment name (optional), e.g. CanadaESLZ-main + description: Environment name (optional), e.g. CanadaPubSecALZ-main required: false defaults: diff --git a/.github/workflows/5-hub-network-with-nva.yml b/.github/workflows/5-hub-network-with-nva.yml index 65557837..35423d11 100644 --- a/.github/workflows/5-hub-network-with-nva.yml +++ b/.github/workflows/5-hub-network-with-nva.yml @@ -14,7 +14,7 @@ on: inputs: environmentName: type: string - description: Environment name (optional), e.g. CanadaESLZ-main + description: Environment name (optional), e.g. CanadaPubSecALZ-main required: false defaults: diff --git a/.github/workflows/6-identity.yml b/.github/workflows/6-identity.yml new file mode 100644 index 00000000..5f97362e --- /dev/null +++ b/.github/workflows/6-identity.yml @@ -0,0 +1,46 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +name: 6 - Identity + +on: + workflow_dispatch: + inputs: + environmentName: + type: string + description: Environment name (optional), e.g. CanadaPubSecALZ-main + required: false + +defaults: + run: + shell: pwsh + working-directory: scripts/deployments + +jobs: + identity: + name: Identity + runs-on: ubuntu-latest + steps: + + - name: Checkout + uses: actions/checkout@v3 + + - name: Configure PowerShell modules + run: | + Install-Module Az -Force + Install-Module powershell-yaml -Force + + - name: Deploy Identity + run: | + ./RunWorkflows.ps1 ` + -DeployIdentity ` + -EnvironmentName '${{github.event.inputs.environmentName}}' ` + -LoginServicePrincipalJson (ConvertTo-SecureString -String '${{secrets.ALZ_CREDENTIALS}}' -AsPlainText -Force) ` + -GitHubRepo ${env:GITHUB_REPOSITORY} ` + -GitHubRef ${env:GITHUB_REF} diff --git a/.github/workflows/7-subscriptions.yml b/.github/workflows/7-subscriptions.yml new file mode 100644 index 00000000..ceb3bd46 --- /dev/null +++ b/.github/workflows/7-subscriptions.yml @@ -0,0 +1,84 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + + +name: 7 - Subscriptions + +on: + workflow_dispatch: + inputs: + subscriptionIds: + type: string + description: Subscription ID(s), e.g. "abcd", "1234" + required: true + environmentName: + type: string + description: Environment name (optional), e.g. CanadaPubSecALZ-main + required: false + +defaults: + run: + shell: pwsh + working-directory: scripts/deployments + +jobs: + SubscriptionMatrix: + if: github.event.inputs.subscriptionIds != '' + + name: Create subscription deployment(s) as matrix + + runs-on: ubuntu-latest + + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - id: set-matrix + run: | + $SubscriptionIdJsonObject = @{ + SubscriptionId = ${{github.event.inputs.subscriptionIds}} -Split "," + } + + $SubscriptionIdJson = $SubscriptionIdJsonObject | ConvertTo-Json -Compress + + Write-Output "::set-output name=matrix::$SubscriptionIdJson" + + Subscriptions: + if: github.event.inputs.subscriptionIds != '' + needs: + - SubscriptionMatrix + + name: Subscriptions + + runs-on: ubuntu-latest + + strategy: + matrix: ${{fromJSON(needs.SubscriptionMatrix.outputs.matrix)}} + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Configure PowerShell modules + run: | + Install-Module Az -Force + Install-Module powershell-yaml -Force + + - name: Deploy Subscription + run: | + ./RunWorkflows.ps1 ` + -DeploySubscriptionIds '${{ matrix.subscriptionId }}' ` + -EnvironmentName '${{github.event.inputs.environmentName}}' ` + -LoginServicePrincipalJson (ConvertTo-SecureString -String '${{secrets.ALZ_CREDENTIALS}}' -AsPlainText -Force) ` + -GitHubRepo ${env:GITHUB_REPOSITORY} ` + -GitHubRef ${env:GITHUB_REF} diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 800218a8..72cc59ac 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -22,7 +22,8 @@ The following workflows are present in the `.github/workflows` repository folder | 5 | Azure Firewall Policy (required for Hub Networking with Azure Firewall) | `5-azure-firewall-policy.yml` | 5 | Hub Networking with Azure Firewall | `5-hub-network-with-azure-firewall.yml` | 5 | Hub Networking with NVA | `5-hub-network-with-nva.yml` -| 6 | Subscriptions | `6-subscriptions.yml` +| 6 | Identity | `6-identity.yml` +| 7 | Subscriptions | `7-subscriptions.yml` With the exception of the `Everything` workflow, all other workflows need to be run in the order specified. For example, the `Policy` workflow is dependent on resources deployed by the `Logging` workflow. Think of it as a layered approach; once the layer is deployed, it only requires re-running if some configuration at that layer changes. diff --git a/.github/workflows/consistency-check.yml b/.github/workflows/consistency-check.yml index 6b6367f5..81b8d980 100644 --- a/.github/workflows/consistency-check.yml +++ b/.github/workflows/consistency-check.yml @@ -9,6 +9,7 @@ env: SCHEMA_FOLDER: schemas/latest/landingzones LOGGING_PATH_FROM_ROOT: config/logging NETWORKING_PATH_FROM_ROOT: config/networking + IDENTITY_PATH_FROM_ROOT: config/identity SUBSCRIPTIONS_PATH_FROM_ROOT: config/subscriptions jobs: @@ -82,6 +83,14 @@ jobs: Get-Content -Raw $_ | Test-Json -SchemaFile $HubNetworkWithNVASchemaFile } + $IdentityFileFilter="*.json" + $IdentitySchemaFile="${{env.SCHEMA_FOLDER}}/lz-platform-identity.json" + + Get-ChildItem -Recurse -Filter $IdentityFileFilter -Path "${{env.IDENTITY_PATH_FROM_ROOT}}" | ForEach-Object { + Write-Host "Validating: $_ with $IdentitySchemaFile" + Get-Content -Raw $_ | Test-Json -SchemaFile $IdentitySchemaFile + } + $GenericSubscriptionFileFilter="*generic-subscription*.json" $GenericSubscriptionSchemaFile="${{env.SCHEMA_FOLDER}}/lz-generic-subscription.json" diff --git a/.github/workflows/pull-request-check.yml b/.github/workflows/pull-request-check.yml index b4adfe81..67eb8afd 100644 --- a/.github/workflows/pull-request-check.yml +++ b/.github/workflows/pull-request-check.yml @@ -12,6 +12,7 @@ env: SCHEMA_FOLDER: schemas/latest/landingzones LOGGING_PATH_FROM_ROOT: config/logging NETWORKING_PATH_FROM_ROOT: config/networking + IDENTITY_PATH_FROM_ROOT: config/identity SUBSCRIPTIONS_PATH_FROM_ROOT: config/subscriptions jobs: @@ -84,6 +85,14 @@ jobs: Write-Host "Validating: $_ with $HubNetworkWithNVASchemaFile" Get-Content -Raw $_ | Test-Json -SchemaFile $HubNetworkWithNVASchemaFile } + + $IdentityFileFilter="*.json" + $IdentitySchemaFile="${{env.SCHEMA_FOLDER}}/lz-platform-identity.json" + + Get-ChildItem -Recurse -Filter $IdentityFileFilter -Path "${{env.IDENTITY_PATH_FROM_ROOT}}" | ForEach-Object { + Write-Host "Validating: $_ with $IdentitySchemaFile" + Get-Content -Raw $_ | Test-Json -SchemaFile $IdentitySchemaFile + } $GenericSubscriptionFileFilter="*generic-subscription*.json" $GenericSubscriptionSchemaFile="${{env.SCHEMA_FOLDER}}/lz-generic-subscription.json" diff --git a/.gitignore b/.gitignore index d0b2c249..bb915074 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ experiments/* **/*.swp +**/*.diff .vscode/* /*.sh /*.ps1 diff --git a/.pipelines/platform-identity.yml b/.pipelines/platform-identity.yml new file mode 100644 index 00000000..09e53144 --- /dev/null +++ b/.pipelines/platform-identity.yml @@ -0,0 +1,64 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +trigger: none + +pr: none + +variables: +- name: devops-org-name + value: ${{ replace(replace(variables['System.CollectionUri'], 'https://dev.azure.com/' , ''), '/', '') }} +- name: logging-config-directory + value: $(System.DefaultWorkingDirectory)/$(loggingPathFromRoot)/${{ variables['devops-org-name'] }}-${{ variables['Build.SourceBranchName'] }} +- name: identity-config-directory + value: $(System.DefaultWorkingDirectory)/$(identityPathFromRoot)/${{ variables['devops-org-name'] }}-${{ variables['Build.SourceBranchName'] }} +- name: variable-template-file + value: ${{ variables['devops-org-name'] }}-${{ variables['Build.SourceBranchName'] }}.yml +- template: ../config/variables/common.yml +- template: ../config/variables/${{ variables['variable-template-file'] }} + + +pool: + vmImage: $[ variables.vmImage ] + +stages: + +- stage: DeployNetworkingStage + displayName: Deploy Networking Stage + + jobs: + + - deployment: DeployIdentityJob + displayName: Deploy Identity Job + environment: ${{ variables['Build.SourceBranchName'] }} + strategy: + runOnce: + deploy: + steps: + - checkout: self + + - template: templates/steps/load-variables.yml + + - template: templates/steps/load-log-analytics-vars.yml + parameters: + logAnalyticsSubscriptionId: $(var-logging-subscriptionId) + logAnalyticsConfigurationFile: ${{ variables['logging-config-directory'] }}/$(var-logging-configurationFileName) + + - template: templates/steps/show-variables.yml + parameters: + json: ${{ convertToJson(variables) }} + + - template: templates/steps/deploy-platform-identity.yml + parameters: + workingDir: $(System.DefaultWorkingDirectory)/landingzones + deployOperation: ${{ variables['deployOperation'] }} + identityManagementGroupId: $(var-identity-managementGroupId) + identitySubscriptionId: $(var-identity-subscriptionId) + identityRegion: $(var-identity-region) + identityConfigurationPath: ${{ variables['identity-config-directory'] }}/$(var-identity-configurationFileName) diff --git a/.pipelines/policy.yml b/.pipelines/policy.yml index 02884583..0ea2c990 100644 --- a/.pipelines/policy.yml +++ b/.pipelines/policy.yml @@ -96,7 +96,7 @@ stages: - template: templates/steps/define-policyset.yml parameters: description: 'Define Policy Set' - deployTemplates: [AKS, DefenderForCloud, LogAnalytics, Network, DNSPrivateEndpoints, Tags] + deployTemplates: [AKS, DefenderForCloud, DNSPrivateEndpoints, LogAnalytics, Network, Tags] deployOperation: ${{ variables['deployOperation'] }} workingDir: $(System.DefaultWorkingDirectory)/policy/custom/definitions/policyset diff --git a/.pipelines/templates/jobs/trigger-subscriptions.yml b/.pipelines/templates/jobs/trigger-subscriptions.yml index 679946e1..bf069428 100644 --- a/.pipelines/templates/jobs/trigger-subscriptions.yml +++ b/.pipelines/templates/jobs/trigger-subscriptions.yml @@ -89,13 +89,17 @@ jobs: { $url = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/pipelines/$($env:SYSTEM_DEFINITIONID)/runs?api-version=6.0-preview.1" Write-Host "Invoking pipeline definition with URL: $url" + $paths = $env:SUBSCRIPTION_CHANGES -split ',' + $guids = $paths -replace '.*?([0-9a-f]{8}[-]?([0-9a-f]{4}[-]?){3}[0-9a-f]{12}).*', '$1' + $changes = $guids -join ',' $body = @" { "templateParameters": { - "subscriptions":"[$env:SUBSCRIPTION_CHANGES]" + "subscriptions":"[$changes]" }, } "@ + Write-Host "Invoking pipeline definition with body: $body" $headers = @{ Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN" } $pipeline = Invoke-RestMethod -Uri $url -Headers $headers -Method Post -Body $body -ContentType application/json Write-Host "Pipeline invocation result = $($pipeline | ConvertTo-Json -Depth 100)" diff --git a/.pipelines/templates/steps/deploy-platform-identity.yml b/.pipelines/templates/steps/deploy-platform-identity.yml new file mode 100644 index 00000000..358fb190 --- /dev/null +++ b/.pipelines/templates/steps/deploy-platform-identity.yml @@ -0,0 +1,83 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: workingDir + type: string + - name: deployOperation + type: string + default: create + values: + - create + - what-if + - name: identityManagementGroupId + type: string + - name: identitySubscriptionId + type: string + - name: identityRegion + type: string + - name: identityConfigurationPath + type: string + +steps: + +- task: PowerShell@2 + displayName: Validate identity Parameters + inputs: + targetType: 'inline' + script: | + $schemaFile="$(Build.SourcesDirectory)/schemas/latest/landingzones/lz-platform-identity.json" + + Write-Host "Parameters File: ${{ parameters.identityConfigurationPath }}" + Write-Host "Schema File: ${schemaFile}" + + Get-Content -Raw "${{ parameters.identityConfigurationPath }}" | Test-Json -SchemaFile "${schemaFile}" + +- template: ./move-subscription.yml + parameters: + managementGroup: ${{ parameters.identityManagementGroupId }} + subscriptionGuid: ${{ parameters.identitySubscriptionId }} + subscriptionLocation: ${{ parameters.identityRegion }} + templateDirectory: $(Build.SourcesDirectory)/landingzones/utils/mg-move + templateFile: move-subscription.bicep + workingDir: ${{ parameters.workingDir }}/utils/mg-move + +- task: AzureCLI@2 + displayName: Configure Identity LZ + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + # Check if the log analytics workspace id is provided in the parameters json. + # If present, then do no change it. Otherwise add it to the json parameter file. + LOG_ANALYTICS_WORKSPACE_RESOURCE_ID_IN_PARAMETERS=`jq -r .parameters.logAnalyticsWorkspaceResourceId.value ${{ parameters.identityConfigurationPath }}` + + if [[ $LOG_ANALYTICS_WORKSPACE_RESOURCE_ID_IN_PARAMETERS != null && "$LOG_ANALYTICS_WORKSPACE_RESOURCE_ID_IN_PARAMETERS" != "" ]]; + then + echo "Log Analytics Workspace Resource ID is set in ${{ parameters.identityConfigurationPath }} to $LOG_ANALYTICS_WORKSPACE_RESOURCE_ID_IN_PARAMETERS" + else + echo "Log Analytics Workspace Resource ID is not set in ${{ parameters.identityConfigurationPath }}. Updating ${{ parameters.identityConfigurationPath }} with $(var-logging-logAnalyticsWorkspaceResourceId)" + + # use jq to update the json parameter file + echo "$( jq '.parameters.logAnalyticsWorkspaceResourceId.value = "$(var-logging-logAnalyticsWorkspaceResourceId)"' ${{ parameters.identityConfigurationPath }} )" > ${{ parameters.identityConfigurationPath }} + fi + + echo "Deploying main.bicep using ${{ parameters.deployOperation}} operation using ${{ parameters.identityConfigurationPath }}..." + + az deployment sub ${{ parameters.deployOperation }} \ + --location ${{ parameters.identityRegion }} \ + --subscription ${{ parameters.identitySubscriptionId }} \ + --template-file main.bicep \ + --parameters @${{ parameters.identityConfigurationPath }} + + $(var-bashPostInjectScript) + workingDirectory: '${{ parameters.workingDir }}/lz-platform-identity' diff --git a/.pipelines/templates/steps/show-variables.yml b/.pipelines/templates/steps/show-variables.yml index 10f8c424..5f750473 100644 --- a/.pipelines/templates/steps/show-variables.yml +++ b/.pipelines/templates/steps/show-variables.yml @@ -70,4 +70,10 @@ steps: echo printenv -0 | grep -zi '^var-hubnetwork-nva-' | xargs -0 -L 1 echo + echo + echo + echo "IDENTITY" + echo + printenv -0 | grep -zi '^var-identity-' | xargs -0 -L 1 echo + $(var-bashPostInjectScript) diff --git a/README.md b/README.md index 63c77875..d07ef966 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@ ## Introduction -The purpose of the reference implementation is to guide Canadian Public Sector customers on building Landing Zones in their Azure environment. The reference implementation is based on [Cloud Adoption Framework for Azure](https://docs.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/) and provides an opinionated implementation that enables ITSG-33 regulatory compliance by using [NIST SP 800-53 Rev. 4](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4) and [Canada Federal PBMM](https://docs.microsoft.com/azure/governance/policy/samples/canada-federal-pbmm) Regulatory Compliance Policy Sets. +The purpose of the reference implementation is to guide Canadian Public Sector customers on building Landing Zones in their Azure environment. The reference implementation is based on [Cloud Adoption Framework for Azure](https://learn.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/) and provides an opinionated implementation that enables ITSG-33 regulatory compliance by using [NIST SP 800-53 Rev. 4](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4) and [Canada Federal PBMM](https://learn.microsoft.com/azure/governance/policy/samples/canada-federal-pbmm) Regulatory Compliance Policy Sets. Architecture supported up to Treasury Board of Canada Secretariat (TBS) Cloud Profile 3 - Cloud Only Applications. This profile is applicable to Infrastructure as a Service (IaaS) and Platform as a Service (PaaS) with [characteristics](https://github.com/canada-ca/cloud-guardrails/blob/master/EN/00_Applicable-Scope.md): * Cloud-based services hosting sensitive (up to Protected B) information * No direct system to system network interconnections required with GC data centers -> This implementation is specific to **Canadian Public Sector departments**. Please see [Implement Cloud Adoption Framework enterprise-scale landing zones in Azure](https://docs.microsoft.com/azure/cloud-adoption-framework/ready/enterprise-scale/implementation) if you are looking for implementation for other industries or customers. +> This implementation is specific to **Canadian Public Sector departments**. Please see [Implement Cloud Adoption Framework enterprise-scale landing zones in Azure](https://learn.microsoft.com/azure/cloud-adoption-framework/ready/enterprise-scale/implementation) if you are looking for implementation for other industries or customers. ## Architecture @@ -34,6 +34,7 @@ See the following onboarding guides for setup instructions: * [Azure DevOps Setup](docs/onboarding/azure-devops-setup.md) provides guidance on considerations and recommended practices when creating and configuring your Azure DevOps Services environment. * [Azure DevOps Scripts](docs/onboarding/azure-devops-scripts.md) provides guidance on the scripts available to help simplify the onboarding process to Azure Landing Zones design using Azure DevOps pipelines. * [Azure DevOps Pipelines](docs/onboarding/azure-devops-pipelines.md) provides guidance on the manual steps for onboarding to the Azure Landing Zones design using Azure DevOps Pipelines. +* [Configuration Scripts](docs/onboarding/configuration-scripts.md) provides guidance on the scripts available to help simplify the configuration process of the Azure Landing Zones design. ## Goals @@ -54,7 +55,7 @@ multiple types of workloads including App Dev and Data & AI. * Automatic approval for Canada Federal PBMM nor Authority to Operate (ATO). Customers must collect evidence, customize to meet their departmental requirements and submit for Authority to Operate based on their risk profile, requirements and process. -* Compliant on all Azure Policies when the reference implementation is deployed. This is due to the shared responsibility of cloud and customers can choose the Azure Policies to exclude. For example, using Azure Firewall is an Azure Policy that will be non-compliant since majority of the Public Sector customers use Network Virtual Appliances such as Fortinet. Customers must review [Microsoft Defender for Cloud Regulatory Compliance dashboard](https://docs.microsoft.com/azure/defender-for-cloud/update-regulatory-compliance-packages) and apply appropriate exemptions. +* Compliant on all Azure Policies when the reference implementation is deployed. This is due to the shared responsibility of cloud and customers can choose the Azure Policies to exclude. For example, using Azure Firewall is an Azure Policy that will be non-compliant since majority of the Public Sector customers use Network Virtual Appliances such as Fortinet. Customers must review [Microsoft Defender for Cloud Regulatory Compliance dashboard](https://learn.microsoft.com/azure/defender-for-cloud/update-regulatory-compliance-packages) and apply appropriate exemptions. ## Contributing @@ -64,7 +65,7 @@ See [Contributing Reference Implementation](CONTRIBUTING.md) for information on **November 11, 2021 onward** -> Microsoft can identify the deployments of the Azure Resource Manager and Bicep templates with the deployed Azure resources. Microsoft can correlate these resources used to support the deployments. Microsoft collects this information to provide the best experiences with their products and to operate their business. The telemetry is collected through [customer usage attribution](https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution). The data is collected and governed by Microsoft's privacy policies, located at [https://www.microsoft.com/trustcenter](https://www.microsoft.com/trustcenter). +> Microsoft can identify the deployments of the Azure Resource Manager and Bicep templates with the deployed Azure resources. Microsoft can correlate these resources used to support the deployments. Microsoft collects this information to provide the best experiences with their products and to operate their business. The telemetry is collected through [customer usage attribution](https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution). The data is collected and governed by Microsoft's privacy policies, located at [https://www.microsoft.com/trustcenter](https://www.microsoft.com/trustcenter). > > If you don't wish to send usage data to Microsoft, you can set the `customerUsageAttribution.enabled` setting to `false` in `config/telemetry.json`. Learn more in our [Azure DevOps Pipelines](docs/onboarding/azure-devops-pipelines.md#telemetry) onboarding guide. > diff --git a/SECURITY.md b/SECURITY.md index f7b89984..5e580512 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,7 +4,7 @@ Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). -If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://learn.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. ## Reporting Security Issues diff --git a/azresources/compute/web/appservice-linux-container.bicep b/azresources/compute/web/appservice-linux-container.bicep index 51ce2f86..7ec70f2d 100644 --- a/azresources/compute/web/appservice-linux-container.bicep +++ b/azresources/compute/web/appservice-linux-container.bicep @@ -56,7 +56,7 @@ resource app 'Microsoft.Web/sites@2021-02-01' = { clientAffinityEnabled: true siteConfig: { // for Linux Apps Azure DNS private zones only works if Route All is enabled. - // https://docs.microsoft.com/azure/app-service/web-sites-integrate-with-vnet#azure-dns-private-zones + // https://learn.microsoft.com/azure/app-service/web-sites-integrate-with-vnet#azure-dns-private-zones linuxFxVersion: 'DOCKER|mcr.microsoft.com/appsvc/staticsite:latest' vnetRouteAllEnabled: true use32BitWorkerProcess: false diff --git a/azresources/compute/web/appservice-linux.bicep b/azresources/compute/web/appservice-linux.bicep index 23ece896..d51f72aa 100644 --- a/azresources/compute/web/appservice-linux.bicep +++ b/azresources/compute/web/appservice-linux.bicep @@ -58,7 +58,7 @@ resource app 'Microsoft.Web/sites@2020-06-01' = { clientAffinityEnabled: true siteConfig: { // for Linux Apps Azure DNS private zones only works if Route All is enabled. - // https://docs.microsoft.com/azure/app-service/web-sites-integrate-with-vnet#azure-dns-private-zones + // https://learn.microsoft.com/azure/app-service/web-sites-integrate-with-vnet#azure-dns-private-zones vnetRouteAllEnabled: true linuxFxVersion: stack diff --git a/azresources/containers/acr/acr-with-cmk.bicep b/azresources/containers/acr/acr-with-cmk.bicep index aaa67de9..658e2272 100644 --- a/azresources/containers/acr/acr-with-cmk.bicep +++ b/azresources/containers/acr/acr-with-cmk.bicep @@ -66,7 +66,7 @@ param tempKeyVaultName string = 'tmpkv${uniqueString(utcNow())}' /* Create a temporary key vault and key to setup CMK. These will be deleted at the end of deployment using deployment script. - See: https://docs.microsoft.com/en-us/azure/container-registry/container-registry-customer-managed-keys#advanced-scenario-key-vault-firewall + See: https://learn.microsoft.com/en-us/azure/container-registry/container-registry-customer-managed-keys#advanced-scenario-key-vault-firewall */ module tempAkv '../../security/key-vault.bicep' = { name: 'deploy-keyvault-temp' diff --git a/azresources/containers/aks/main.bicep b/azresources/containers/aks/main.bicep index 8c8dcdde..c4c98bf1 100644 --- a/azresources/containers/aks/main.bicep +++ b/azresources/containers/aks/main.bicep @@ -108,7 +108,7 @@ var virtualNetworkName = subnetIdSplit[8] var privateDnsZoneIdSplit = split(privateDNSZoneId, '/') var privateDnsZoneSubscriptionId = privateDnsZoneIdSplit[2] var privateZoneDnsResourceGroupName = privateDnsZoneIdSplit[4] -var privateZoneResourceName = last(privateDnsZoneIdSplit) +var privateZoneResourceName = last(privateDnsZoneIdSplit)! module identity '../../iam/user-assigned-identity.bicep' = { name: 'deploy-aks-identity' @@ -118,7 +118,7 @@ module identity '../../iam/user-assigned-identity.bicep' = { } } -// assign permissions to identity per https://docs.microsoft.com/en-us/azure/aks/private-clusters#configure-private-dns-zone +// assign permissions to identity per https://learn.microsoft.com/en-us/azure/aks/private-clusters#configure-private-dns-zone module rbacPrivateDnsZoneContributor '../../iam/resource/private-dns-zone-role-assignment-to-sp.bicep' = { name: 'rbac-private-dns-zone-contributor-${name}' scope: resourceGroup(privateDnsZoneSubscriptionId, privateZoneDnsResourceGroupName) diff --git a/azresources/data/sqldb/sqldb-with-cmk.bicep b/azresources/data/sqldb/sqldb-with-cmk.bicep index 21d3af1b..4e85c2a9 100644 --- a/azresources/data/sqldb/sqldb-with-cmk.bicep +++ b/azresources/data/sqldb/sqldb-with-cmk.bicep @@ -107,9 +107,9 @@ resource sqlserver 'Microsoft.Sql/servers@2021-02-01-preview' = { } resource sqlserver_va 'Microsoft.Sql/servers/vulnerabilityAssessments@2020-11-01-preview' = { - name: '${sqlServerName}/default' + parent: sqlserver + name: 'default' dependsOn: [ - sqlserver roleAssignSQLToSALogging ] properties: { diff --git a/azresources/data/sqldb/sqldb-without-cmk.bicep b/azresources/data/sqldb/sqldb-without-cmk.bicep index 285716c9..487fa169 100644 --- a/azresources/data/sqldb/sqldb-without-cmk.bicep +++ b/azresources/data/sqldb/sqldb-without-cmk.bicep @@ -86,9 +86,9 @@ resource sqlserver 'Microsoft.Sql/servers@2021-02-01-preview' = { } resource sqlserver_va 'Microsoft.Sql/servers/vulnerabilityAssessments@2020-11-01-preview' = { - name: '${sqlServerName}/default' + parent: sqlserver + name: 'default' dependsOn: [ - sqlserver roleAssignSQLToSALogging ] properties: { diff --git a/azresources/data/sqlmi/sqlmi-with-cmk.bicep b/azresources/data/sqlmi/sqlmi-with-cmk.bicep index c164b89b..1cca1600 100644 --- a/azresources/data/sqlmi/sqlmi-with-cmk.bicep +++ b/azresources/data/sqlmi/sqlmi-with-cmk.bicep @@ -98,9 +98,9 @@ resource sqlmi 'Microsoft.Sql/managedInstances@2020-11-01-preview' = { } resource sqlmi_va 'Microsoft.Sql/managedInstances/vulnerabilityAssessments@2020-11-01-preview' = { - name: '${name}/default' + parent: sqlmi + name: 'default' dependsOn: [ - sqlmi roleAssignSQLMIToSALogging ] properties: { diff --git a/azresources/data/sqlmi/sqlmi-without-cmk.bicep b/azresources/data/sqlmi/sqlmi-without-cmk.bicep index 704f9e4e..f89d4c84 100644 --- a/azresources/data/sqlmi/sqlmi-without-cmk.bicep +++ b/azresources/data/sqlmi/sqlmi-without-cmk.bicep @@ -77,9 +77,9 @@ resource sqlmi 'Microsoft.Sql/managedInstances@2020-11-01-preview' = { } resource sqlmi_va 'Microsoft.Sql/managedInstances/vulnerabilityAssessments@2020-11-01-preview' = { - name: '${name}/default' + parent: sqlmi + name: 'default' dependsOn: [ - sqlmi roleAssignSQLMIToSALogging ] properties: { diff --git a/azresources/network/dns-forwarding-ruleset.bicep b/azresources/network/dns-forwarding-ruleset.bicep new file mode 100644 index 00000000..8293ef32 --- /dev/null +++ b/azresources/network/dns-forwarding-ruleset.bicep @@ -0,0 +1,57 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +param name string +param location string = resourceGroup().location + +@description('Outbound endpoint id') +param outEndpointId string + +param forwardingRuleSet array + +param linkRuleSetToVnet bool = false +param linkName string = '' +param vnetId string = '' + + + +resource ruleset 'Microsoft.Network/dnsForwardingRulesets@2022-07-01' = { + name: name + location: location + properties: { + dnsResolverOutboundEndpoints: [ + { + id: outEndpointId + } + ] + } +} + +resource fwRule 'Microsoft.Network/dnsForwardingRulesets/forwardingRules@2022-07-01' = [for rule in forwardingRuleSet: { + name: rule.name + parent: ruleset + properties: { + forwardingRuleState: rule.state + domainName: endsWith(rule.domain, '.') ? rule.domain : '${rule.domain}.' //Adding a '.' at the end of the domain name if it is not present + targetDnsServers: rule.targetDnsServers + } +}] + + +module dnsResolverLinkVnet 'dnsresolver-vnet-link.bicep'= if(linkRuleSetToVnet){ + name:'deploy-private-dns-resolver-vnet-link' + params:{ + forwardingRulesetName: ruleset.name + linkName: linkName + vnetId: vnetId + } +} + +output ruleSetName string = ruleset.name + diff --git a/azresources/network/dnsresolver-vnet-link.bicep b/azresources/network/dnsresolver-vnet-link.bicep new file mode 100644 index 00000000..aba56f55 --- /dev/null +++ b/azresources/network/dnsresolver-vnet-link.bicep @@ -0,0 +1,26 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +param linkName string +param vnetId string +param forwardingRulesetName string + +resource dnsResolver 'Microsoft.Network/dnsForwardingRulesets@2022-07-01' existing = { + name: forwardingRulesetName +} + +resource resolverLink 'Microsoft.Network/dnsForwardingRulesets/virtualNetworkLinks@2022-07-01' = { + name: linkName + parent: dnsResolver + properties: { + virtualNetwork: { + id: vnetId + } + } +} diff --git a/azresources/network/dnsresolver.bicep b/azresources/network/dnsresolver.bicep new file mode 100644 index 00000000..a7a5806f --- /dev/null +++ b/azresources/network/dnsresolver.bicep @@ -0,0 +1,73 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +param name string +param location string = resourceGroup().location +param vnetId string + +@description('Name of the private dns resolver outbound endpoint') +param inboundEndpointName string + +@description('Name of the private dns resolver outbound endpoint') +param outboundEndpointName string + +@description('name of the subnet that will be used for private resolver inbound endpoint') +param inboundSubnetName string + +@description('name of the subnet that will be used for private resolver outbound endpoint') +param outboundSubnetName string + +param vnetResourceGroupName string +param vnetName string + +var subscriptionId = subscription().subscriptionId + +var inboundSubnetId = resourceId(subscriptionId, vnetResourceGroupName, 'Microsoft.Network/virtualNetworks/subnets', vnetName, inboundSubnetName) +var outboundSubnetId = resourceId(subscriptionId, vnetResourceGroupName, 'Microsoft.Network/virtualNetworks/subnets', vnetName, outboundSubnetName) + +resource resolver 'Microsoft.Network/dnsResolvers@2022-07-01' = { + name: name + location: location + properties: { + virtualNetwork: { + id: vnetId + } + } +} + +resource inEndPoint 'Microsoft.Network/dnsResolvers/inboundEndpoints@2022-07-01' = { + parent: resolver + name: inboundEndpointName + location: location + properties: { + ipConfigurations: [ + { + privateIpAllocationMethod: 'dynamic' + subnet: { + id: inboundSubnetId + } + } + ] + } +} + + +resource outEndpoint 'Microsoft.Network/dnsResolvers/outboundEndpoints@2022-07-01' = { + parent: resolver + name: outboundEndpointName + location: location + properties: { + subnet: { + id: outboundSubnetId + } + } +} + +output inboundDnsIp string = inEndPoint.properties.ipConfigurations[0].privateIpAddress +output outboundEndpointId string = outEndpoint.id diff --git a/azresources/network/vnet-peering.bicep b/azresources/network/vnet-peering.bicep index 4df38555..caa8ba34 100644 --- a/azresources/network/vnet-peering.bicep +++ b/azresources/network/vnet-peering.bicep @@ -19,6 +19,9 @@ param peeringName string @description('Boolean flag to determine whether remote gateways are used. Default: false') param useRemoteGateways bool = false +@description('Boolean flag to determine whether to allow local gateways transit. Default: false') +param allowGatewayTransit bool = false + @description('Boolean flag to determine virtual network access through the peer. Default: true') param allowVirtualNetworkAccess bool = true @@ -30,6 +33,7 @@ resource vnetPeering 'Microsoft.Network/virtualNetworks/virtualNetworkPeerings@2 properties: { useRemoteGateways: useRemoteGateways allowVirtualNetworkAccess: allowVirtualNetworkAccess + allowGatewayTransit: allowGatewayTransit allowForwardedTraffic: allowForwardedTraffic remoteVirtualNetwork: { id: targetVnetId diff --git a/azresources/security/key-vault-key-rsa2048.bicep b/azresources/security/key-vault-key-rsa2048.bicep index 26b02986..a4c9aeec 100644 --- a/azresources/security/key-vault-key-rsa2048.bicep +++ b/azresources/security/key-vault-key-rsa2048.bicep @@ -27,6 +27,6 @@ resource akvKey 'Microsoft.KeyVault/vaults/keys@2020-04-01-preview' = { // Outputs output keyName string = keyName output keyId string = akvKey.id -output keyVersion string = last(split(akvKey.properties.keyUriWithVersion, '/')) +output keyVersion string = last(split(akvKey.properties.keyUriWithVersion, '/'))! output keyUri string = akvKey.properties.keyUri output keyUriWithVersion string = akvKey.properties.keyUriWithVersion diff --git a/azresources/service-health/service-health-caller-params.json b/azresources/service-health/service-health-caller-params.json index 110b1144..119becf4 100644 --- a/azresources/service-health/service-health-caller-params.json +++ b/azresources/service-health/service-health-caller-params.json @@ -11,8 +11,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "ALZ action group", "actionGroupShortName": "alz-alert", diff --git a/azresources/telemetry/customer-usage-attribution-management-group.bicep b/azresources/telemetry/customer-usage-attribution-management-group.bicep index 7cbd8d76..3f982bd1 100644 --- a/azresources/telemetry/customer-usage-attribution-management-group.bicep +++ b/azresources/telemetry/customer-usage-attribution-management-group.bicep @@ -9,4 +9,4 @@ targetScope = 'managementGroup' // This is an empty deployment by design -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution diff --git a/azresources/telemetry/customer-usage-attribution-resource-group.bicep b/azresources/telemetry/customer-usage-attribution-resource-group.bicep index dc5396bd..3c69042a 100644 --- a/azresources/telemetry/customer-usage-attribution-resource-group.bicep +++ b/azresources/telemetry/customer-usage-attribution-resource-group.bicep @@ -9,4 +9,4 @@ targetScope = 'resourceGroup' // This is an empty deployment by design -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution diff --git a/azresources/telemetry/customer-usage-attribution-subscription.bicep b/azresources/telemetry/customer-usage-attribution-subscription.bicep index a355c55a..03086639 100644 --- a/azresources/telemetry/customer-usage-attribution-subscription.bicep +++ b/azresources/telemetry/customer-usage-attribution-subscription.bicep @@ -9,4 +9,4 @@ targetScope = 'subscription' // This is an empty deployment by design -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution diff --git a/azresources/telemetry/customer-usage-attribution-tenant.bicep b/azresources/telemetry/customer-usage-attribution-tenant.bicep index 385e1c4e..6a9b46aa 100644 --- a/azresources/telemetry/customer-usage-attribution-tenant.bicep +++ b/azresources/telemetry/customer-usage-attribution-tenant.bicep @@ -9,4 +9,4 @@ targetScope = 'tenant' // This is an empty deployment by design -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution diff --git a/bicepconfig.json b/bicepconfig.json index efe52787..15771807 100644 --- a/bicepconfig.json +++ b/bicepconfig.json @@ -1,40 +1,94 @@ { "analyzers": { "core": { - "verbose": false, - "enabled": true, - "rules": { - "adminusername-should-not-be-literal": { - "level": "error" - }, - "no-hardcoded-env-urls": { - "level": "error" - }, - "no-unnecessary-dependson": { - "level": "error" - }, - "no-unused-params": { - "level": "error" - }, - "no-unused-vars": { - "level": "error" - }, - "prefer-interpolation": { - "level": "error" - }, - "secure-parameter-default": { - "level": "error" - }, - "simplify-interpolation": { - "level": "error" - }, - "no-loc-expr-outside-params": { - "level": "error" - }, - "explicit-values-for-loc-params": { - "level": "error" - } + "verbose": false, + "enabled": true, + "rules": { + "adminusername-should-not-be-literal": { + "level": "error" + }, + "artifacts-parameters": { + "level": "warning" + }, + "decompiler-cleanup": { + "level": "warning" + }, + "explicit-values-for-loc-params": { + "level": "error" + }, + "max-outputs": { + "level": "warning" + }, + "max-params": { + "level": "warning" + }, + "max-resources": { + "level": "warning" + }, + "max-variables": { + "level": "warning" + }, + "no-hardcoded-env-urls": { + "level": "warning" + }, + "no-hardcoded-location": { + "level": "error" + }, + "no-loc-expr-outside-params": { + "level": "error" + }, + "no-unnecessary-dependson": { + "level": "error" + }, + "no-unused-existing-resources": { + "level": "warning" + }, + "no-unused-params": { + "level": "error" + }, + "no-unused-vars": { + "level": "error" + }, + "outputs-should-not-contain-secrets": { + "level": "warning" + }, + "prefer-interpolation": { + "level": "error" + }, + "prefer-unquoted-property-names": { + "level": "warning" + }, + "protect-commandtoexecute-secrets": { + "level": "warning" + }, + "secure-parameter-default": { + "level": "error" + }, + "secure-params-in-nested-deploy": { + "level": "warning" + }, + "secure-secrets-in-params": { + "level": "warning" + }, + "simplify-interpolation": { + "level": "error" + }, + "use-parent-property": { + "level": "warning" + }, + "use-recent-api-versions": { + "level": "off" + }, + "use-resource-id-functions": { + "level": "warning" + }, + "use-stable-resource-identifiers": { + "level": "warning" + }, + "use-stable-vm-image": { + "level": "warning" } } + } } } \ No newline at end of file diff --git a/config/identity/CanadaPubSecALZ-main/identity.parameters.json b/config/identity/CanadaPubSecALZ-main/identity.parameters.json new file mode 100644 index 00000000..de75793c --- /dev/null +++ b/config/identity/CanadaPubSecALZ-main/identity.parameters.json @@ -0,0 +1,187 @@ +{ + "$schema": "https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-platform-identity.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "alertRuleName": "Identity Alerts", + "receivers": { + "app": [ + "identity@example.com" + ], + "sms": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ], + "email": [ + "identity@example.com" + ], + "voice": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ] + }, + "regions": [ + "Global", + "Canada Central", + "Canada East" + ], + "resourceGroupName": "service-health-alerts-rg", + "actionGroupName": "Identity Alerts", + "actionGroupShortName": "identity-ag", + "incidentTypes": [ + "Incident", + "Security" + ], + "alertRuleDescription": "Identity Alerts for Incidents and Security" + } + }, + "securityCenter": { + "value": { + "email": "security@example.com", + "phone": "6135555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Contributor Role", + "securityGroupObjectIds": [ + "b4df54ba-7232-40fa-8f51-f84e8d149322" + ], + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c" + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tbd", + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "automation", + "networking": "networking", + "networkWatcher": "NetworkWatcherRG", + "backupRecoveryVault": "backup", + "domainControllers": "DomainControllersRG", + "dnsResolver": "dns-resolverRG", + "dnsCondionalForwarders": "dns-CondionalForwardersRG", + "privateDnsZones": "pubsec-dns" + } + }, + "automation": { + "value": { + "name": "automation" + } + }, + "backupRecoveryVault": { + "value": { + "enabled": true, + "name": "backup-vault" + } + }, + "privateDnsZones": { + "value": { + "enabled": false, + "resourceGroupName": "pubsec-dns" + } + }, + "privateDnsResolver": { + "value": { + "enabled": true, + "name": "dns-resolver", + "inboundEndpointName": "dns-resolver-Inbound", + "outboundEndpointName": "dns-resolver-Outbound" + } + }, + "privateDnsResolverRuleset": { + "value": { + "enabled": true, + "name": "dns-resolver-ruleset", + "linkRuleSetToVnet": true, + "linkRuleSetToVnetName": "dns-resolver-vnet-link", + "forwardingRules": [ + { + "name": "default", + "domain": "dontMakeMeThink.local", + "state": "Enabled", + "targetDnsServers": [ + { + "ipAddress": "10.99.99.100" + }, + { + "ipAddress": "10.99.99.99" + } + ] + } + ] + } + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/4fd845de-f6c8-4e6d-9a87-c21c4ebf7edd/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4" + } + }, + "network": { + "value": { + "deployVnet": true, + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "id-vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.15.0.0/24" + ], + "subnets": { + "domainControllers": { + "comments": "Identity Subnet for Domain Controllers and VM-Based DNS Servers", + "name": "DomainControllers", + "addressPrefix": "10.15.0.0/27" + }, + "dnsResolverInbound": { + "comments": "Azure DNS Resolver Inbound Requests subnet", + "name": "AzureDNSResolver-Inbound", + "addressPrefix": "10.15.0.32/27" + }, + "dnsResolverOutbound": { + "comments": "Azure DNS Resolver Outbound Requests subnet", + "name": "AzureDNSResolver-Outbound", + "addressPrefix": "10.15.0.64/27" + }, + "optional": [] + } + } + } + } +} diff --git a/config/logging/CanadaPubSecALZ-main/logging.parameters.json b/config/logging/CanadaPubSecALZ-main/logging.parameters.json new file mode 100644 index 00000000..ee0ee242 --- /dev/null +++ b/config/logging/CanadaPubSecALZ-main/logging.parameters.json @@ -0,0 +1,149 @@ +{ + "$schema": "https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-platform-logging.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "alertRuleName": "Logging Alerts", + "receivers": { + "app": [ + "logging@example.com" + ], + "sms": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ], + "email": [ + "logging@example.com" + ], + "voice": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ] + }, + "regions": [ + "Global", + "Canada Central", + "Canada East" + ], + "resourceGroupName": "service-health-alerts-rg", + "actionGroupName": "Logging Alerts", + "actionGroupShortName": "logging-ag", + "incidentTypes": [ + "Incident", + "Security" + ], + "alertRuleDescription": "Logging Alerts for Incidents and Security" + } + }, + "securityCenter": { + "value": { + "email": "security@example.com", + "phone": "6135555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Contributor Role", + "securityGroupObjectIds": [ + "9e16fb9d-7ea4-43fb-a92c-a5dbe308f921" + ], + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c" + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tbd" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "logAnalyticsResourceGroupName": { + "value": "pubsec-central-logging" + }, + "logAnalyticsWorkspaceName": { + "value": "log-analytics-workspace" + }, + "logAnalyticsRetentionInDays": { + "value": 730 + }, + "logAnalyticsAutomationAccountName": { + "value": "automation-account" + }, + "dataCollectionRule": { + "value": { + "enabled": false, + "name": "DCR-AzureMonitorLogs", + "windowsEventLogs": [ + { + "streams": [ + "Microsoft-Event" + ], + "xPathQueries": [ + "Application!*[System[(Level=1 or Level=2 or Level=3)]]", + "Security!*[System[(band(Keywords,13510798882111488))]]", + "System!*[System[(Level=1 or Level=2 or Level=3)]]" + ], + "name": "eventLogsDataSource" + } + ], + "syslog": [ + { + "streams": [ + "Microsoft-Syslog" + ], + "facilityNames": [ + "auth", + "authpriv", + "cron", + "daemon", + "mark", + "kern", + "local0", + "local1", + "local2", + "local3", + "local4", + "local5", + "local6", + "local7", + "lpr", + "mail", + "news", + "syslog", + "user", + "uucp" + ], + "logLevels": [ + "Warning", + "Error", + "Critical", + "Alert", + "Emergency" + ], + "name": "sysLogsDataSource" + } + ] + } + } + } +} diff --git a/config/networking/CanadaPubSecALZ-main/hub-azfw-policy/azure-firewall-policy.parameters.json b/config/networking/CanadaPubSecALZ-main/hub-azfw-policy/azure-firewall-policy.parameters.json new file mode 100644 index 00000000..becb6455 --- /dev/null +++ b/config/networking/CanadaPubSecALZ-main/hub-azfw-policy/azure-firewall-policy.parameters.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-platform-connectivity-hub-azfw-policy.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroupName": { + "value": "pubsec-azure-firewall-policy" + }, + "policyName": { + "value": "pubsecAzureFirewallPolicy" + } + } +} diff --git a/config/networking/CanadaPubSecALZ-main/hub-azfw/hub-network.parameters.json b/config/networking/CanadaPubSecALZ-main/hub-azfw/hub-network.parameters.json new file mode 100644 index 00000000..b6ecc49b --- /dev/null +++ b/config/networking/CanadaPubSecALZ-main/hub-azfw/hub-network.parameters.json @@ -0,0 +1,231 @@ +{ + "$schema": "https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-platform-connectivity-hub-azfw.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "alertRuleName": "Networking Alerts", + "receivers": { + "app": [ + "networking@example.com" + ], + "sms": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ], + "email": [ + "networking@example.com" + ], + "voice": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ] + }, + "regions": [ + "Global", + "Canada Central", + "Canada East" + ], + "resourceGroupName": "service-health-alerts-rg", + "actionGroupName": "Networking Alerts", + "actionGroupShortName": "network-ag", + "incidentTypes": [ + "Incident", + "Security" + ], + "alertRuleDescription": "Networking Alerts for Incidents and Security" + } + }, + "securityCenter": { + "value": { + "email": "security@example.com", + "phone": "6135555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Contributor Role", + "securityGroupObjectIds": [ + "12d01649-fdb7-4769-afe5-66248082a064" + ], + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c" + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tbd" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "privateDnsZones": { + "value": { + "enabled": true, + "resourceGroupName": "private-dns-rg" + } + }, + "ddosStandard": { + "value": { + "resourceGroupName": "ddos-rg", + "enabled": false, + "planName": "ddos-plan" + } + }, + "publicAccessZone": { + "value": { + "enabled": true, + "resourceGroupName": "pubsec-public-access-zone" + } + }, + "managementRestrictedZone": { + "value": { + "enabled": true, + "resourceGroupName": "pubsec-management-restricted-zone", + "network": { + "name": "management-restricted-vnet", + "addressPrefixes": [ + "10.18.4.0/22" + ], + "subnets": [ + { + "comments": "Management (Access Zone) Subnet", + "name": "MazSubnet", + "addressPrefix": "10.18.4.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "Infrastructure Services (Restricted Zone) Subnet", + "name": "InfSubnet", + "addressPrefix": "10.18.4.128/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "Security Services (Restricted Zone) Subnet", + "name": "SecSubnet", + "addressPrefix": "10.18.5.0/26", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "Logging Services (Restricted Zone) Subnet", + "name": "LogSubnet", + "addressPrefix": "10.18.5.64/26", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "Core Management Interfaces (Restricted Zone) Subnet", + "name": "MgmtSubnet", + "addressPrefix": "10.18.5.128/26", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + } + ] + } + } + }, + "hub": { + "value": { + "resourceGroupName": "pubsec-hub-networking", + "bastion": { + "enabled": true, + "name": "bastion", + "sku": "Standard", + "scaleUnits": 2 + }, + "azureFirewall": { + "name": "pubsecAzureFirewall", + "availabilityZones": [ + "1", + "2", + "3" + ], + "forcedTunnelingEnabled": false, + "forcedTunnelingNextHop": "10.17.1.4" + }, + "network": { + "name": "hub-vnet", + "addressPrefixes": [ + "10.18.0.0/22", + "100.60.0.0/16" + ], + "addressPrefixBastion": "192.168.0.0/16", + "subnets": { + "gateway": { + "comments": "Gateway Subnet used for VPN and/or Express Route connectivity", + "name": "GatewaySubnet", + "addressPrefix": "10.18.0.0/27" + }, + "firewall": { + "comments": "Azure Firewall", + "name": "AzureFirewallSubnet", + "addressPrefix": "10.18.1.0/24" + }, + "firewallManagement": { + "comments": "Azure Firewall Management", + "name": "AzureFirewallManagementSubnet", + "addressPrefix": "10.18.2.0/26" + }, + "bastion": { + "comments": "Azure Bastion", + "name": "AzureBastionSubnet", + "addressPrefix": "192.168.0.0/24" + }, + "publicAccess": { + "comments": "Public Access Zone (Application Gateway)", + "name": "PAZSubnet", + "addressPrefix": "100.60.1.0/24" + }, + "optional": [] + } + } + } + }, + "networkWatcher": { + "value": { + "resourceGroupName": "NetworkWatcherRG" + } + } + } +} diff --git a/config/networking/CanadaPubSecALZ-main/hub-nva/hub-network.parameters.json b/config/networking/CanadaPubSecALZ-main/hub-nva/hub-network.parameters.json new file mode 100644 index 00000000..2dcd55f5 --- /dev/null +++ b/config/networking/CanadaPubSecALZ-main/hub-nva/hub-network.parameters.json @@ -0,0 +1,318 @@ +{ + "$schema": "https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-platform-connectivity-hub-nva.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "alertRuleName": "Networking Alerts", + "receivers": { + "app": [ + "networking@example.com" + ], + "sms": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ], + "email": [ + "networking@example.com" + ], + "voice": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ] + }, + "regions": [ + "Global", + "Canada Central", + "Canada East" + ], + "resourceGroupName": "service-health-alerts-rg", + "actionGroupName": "Networking Alerts", + "actionGroupShortName": "network-ag", + "incidentTypes": [ + "Incident", + "Security" + ], + "alertRuleDescription": "Networking Alerts for Incidents and Security" + } + }, + "securityCenter": { + "value": { + "email": "security@example.com", + "phone": "6135555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Contributor Role", + "securityGroupObjectIds": [ + "12d01649-fdb7-4769-afe5-66248082a064" + ], + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c" + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tbd" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "privateDnsZones": { + "value": { + "enabled": true, + "resourceGroupName": "private-dns-rg" + } + }, + "ddosStandard": { + "value": { + "resourceGroupName": "ddos-rg", + "enabled": false, + "planName": "ddos-plan" + } + }, + "publicAccessZone": { + "value": { + "enabled": true, + "resourceGroupName": "pubsec-public-access-zone" + } + }, + "managementRestrictedZone": { + "value": { + "enabled": true, + "resourceGroupName": "pubsec-management-restricted-zone", + "network": { + "name": "management-restricted-vnet", + "addressPrefixes": [ + "10.18.4.0/22" + ], + "subnets": [ + { + "comments": "Management (Access Zone) Subnet", + "name": "MazSubnet", + "addressPrefix": "10.18.4.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "Infrastructure Services (Restricted Zone) Subnet", + "name": "InfSubnet", + "addressPrefix": "10.18.4.128/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "Security Services (Restricted Zone) Subnet", + "name": "SecSubnet", + "addressPrefix": "10.18.5.0/26", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "Logging Services (Restricted Zone) Subnet", + "name": "LogSubnet", + "addressPrefix": "10.18.5.64/26", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "Core Management Interfaces (Restricted Zone) Subnet", + "name": "MgmtSubnet", + "addressPrefix": "10.18.5.128/26", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + } + ] + } + } + }, + "hub": { + "value": { + "resourceGroupName": "pubsec-hub-networking", + "bastion": { + "enabled": true, + "name": "bastion", + "sku": "Standard", + "scaleUnits": 2 + }, + "network": { + "name": "hub-vnet", + "addressPrefixes": [ + "10.18.0.0/22", + "100.60.0.0/16" + ], + "addressPrefixBastion": "192.168.0.0/16", + "subnets": { + "gateway": { + "comments": "Gateway Subnet used for VPN and/or Express Route connectivity", + "name": "GatewaySubnet", + "addressPrefix": "10.18.1.0/27" + }, + "bastion": { + "comments": "Azure Bastion", + "name": "AzureBastionSubnet", + "addressPrefix": "192.168.0.0/24" + }, + "public": { + "comments": "Public Subnet Name (External Facing (Internet/Ground))", + "name": "PublicSubnet", + "addressPrefix": "100.60.0.0/24" + }, + "publicAccessZone": { + "comments": "Public Access Zone (i.e. Application Gateway)", + "name": "PAZSubnet", + "addressPrefix": "100.60.1.0/24" + }, + "externalAccessNetwork": { + "comments": "External Access Network", + "name": "EanSubnet", + "addressPrefix": "10.18.0.0/27" + }, + "nonProductionInternal": { + "comments": "Non-production Internal for firewall appliances (Internal Facing Non-Production Traffic)", + "name": "DevIntSubnet", + "addressPrefix": "10.18.0.64/27" + }, + "productionInternal": { + "comments": "Production Internal for firewall appliances (Internal Facing Production Traffic)", + "name": "PrdIntSubnet", + "addressPrefix": "10.18.0.32/27" + }, + "managementRestrictedZoneInternal": { + "comments": "Management Restricted Zone", + "name": "MrzSubnet", + "addressPrefix": "10.18.0.96/27" + }, + "highAvailability": { + "comments": "High Availability (Firewall to Firewall heartbeat)", + "name": "HASubnet", + "addressPrefix": "10.18.0.128/28" + }, + "optional": [] + } + }, + "nvaFirewall": { + "image": { + "publisher": "fortinet", + "offer": "fortinet_fortigate-vm_v5", + "sku": "fortinet_fg-vm", + "version": "6.4.5", + "plan": "fortinet_fg-vm" + }, + "nonProduction": { + "internalLoadBalancer": { + "name": "pubsecDevFWILB", + "tcpProbe": { + "name": "lbprobe", + "port": 8008, + "intervalInSeconds": 5, + "numberOfProbes": 2 + }, + "internalIp": "10.18.0.68", + "externalIp": "100.60.0.7" + }, + "deployVirtualMachines": false, + "virtualMachines": [ + { + "name": "pubsecDevFW1", + "vmSku": "Standard_D8s_v4", + "internalIp": "10.18.0.69", + "externalIp": "100.60.0.8", + "mrzInternalIp": "10.18.0.104", + "highAvailabilityIp": "10.18.0.134", + "availabilityZone": "2" + }, + { + "name": "pubsecDevFW2", + "vmSku": "Standard_D8s_v4", + "internalIp": "10.18.0.70", + "externalIp": "100.60.0.9", + "mrzInternalIp": "10.18.0.105", + "highAvailabilityIp": "10.18.0.135", + "availabilityZone": "3" + } + ] + }, + "production": { + "internalLoadBalancer": { + "name": "pubsecProdFWILB", + "tcpProbe": { + "name": "lbprobe", + "port": 8008, + "intervalInSeconds": 5, + "numberOfProbes": 2 + }, + "internalIp": "10.18.0.36", + "externalIp": "100.60.0.4" + }, + "deployVirtualMachines": false, + "virtualMachines": [ + { + "name": "pubsecProdFW1", + "vmSku": "Standard_F8s_v2", + "internalIp": "10.18.0.37", + "externalIp": "100.60.0.5", + "mrzInternalIp": "10.18.0.101", + "highAvailabilityIp": "10.18.0.132", + "availabilityZone": "1" + }, + { + "name": "pubsecProdFW2", + "vmSku": "Standard_F8s_v2", + "internalIp": "10.18.0.38", + "externalIp": "100.60.0.6", + "mrzInternalIp": "10.18.0.102", + "highAvailabilityIp": "10.18.0.133", + "availabilityZone": "2" + } + ] + } + } + } + }, + "networkWatcher": { + "value": { + "resourceGroupName": "NetworkWatcherRG" + } + } + } +} diff --git a/config/subscriptions/CanadaPubSecALZ-main/DevTest/0a5970ed-b9d5-464a-acef-8a06f0d7a360_machinelearning_canadacentral.json b/config/subscriptions/CanadaPubSecALZ-main/DevTest/0a5970ed-b9d5-464a-acef-8a06f0d7a360_machinelearning_canadacentral.json new file mode 100644 index 00000000..eb015a70 --- /dev/null +++ b/config/subscriptions/CanadaPubSecALZ-main/DevTest/0a5970ed-b9d5-464a-acef-8a06f0d7a360_machinelearning_canadacentral.json @@ -0,0 +1,298 @@ +{ + "$schema": "https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-machinelearning.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/FullDeployment-With-OptionalSubnets.json + "resourceGroupName": "service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + }, + "actionGroupName": "Service health action group", + "actionGroupShortName": "health-alert", + "alertRuleName": "Incidents and Security", + "alertRuleDescription": "Service Health: Incidents and Security" +======== + "alertRuleName": "Subscription Owners Alerts", + "receivers": { + "app": [ + "subscription-owners@example.com" + ], + "sms": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ], + "email": [ + "subscription-owners@example.com" + ], + "voice": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ] + }, + "regions": [ + "Global", + "Canada Central", + "Canada East" + ], + "resourceGroupName": "service-health-alerts-rg", + "actionGroupName": "Subscription Owners Alerts", + "actionGroupShortName": "sub-own-ag", + "incidentTypes": [ + "Incident", + "Security" + ], + "alertRuleDescription": "Subscription Owners Alerts for Incidents and Security" +>>>>>>>> upstream/main:config/subscriptions/CanadaPubSecALZ-main/DevTest/0a5970ed-b9d5-464a-acef-8a06f0d7a360_machinelearning_canadacentral.json + } + }, + "securityCenter": { + "value": { + "email": "security@example.com", + "phone": "6135555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Contributor Role", + "securityGroupObjectIds": [ + "49f9256a-d77a-43ba-8cc5-957eb40dedee" + ], + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c" + }, + { + "comments": "Custom Role: Landing Zone Application Owner", + "securityGroupObjectIds": [ + "43d97b7e-27b8-4fa7-a339-c935f7752bb9" + ], + "roleDefinitionId": "b4c87314-c1a1-5320-9c43-779585186bcc" + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": true, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ + "alzcanadapubsec@microsoft.com" + ] + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "logAnalyticsWorkspaceResourceId": { + "value": "/subscriptions/bc0a4f9f-07fa-4284-b1bd-fbad38578d3a/resourcegroups/pubsec-central-logging/providers/microsoft.operationalinsights/workspaces/log-analytics-workspace" + }, + "resourceGroups": { + "value": { +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/FullDeployment-With-OptionalSubnets.json + "automation": "azml-automation", + "compute": "azml-compute", + "monitor": "azml-monitor", + "networking": "azml-networking", + "networkWatcher": "NetworkWatcherRG", + "security": "azml-security", + "storage": "azml-storage" +======== + "automation": "azmlnocmk-automation", + "compute": "azmlnocmk-compute", + "monitor": "azmlnocmk-monitor", + "networking": "azmlnocmk-networking", + "networkWatcher": "NetworkWatcherRG", + "security": "azmlnocmk-security", + "storage": "azmlnocmk-storage" +>>>>>>>> upstream/main:config/subscriptions/CanadaPubSecALZ-main/DevTest/0a5970ed-b9d5-464a-acef-8a06f0d7a360_machinelearning_canadacentral.json + } + }, + "useCMK": { + "value": true + }, + "automation": { + "value": { + "name": "automation" + } + }, + "keyVault": { + "value": { + "secretExpiryInDays": 3650 + } + }, + "aks": { + "value": { +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/FullDeployment-With-OptionalSubnets.json + "version": "1.22.6", +======== + "version": "1.25.5", +>>>>>>>> upstream/main:config/subscriptions/CanadaPubSecALZ-main/DevTest/0a5970ed-b9d5-464a-acef-8a06f0d7a360_machinelearning_canadacentral.json + "enabled": true, + "networkPlugin": "kubenet", + "networkPolicy": "calico", + "podCidr": "11.0.0.0/16", + "serviceCidr": "20.0.0.0/16", + "dnsServiceIP": "20.0.0.10", + "dockerBridgeCidr": "30.0.0.1/16" + } + }, + "appServiceLinuxContainer": { + "value": { + "enabled": true, + "skuName": "P1V2", + "skuTier": "Premium", + "enablePrivateEndpoint": true + } + }, + "sqldb": { + "value": { + "enabled": true, +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/FullDeployment-With-OptionalSubnets.json + "aadAuthenticationOnly": true, + "aadLoginName": "DBA Group", + "aadLoginObjectID": "4e4ea47c-ee21-4add-ad2f-a75d0d8014e0", + "aadLoginType": "Group" +======== + "sqlAuthenticationUsername": "azadmin", + "aadAuthenticationOnly": false +>>>>>>>> upstream/main:config/subscriptions/CanadaPubSecALZ-main/DevTest/0a5970ed-b9d5-464a-acef-8a06f0d7a360_machinelearning_canadacentral.json + } + }, + "sqlmi": { + "value": { + "enabled": true, + "username": "azadmin" + } + }, + "aml": { + "value": { + "enableHbiWorkspace": false + } + }, + "hubNetwork": { + "value": { +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/FullDeployment-With-OptionalSubnets.json + "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet", +======== + "virtualNetworkId": "/subscriptions/4fd845de-f6c8-4e6d-9a87-c21c4ebf7edd/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet", +>>>>>>>> upstream/main:config/subscriptions/CanadaPubSecALZ-main/DevTest/0a5970ed-b9d5-464a-acef-8a06f0d7a360_machinelearning_canadacentral.json + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4", + "privateDnsManagedByHub": true, +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/FullDeployment-With-OptionalSubnets.json + "privateDnsManagedByHubSubscriptionId": "ed7f4eed-9010-4227-b115-2a5e37728f27", + "privateDnsManagedByHubResourceGroupName": "pubsec-dns" +======== + "privateDnsManagedByHubSubscriptionId": "4fd845de-f6c8-4e6d-9a87-c21c4ebf7edd", + "privateDnsManagedByHubResourceGroupName": "private-dns-rg" +>>>>>>>> upstream/main:config/subscriptions/CanadaPubSecALZ-main/DevTest/0a5970ed-b9d5-464a-acef-8a06f0d7a360_machinelearning_canadacentral.json + } + }, + "network": { + "value": { + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/FullDeployment-With-OptionalSubnets.json + "name": "azml-vnet", +======== + "name": "azmlnocmk-vnet", +>>>>>>>> upstream/main:config/subscriptions/CanadaPubSecALZ-main/DevTest/0a5970ed-b9d5-464a-acef-8a06f0d7a360_machinelearning_canadacentral.json + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.1.0.0/16" + ], + "subnets": { + "sqlmi": { + "comments": "SQL Managed Instances Delegated Subnet", + "name": "sqlmi", + "addressPrefix": "10.1.5.0/25" + }, + "databricksPublic": { + "comments": "Databricks Public Delegated Subnet", + "name": "databrickspublic", + "addressPrefix": "10.1.6.0/25" + }, + "databricksPrivate": { + "comments": "Databricks Private Delegated Subnet", + "name": "databricksprivate", + "addressPrefix": "10.1.7.0/25" + }, + "privateEndpoints": { + "comments": "Private Endpoints Subnet", + "name": "privateendpoints", + "addressPrefix": "10.1.8.0/25" + }, + "aks": { + "comments": "AKS Subnet", + "name": "aks", + "addressPrefix": "10.1.9.0/25" + }, + "appService": { + "comments": "App Service Subnet", + "name": "appService", +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/FullDeployment-With-OptionalSubnets.json + "addressPrefix": "10.1.10.0/25" + }, + "optional": [ + { + "comments": "Optional Subnet 1", + "name": "virtualMachines", + "addressPrefix": "10.1.11.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "Optional Subnet 2 with delegation for NetApp Volumes", + "name": "NetappVolumes", + "addressPrefix": "10.1.12.0/25", + "nsg": { + "enabled": false + }, + "udr": { + "enabled": false + }, + "delegations": { + "serviceName": "Microsoft.NetApp/volumes" + } + } + ] +======== + "addressPrefix": "10.3.10.0/25" + }, + "optional": [] +>>>>>>>> upstream/main:config/subscriptions/CanadaPubSecALZ-main/DevTest/0a5970ed-b9d5-464a-acef-8a06f0d7a360_machinelearning_canadacentral.json + } + } + } + } +} diff --git a/config/subscriptions/CanadaPubSecALZ-main/DevTest/1f519216-5e39-4b51-a9b6-10cc2b79b6c7_healthcare_canadacentral.json b/config/subscriptions/CanadaPubSecALZ-main/DevTest/1f519216-5e39-4b51-a9b6-10cc2b79b6c7_healthcare_canadacentral.json new file mode 100644 index 00000000..33ad6983 --- /dev/null +++ b/config/subscriptions/CanadaPubSecALZ-main/DevTest/1f519216-5e39-4b51-a9b6-10cc2b79b6c7_healthcare_canadacentral.json @@ -0,0 +1,200 @@ +{ + "$schema": "https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-healthcare.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "alertRuleName": "Subscription Owners Alerts", + "receivers": { + "app": [ + "subscription-owners@example.com" + ], + "sms": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ], + "email": [ + "subscription-owners@example.com" + ], + "voice": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ] + }, + "regions": [ + "Global", + "Canada Central", + "Canada East" + ], + "resourceGroupName": "service-health-alerts-rg", + "actionGroupName": "Subscription Owners Alerts", + "actionGroupShortName": "sub-own-ag", + "incidentTypes": [ + "Incident", + "Security" + ], + "alertRuleDescription": "Subscription Owners Alerts for Incidents and Security" + } + }, + "securityCenter": { + "value": { + "email": "security@example.com", + "phone": "6135555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Contributor Role", + "securityGroupObjectIds": [ + "49f9256a-d77a-43ba-8cc5-957eb40dedee" + ], + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c" + }, + { + "comments": "Custom Role: Landing Zone Application Owner", + "securityGroupObjectIds": [ + "43d97b7e-27b8-4fa7-a339-c935f7752bb9" + ], + "roleDefinitionId": "b4c87314-c1a1-5320-9c43-779585186bcc" + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "health-automation", + "compute": "health-compute", + "monitor": "health-monitor", + "networking": "health-network", + "networkWatcher": "NetworkWatcherRG", + "security": "health-security", + "storage": "health-storage" + } + }, + "useCMK": { + "value": true + }, + "keyVault": { + "value": { + "secretExpiryInDays": 3650 + } + }, + "automation": { + "value": { + "name": "automation" + } + }, + "sqldb": { + "value": { + "enabled": true, + "sqlAuthenticationUsername": "azadmin", + "aadAuthenticationOnly": false + } + }, + "synapse": { + "value": { + "aadAuthenticationOnly": true, + "aadLoginName": "az.admins", + "aadLoginObjectID": "e0357d81-55d8-44e9-9d9c-ab09dc710785", + "aadLoginType": "Group" + } + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/4fd845de-f6c8-4e6d-9a87-c21c4ebf7edd/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4", + "privateDnsManagedByHub": true, + "privateDnsManagedByHubSubscriptionId": "4fd845de-f6c8-4e6d-9a87-c21c4ebf7edd", + "privateDnsManagedByHubResourceGroupName": "private-dns-rg" + } + }, + "network": { + "value": { + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "health-vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.5.0.0/16" + ], + "subnets": { + "databricksPublic": { + "comments": "Databricks Public Delegated Subnet", + "name": "databrickspublic", + "addressPrefix": "10.5.5.0/25" + }, + "databricksPrivate": { + "comments": "Databricks Private Delegated Subnet", + "name": "databricksprivate", + "addressPrefix": "10.5.6.0/25" + }, + "privateEndpoints": { + "comments": "Private Endpoints Subnet", + "name": "privateendpoints", + "addressPrefix": "10.5.7.0/25" + }, + "web": { + "comments": "Azure Web App Delegated Subnet", + "name": "webapp", + "addressPrefix": "10.5.8.0/25" + }, + "optional": [ + { + "comments": "Optional Subnet 1", + "name": "virtualMachines", + "addressPrefix": "10.5.9.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "Optional Subnet 2 with delegation for NetApp Volumes", + "name": "NetappVolumes", + "addressPrefix": "10.5.10.0/25", + "nsg": { + "enabled": false + }, + "udr": { + "enabled": false + }, + "delegations": { + "serviceName": "Microsoft.NetApp/volumes" + } + } + ] + } + } + } + } +} diff --git a/config/subscriptions/CanadaPubSecALZ-main/DevTest/8422552f-3840-4934-a971-6ee349ffbb05_generic-subscription_canadacentral.json b/config/subscriptions/CanadaPubSecALZ-main/DevTest/8422552f-3840-4934-a971-6ee349ffbb05_generic-subscription_canadacentral.json new file mode 100644 index 00000000..f7cd93c6 --- /dev/null +++ b/config/subscriptions/CanadaPubSecALZ-main/DevTest/8422552f-3840-4934-a971-6ee349ffbb05_generic-subscription_canadacentral.json @@ -0,0 +1,192 @@ +{ + "$schema": "https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-generic-subscription.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "location": { + "value": "canadacentral" + }, + "serviceHealthAlerts": { + "value": { + "alertRuleName": "Subscription Owners Alerts", + "receivers": { + "app": [ + "subscription-owners@example.com" + ], + "sms": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ], + "email": [ + "subscription-owners@example.com" + ], + "voice": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ] + }, + "regions": [ + "Global", + "Canada Central", + "Canada East" + ], + "resourceGroupName": "service-health-alerts-rg", + "actionGroupName": "Subscription Owners Alerts", + "actionGroupShortName": "sub-own-ag", + "incidentTypes": [ + "Incident", + "Security" + ], + "alertRuleDescription": "Subscription Owners Alerts for Incidents and Security" + } + }, + "securityCenter": { + "value": { + "email": "security@example.com", + "phone": "6135555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Contributor Role", + "securityGroupObjectIds": [ + "49f9256a-d77a-43ba-8cc5-957eb40dedee" + ], + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c" + }, + { + "comments": "Custom Role: Landing Zone Application Owner", + "securityGroupObjectIds": [ + "43d97b7e-27b8-4fa7-a339-c935f7752bb9" + ], + "roleDefinitionId": "b4c87314-c1a1-5320-9c43-779585186bcc" + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "automation", + "networking": "networking", + "networkWatcher": "NetworkWatcherRG", + "backupRecoveryVault": "backup" + } + }, + "automation": { + "value": { + "name": "automation" + } + }, + "backupRecoveryVault": { + "value": { + "enabled": true, + "name": "backup-vault" + } + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/4fd845de-f6c8-4e6d-9a87-c21c4ebf7edd/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4" + } + }, + "network": { + "value": { + "deployVnet": true, + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.2.0.0/16" + ], + "subnets": [ + { + "comments": "App Management Zone (OZ)", + "name": "appManagement", + "addressPrefix": "10.2.1.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "Presentation Zone (PAZ)", + "name": "web", + "addressPrefix": "10.2.2.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "Application Zone (RZ)", + "name": "app", + "addressPrefix": "10.2.3.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "Data Zone (HRZ)", + "name": "data", + "addressPrefix": "10.2.4.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "App Service", + "name": "appservice", + "addressPrefix": "10.2.5.0/25", + "nsg": { + "enabled": false + }, + "udr": { + "enabled": false + }, + "delegations": { + "serviceName": "Microsoft.Web/serverFarms" + } + } + ] + } + } + } +} diff --git a/config/subscriptions/CanadaPubSecALZ-main/DevTest/9d9817f5-c218-4553-b686-58be8d9ea82c_machinelearning_canadacentral.json b/config/subscriptions/CanadaPubSecALZ-main/DevTest/9d9817f5-c218-4553-b686-58be8d9ea82c_machinelearning_canadacentral.json new file mode 100644 index 00000000..e69de29b diff --git a/config/subscriptions/CanadaPubSecALZ-main/DevTest/bbe30f1a-fda5-4873-b51b-c838cf3f1a61_machinelearning_canadacentral.json b/config/subscriptions/CanadaPubSecALZ-main/DevTest/bbe30f1a-fda5-4873-b51b-c838cf3f1a61_machinelearning_canadacentral.json new file mode 100644 index 00000000..e6c64226 --- /dev/null +++ b/config/subscriptions/CanadaPubSecALZ-main/DevTest/bbe30f1a-fda5-4873-b51b-c838cf3f1a61_machinelearning_canadacentral.json @@ -0,0 +1,207 @@ +{ + "$schema": "https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-machinelearning.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "alertRuleName": "Subscription Owners Alerts", + "receivers": { + "app": [ + "subscription-owners@example.com" + ], + "sms": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ], + "email": [ + "subscription-owners@example.com" + ], + "voice": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ] + }, + "regions": [ + "Global", + "Canada Central", + "Canada East" + ], + "resourceGroupName": "service-health-alerts-rg", + "actionGroupName": "Subscription Owners Alerts", + "actionGroupShortName": "sub-own-ag", + "incidentTypes": [ + "Incident", + "Security" + ], + "alertRuleDescription": "Subscription Owners Alerts for Incidents and Security" + } + }, + "securityCenter": { + "value": { + "email": "security@example.com", + "phone": "6135555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Contributor Role", + "securityGroupObjectIds": [ + "49f9256a-d77a-43ba-8cc5-957eb40dedee" + ], + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c" + }, + { + "comments": "Custom Role: Landing Zone Application Owner", + "securityGroupObjectIds": [ + "43d97b7e-27b8-4fa7-a339-c935f7752bb9" + ], + "roleDefinitionId": "b4c87314-c1a1-5320-9c43-779585186bcc" + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "azmlcmksqlmi-automation", + "compute": "azmlcmksqlmi-compute", + "monitor": "azmlcmksqlmi-monitor", + "networking": "azmlcmksqlmi-networking", + "networkWatcher": "NetworkWatcherRG", + "security": "azmlcmksqlmi-security", + "storage": "azmlcmksqlmi-storage" + } + }, + "useCMK": { + "value": true + }, + "automation": { + "value": { + "name": "automation" + } + }, + "keyVault": { + "value": { + "secretExpiryInDays": 3650 + } + }, + "aks": { + "value": { + "version": "1.25.5", + "enabled": true, + "networkPlugin": "kubenet", + "networkPolicy": "calico", + "podCidr": "11.0.0.0/16", + "serviceCidr": "20.0.0.0/16", + "dnsServiceIP": "20.0.0.10", + "dockerBridgeCidr": "30.0.0.1/16" + } + }, + "appServiceLinuxContainer": { + "value": { + "enabled": true, + "skuName": "P1V2", + "skuTier": "Premium", + "enablePrivateEndpoint": true + } + }, + "sqldb": { + "value": { + "enabled": true, + "sqlAuthenticationUsername": "azadmin", + "aadAuthenticationOnly": false + } + }, + "sqlmi": { + "value": { + "enabled": true, + "username": "azadmin" + } + }, + "aml": { + "value": { + "enableHbiWorkspace": false + } + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/4fd845de-f6c8-4e6d-9a87-c21c4ebf7edd/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4", + "privateDnsManagedByHub": true, + "privateDnsManagedByHubSubscriptionId": "4fd845de-f6c8-4e6d-9a87-c21c4ebf7edd", + "privateDnsManagedByHubResourceGroupName": "private-dns-rg" + } + }, + "network": { + "value": { + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "azmlcmksqlmi-vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.4.0.0/16" + ], + "subnets": { + "sqlmi": { + "comments": "SQL Managed Instances Delegated Subnet", + "name": "sqlmi", + "addressPrefix": "10.4.5.0/25" + }, + "databricksPublic": { + "comments": "Databricks Public Delegated Subnet", + "name": "databrickspublic", + "addressPrefix": "10.4.6.0/25" + }, + "databricksPrivate": { + "comments": "Databricks Private Delegated Subnet", + "name": "databricksprivate", + "addressPrefix": "10.4.7.0/25" + }, + "privateEndpoints": { + "comments": "Private Endpoints Subnet", + "name": "privateendpoints", + "addressPrefix": "10.4.8.0/25" + }, + "aks": { + "comments": "AKS Subnet", + "name": "aks", + "addressPrefix": "10.4.9.0/25" + }, + "appService": { + "comments": "App Service Subnet", + "name": "appService", + "addressPrefix": "10.4.10.0/25" + }, + "optional": [] + } + } + } + } +} diff --git a/config/subscriptions/CanadaPubSecALZ-main/DevTest/f881fccb-2598-4b9c-b87c-b392f5e16f12_machinelearning_canadacentral.json b/config/subscriptions/CanadaPubSecALZ-main/DevTest/f881fccb-2598-4b9c-b87c-b392f5e16f12_machinelearning_canadacentral.json new file mode 100644 index 00000000..3be3347c --- /dev/null +++ b/config/subscriptions/CanadaPubSecALZ-main/DevTest/f881fccb-2598-4b9c-b87c-b392f5e16f12_machinelearning_canadacentral.json @@ -0,0 +1,235 @@ +{ + "$schema": "https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-machinelearning.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "alertRuleName": "Subscription Owners Alerts", + "receivers": { + "app": [ + "subscription-owners@example.com" + ], + "sms": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ], + "email": [ + "subscription-owners@example.com" + ], + "voice": [ + { + "countryCode": "1", + "phoneNumber": "6135555555" + } + ] + }, + "regions": [ + "Global", + "Canada Central", + "Canada East" + ], + "resourceGroupName": "service-health-alerts-rg", + "actionGroupName": "Subscription Owners Alerts", + "actionGroupShortName": "sub-own-ag", + "incidentTypes": [ + "Incident", + "Security" + ], + "alertRuleDescription": "Subscription Owners Alerts for Incidents and Security" + } + }, + "securityCenter": { + "value": { + "email": "security@example.com", + "phone": "6135555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Contributor Role", + "securityGroupObjectIds": [ + "49f9256a-d77a-43ba-8cc5-957eb40dedee" + ], + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c" + }, + { + "comments": "Custom Role: Landing Zone Application Owner", + "securityGroupObjectIds": [ + "43d97b7e-27b8-4fa7-a339-c935f7752bb9" + ], + "roleDefinitionId": "b4c87314-c1a1-5320-9c43-779585186bcc" + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "azmlsqlauth-automation", + "compute": "azmlsqlauth-compute", + "monitor": "azmlsqlauth-monitor", + "networking": "azmlsqlauth-networking", + "networkWatcher": "NetworkWatcherRG", + "security": "azmlsqlauth-security", + "storage": "azmlsqlauth-storage" + } + }, + "useCMK": { + "value": false + }, + "automation": { + "value": { + "name": "automation" + } + }, + "keyVault": { + "value": { + "secretExpiryInDays": 3650 + } + }, + "aks": { + "value": { + "version": "1.25.5", + "enabled": true, + "networkPlugin": "kubenet", + "networkPolicy": "calico", + "podCidr": "11.0.0.0/16", + "serviceCidr": "20.0.0.0/16", + "dnsServiceIP": "20.0.0.10", + "dockerBridgeCidr": "30.0.0.1/16" + } + }, + "appServiceLinuxContainer": { + "value": { + "enabled": true, + "skuName": "P1V2", + "skuTier": "Premium", + "enablePrivateEndpoint": true + } + }, + "sqldb": { + "value": { + "enabled": true, + "sqlAuthenticationUsername": "azadmin", + "aadAuthenticationOnly": false, + "aadLoginName": "DBA Security Group", + "aadLoginObjectID": "e0357d81-55d8-44e9-9d9c-ab09dc710785", + "aadLoginType": "Group" + } + }, + "sqlmi": { + "value": { + "enabled": false + } + }, + "aml": { + "value": { + "enableHbiWorkspace": false + } + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/4fd845de-f6c8-4e6d-9a87-c21c4ebf7edd/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4", + "privateDnsManagedByHub": true, + "privateDnsManagedByHubSubscriptionId": "4fd845de-f6c8-4e6d-9a87-c21c4ebf7edd", + "privateDnsManagedByHubResourceGroupName": "private-dns-rg" + } + }, + "network": { + "value": { + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "azmlsqlauth-vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.6.0.0/16" + ], + "subnets": { + "sqlmi": { + "comments": "SQL Managed Instances Delegated Subnet", + "name": "sqlmi", + "addressPrefix": "10.6.5.0/25" + }, + "databricksPublic": { + "comments": "Databricks Public Delegated Subnet", + "name": "databrickspublic", + "addressPrefix": "10.6.6.0/25" + }, + "databricksPrivate": { + "comments": "Databricks Private Delegated Subnet", + "name": "databricksprivate", + "addressPrefix": "10.6.7.0/25" + }, + "privateEndpoints": { + "comments": "Private Endpoints Subnet", + "name": "privateendpoints", + "addressPrefix": "10.6.8.0/25" + }, + "aks": { + "comments": "AKS Subnet", + "name": "aks", + "addressPrefix": "10.6.9.0/25" + }, + "appService": { + "comments": "App Service Subnet", + "name": "appService", + "addressPrefix": "10.6.10.0/25" + }, + "optional": [ + { + "comments": "Optional Subnet 1", + "name": "virtualMachines", + "addressPrefix": "10.6.11.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + { + "comments": "Optional Subnet 2 with delegation for NetApp Volumes", + "name": "NetappVolumes", + "addressPrefix": "10.6.12.0/25", + "nsg": { + "enabled": false + }, + "udr": { + "enabled": false + }, + "delegations": { + "serviceName": "Microsoft.NetApp/volumes" + } + } + ] + } + } + } + } +} diff --git a/config/telemetry.json b/config/telemetry.json index a66e9a87..2cf9d111 100644 --- a/config/telemetry.json +++ b/config/telemetry.json @@ -10,6 +10,7 @@ "nvaFortinet": "a83f6385-f514-415f-991b-2d9bd7aed658", "azureFirewall": "a83f6385-f514-415f-991b-2d9bd7aed658" }, + "identity": "a83f6385-f514-415f-991b-2d9bd7aed658", "archetypes": { "genericSubscription": "a83f6385-f514-415f-991b-2d9bd7aed658", "machineLearning": "a83f6385-f514-415f-991b-2d9bd7aed658", diff --git a/config/variables/CanadaPubSecALZ-main.yml b/config/variables/CanadaPubSecALZ-main.yml new file mode 100644 index 00000000..010a4503 --- /dev/null +++ b/config/variables/CanadaPubSecALZ-main.yml @@ -0,0 +1,78 @@ +variables: + var-hubnetwork-region: canadacentral + var-hubnetwork-managementGroupId: Connectivity + var-hubnetwork-azfw-configurationFileName: hub-azfw/hub-network.parameters.json + var-hubnetwork-nva-configurationFileName: hub-nva/hub-network.parameters.json + var-hubnetwork-subscriptionId: 4fd845de-f6c8-4e6d-9a87-c21c4ebf7edd + var-hubnetwork-azfwPolicy-configurationFileName: hub-azfw-policy/azure-firewall-policy.parameters.json + var-logging-managementGroupId: Management + var-logging-subscriptionId: 91e1aaa2-a0a0-4770-8d90-02daa39bf57a + deploymentRegion: canadacentral + var-identity-managementGroupId: Identity + var-logging-diagnosticSettingsforNetworkSecurityGroupsStoragePrefix: pubsecnsg + var-logging-configurationFileName: logging.parameters.json + var-managementgroup-hierarchy: |- + { + "id": "0d466ba2-7ea1-420f-9820-2583fc040733", + "name": "Tenant Root Group", + "children": [ + { + "id": "pubsec", + "name": "Canadian Public Sector Azure Landing Zones", + "children": [ + { + "id": "Platform", + "name": "Platform", + "children": [ + { + "id": "Management", + "name": "Management", + "children": [] + }, + { + "id": "Connectivity", + "name": "Connectivity", + "children": [] + }, + { + "id": "Identity", + "name": "Identity", + "children": [] + } + ] + }, + { + "id": "LandingZones", + "name": "LandingZones", + "children": [ + { + "id": "DevTest", + "name": "DevTest", + "children": [] + }, + { + "id": "QA", + "name": "QA", + "children": [] + }, + { + "id": "Prod", + "name": "Prod", + "children": [] + } + ] + }, + { + "id": "Sandbox", + "name": "Sandbox", + "children": [] + } + ] + } + ] + } + var-logging-region: canadacentral + var-identity-configurationFileName: identity.parameters.json + var-identity-region: canadacentral + var-identity-subscriptionId: 2987c7a3-1e43-4b28-b983-ac0925e37d03 + diff --git a/config/variables/common.yml b/config/variables/common.yml index e605f06d..4754396e 100644 --- a/config/variables/common.yml +++ b/config/variables/common.yml @@ -16,6 +16,7 @@ variables: loggingPathFromRoot: 'config/logging' networkPathFromRoot: 'config/networking' + identityPathFromRoot: 'config/identity' subscriptionsPathFromRoot: 'config/subscriptions' var-bashPreInjectScript: 'set -E; function catch { echo "##vso[task.logissue type=error]Caller: $(caller), LineNo: $LINENO, Command: $BASH_COMMAND" ; exit 1 ; } ; echo ; echo "Current working directory: $(pwd)" ; echo ; trap catch ERR' diff --git a/docs/archetypes/authoring-guide.md b/docs/archetypes/authoring-guide.md index bd1653c3..612e2fbe 100644 --- a/docs/archetypes/authoring-guide.md +++ b/docs/archetypes/authoring-guide.md @@ -1,6 +1,6 @@ # Archetype Authoring Guide -[Azure landing zones](https://docs.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/) are the output of a multi-subscription Azure environment that accounts for scale, security governance, networking, and identity. Therefore, deploying an archetype will result in an Azure landing zone that can be enhanced, scaled and refined based on business need. +[Azure landing zones](https://learn.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/) are the output of a multi-subscription Azure environment that accounts for scale, security governance, networking, and identity. Therefore, deploying an archetype will result in an Azure landing zone that can be enhanced, scaled and refined based on business need. This reference implementation provides a number of archetypes that can be used as-is or customized further to suit business needs. Archetypes are self-contained Bicep deployment templates that are used to configure multiple subscriptions. Archetypes provide the ability to configure new subscriptions with use case specific architecture in a repeatable method. One archetype can be used to configure many subscriptions. @@ -233,7 +233,7 @@ module subScaffold '../scaffold-subscription.bicep' = { ## JSON Schema for deployment parameters -Spoke archetypes are deployed to a subscription using a JSON parameters file. This parameters file defines all configuration expected by the archetype in order to deploy and configure a subscription. An archetype can have an arbitrary number of parameters (up to a [maximum of 256 parameters](https://docs.microsoft.com/azure/azure-resource-manager/templates/best-practices#template-limits)). +Spoke archetypes are deployed to a subscription using a JSON parameters file. This parameters file defines all configuration expected by the archetype in order to deploy and configure a subscription. An archetype can have an arbitrary number of parameters (up to a [maximum of 256 parameters](https://learn.microsoft.com/azure/azure-resource-manager/templates/best-practices#template-limits)). While these parameters offer customization benefits, they incur overhead when defining input values and correlating them to the resources that are deployed. To keep all related parameters together and to make them contextual, we've chosen to use `object` parameter type. This type can contain simple and complex nested types and offers greater flexibility when defining many related parameters together. For example: @@ -243,7 +243,7 @@ A simple object parameter used for configuring Microsoft Defender for Cloud: "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } } ``` @@ -259,8 +259,8 @@ A complex object parameter used for configuring Service Health alerts: "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Sub5 ALZ action group", "actionGroupShortName": "sub5-alert", @@ -270,7 +270,7 @@ A complex object parameter used for configuring Service Health alerts: } ``` -Azure Resource Manager templates (and by extension Bicep) does not support parameter validation for `object` type. Therefore, it's not possible to depend on Azure Resource Manager to perform pre-deployment validation. The input validation supported for parameters are described in [Azure Docs](https://docs.microsoft.com/azure/azure-resource-manager/templates/parameters). +Azure Resource Manager templates (and by extension Bicep) does not support parameter validation for `object` type. Therefore, it's not possible to depend on Azure Resource Manager to perform pre-deployment validation. The input validation supported for parameters are described in [Azure Docs](https://learn.microsoft.com/azure/azure-resource-manager/templates/parameters). As a result, we could either @@ -286,7 +286,7 @@ We chose to check the input parameters prior to deployment to identify misconfig ## Telemetry -This reference implementation is instrumented to track deployment telemetry per module through [customer usage attribution](https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution). When a new archetype is developed, the telemetry settings must be updated to reference the tracking id. Telemetry configuration is located at [`config/telemetry.json`](../../config/telemetry.json). +This reference implementation is instrumented to track deployment telemetry per module through [customer usage attribution](https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution). When a new archetype is developed, the telemetry settings must be updated to reference the tracking id. Telemetry configuration is located at [`config/telemetry.json`](../../config/telemetry.json). To support per-module tracking, we've split each archetype to be tracked independently. At the moment, a single tracking id is used for all modules and can be modified in the future when required. @@ -321,7 +321,7 @@ To support per-module tracking, we've split each archetype to be tracked indepen ```bicep // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../azresources/telemetry/customer-usage-attribution-subscription.bicep' = if (telemetry.customerUsageAttribution.enabled) { @@ -335,28 +335,28 @@ module telemetryCustomerUsageAttribution '../../azresources/telemetry/customer-u > Use the [Onboarding Guide for Azure DevOps](../onboarding/azure-devops-pipelines.md) to configure the `subscription` pipeline. This pipeline will deploy workload archetypes such as Generic Subscription, Machine Learning and Healthcare. -Azure Resource Manager (ARM) parameters files provide deployment information to setup subscriptions. Deployment information can include `location`, `resource group names`, `resource names` and `networking`. You can find more information in [Azure Docs](https://docs.microsoft.com/azure/azure-resource-manager/templates/parameter-files) on ARM parameter files. +Azure Resource Manager (ARM) parameters files provide deployment information to setup subscriptions. Deployment information can include `location`, `resource group names`, `resource names` and `networking`. You can find more information in [Azure Docs](https://learn.microsoft.com/azure/azure-resource-manager/templates/parameter-files) on ARM parameter files. These parameter files are located in [config/subscription](../../config/subscriptions) folder. This folder is configurable in `common.yml` and you can override in environment configuration files using the `subscriptionsPathFromRoot` setting. By default it is set to `config/subscriptions`. -Immediate subfolder defines the environment which is based on Azure DevOps Organization (i.e. `CanadaESLZ`) & Git branch name (i.e. `main`), for example the subfolder will be called `CanadaESLZ-main`. You can have many environments based on Git branch names such as `CanadaESLZ-feature-1`, `CanadaESLZ-dev`, etc. +Immediate subfolder defines the environment which is based on Azure DevOps Organization (i.e. `CanadaPubSecALZ`) & Git branch name (i.e. `main`), for example the subfolder will be called `CanadaPubSecALZ-main`. You can have many environments based on Git branch names such as `CanadaPubSecALZ-feature-1`, `CanadaPubSecALZ-dev`, etc. ARM parameter files are used by `subscriptions-ci` Azure DevOps Pipeline when configuring subscriptions with Azure resources. The pipeline will detect environment, management group, subscription, deployment location and deployment parameters using the folder hierarchy, file name and file content. For example when the file path is: -`config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/8c6e48a4-4c73-4a1f-9f95-9447804f2c98_machinelearning_canadacentral.json` +`config/subscriptions/CanadaPubSecALZ-main/pubsec/LandingZones/DevTest/8c6e48a4-4c73-4a1f-9f95-9447804f2c98_machinelearning_canadacentral.json` -- **Folder hierarchy:** config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/ +- **Folder hierarchy:** config/subscriptions/CanadaPubSecALZ-main/pubsec/LandingZones/DevTest/ - **File name:** 8c6e48a4-4c73-4a1f-9f95-9447804f2c98_machinelearning_canadacentral.json | Deployment Information | Approach | Example | |:---------------------- |:-------- |:------- | -| Environment | DevOps organization name & Git branch name | `CanadaESLZ-main` | -| Management Group | Calculated based on concatenating the folder hierarchy under `config/subscription/CanadaESLZ-main` | pubsecLandingZonesDevTest (without the `/`). [See below for details](#management-group-id-detection). +| Environment | DevOps organization name & Git branch name | `CanadaPubSecALZ-main` | +| Management Group | Calculated based on concatenating the folder hierarchy under `config/subscription/CanadaPubSecALZ-main` | pubsecLandingZonesDevTest (without the `/`). [See below for details](#management-group-id-detection). | Subscription | Part of the file name | `8c6e48a4-4c73-4a1f-9f95-9447804f2c98` | | Deployment location | Part of the file name | `canadacentral` | -| Deployment parameters | Content of the file | [See file content](../../config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/8c6e48a4-4c73-4a1f-9f95-9447804f2c98_machinelearning_canadacentral.json) | +| Deployment parameters | Content of the file | [See file content](../../config/subscriptions/CanadaPubSecALZ-main/pubsec/LandingZones/DevTest/8c6e48a4-4c73-4a1f-9f95-9447804f2c98_machinelearning_canadacentral.json) | The ARM parameter file name can be in one of two formats: @@ -402,7 +402,7 @@ The `subscriptions-ci` management group detection logic is built to accommodate - Folder structure in `config/subscription/` is created without including the prefixes. For example: ```none - config/subscription/CanadaESLZ-main + config/subscription/CanadaPubSecALZ-main - pubsec - LandingZones - DevTest @@ -415,7 +415,7 @@ The `subscriptions-ci` management group detection logic is built to accommodate - Folder structure in `config/subscription/` should be flat. For example: ```none - config/subscription/CanadaESLZ-main + config/subscription/CanadaPubSecALZ-main - pubsec - LandingZones - DevTest diff --git a/docs/archetypes/generic-subscription.md b/docs/archetypes/generic-subscription.md index e48f9460..7260d264 100644 --- a/docs/archetypes/generic-subscription.md +++ b/docs/archetypes/generic-subscription.md @@ -92,15 +92,15 @@ Reference implementation uses parameter files with `object` parameters to consol ### Delete Locks -As an administrator, you can lock a subscription, resource group, or resource to prevent other users in your organization from accidentally deleting or modifying critical resources. The lock overrides any permissions the user might have. You can set the lock level to `CanNotDelete` or `ReadOnly`. Please see [Azure Docs](https://docs.microsoft.com/azure/azure-resource-manager/management/lock-resources) for more information. +As an administrator, you can lock a subscription, resource group, or resource to prevent other users in your organization from accidentally deleting or modifying critical resources. The lock overrides any permissions the user might have. You can set the lock level to `CanNotDelete` or `ReadOnly`. Please see [Azure Docs](https://learn.microsoft.com/azure/azure-resource-manager/management/lock-resources) for more information. **This archetype does not use `CanNotDelete` nor `ReadOnly` locks as part of the deployment. You may customize the deployment templates when it's required for your environment.** ### Service Health -[Service health notifications](https://docs.microsoft.com/azure/service-health/service-health-notifications-properties) are published by Azure, and contain information about the resources under your subscription. Service health notifications can be informational or actionable, depending on the category. +[Service health notifications](https://learn.microsoft.com/azure/service-health/service-health-notifications-properties) are published by Azure, and contain information about the resources under your subscription. Service health notifications can be informational or actionable, depending on the category. -Our examples configure service health alerts for `Security` and `Incident`. However, these categories can be customized based on your need. Please review the possible options in [Azure Docs](https://docs.microsoft.com/azure/service-health/service-health-notifications-properties#details-on-service-health-level-information). +Our examples configure service health alerts for `Security` and `Incident`. However, these categories can be customized based on your need. Please review the possible options in [Azure Docs](https://learn.microsoft.com/azure/service-health/service-health-notifications-properties#details-on-service-health-level-information). ### Deployment Scenarios @@ -154,8 +154,8 @@ This example configures: "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -166,7 +166,7 @@ This example configures: "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/docs/archetypes/healthcare.md b/docs/archetypes/healthcare.md index f91c5aa2..fe903568 100644 --- a/docs/archetypes/healthcare.md +++ b/docs/archetypes/healthcare.md @@ -78,19 +78,19 @@ Subscription can be moved to a target Management Group through Azure ARM Templat | Category | Service | Configuration | Reference | | --- | --- | --- | --- | -| Storage | Azure Data Lake Gen 2 - Cloud storage enabling big data analytics. | Hierarchical namespace enabled. Optional – Customer Managed Keys. | [Azure Docs](https://docs.microsoft.com/azure/storage/blobs/data-lake-storage-introduction) -| Compute | Azure Databricks - Managed Spark cloud platform for data analytics and data science | Premium tier; Secured Cluster Connectivity enabled with load balancer for egress. | [Azure Docs](https://docs.microsoft.com/azure/databricks/scenarios/what-is-azure-databricks) | -| Compute | Azure Synapse - End-to-end cloud analytics and data warehousing platform. | Disabled public network access by default. Managed Private Endpoints for Compute & Synapse Studio. Optional – Customer Managed Keys. | [Managed Private Endpoints](https://docs.microsoft.com/azure/synapse-analytics/security/synapse-workspace-managed-private-endpoints) / [Connect to Synapse Studio with private links](https://docs.microsoft.com/azure/synapse-analytics/security/synapse-private-link-hubs) -| Compute | FHIR API - Fast Healthcare Interoperability Resources for healthcare medical exchange. | Private endpoint by default. | [Azure Docs](https://docs.microsoft.com/azure/healthcare-apis/fhir/) | -| Compute | Azure Stream Analytics | Real-time analytics and event-processing engine for process high volumes of fast streaming data from multiple sources simultaneously. | [Azure Docs](https://docs.microsoft.com/azure/stream-analytics/stream-analytics-introduction) -| Compute | Azure Function App - Serverless computing service | Virtual Network Integration for accessing resources in virtual network. | [Azure Docs](https://docs.microsoft.com/azure/azure-functions/functions-overview) -| Ingestion | Azure Data Factory - Managed cloud service for data integration and orchestration | Managed virtual network. Optional – Customer Managed Keys | [Azure Docs](https://docs.microsoft.com/azure/data-factory/introduction) | -| Ingestion | Event Hub - Data streaming platform and event ingestion service | N/A | [Azure Docs](https://docs.microsoft.com/azure/event-hubs/event-hubs-about) -| Machine learning and deployment | Azure Machine Learning - Cloud platform for end-to-end machine learning workflows | Optional – Customer Managed Keys, High Business Impact Workspace | [Azure Docs](https://docs.microsoft.com/azure/machine-learning/overview-what-is-azure-ml) | -| Machine learning and deployment | Azure Container Registry - Managed private Docker cloud registry | Premium SKU. Optional – Customer Managed Keys | [Azure Docs](https://docs.microsoft.com/azure/container-registry/container-registry-intro) | -| SQL Storage | Azure SQL Database - Fully managed cloud database engine | Optional – Customer Managed Keys | [Azure Docs](https://docs.microsoft.com/azure/azure-sql/database/sql-database-paas-overview) | -| Key Management | Azure Key Vault - Centralized cloud storage of secrets and keys | Private Endpoint | [Azure Docs](https://docs.microsoft.com/azure/key-vault/general/overview) -| Monitoring | Application Insights - Application performance and monitoring cloud service | - | [Azure Docs](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) +| Storage | Azure Data Lake Gen 2 - Cloud storage enabling big data analytics. | Hierarchical namespace enabled. Optional – Customer Managed Keys. | [Azure Docs](https://learn.microsoft.com/azure/storage/blobs/data-lake-storage-introduction) +| Compute | Azure Databricks - Managed Spark cloud platform for data analytics and data science | Premium tier; Secured Cluster Connectivity enabled with load balancer for egress. | [Azure Docs](https://learn.microsoft.com/azure/databricks/scenarios/what-is-azure-databricks) | +| Compute | Azure Synapse - End-to-end cloud analytics and data warehousing platform. | Disabled public network access by default. Managed Private Endpoints for Compute & Synapse Studio. Optional – Customer Managed Keys. | [Managed Private Endpoints](https://learn.microsoft.com/azure/synapse-analytics/security/synapse-workspace-managed-private-endpoints) / [Connect to Synapse Studio with private links](https://learn.microsoft.com/azure/synapse-analytics/security/synapse-private-link-hubs) +| Compute | FHIR API - Fast Healthcare Interoperability Resources for healthcare medical exchange. | Private endpoint by default. | [Azure Docs](https://learn.microsoft.com/azure/healthcare-apis/fhir/) | +| Compute | Azure Stream Analytics | Real-time analytics and event-processing engine for process high volumes of fast streaming data from multiple sources simultaneously. | [Azure Docs](https://learn.microsoft.com/azure/stream-analytics/stream-analytics-introduction) +| Compute | Azure Function App - Serverless computing service | Virtual Network Integration for accessing resources in virtual network. | [Azure Docs](https://learn.microsoft.com/azure/azure-functions/functions-overview) +| Ingestion | Azure Data Factory - Managed cloud service for data integration and orchestration | Managed virtual network. Optional – Customer Managed Keys | [Azure Docs](https://learn.microsoft.com/azure/data-factory/introduction) | +| Ingestion | Event Hub - Data streaming platform and event ingestion service | N/A | [Azure Docs](https://learn.microsoft.com/azure/event-hubs/event-hubs-about) +| Machine learning and deployment | Azure Machine Learning - Cloud platform for end-to-end machine learning workflows | Optional – Customer Managed Keys, High Business Impact Workspace | [Azure Docs](https://learn.microsoft.com/azure/machine-learning/overview-what-is-azure-ml) | +| Machine learning and deployment | Azure Container Registry - Managed private Docker cloud registry | Premium SKU. Optional – Customer Managed Keys | [Azure Docs](https://learn.microsoft.com/azure/container-registry/container-registry-intro) | +| SQL Storage | Azure SQL Database - Fully managed cloud database engine | Optional – Customer Managed Keys | [Azure Docs](https://learn.microsoft.com/azure/azure-sql/database/sql-database-paas-overview) | +| Key Management | Azure Key Vault - Centralized cloud storage of secrets and keys | Private Endpoint | [Azure Docs](https://learn.microsoft.com/azure/key-vault/general/overview) +| Monitoring | Application Insights - Application performance and monitoring cloud service | - | [Azure Docs](https://learn.microsoft.com/azure/azure-monitor/app/app-insights-overview) The intended cloud service workflows and data movements for this archetype include: @@ -253,15 +253,15 @@ Reference implementation uses parameter files with `object` parameters to consol ### Delete Locks -As an administrator, you can lock a subscription, resource group, or resource to prevent other users in your organization from accidentally deleting or modifying critical resources. The lock overrides any permissions the user might have. You can set the lock level to `CanNotDelete` or `ReadOnly`. Please see [Azure Docs](https://docs.microsoft.com/azure/azure-resource-manager/management/lock-resources) for more information. +As an administrator, you can lock a subscription, resource group, or resource to prevent other users in your organization from accidentally deleting or modifying critical resources. The lock overrides any permissions the user might have. You can set the lock level to `CanNotDelete` or `ReadOnly`. Please see [Azure Docs](https://learn.microsoft.com/azure/azure-resource-manager/management/lock-resources) for more information. **This archetype does not use `CanNotDelete` nor `ReadOnly` locks as part of the deployment. You may customize the deployment templates when it's required for your environment.** ### Service Health -[Service health notifications](https://docs.microsoft.com/azure/service-health/service-health-notifications-properties) are published by Azure, and contain information about the resources under your subscription. Service health notifications can be informational or actionable, depending on the category. +[Service health notifications](https://learn.microsoft.com/azure/service-health/service-health-notifications-properties) are published by Azure, and contain information about the resources under your subscription. Service health notifications can be informational or actionable, depending on the category. -Our examples configure service health alerts for `Security` and `Incident`. However, these categories can be customized based on your need. Please review the possible options in [Azure Docs](https://docs.microsoft.com/azure/service-health/service-health-notifications-properties#details-on-service-health-level-information). +Our examples configure service health alerts for `Security` and `Incident`. However, these categories can be customized based on your need. Please review the possible options in [Azure Docs](https://learn.microsoft.com/azure/service-health/service-health-notifications-properties#details-on-service-health-level-information). ### Deployment Scenarios @@ -318,8 +318,8 @@ This example configures: "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -330,7 +330,7 @@ This example configures: "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/docs/archetypes/hubnetwork-azfw.md b/docs/archetypes/hubnetwork-azfw.md index e1b7488f..5b97bbb1 100644 --- a/docs/archetypes/hubnetwork-azfw.md +++ b/docs/archetypes/hubnetwork-azfw.md @@ -42,7 +42,7 @@ The recommended network design achieves the purpose of hosting [**Protected B** Application Gateway with WAFv2 will be used for ingress traffic and application delivery. Application Gateways will be placed on the shared Public Access Zone (a subnet in the Hub), where public IPs will be protected with Azure DDoS (either Basic or Standard). -Other possible topologies are explained in [Azure documentation](https://docs.microsoft.com/azure/architecture/example-scenario/gateway/firewall-application-gateway) and we recommend reviewing to ensure the topology aligns to your department's network design. +Other possible topologies are explained in [Azure documentation](https://learn.microsoft.com/azure/architecture/example-scenario/gateway/firewall-application-gateway) and we recommend reviewing to ensure the topology aligns to your department's network design. There will be at least one shared Application Gateway instance and multiple dedicated Application Gateways for those line of businesses that require their own deployment (i.e. performance or cost allocation). All egress traffic from the spokes will be routed to the hub's edge firewall, inspected, and authorized/denied based on network (IP/Port) or application rules (FQDNs). @@ -62,9 +62,9 @@ Network design will require 3 IP blocks: ## Hub Virtual Network * Azure Firewall Premium instance configured with - * Either [forced tunneling](https://docs.microsoft.com/azure/firewall/forced-tunneling) (requires the next hop as another device such as NVA, on-premises or another Azure Firewall at the edge) or without forced tunneling. When forced tunneling is turned on, all management traffic will flow through the separate `AzureFirewallManagementSubnet` subnet. - * [DNS Proxy](https://docs.microsoft.com/azure/firewall/dns-details) - * [Threat Intelligence in Alert mode](https://docs.microsoft.com/azure/firewall/threat-intel) + * Either [forced tunneling](https://learn.microsoft.com/azure/firewall/forced-tunneling) (requires the next hop as another device such as NVA, on-premises or another Azure Firewall at the edge) or without forced tunneling. When forced tunneling is turned on, all management traffic will flow through the separate `AzureFirewallManagementSubnet` subnet. + * [DNS Proxy](https://learn.microsoft.com/azure/firewall/dns-details) + * [Threat Intelligence in Alert mode](https://learn.microsoft.com/azure/firewall/threat-intel) * IDPS in Alert mode * Azure Firewall Policy * Base firewall rules to support spoke archetypes @@ -73,7 +73,7 @@ Network design will require 3 IP blocks: ## Management Restricted Zone Virtual Network * Management Access Zone (OZ) - to host any privileged access workstations (PAW), with Management Public IPs forwarded via the hub's firewall. -* Management (OZ) – hosting the management servers (domain controllers). +* Management (OZ) – hosting the management servers (DNS Servers). * Infrastructure (OZ) – hosting other common infrastructure, like file shares. * Security Management (OZ) – hosting security, proxies and patching servers. * Logging (OZ) – hosting logging relays. @@ -88,7 +88,7 @@ To simplify management and compliance, all public-facing web servers, reverse pr Application Gateway can have either public or private frontends (also with [RFC 6598][rfc6598] space) and it requires a full subnet for it's instances. -The Backend URL should map to a VIP and Port mapping in the firewall's External network. In the future, Backend URLs could be directly pointed to the Frontend subnets in the spoke. The firewall performs DNAT and sends to the webserver, which will answer to the source IP (Application Gateway's internal IP), which means the webserver may need a UDR to force traffic destined to Application Gateway to re-traverse the firewall (next-hop), which is considered asymmetric routing ([other example topologies](https://docs.microsoft.com/azure/architecture/example-scenario/gateway/firewall-application-gateway#application-gateway-before-firewall)). +The Backend URL should map to a VIP and Port mapping in the firewall's External network. In the future, Backend URLs could be directly pointed to the Frontend subnets in the spoke. The firewall performs DNAT and sends to the webserver, which will answer to the source IP (Application Gateway's internal IP), which means the webserver may need a UDR to force traffic destined to Application Gateway to re-traverse the firewall (next-hop), which is considered asymmetric routing ([other example topologies](https://learn.microsoft.com/azure/architecture/example-scenario/gateway/firewall-application-gateway#application-gateway-before-firewall)). ## User Defined Routes @@ -193,7 +193,7 @@ Azure Firewall forwards it's logs to Log Analytics Workspace. This integration ![Diagnostic Settings](../media/architecture/hubnetwork-azfw/azfw-diagnostic-settings.jpg) -Once Log Analytics Workspace has collected logs, [Azure Monitor Workbook for Azure Firewall](https://docs.microsoft.com/azure/firewall/firewall-workbook) can be used to monitor traffic flows. +Once Log Analytics Workspace has collected logs, [Azure Monitor Workbook for Azure Firewall](https://learn.microsoft.com/azure/firewall/firewall-workbook) can be used to monitor traffic flows. Below are sample queries that can also be used to query Log Analytics Workspace directly. @@ -225,9 +225,9 @@ AzureDiagnostics [cloudUsageProfiles]: https://github.com/canada-ca/cloud-guardrails/blob/master/EN/00_Applicable-Scope.md [rfc1918]: https://tools.ietf.org/html/rfc1918 [rfc6598]: https://tools.ietf.org/html/rfc6598 -[nsgAzureLoadBalancer]: https://docs.microsoft.com/azure/virtual-network/network-security-groups-overview#allowazureloadbalancerinbound -[nsgAzureBastion]: https://docs.microsoft.com/azure/bastion/bastion-nsg#apply -[nsgAppGatewayV2]: https://docs.microsoft.com/azure/application-gateway/configuration-infrastructure#network-security-groups +[nsgAzureLoadBalancer]: https://learn.microsoft.com/azure/virtual-network/network-security-groups-overview#allowazureloadbalancerinbound +[nsgAzureBastion]: https://learn.microsoft.com/azure/bastion/bastion-nsg#apply +[nsgAppGatewayV2]: https://learn.microsoft.com/azure/application-gateway/configuration-infrastructure#network-security-groups ## Azure Deployment @@ -253,7 +253,7 @@ Reference implementation uses parameter files with `object` parameters to consol ### Delete Locks -As an administrator, you can lock a subscription, resource group, or resource to prevent other users in your organization from accidentally deleting or modifying critical resources. The lock overrides any permissions the user might have. You can set the lock level to `CanNotDelete` or `ReadOnly`. Please see [Azure Docs](https://docs.microsoft.com/azure/azure-resource-manager/management/lock-resources) for more information. +As an administrator, you can lock a subscription, resource group, or resource to prevent other users in your organization from accidentally deleting or modifying critical resources. The lock overrides any permissions the user might have. You can set the lock level to `CanNotDelete` or `ReadOnly`. Please see [Azure Docs](https://learn.microsoft.com/azure/azure-resource-manager/management/lock-resources) for more information. By default, this archetype deploys `CanNotDelete` lock to prevent accidental deletion at: @@ -264,9 +264,9 @@ By default, this archetype deploys `CanNotDelete` lock to prevent accidental del ### Service Health -[Service health notifications](https://docs.microsoft.com/azure/service-health/service-health-notifications-properties) are published by Azure, and contain information about the resources under your subscription. Service health notifications can be informational or actionable, depending on the category. +[Service health notifications](https://learn.microsoft.com/azure/service-health/service-health-notifications-properties) are published by Azure, and contain information about the resources under your subscription. Service health notifications can be informational or actionable, depending on the category. -Our examples configure service health alerts for `Security` and `Incident`. However, these categories can be customized based on your need. Please review the possible options in [Azure Docs](https://docs.microsoft.com/azure/service-health/service-health-notifications-properties#details-on-service-health-level-information). +Our examples configure service health alerts for `Security` and `Incident`. However, these categories can be customized based on your need. Please review the possible options in [Azure Docs](https://learn.microsoft.com/azure/service-health/service-health-notifications-properties#details-on-service-health-level-information). ### Deployment Scenarios for Azure Firewall Policy @@ -347,8 +347,8 @@ This example configures: "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -359,7 +359,7 @@ This example configures: "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/docs/archetypes/hubnetwork-nva-fortigate.md b/docs/archetypes/hubnetwork-nva-fortigate.md index 2c03c531..9404f74a 100644 --- a/docs/archetypes/hubnetwork-nva-fortigate.md +++ b/docs/archetypes/hubnetwork-nva-fortigate.md @@ -41,7 +41,7 @@ The recommended network design achieves the purpose of hosting [**Protected B** Application Gateway with WAFv2 will be used for ingress traffic and application delivery. Application Gateways will be placed on the shared Public Access Zone (a subnet in the Hub), where public IPs will be protected with Azure DDoS (either Basic or Standard). -Other possible topologies are explained in [Azure documentation](https://docs.microsoft.com/azure/architecture/example-scenario/gateway/firewall-application-gateway) and we recommend reviewing to ensure the topology aligns to your department's network design. +Other possible topologies are explained in [Azure documentation](https://learn.microsoft.com/azure/architecture/example-scenario/gateway/firewall-application-gateway) and we recommend reviewing to ensure the topology aligns to your department's network design. There will be at least one shared Application Gateway instance and multiple dedicated Application Gateways for those line of businesses that require their own deployment (i.e. performance or cost allocation). All egress traffic from the spokes will be routed to the hub's edge firewall, inspected, and authorized/denied based on network (IP/Port) or application rules (FQDNs). @@ -84,7 +84,7 @@ To simplify management and compliance, all public-facing web servers, reverse pr Application Gateway can have either public or private frontends (also with [RFC 6598][rfc6598] space) and it requires a full subnet for it's instances. -The Backend URL should map to a VIP and Port mapping in the firewall's External network. In the future, Backend URLs could be directly pointed to the Frontend subnets in the spoke. The firewall performs DNAT and sends to the webserver, which will answer to the source IP (Application Gateway's internal IP), which means the webserver may need a UDR to force traffic destined to Application Gateway to re-traverse the firewall (next-hop), which is considered asymmetric routing ([other example topologies](https://docs.microsoft.com/azure/architecture/example-scenario/gateway/firewall-application-gateway#application-gateway-before-firewall)). +The Backend URL should map to a VIP and Port mapping in the firewall's External network. In the future, Backend URLs could be directly pointed to the Frontend subnets in the spoke. The firewall performs DNAT and sends to the webserver, which will answer to the source IP (Application Gateway's internal IP), which means the webserver may need a UDR to force traffic destined to Application Gateway to re-traverse the firewall (next-hop), which is considered asymmetric routing ([other example topologies](https://learn.microsoft.com/azure/architecture/example-scenario/gateway/firewall-application-gateway#application-gateway-before-firewall)). ## User Defined Routes @@ -271,7 +271,7 @@ Reference implementation uses parameter files with `object` parameters to consol ### Delete Locks -As an administrator, you can lock a subscription, resource group, or resource to prevent other users in your organization from accidentally deleting or modifying critical resources. The lock overrides any permissions the user might have. You can set the lock level to `CanNotDelete` or `ReadOnly`. Please see [Azure Docs](https://docs.microsoft.com/azure/azure-resource-manager/management/lock-resources) for more information. +As an administrator, you can lock a subscription, resource group, or resource to prevent other users in your organization from accidentally deleting or modifying critical resources. The lock overrides any permissions the user might have. You can set the lock level to `CanNotDelete` or `ReadOnly`. Please see [Azure Docs](https://learn.microsoft.com/azure/azure-resource-manager/management/lock-resources) for more information. By default, this archetype deploys `CanNotDelete` lock to prevent accidental deletion at: @@ -282,9 +282,9 @@ By default, this archetype deploys `CanNotDelete` lock to prevent accidental del ### Service Health -[Service health notifications](https://docs.microsoft.com/azure/service-health/service-health-notifications-properties) are published by Azure, and contain information about the resources under your subscription. Service health notifications can be informational or actionable, depending on the category. +[Service health notifications](https://learn.microsoft.com/azure/service-health/service-health-notifications-properties) are published by Azure, and contain information about the resources under your subscription. Service health notifications can be informational or actionable, depending on the category. -Our examples configure service health alerts for `Security` and `Incident`. However, these categories can be customized based on your need. Please review the possible options in [Azure Docs](https://docs.microsoft.com/azure/service-health/service-health-notifications-properties#details-on-service-health-level-information). +Our examples configure service health alerts for `Security` and `Incident`. However, these categories can be customized based on your need. Please review the possible options in [Azure Docs](https://learn.microsoft.com/azure/service-health/service-health-notifications-properties#details-on-service-health-level-information). ### Deployment Scenarios @@ -327,8 +327,8 @@ This example configures: "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -339,7 +339,7 @@ This example configures: "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { @@ -622,7 +622,7 @@ This example configures: [cloudUsageProfiles]: https://github.com/canada-ca/cloud-guardrails/blob/master/EN/00_Applicable-Scope.md [rfc1918]: https://tools.ietf.org/html/rfc1918 [rfc6598]: https://tools.ietf.org/html/rfc6598 -[nsgAzureLoadBalancer]: https://docs.microsoft.com/azure/virtual-network/network-security-groups-overview#allowazureloadbalancerinbound -[nsgAzureBastion]: https://docs.microsoft.com/azure/bastion/bastion-nsg#apply -[nsgAppGatewayV2]: https://docs.microsoft.com/azure/application-gateway/configuration-infrastructure#network-security-groups +[nsgAzureLoadBalancer]: https://learn.microsoft.com/azure/virtual-network/network-security-groups-overview#allowazureloadbalancerinbound +[nsgAzureBastion]: https://learn.microsoft.com/azure/bastion/bastion-nsg#apply +[nsgAppGatewayV2]: https://learn.microsoft.com/azure/application-gateway/configuration-infrastructure#network-security-groups [azmarketplacefortinet]: https://portal.azure.com/#blade/Microsoft_Azure_Marketplace/GalleryItemDetailsBladeNopdl/id/fortinet.fortigatengfw-high-availability/product/%7B%22displayName%22%3A%22FortiGate%20NGFW%20for%20Azure%20LB%20HA%20with%20ARM%20template%22%2C%22itemDisplayName%22%3A%22FortiGate%20NGFW%20for%20Azure%20LB%20HA%20with%20ARM%20template%22%2C%22id%22%3A%22fortinet.fortigatengfw-high-availability%22%2C%22offerId%22%3A%22fortigatengfw-high-availability%22%2C%22publisherId%22%3A%22fortinet%22%2C%22publisherDisplayName%22%3A%22Fortinet%22%2C%22summary%22%3A%22FortiGate%20NGFW%20improves%20on%20the%20Azure%20firewall%20with%20complete%20data%2C%20application%20and%20network%20security%22%2C%22longSummary%22%3A%22Automated%20deployment%20for%20the%20combined%20use%20of%20Azure%20LB%20and%20NGFW%20configurations%20(2%20FortiGate%20virtual%20machines)%20to%20support%20your%20Enterprise%20Cloud%20workload%22%2C%22description%22%3A%22%3Cp%3EThe%20FortiGate%20Next-Generation%20Firewall%20combines%20a%20comprehensive%20suite%20of%20powerful%20security%20tools%20into%20a%20high-performance%20virtual%20device.%20FortiGate%20NGFWs%20can%20be%20combined%20with%20other%20Fortinet%20solutions%20to%20form%20a%20unified%20security%20fabric%20to%20secure%20your%20network%2C%20users%2C%20data%20and%20applications%20across%20clouds%20and%20enterprises.%3Cbr%3E%3C%2Fp%3E%3Cp%20class%3D%5C%22x_xmsonormal%5C%22%3EThe%20FortiGate%20NGFW%20includes%20application%20aware%20network%20security%2C%20secure%20SD-WAN%2C%20virus%20protection%2C%20IPS%2C%20Web%20filtering%20and%20VPN%20along%20with%20advanced%20features%20such%20as%20an%20extreme%20threat%20database%2C%20vulnerability%20management%20and%20flow-based%20inspection%20work%20in%20concert%20to%20identify%20and%20mitigate%20the%20latest%20complex%20security%20threats.%20The%20security-hardened%20FortiOS%20operating%20system%20is%20purpose-built%20for%20inspection%20and%20identification%20of%20malware.%3C%2Fp%3E%3Cp%20class%3D%5C%22x_xmsonormal%5C%22%3EDesigned%20to%20ensure%20easy%2C%20consistent%20deployment%20for%20the%20most%20ef diff --git a/docs/archetypes/identity.md b/docs/archetypes/identity.md new file mode 100644 index 00000000..bc0d4087 --- /dev/null +++ b/docs/archetypes/identity.md @@ -0,0 +1,342 @@ +# Archetype: Identity and Access Management + +## Table of Contents + +- [Archetype: Generic Subscription](#archetype-generic-subscription) + - [Table of Contents](#table-of-contents) + - [Overview](#overview) + - [Azure Deployment](#azure-deployment) + - [Schema Definition](#schema-definition) + - [Delete Locks](#delete-locks) + - [Service Health](#service-health) + - [Deployment Scenarios](#deployment-scenarios) + - [Example Deployment Parameters](#example-deployment-parameters) + - [Recommended Parameter Property Updates](#recommended-parameter-property-updates) + - [Service Health Alerts](#service-health-alerts) + - [Security Center](#security-center) + - [Subscription Role Assignments](#subscription-role-assignments) + - [Resource Tags and Preferred Naming Convention](#resource-tags-and-preferred-naming-convention) + - [Hub Virtual Network ID](#hub-virtual-network-id) + - [Deployment Instructions](#deployment-instructions) + +## Overview + +Identity and access management are core features of the Azure landing zone implementation. The deployment includes a subscription dedicated to identity, where customers can deploy the Active Directory domain controllers their environments require. This landing zone will be in the `pubsecPlatformIdentity` management group. + +![Archetype: Generic Subscription](../media/architecture/archetype-identity.jpg) + + +**Workflow** + +* A new subscription is created through existing process (either via ea.azure.com or Azure Portal). +* The subscription will automatically be assigned to the **pubsecSandbox** management group. +* Update configuration in Azure DevOps Git repo +* Execute the Platofrm - Identity azure DevOps Pipeline. The pipeline will: + * Move it to the target management group + * Scaffold the subscription with baseline configuration. + +**Subscription Move** + +Subscription can be moved to a target Management Group through Azure ARM Templates/Bicep. Move has been incorporated into the landing zone Azure DevOps Pipeline automation. + +**Capabilities** + +| Capability | Description | +| --- | --- | +| Service Health Alerts | Configures Service Health alerts such as Security, Incident, Maintenance. Alerts are configured with email, sms and voice notifications. | +| Microsoft Defender for Cloud | Configures security contact information (email and phone). | +| Subscription Role Assignments | Configures subscription scoped role assignments. Roles can be built-in or custom. | +| Subscription Budget | Configures monthly subscription budget with email notification. Budget is configured by default for 10 years and the amount. | +| Subscription Tags | A set of tags that are assigned to the subscription. | +| Resource Tags | A set of tags that are assigned to the resource group and resources. These tags must include all required tags as defined the Tag Governance policy. | +| Automation | Deploys an Azure Automation Account in each subscription. | +| Backup Recovery Vault | Configures a backup recovery vault . | +| Hub Networking | Configures virtual network peering to Hub Network which is required for egress traffic flow and hub-managed DNS resolution (on-premises or other spokes, private endpoints). +| Networking | A spoke virtual network with 1 to 3 subnets: Domain Controllers, DNS Resolver Inbound, DNS Resolver Outbound. Additional subnets can be configured at deployment time using configuration (see below). +| DNS Resolver | Configures DNS Resolver with inbound and outbound forwarding. Inbound forwarding is configured to forward DNS queries to the Hub Network DNS Resolver. Outbound forwarding is configured to forward DNS queries to the Hub Network DNS Resolver. | +| DNS Forwarding Ruleset | Configures DNS Forwarding Ruleset to forward DNS queries to the Hub Network DNS Resolver. +| Private DNS Zones | Optionally configures the PrivateLink DNS zones to be linked to the Identity vNET + +## Azure Deployment + +### Schema Definition + +Reference implementation uses parameter files with `object` parameters to consolidate parameters based on their context. The schemas types are: + +* Schema (version: `latest`) + + * [Identity deployment parameters definition](../../schemas/latest/landingzones/lz-platform-identity.json) + + * Common types + * [Location](../../schemas/latest/landingzones/types/location.json) + * [Service Health Alerts](../../schemas/latest/landingzones/types/serviceHealthAlerts.json) + * [Microsoft Defender for Cloud](../../schemas/latest/landingzones/types/securityCenter.json) + * [Subscription Role Assignments](../../schemas/latest/landingzones/types/subscriptionRoleAssignments.json) + * [Subscription Budget](../../schemas/latest/landingzones/types/subscriptionBudget.json) + * [Subscription Tags](../../schemas/latest/landingzones/types/subscriptionTags.json) + * [Resource Tags](../../schemas/latest/landingzones/types/resourceTags.json) + * [Log Analytics Workspace](../../schemas/latest/landingzones/types/logAnalyticsWorkspaceId.json) + * [Automation](../../schemas/latest/landingzones/types/automation.json) + * [Backup Recovery Vault](../../schemas/latest/landingzones/types/backupRecoveryVault.json) + + +### Service Health + +[Service health notifications](https://learn.microsoft.com/azure/service-health/service-health-notifications-properties) are published by Azure, and contain information about the resources under your subscription. Service health notifications can be informational or actionable, depending on the category. + +Our examples configure service health alerts for `Security` and `Incident`. However, these categories can be customized based on your need. Please review the possible options in [Azure Docs](https://learn.microsoft.com/azure/service-health/service-health-notifications-properties#details-on-service-health-level-information). + +### Deployment Scenarios + +> Sample deployment scenarios are based on the latest JSON parameters file schema definition. If you have an older version of this repository, please use the examples from your repository. + +| Scenario | Example JSON Parameters | Notes | +|:-------- |:----------------------- |:----- | +| Deployment with DNS Resolver | [tests/schemas/lz-platform-identity/lz-Identity-With-DNS-Resolver.json](../../tests/schemas/lz-platform-identity/lz-Identity-With-DNS-Resolver.json) | Does not deploy Private DNS Zones | +| Deployment without DNS Resolver| [tests/schemas/lz-platform-identity/lz-Identity-Without-DNS-Resolver.json](../../tests/schemas/lz-platform-identity/lz-Identity-Without-DNS-Resolver.json) | `parameters.privateDnsResolver.value.enabled` is `false`. If this setting is false the DNS Conditional Setting ruleset will not be deployed even if it's set to true | +| Deployment with DNS Resolver and Private DNS Zones | [tests/schemas/lz-platform-identity/lz-Identity-With-Private-DNS-Zones-And-DNS-Resolver.json](../../tests/schemas/lz-platform-identity/lz-Identity-With-Private-DNS-Zones-And-DNS-Resolver.json) | `parameters.privatednszones.value.enabled` is set to `true` && `parameters.privateDnsResolver.value.enabled` is `true` | +| Deployment with Private DNS Zones | [tests/schemas/lz-platform-identity/lz-Identity-With-Private-DNS-Zones.json](../../tests/schemas/lz-platform-identity/lz-Identity-With-Private-DNS-Zones.json) | `parameters.privatednszones.value.enabled` is set to `true`.| + +### Example Deployment Parameters + +This example configures: + +1. Service Health Alerts +2. Microsoft Defender for Cloud +3. Subscription Role Assignments using built-in and custom roles +4. Subscription Budget with $1000 +5. Subscription Tags +6. Resource Tags (aligned to the default tags defined in [Policies](../../policy/custom/definitions/policyset/Tags.parameters.json)) +7. Log Analytics Workspace integration through Azure Defender for Cloud +8. Automation Account +9. Backup Recovery Vault +10. Spoke Virtual Network with Hub-managed DNS, Virtual Network Peering and 3 subnets. + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "logAnalyticsWorkspaceResourceId": { + "value": "/subscriptions/46e3c98c-7c51-4634-be53-df9916714a46/resourceGroups/pubsec-central-logging/providers/Microsoft.OperationalInsights/workspaces/bwill-log-analytics-workspace" + }, + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + }, + "actionGroupName": "Service health action group", + "actionGroupShortName": "health-alert", + "alertRuleName": "Incidents and Security", + "alertRuleDescription": "Service Health: Incidents and Security" + } + }, + "securityCenter": { + "value": { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Owner Role", + "roleDefinitionId": "8e3af657-a8ff-443c-a75c-2fe8c4bcb635", + "securityGroupObjectIds": [ + "3af30d9f-04aa-4d64-b4d5-407d7ff64ff8" + ] + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tbd", + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "automation", + "networking": "networking", + "networkWatcher": "NetworkWatcherRG", + "backupRecoveryVault": "backup", + "domainControllers": "DomainControllersRG", + "dnsResolver": "dns-resolverRG", + "dnsCondionalForwarders": "dns-CondionalForwardersRG", + "privateDnsZones": "pubsec-dns" + } + }, + "automation": { + "value": { + "name": "automation" + } + }, + "backupRecoveryVault": { + "value": { + "enabled": true, + "name": "backup-vault" + } + }, + "privateDnsZones": { + "value": { + "enabled": false, + "resourceGroupName": "pubsec-dns" + } + }, + + "privateDnsResolver": { + "value": { + "enabled": true, + "name": "dns-resolver", + "inboundEndpointName": "dns-resolver-Inbound", + "outboundEndpointName": "dns-resolver-Outbound" + } + }, + + "privateDnsResolverRuleset": { + "value": { + "enabled": true, + "name": "dns-resolver-ruleset", + "linkRuleSetToVnet": true, + "linkRuleSetToVnetName": "dns-resolver-vnet-link", + "forwardingRules": [ + { + "name": "default", + "domain": "dontMakeMeThink.local", + "state": "Enabled", + "targetDnsServers": [ + { + "ipAddress": "10.99.99.100" + }, + { + "ipAddress": "10.99.99.99" + } + ] + } + ] + } + }, + + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/db8a3c31-7dbb-4368-8883-f9e6333ff23a/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4" + } + }, + + "network": { + "value": { + "deployVnet": true, + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "id-vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.15.0.0/24" + ], + "subnets": { + "domainControllers": { + "comments": "Identity Subnet for Domain Controllers and VM-Based DNS Servers", + "name": "DomainControllers", + "addressPrefix": "10.15.0.0/27" + }, + "dnsResolverInbound": { + "comments": "Azure DNS Resolver Inbound Requests subnet", + "name": "AzureDNSResolver-Inbound", + "addressPrefix": "10.15.0.32/27" + }, + "dnsResolverOutbound": { + "comments": "Azure DNS Resolver Outbound Requests subnet", + "name": "AzureDNSResolver-Outbound", + "addressPrefix": "10.15.0.64/27" + }, + "optional": [] + } + } + } + } + } +``` + +## Recommended Parameter Property Updates + +### Service Health Alerts + +Update the **serviceHealthAlerts** properties with specific email addresses and phone numbers as required. + +![Generic Subscription: Service Health Alerts](../../docs/media/archetypes/service-health-alerts-receivers.jpg) + +### Security Center + +Change the **securityCenter** properties with specific email and address values to reflect your actual point of contact. + +![Generic Subscription: Security Center](../../docs/media/archetypes/security-center-contact-info.jpg) + +### Subscription Role Assignments + +Modify the two **subscriptionRoleAssignments** properties with your specific unique object ids of the respective groups for the **Contributor** built-in +and **Custom Role: Landing Zone Application Owner** roles for this landing zone subscription. These assignments are optional and can be 0 or more role assignments using either Built-In or Custom roles and security groups. + +![Generic Subscription: Subscription Role Assignments](../../docs/media/archetypes/subscription-role-assignments.jpg) + +### Resource Tags and Preferred Naming Convention + +1. Specify the desired custom values for the **resourceTags** properties. +You may also include any additional name value pairs of tags required. Generally, these tags can be modified and even replaced as required, and should also align to the Tagging policy set paramters at: [Tag Policy](https://github.com/Azure/CanadaPubSecALZ/blob/main/policy/custom/definitions/policyset/Tags.parameters.json). + +2. Addtionally, you can customize default resources and resource group names with any specific preferred naming convention, as indicated by the item **2** circles shown below. + + +![Generic Subscription: Tags and Naming Conventions](../../docs/media/archetypes/resource-tags-and-naming-conventions.jpg) + +### Hub Virtual Network ID + +**IMPORTANT** + +To avoid a failure when running any of the connectivity pipelines, the subscriptionId segment value of the **hubNetwork** string (item **1**), must be updated from it's default value to the specific hubNetwork subscriptionId that was actually deployed previously, so that the virtual network in this spoke subscription can be VNET Peered to the Hub Network. + +![Generic Subscription: Hub Virtual Network ID](../../docs/media/archetypes/virtual-network-id.jpg) + +The rest of the segments for the **virtualNetworkId** string must also match the actual resources that were deployed from the connectivity pipeline, such as the name of the resource group, +in case a different prefix besides **pubsec** was used to conform to a specific and preferred naming convention or organization prefix (item **2**), or the default VNET name of hub-vnet was also changed to something else, +(**item 3**) - again based on a specific and preferred naming convention that may have been used before when the actual hub VNET was deployed. + +### Deployment Instructions + +### Virtual Appliance IP +To ensure traffic is routed/filtered via the firewall, please validate or update the "egressVirtualApplianceIp" value to the firewall IP in your environment: + - For Azure Firewall, use the firewall IP address + - For Network Virtual Appliances (i.e. Fortigate firewalls), use the internal load-balancer IP (item **1**) +![Generic Subscription:Egress Virtual Appliance IP](../../docs/media/archetypes/egressvirtualApplianceIP.jpg) + +Please see [archetype authoring guide for deployment instructions](authoring-guide.md#deployment-instructions). diff --git a/docs/archetypes/logging.md b/docs/archetypes/logging.md index 0585d51f..ffb8be23 100644 --- a/docs/archetypes/logging.md +++ b/docs/archetypes/logging.md @@ -2,15 +2,13 @@ ## Table of Contents -- [Archetype: Logging](#archetype--logging) - - [Table of Contents](#table-of-contents) - - [Overview](#overview) - - [Schema Definition](#schema-definition) - - [Delete Locks](#delete-locks) - - [Service Health](#service-health) - - [Deployment Scenarios](#deployment-scenarios) - - [Example Deployment Parameters](#example-deployment-parameters) - - [Deployment Instructions](#deployment-instructions) +* [Overview](#overview) +* [Schema Definition](#schema-definition) +* [Delete Locks](#delete-locks) +* [Service Health](#service-health) +* [Deployment Scenarios](#deployment-scenarios) +* [Example Deployment Parameters](#example-deployment-parameters) +* [Deployment Instructions](#deployment-instructions) ## Overview @@ -62,15 +60,15 @@ Reference implementation uses parameter files with `object` parameters to consol ## Delete Locks -As an administrator, you can lock a subscription, resource group, or resource to prevent other users in your organization from accidentally deleting or modifying critical resources. The lock overrides any permissions the user might have. You can set the lock level to `CanNotDelete` or `ReadOnly`. Please see [Azure Docs](https://docs.microsoft.com/azure/azure-resource-manager/management/lock-resources) for more information. +As an administrator, you can lock a subscription, resource group, or resource to prevent other users in your organization from accidentally deleting or modifying critical resources. The lock overrides any permissions the user might have. You can set the lock level to `CanNotDelete` or `ReadOnly`. Please see [Azure Docs](https://learn.microsoft.com/azure/azure-resource-manager/management/lock-resources) for more information. By default, this archetype deploys `CanNotDelete` lock to prevent accidental deletion on all resource groups it creates. ## Service Health -[Service health notifications](https://docs.microsoft.com/azure/service-health/service-health-notifications-properties) are published by Azure, and contain information about the resources under your subscription. Service health notifications can be informational or actionable, depending on the category. +[Service health notifications](https://learn.microsoft.com/azure/service-health/service-health-notifications-properties) are published by Azure, and contain information about the resources under your subscription. Service health notifications can be informational or actionable, depending on the category. -Our examples configure service health alerts for `Security` and `Incident`. However, these categories can be customized based on your need. Please review the possible options in [Azure Docs](https://docs.microsoft.com/azure/service-health/service-health-notifications-properties#details-on-service-health-level-information). +Our examples configure service health alerts for `Security` and `Incident`. However, these categories can be customized based on your need. Please review the possible options in [Azure Docs](https://learn.microsoft.com/azure/service-health/service-health-notifications-properties#details-on-service-health-level-information). ## Deployment Scenarios @@ -112,8 +110,8 @@ This example configures: "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -124,7 +122,7 @@ This example configures: "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/docs/archetypes/machinelearning.md b/docs/archetypes/machinelearning.md index 6506cef1..7d3da3ee 100644 --- a/docs/archetypes/machinelearning.md +++ b/docs/archetypes/machinelearning.md @@ -78,17 +78,17 @@ Subscription can be moved to a target Management Group through Azure ARM Templat | Category | Service | Configuration | Reference | | --- | --- | --- | --- | -| Storage | Azure Data Lake Gen 2 - Cloud storage enabling big data analytics | Hierarchical namespace enabled. Optional – Customer Managed Keys | [Azure Docs](https://docs.microsoft.com/azure/storage/blobs/data-lake-storage-introduction) -| Compute | Azure Databricks - Managed Spark cloud platform for data analytics and data science | Premium tier; Secured Cluster Connectivity enabled with load balancer for egress | [Azure Docs](https://docs.microsoft.com/azure/databricks/scenarios/what-is-azure-databricks) | -| Ingestion | Azure Data Factory - Managed cloud service for data integration and orchestration | Managed virtual network. Optional – Customer Managed Keys | [Azure Docs](https://docs.microsoft.com/azure/data-factory/introduction) | -| Machine learning and deployment | Azure Machine Learning - Cloud platform for end-to-end machine learning workflows | Optional – Customer Managed Keys, High Business Impact Workspace | [Azure Docs](https://docs.microsoft.com/azure/machine-learning/overview-what-is-azure-ml) | -| Machine learning and deployment | Azure Container Registry - Managed private Docker cloud registry | Premium SKU. Optional – Customer Managed Keys | [Azure Docs](https://docs.microsoft.com/azure/container-registry/container-registry-intro) | -| Machine learning and deployment | Azure Kubernetes Service - Cloud hosted Kubernetes service | Private cluster enabled; Managed identity type; Network plugin set to kubenet. Optional – Customer Managed Keys for Managed Disks | [Azure Docs](https://docs.microsoft.com/azure/aks/intro-kubernetes) | -| Machine learning and deployment | Azure App Service on Linux (container) - Cloud hosted web app for model deployment | With App Service Plan SKU default as Premium 1 V2. Virtual network integration | [Azure Docs](https://docs.microsoft.com/en-us/azure/app-service/overview) | -| SQL Storage | Azure SQL Managed Instance - Cloud database storage enabling lift and shift on-premise application migrations | Optional – Customer Managed Keys | [Azure Docs](https://docs.microsoft.com/azure/azure-sql/managed-instance/sql-managed-instance-paas-overview) -| SQL Storage | Azure SQL Database - Fully managed cloud database engine | Optional – Customer Managed Keys | [Azure Docs](https://docs.microsoft.com/azure/azure-sql/database/sql-database-paas-overview) | -| Key Management | Azure Key Vault - Centralized cloud storage of secrets and keys | Private Endpoint | [Azure Docs](https://docs.microsoft.com/azure/key-vault/general/overview) -| Monitoring | Application Insights - Application performance and monitoring cloud service | - | [Azure Docs](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) +| Storage | Azure Data Lake Gen 2 - Cloud storage enabling big data analytics | Hierarchical namespace enabled. Optional – Customer Managed Keys | [Azure Docs](https://learn.microsoft.com/azure/storage/blobs/data-lake-storage-introduction) +| Compute | Azure Databricks - Managed Spark cloud platform for data analytics and data science | Premium tier; Secured Cluster Connectivity enabled with load balancer for egress | [Azure Docs](https://learn.microsoft.com/azure/databricks/scenarios/what-is-azure-databricks) | +| Ingestion | Azure Data Factory - Managed cloud service for data integration and orchestration | Managed virtual network. Optional – Customer Managed Keys | [Azure Docs](https://learn.microsoft.com/azure/data-factory/introduction) | +| Machine learning and deployment | Azure Machine Learning - Cloud platform for end-to-end machine learning workflows | Optional – Customer Managed Keys, High Business Impact Workspace | [Azure Docs](https://learn.microsoft.com/azure/machine-learning/overview-what-is-azure-ml) | +| Machine learning and deployment | Azure Container Registry - Managed private Docker cloud registry | Premium SKU. Optional – Customer Managed Keys | [Azure Docs](https://learn.microsoft.com/azure/container-registry/container-registry-intro) | +| Machine learning and deployment | Azure Kubernetes Service - Cloud hosted Kubernetes service | Private cluster enabled; Managed identity type; Network plugin set to kubenet. Optional – Customer Managed Keys for Managed Disks | [Azure Docs](https://learn.microsoft.com/azure/aks/intro-kubernetes) | +| Machine learning and deployment | Azure App Service on Linux (container) - Cloud hosted web app for model deployment | With App Service Plan SKU default as Premium 1 V2. Virtual network integration | [Azure Docs](https://learn.microsoft.com/en-us/azure/app-service/overview) | +| SQL Storage | Azure SQL Managed Instance - Cloud database storage enabling lift and shift on-premise application migrations | Optional – Customer Managed Keys | [Azure Docs](https://learn.microsoft.com/azure/azure-sql/managed-instance/sql-managed-instance-paas-overview) +| SQL Storage | Azure SQL Database - Fully managed cloud database engine | Optional – Customer Managed Keys | [Azure Docs](https://learn.microsoft.com/azure/azure-sql/database/sql-database-paas-overview) | +| Key Management | Azure Key Vault - Centralized cloud storage of secrets and keys | Private Endpoint | [Azure Docs](https://learn.microsoft.com/azure/key-vault/general/overview) +| Monitoring | Application Insights - Application performance and monitoring cloud service | - | [Azure Docs](https://learn.microsoft.com/azure/azure-monitor/app/app-insights-overview) The intended cloud service workflows and data movements for this archetype include: @@ -134,7 +134,7 @@ Once the machine learning archetype is deployed and available to use, access con | Azure Container Registry | Network ACL deny, public network access disabled | Private endpoint on `registry` + DNS registration to either hub or spoke | `privateEndpoints`|f | Azure Application Insights | N/A | N/A | N/A | -> For App Service, private endpoint requires the SKU tier `Premium`: https://docs.microsoft.com/azure/app-service/networking/private-endpoint so this may require a quota increase. +> For App Service, private endpoint requires the SKU tier `Premium`: https://learn.microsoft.com/azure/app-service/networking/private-endpoint so this may require a quota increase. This archetype also has the following security features as options for deployment: @@ -254,15 +254,15 @@ Reference implementation uses parameter files with `object` parameters to consol ### Delete Locks -As an administrator, you can lock a subscription, resource group, or resource to prevent other users in your organization from accidentally deleting or modifying critical resources. The lock overrides any permissions the user might have. You can set the lock level to `CanNotDelete` or `ReadOnly`. Please see [Azure Docs](https://docs.microsoft.com/azure/azure-resource-manager/management/lock-resources) for more information. +As an administrator, you can lock a subscription, resource group, or resource to prevent other users in your organization from accidentally deleting or modifying critical resources. The lock overrides any permissions the user might have. You can set the lock level to `CanNotDelete` or `ReadOnly`. Please see [Azure Docs](https://learn.microsoft.com/azure/azure-resource-manager/management/lock-resources) for more information. **This archetype does not use `CanNotDelete` nor `ReadOnly` locks as part of the deployment. You may customize the deployment templates when it's required for your environment.** ### Service Health -[Service health notifications](https://docs.microsoft.com/azure/service-health/service-health-notifications-properties) are published by Azure, and contain information about the resources under your subscription. Service health notifications can be informational or actionable, depending on the category. +[Service health notifications](https://learn.microsoft.com/azure/service-health/service-health-notifications-properties) are published by Azure, and contain information about the resources under your subscription. Service health notifications can be informational or actionable, depending on the category. -Our examples configure service health alerts for `Security` and `Incident`. However, these categories can be customized based on your need. Please review the possible options in [Azure Docs](https://docs.microsoft.com/azure/service-health/service-health-notifications-properties#details-on-service-health-level-information). +Our examples configure service health alerts for `Security` and `Incident`. However, these categories can be customized based on your need. Please review the possible options in [Azure Docs](https://learn.microsoft.com/azure/service-health/service-health-notifications-properties#details-on-service-health-level-information). ### Deployment Scenarios @@ -323,8 +323,8 @@ This example configures: "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -335,7 +335,7 @@ This example configures: "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/docs/architecture.md b/docs/architecture.md index 9f5b146d..adf26610 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -51,7 +51,7 @@ The table below outlines the key decisions each department must consider as part Departments are targeting workloads with **Unclassified**, **Protected A** and **Protected B** data classifications in Azure. These classifications are based on [ITSG-33][itsg33] which is derived from [NIST SP 800-53 Revision 4][nist80053R4]. -Guardrails in Azure are deployed through [Azure Policy](https://docs.microsoft.com/azure/governance/policy/overview). Azure Policy helps to enforce organizational standards and to assess compliance at-scale. Through its compliance dashboard, it provides an aggregated view to evaluate the overall state of the environment, with the ability to drill down to the per-resource, per-policy granularity. It also helps to bring your resources to compliance through bulk remediation for existing resources and automatic remediation for new resources. +Guardrails in Azure are deployed through [Azure Policy](https://learn.microsoft.com/azure/governance/policy/overview). Azure Policy helps to enforce organizational standards and to assess compliance at-scale. Through its compliance dashboard, it provides an aggregated view to evaluate the overall state of the environment, with the ability to drill down to the per-resource, per-policy granularity. It also helps to bring your resources to compliance through bulk remediation for existing resources and automatic remediation for new resources. Common use cases for Azure Policy include implementing governance for resource consistency, regulatory compliance, security, cost, and management. Policy definitions for these common use cases are already available in your Azure environment as built-ins to help you get started. @@ -91,7 +91,7 @@ The compliance reporting will outline the Azure Policies, the resource types, th ### 2.5 Compliance Data Export -For custom reporting requirements, the raw compliance data can be exported using [Azure Resource Graph](https://docs.microsoft.com/azure/governance/resource-graph/overview). This export allows for additional analysis and align to operational requirements. A custom data export pipeline and processes will be needed to operationalize the dataset. Primary queries to access the data are: +For custom reporting requirements, the raw compliance data can be exported using [Azure Resource Graph](https://learn.microsoft.com/azure/governance/resource-graph/overview). This export allows for additional analysis and align to operational requirements. A custom data export pipeline and processes will be needed to operationalize the dataset. Primary queries to access the data are: ```none securityresources @@ -108,7 +108,7 @@ securityresources ## 3. Management Groups -[Management Groups](https://docs.microsoft.com/azure/governance/management-groups/overview) enable organizations to efficiently manage access, governance and compliance across all subscriptions. Azure management groups provide a level of scope above subscriptions. Subscriptions are organized into containers called "management groups" and apply Azure Policies and role-based access control to the management groups. All subscriptions within a management group automatically inherit the settings applied to the management group. +[Management Groups](https://learn.microsoft.com/azure/governance/management-groups/overview) enable organizations to efficiently manage access, governance and compliance across all subscriptions. Azure management groups provide a level of scope above subscriptions. Subscriptions are organized into containers called "management groups" and apply Azure Policies and role-based access control to the management groups. All subscriptions within a management group automatically inherit the settings applied to the management group. Management groups give you enterprise-grade management at a large scale no matter what type of subscriptions you might have. All subscriptions within a single management group must trust the same Azure Active Directory tenant. @@ -170,7 +170,7 @@ When choosing a management group hierarchy, consider the following: * Authoritative guidance from subject matter experts * Your organizational requirements * Recommended best practices -* [Important facts about management groups](https://docs.microsoft.com/azure/governance/management-groups/overview#important-facts-about-management-groups) +* [Important facts about management groups](https://learn.microsoft.com/azure/governance/management-groups/overview#important-facts-about-management-groups) Customers with existing management group structure can consider merging the recommended structure to continue to use the existing structure. The new structure deployed side-by-side will enable the ability to: @@ -204,7 +204,7 @@ The service principal requires `Owner` role to configure role assignments for: * Policy Assignments that provide remediation (i.e. `deployIfNotExists` policies) * Archetype deployments (i.e. workload deployments) with role assignments between Azure Services for integration and to Security Groups for user access -> **Recommendation:** Consider setting up approval flow through Azure DevOps to ensure better control over pipeline execution. See [Release gates and approvals overview](https://docs.microsoft.com/azure/devops/pipelines/release/approvals/?view=azure-devops) in Azure Docs. +> **Recommendation:** Consider setting up approval flow through Azure DevOps to ensure better control over pipeline execution. See [Release gates and approvals overview](https://learn.microsoft.com/azure/devops/pipelines/release/approvals/?view=azure-devops) in Azure Docs. Additional service principal accounts must be created and scoped to child management groups, subscriptions or resource groups based on tasks that are expected of the service principal accounts. @@ -220,24 +220,24 @@ Access Control at Management Group scope enables management and oversight at sca | Scenario | Permanent Assignment | On-Demand Assignment (through Azure AD PIM) | | --- | --- | --- | -| Global Reader | [Reader](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#reader) | - | -| Governance | - | [Resource Policy Contributor](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#resource-policy-contributor) | -| Log Management | [Log Analytics Reader](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#log-analytics-reader) | [Log Analytics Contributor](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#log-analytics-contributor) | -| Security Management | [Security Reader](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#security-reader) | [Security Admin](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#security-admin) | -| User Management | - | [User Access Administrator](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#user-access-administrator) | -| Cost Management | [Billing Reader](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#billing-reader) | - | +| Global Reader | [Reader](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#reader) | - | +| Governance | - | [Resource Policy Contributor](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#resource-policy-contributor) | +| Log Management | [Log Analytics Reader](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#log-analytics-reader) | [Log Analytics Contributor](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#log-analytics-contributor) | +| Security Management | [Security Reader](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#security-reader) | [Security Admin](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#security-admin) | +| User Management | - | [User Access Administrator](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#user-access-administrator) | +| Cost Management | [Billing Reader](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#billing-reader) | - | ### 4.4 Recommendations for Subscriptions The table provides the 3 generic roles that are commonly used in Azure environment. Granular built-in roles can be used based on use case to further limit the access control. Our recommendation is to assign the least privileged role that is required for a person or service principal to complete the tasks. -Review the [Azure Built-In roles](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles) to evaluate applicability. +Review the [Azure Built-In roles](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles) to evaluate applicability. | Environment | Scenario | Considerations | Permanent Assignment | On-Demand Assignment (through Azure AD PIM) | --- | --- | --- | --- | --- | -| All | Read Access | Permanent role assigned to all users who need access to the Azure resources. | [Reader](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#reader) | - | -| Dev/Test, QA | Manage Azure resources | Contributor role can deploy all Azure resources, however any RBAC assignments will require the permissions to be elevated to Owner.

Alternative is to leverage DevOps Pipeline and the Service Principal Account with elevated permissions. | [Contributor](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#contributor) | [Owner](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#owner) | -| Production | Manage Azure resources | No standing management permissions in Production.

Owner role is only required for RBAC changes, otherwise, use Contributor role or another built-in role for all other operations. | - | [Contributor](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#contributor) or [Owner](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#owner) +| All | Read Access | Permanent role assigned to all users who need access to the Azure resources. | [Reader](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#reader) | - | +| Dev/Test, QA | Manage Azure resources | Contributor role can deploy all Azure resources, however any RBAC assignments will require the permissions to be elevated to Owner.

Alternative is to leverage DevOps Pipeline and the Service Principal Account with elevated permissions. | [Contributor](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#contributor) | [Owner](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#owner) | +| Production | Manage Azure resources | No standing management permissions in Production.

Owner role is only required for RBAC changes, otherwise, use Contributor role or another built-in role for all other operations. | - | [Contributor](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#contributor) or [Owner](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#owner) ### 4.5 Recommendations for Resource Groups @@ -273,11 +273,11 @@ Reference implementation provides two topologies for Hub Network design: ### Azure Bastion -Bastion [does not support User Defined Route](https://docs.microsoft.com/azure/bastion/bastion-faq#udr) but can work with Virtual Machines on peered virtual networks as long as the [Network Security Groups allow][nsgAzureBastion] it and the user has the [required role based access control](https://docs.microsoft.com/azure/bastion/bastion-faq#i-have-access-to-the-peered-vnet-but-i-cant-see-the-vm-deployed-there) +Bastion [does not support User Defined Route](https://learn.microsoft.com/azure/bastion/bastion-faq#udr) but can work with Virtual Machines on peered virtual networks as long as the [Network Security Groups allow][nsgAzureBastion] it and the user has the [required role based access control](https://learn.microsoft.com/azure/bastion/bastion-faq#i-have-access-to-the-peered-vnet-but-i-cant-see-the-vm-deployed-there) ### Azure Application Gateway -Application Gateway [does not support default UDRs to an NVA](https://docs.microsoft.com/en-us/azure/application-gateway/configuration-infrastructure): +Application Gateway [does not support default UDRs to an NVA](https://learn.microsoft.com/en-us/azure/application-gateway/configuration-infrastructure): > "Any scenario where 0.0.0.0/0 needs to be redirected through any virtual appliance, a hub/spoke virtual network, or on-premise (forced tunneling) isn't supported for V2.". @@ -293,19 +293,28 @@ Azure PaaS services use Private DNS Zones to map their fully qualified domain na * Private DNS Zones from being created in the spoke subscriptions. These can only be created in the designated resource group in the Hub Subscription. * Ensure private endpoints can be automatically mapped to the centrally managed Private DNS Zones. -The following diagram shows a typical high-level architecture for enterprise environments with central DNS resolution and name resolution for Private Link resources via Azure Private DNS. This topology provides: +The following diagrams show a typical high-level architecture for enterprise environments with central DNS resolution and name resolution for Private Link resources via Azure Private DNS. This topology provides: * Name resolution from hub to spoke * Name resolution from spoke to spoke * Name resolution from on-premises to Azure (Hub & Spoke resources). Additional configuration is required to deploy DNS resolvers in the Hub Network & provide DNS forwarding from on-premises to Azure. +**`DNS Resolution using Azure DNS Resolver`** +![DNS using Azure DNS Resolver](https://learn.microsoft.com/en-us/azure/dns/media/dns-resolver-overview/resolver-architecture.png) +**Reference:** [What is Azure DNS Private Resolver?](https://learn.microsoft.com/en-us/azure/dns/dns-resolver-overview) + + + +**`DNS using Virtual Machines managed by IT`** + ![Hub Managed DNS](media/architecture/hubnetwork-private-link-central-dns.png) -**Reference:** [Private Link and DNS integration at scale](https://docs.microsoft.com/azure/cloud-adoption-framework/ready/azure-best-practices/private-link-and-dns-integration-at-scale) +**Reference:** [Private Link and DNS integration at scale](https://learn.microsoft.com/azure/cloud-adoption-framework/ready/azure-best-practices/private-link-and-dns-integration-at-scale) Reference implementation provides the following capabilities: -* Deploy Private DNS Zones to the Hub Networking subscription. Enable/disable via configuration. +* Deploy Private DNS Zones to the Hub Networking or Identity subscription. Enable/disable via configuration. +* Deploy Azure DNS Private Resolver to the Hub or Identity Subscription. Enable/disable via configuration. * Azure Policy to block private zones from being created outside of the designated resource group in the Hub networking subscription. * Azure Policy to automatically detect new private endpoints and add their A records to their respective Private DNS Zone. * Support to ensure Hub managed Private DNS Zones are used when deploying archetypes. @@ -314,9 +323,9 @@ The reference implementation does not deploy DNS Servers (as Virtual Machines) i * Leverage Azure Firewall's DNS Proxy where the Private DNS Zones are linked only to the Hub Virtual Network. DNS resolution for all spokes will be through the VIP provided by Azure Firewall. -* Link Private DNS Zones directly to the spoke virtual networks and use the [built-in DNS resolver in each virtual network](https://docs.microsoft.com/azure/virtual-network/virtual-networks-name-resolution-for-vms-and-role-instances). Virtual network(s) in spoke subscriptions be configured through Virtual Network Link for name resolution. DNS resolution is automatic once the Private DNS Zone is linked to the virtual network. +* Link Private DNS Zones directly to the spoke virtual networks and use the [built-in DNS resolver in each virtual network](https://learn.microsoft.com/azure/virtual-network/virtual-networks-name-resolution-for-vms-and-role-instances). Virtual network(s) in spoke subscriptions be configured through Virtual Network Link for name resolution. DNS resolution is automatic once the Private DNS Zone is linked to the virtual network. -* Leverage DNS Servers on virtual machines that are managed by department's IT. +* Leverage DNS Services from either Azure DNS Private Resolver or on virtual machines that are managed by department's IT. ### Spoke Landing Zone Networks @@ -336,7 +345,7 @@ Following the nomenclature of [ITSG-22][itsg22], these would be the default subn ### 6.1 Scope -Microsoft's recommendation is [one central Log Analytics workspace](https://docs.microsoft.com/azure/azure-monitor/logs/design-logs-deployment#important-considerations-for-an-access-control-strategy) that will be shared by IT, Security Analysts and Application Teams. +Microsoft's recommendation is [one central Log Analytics workspace](https://learn.microsoft.com/azure/azure-monitor/logs/design-logs-deployment#important-considerations-for-an-access-control-strategy) that will be shared by IT, Security Analysts and Application Teams. The design and recommendation are based on the following requirements: @@ -367,9 +376,9 @@ The workspace will be configured as: As the logging strategy evolves, Microsoft recommends considering the following improvements: -* To optimize cost, configure [data retention periods by data type](https://docs.microsoft.com/azure/azure-monitor/logs/manage-cost-storage#retention-by-data-type). +* To optimize cost, configure [data retention periods by data type](https://learn.microsoft.com/azure/azure-monitor/logs/manage-cost-storage#retention-by-data-type). * To optimize cost, collect only the logs that are required for operations and security monitoring. Current requirement is to collect all logs. -* For data retention greater than 2 years, export logs to Azure Storage and [leverage immutable storage](https://docs.microsoft.com/azure/storage/blobs/storage-blob-immutable-storage) with WORM policy (Write Once, Read Many) to make data non-erasable and non-modifiable. +* For data retention greater than 2 years, export logs to Azure Storage and [leverage immutable storage](https://learn.microsoft.com/azure/storage/blobs/storage-blob-immutable-storage) with WORM policy (Write Once, Read Many) to make data non-erasable and non-modifiable. * Use Security Groups to control access to all or per-resource logs. ### 6.2 Design considerations for multiple Log Analytics workspaces @@ -380,7 +389,7 @@ As the logging strategy evolves, Microsoft recommends considering the following | Avoid outbound data transfer charges by having a workspace in the same region as the Azure resources it manages. | Not applicable to current environment since all Azure deployments will be in Canada Central. | | Manage multiple departments or business groups, and need each to see their own data, but not data from others. Also, there is no business requirement for a consolidated cross department or business group view. | Not applicable since security analysts require cross department querying capabilities, but each department or Application Team can only see their data. Data access control is achieved through role-based access control. | -**Reference**: [Designing your Azure Monitor Logs deployment](https://docs.microsoft.com/en-ca/azure/azure-monitor/logs/design-logs-deployment#important-considerations-for-an-access-control-strategy) +**Reference**: [Designing your Azure Monitor Logs deployment](https://learn.microsoft.com/en-ca/azure/azure-monitor/logs/design-logs-deployment#important-considerations-for-an-access-control-strategy) ### 6.3 Access Control - Use resource or workspace permissions @@ -388,11 +397,11 @@ With Azure role-based access control (Azure RBAC), you can grant users and group For example, when you grant access to your team responsible for infrastructure services hosted on Azure virtual machines (VMs), and as a result they'll have access to only the logs generated by those VMs. This is following **resource-context** log model. The basis for this model is for every log record emitted by an Azure resource, it is automatically associated with this resource. Logs are forwarded to a central workspace that respects scoping and Azure RBAC based on the resources. -**Reference**: [Designing your Azure Monitor Logs deployment - Access Control](https://docs.microsoft.com/en-ca/azure/azure-monitor/logs/design-logs-deployment?WT.mc_id=modinfra-11671-pierrer#access-control-overview) +**Reference**: [Designing your Azure Monitor Logs deployment - Access Control](https://learn.microsoft.com/en-ca/azure/azure-monitor/logs/design-logs-deployment?WT.mc_id=modinfra-11671-pierrer#access-control-overview) | Scenario | Log Access Mode | Log Data Visibility | | --- | --- | --- | -| Security Analyst with [Log Analytics Reader or Log Analytics Contributor](https://docs.microsoft.com/en-ca/azure/azure-monitor/logs/manage-access#manage-access-using-azure-permissions) RBAC role assignment. | Access the Log Analytics workspace directly through Azure Portal or through Microsoft Sentinel. | All data in the Log Analytics Workspace. | +| Security Analyst with [Log Analytics Reader or Log Analytics Contributor](https://learn.microsoft.com/en-ca/azure/azure-monitor/logs/manage-access#manage-access-using-azure-permissions) RBAC role assignment. | Access the Log Analytics workspace directly through Azure Portal or through Microsoft Sentinel. | All data in the Log Analytics Workspace. | | IT Teams responsible for one or more line of business with permissions to one or more subscriptions, resource groups or resources with at least Reader role. | Access the logs through the resource's Logs menu for the Azure resource (i.e., VM or Storage Account or Database). | Only to Azure resources based on RBAC. User can query logs for specific resources, resource groups, or subscription they have access to from any workspace but can't query logs for other resources. | | Application Team with permissions to one or more subscriptions, resource groups or resources with at least Reader role. | Access the logs through the resource's Logs menu for the Azure resource (i.e., VM or Storage Account or Database). | Only to Azure resources based on RBAC. User can query logs for specific resources, resource groups, or subscription they have access to from any workspace but can't query logs for other resources. | @@ -556,6 +565,7 @@ Use the [Azure DevOps Pipelines](onboarding/azure-devops-pipelines.md) onboardin | Platform – Hub Networking using NVAs | platform-connectivity-hub-nva.yml | platform-connectivity-hub-nva-ci | Configures Hub Networking with Fortigate Firewalls. | spn-azure-platform-ops | None | | Platform – Hub Networking with Azure Firewall - Firewall Policy | platform-connectivity-hub-azfw-policy.yml | platform-connectivity-hub-azfw-policy-ci | Configures Azure Firewall Policy. A policy contains firewall rules and firewall configuration such as enabling DNS Proxy. Firewall policies can be updated independently of Azure Firewall. | spn-azure-platform-ops | None | | Platform – Hub Networking with Azure Firewall | platform-connectivity-hub-azfw.yml | platform-connectivity-hub-azfw-ci | Configures Hub Networking with Azure Firewall. | spn-azure-platform-ops | None | +| Identity | platform-identity.yml | platform-identity-ci | Configures a Identity Landing Zone that will be used by all landing zones for managing identities services (i.e. Domain Controllers). | spn-azure-platform-ops | None | | Subscriptions | subscriptions.yml | subscriptions-ci | Configures a new subscription based on the archetype defined in the configuration file name. | spn-azure-platform-ops | None | | Pull Request Validation | pull-request-check.yml | pull-request-validation-ci | Checks for breaking changes to Bicep templates & parameter schemas prior to merging the change to main branch. This pipeline must be configured as a check for the `main` branch. | spn-azure-platform-ops | None | @@ -565,13 +575,13 @@ By using gates, approvals, and manual intervention you can take full control of | Scenario | Feature(s) to use | | --- | --- | -| A user must manually validate the change request and approve the deployment to a certain stage. | [Pre-deployment approvals](https://docs.microsoft.com/azure/devops/pipelines/release/approvals/approvals?view=azure-devops) | -| A user must manually sign off after deployment before the release is triggered to other stages. | [Post-deployment approvals](https://docs.microsoft.com/azure/devops/pipelines/release/approvals/approvals?view=azure-devops) | -| A team wants to ensure there are no active issues in the work item or problem management system before deploying a build to a stage. | [Pre-deployment gates](https://docs.microsoft.com/azure/devops/pipelines/release/approvals/gates?view=azure-devops) | -| A team wants to ensure there are no reported incidents after deployment, before triggering a release. | [Post-deployment gates](https://docs.microsoft.com/azure/devops/pipelines/release/approvals/gates?view=azure-devops) | -| After deployment, a team wants to wait for a specified time before prompting users to sign out. | [Post-deployment gates](https://docs.microsoft.com/azure/devops/pipelines/release/approvals/gates?view=azure-devops) and [post-deployment approvals](https://docs.microsoft.com/azure/devops/pipelines/release/approvals/approvals?view=azure-devops) | -| During deployment, a user must manually follow specific instructions and then resume the deployment. | [Manual Intervention](https://docs.microsoft.com/azure/devops/pipelines/release/deploy-using-approvals?view=azure-devops#configure-maninter) or [Manual Validation](https://docs.microsoft.com/azure/devops/pipelines/release/deploy-using-approvals?view=azure-devops#view-approvals) | -| During deployment, a team wants to prompt users to enter a value for a parameter used by the deployment tasks or allow users to edit the release. | [Manual Intervention](https://docs.microsoft.com/azure/devops/pipelines/release/deploy-using-approvals?view=azure-devops#configure-maninter) or [Manual Validation](https://docs.microsoft.com/azure/devops/pipelines/release/deploy-using-approvals?view=azure-devops#view-approvals) | +| A user must manually validate the change request and approve the deployment to a certain stage. | [Pre-deployment approvals](https://learn.microsoft.com/azure/devops/pipelines/release/approvals/approvals?view=azure-devops) | +| A user must manually sign off after deployment before the release is triggered to other stages. | [Post-deployment approvals](https://learn.microsoft.com/azure/devops/pipelines/release/approvals/approvals?view=azure-devops) | +| A team wants to ensure there are no active issues in the work item or problem management system before deploying a build to a stage. | [Pre-deployment gates](https://learn.microsoft.com/azure/devops/pipelines/release/approvals/gates?view=azure-devops) | +| A team wants to ensure there are no reported incidents after deployment, before triggering a release. | [Post-deployment gates](https://learn.microsoft.com/azure/devops/pipelines/release/approvals/gates?view=azure-devops) | +| After deployment, a team wants to wait for a specified time before prompting users to sign out. | [Post-deployment gates](https://learn.microsoft.com/azure/devops/pipelines/release/approvals/gates?view=azure-devops) and [post-deployment approvals](https://learn.microsoft.com/azure/devops/pipelines/release/approvals/approvals?view=azure-devops) | +| During deployment, a user must manually follow specific instructions and then resume the deployment. | [Manual Intervention](https://learn.microsoft.com/azure/devops/pipelines/release/deploy-using-approvals?view=azure-devops#configure-maninter) or [Manual Validation](https://learn.microsoft.com/azure/devops/pipelines/release/deploy-using-approvals?view=azure-devops#view-approvals) | +| During deployment, a team wants to prompt users to enter a value for a parameter used by the deployment tasks or allow users to edit the release. | [Manual Intervention](https://learn.microsoft.com/azure/devops/pipelines/release/deploy-using-approvals?view=azure-devops#configure-maninter) or [Manual Validation](https://learn.microsoft.com/azure/devops/pipelines/release/deploy-using-approvals?view=azure-devops#view-approvals) | | During deployment, a team wants to wait for monitoring or information portals to detect any active incidents, before continuing with other deployment jobs. | Planned, but not yet implemented for YAML pipelines | You can combine all three techniques within a release pipeline to fully achieve your own deployment requirements. @@ -609,9 +619,9 @@ Steps to implement user validation (approval) check: [rfc1918]: https://tools.ietf.org/html/rfc1918 [rfc6598]: https://tools.ietf.org/html/rfc6598 [nist80053r4]: https://csrc.nist.gov/publications/detail/sp/800-53/rev-4/archive/2015-01-22 -[nist80053r4Policyset]: https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4 -[nist80053r5Policyset]: https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5 -[pbmmPolicyset]: https://docs.microsoft.com/azure/governance/policy/samples/canada-federal-pbmm -[cafLandingZones]: https://docs.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/ -[policyRemediation]: https://docs.microsoft.com/azure/governance/policy/how-to/remediate-resources -[nsgAzureBastion]: https://docs.microsoft.com/azure/bastion/bastion-nsg#apply +[nist80053r4Policyset]: https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4 +[nist80053r5Policyset]: https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5 +[pbmmPolicyset]: https://learn.microsoft.com/azure/governance/policy/samples/canada-federal-pbmm +[cafLandingZones]: https://learn.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/ +[policyRemediation]: https://learn.microsoft.com/azure/governance/policy/how-to/remediate-resources +[nsgAzureBastion]: https://learn.microsoft.com/azure/bastion/bastion-nsg#apply diff --git a/docs/gc-30-day-cloud-guardrails.md b/docs/gc-30-day-cloud-guardrails.md index d2334288..86d8c772 100644 --- a/docs/gc-30-day-cloud-guardrails.md +++ b/docs/gc-30-day-cloud-guardrails.md @@ -39,13 +39,13 @@ Many of the guardrails contain identity and access management requirements. Howe ### Azure AD Logging and Monitoring When configuring your Azure AD tenant, ensure that: -- [Azure AD logs are being sent to Log Analytics](https://docs.microsoft.com/azure/active-directory/reports-monitoring/howto-integrate-activity-logs-with-log-analytics) -- [Azure AD logs are being sent to Microsoft Sentinel](https://docs.microsoft.com/azure/sentinel/connect-azure-active-directory) +- [Azure AD logs are being sent to Log Analytics](https://learn.microsoft.com/azure/active-directory/reports-monitoring/howto-integrate-activity-logs-with-log-analytics) +- [Azure AD logs are being sent to Microsoft Sentinel](https://learn.microsoft.com/azure/sentinel/connect-azure-active-directory) > NOTE: Azure AD P1/P2 is required to ingest sign-in logs to Microsoft Sentinel. To create alerts from sign-in logs, refer to: -- [Create, view, and manage log alerts using Azure Monitor](https://docs.microsoft.com/azure/azure-monitor/alerts/alerts-log) -- [Microsoft Sentinel: Create custom analytics rules to detect threats](https://docs.microsoft.com/azure/sentinel/detect-threats-custom) +- [Create, view, and manage log alerts using Azure Monitor](https://learn.microsoft.com/azure/azure-monitor/alerts/alerts-log) +- [Microsoft Sentinel: Create custom analytics rules to detect threats](https://learn.microsoft.com/azure/sentinel/detect-threats-custom) ### Azure AD Recommendations @@ -57,60 +57,60 @@ The following features provide native solutions to several guardrails, including #### Azure AD Conditional Access (Azure AD P1/P2 Required) -Consider implementing [Azure AD Conditional Access](https://docs.microsoft.com/azure/active-directory/conditional-access/overview) to create fine-tuned access policies with contextual factors such as user, device, location, and real-time risk information to control what a specific user can access, and how and when they have access. +Consider implementing [Azure AD Conditional Access](https://learn.microsoft.com/azure/active-directory/conditional-access/overview) to create fine-tuned access policies with contextual factors such as user, device, location, and real-time risk information to control what a specific user can access, and how and when they have access. -Refer to [Plan a Conditional Access deployment](https://docs.microsoft.com/azure/active-directory/conditional-access/plan-conditional-access) to get started. +Refer to [Plan a Conditional Access deployment](https://learn.microsoft.com/azure/active-directory/conditional-access/plan-conditional-access) to get started. #### Azure AD Identity Protection (Azure AD P2 Required) -Consider implementing [Azure AD Identity Protection](https://docs.microsoft.com/azure/active-directory/identity-protection/overview-identity-protection) to detect, investigate, and remediate suspicious user and sign-in behavior in your environment. +Consider implementing [Azure AD Identity Protection](https://learn.microsoft.com/azure/active-directory/identity-protection/overview-identity-protection) to detect, investigate, and remediate suspicious user and sign-in behavior in your environment. When configuring Azure AD Identity Protection, ensure that: -- [Azure AD Identity Protection alerts are configured](https://docs.microsoft.com/azure/active-directory/identity-protection/howto-identity-protection-configure-notifications) -- [Azure AD Identity Protection logs are being sent to Log Analytics](https://docs.microsoft.com/azure/active-directory/identity-protection/howto-export-risk-data) -- [Azure AD Identity Protection logs are being sent to Microsoft Sentinel](https://docs.microsoft.com/azure/sentinel/data-connectors-reference#azure-active-directory-identity-protection). +- [Azure AD Identity Protection alerts are configured](https://learn.microsoft.com/azure/active-directory/identity-protection/howto-identity-protection-configure-notifications) +- [Azure AD Identity Protection logs are being sent to Log Analytics](https://learn.microsoft.com/azure/active-directory/identity-protection/howto-export-risk-data) +- [Azure AD Identity Protection logs are being sent to Microsoft Sentinel](https://learn.microsoft.com/azure/sentinel/data-connectors-reference#azure-active-directory-identity-protection). #### Azure AD Privileged Identity Management (Azure AD P2 Required) -Consider implementing [Azure AD Privileged Identity Management (PIM)](https://docs.microsoft.com/azure/active-directory/privileged-identity-management/pim-configure) to provide time-based and approval-based role activation to mitigate the risks of excessive, unnecessary, or misused access to important resources in your organization +Consider implementing [Azure AD Privileged Identity Management (PIM)](https://learn.microsoft.com/azure/active-directory/privileged-identity-management/pim-configure) to provide time-based and approval-based role activation to mitigate the risks of excessive, unnecessary, or misused access to important resources in your organization -When configuring Azure AD PIM, ensure that [Azure AD PIM alerts are configured](https://docs.microsoft.com/azure/active-directory/privileged-identity-management/pim-how-to-configure-security-alerts). +When configuring Azure AD PIM, ensure that [Azure AD PIM alerts are configured](https://learn.microsoft.com/azure/active-directory/privileged-identity-management/pim-how-to-configure-security-alerts). -Refer to [Plan a Privileged Identity Management deployment](https://docs.microsoft.com/azure/active-directory/privileged-identity-management/pim-deployment-plan) to get started. +Refer to [Plan a Privileged Identity Management deployment](https://learn.microsoft.com/azure/active-directory/privileged-identity-management/pim-deployment-plan) to get started. #### Azure AD Access Reviews (Azure AD P2 Required) -Consider implementing [Azure AD Access Reviews](https://docs.microsoft.com/azure/active-directory/governance/access-reviews-overview) to efficiently manage group memberships, access to enterprise applications, and role assignments. User's access can be reviewed on a regular basis to make sure only the right people have continued access. +Consider implementing [Azure AD Access Reviews](https://learn.microsoft.com/azure/active-directory/governance/access-reviews-overview) to efficiently manage group memberships, access to enterprise applications, and role assignments. User's access can be reviewed on a regular basis to make sure only the right people have continued access. -Refer to [Plan an Azure Active Directory access reviews deployment](https://docs.microsoft.com/azure/active-directory/governance/deploy-access-reviews) to get started. +Refer to [Plan an Azure Active Directory access reviews deployment](https://learn.microsoft.com/azure/active-directory/governance/deploy-access-reviews) to get started. #### User and Entity Behavioral Analytics -Consider enabling [User and Entity Behavioral Analytics](https://docs.microsoft.com/azure/sentinel/identify-threats-with-entity-behavior-analytics) within Microsoft Sentinel to identify anomalous activity and help you determine if an asset has been compromised (usage fees apply). +Consider enabling [User and Entity Behavioral Analytics](https://learn.microsoft.com/azure/sentinel/identify-threats-with-entity-behavior-analytics) within Microsoft Sentinel to identify anomalous activity and help you determine if an asset has been compromised (usage fees apply). ### ALZCPS Identity Management Policies The following policies related to identity management are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 5 AC-2: Account Management](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#account-management) -- [Azure Policy - NIST SP 800-53 Rev. 5 AC-2 (1): Automated System Account Management](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#automated-system-account-management) -- [Azure Policy - NIST SP 800-53 Rev. 5 AC-3: Access Enforcement](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#access-enforcement) -- [Azure Policy - NIST SP 800-53 Rev. 5 AC-5: Separation of Duties](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#separation-of-duties) -- [Azure Policy - NIST SP 800-53 Rev. 5 AC-6: Least Privilege](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#least-privilege) -- [Azure Policy - NIST SP 800-53 Rev. 4 AC-6 (5): Account Management](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#account-management) -- [Azure Policy - NIST SP 800-53 Rev. 4 AC-6 (10): Prohibit Non-privileged Users from Executing Privileged Functions](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#prohibit-non-privileged-users-from-executing-privileged-functions) -- [Azure Policy - NIST SP 800-53 Rev. 4 AC-7: Unsuccessful Logon Attempts](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#unsuccessful-logon-attempts) -- [Azure Policy - NIST SP 800-53 Rev. 4 AC-19: Access Control for Mobile Devices](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#access-control-for-mobile-devices) -- [Azure Policy - NIST SP 800-53 Rev. 5 IA-2: Identification and Authentication (organizational Users)](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#identification-and-authentication-organizational-users) -- [Azure Policy - NIST SP 800-53 Rev. 5 IA-2 (1): Multi-factor Authentication to Privileged Accounts](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#access-control-for-mobile-devices) -- [Azure Policy - NIST SP 800-53 Rev. 5 IA-2 (2): Multi-factor Authentication to Non-privileged Accounts](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#multi-factor-authentication-to-non-privileged-accounts) -- [Azure Policy - NIST SP 800-53 Rev. 4 IA-2 (11): Remote Access - Separate Device](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#remote-access---separate-device) -- [Azure Policy - NIST SP 800-53 Rev. 5 IA-4: Identifier Management](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#identifier-management) -- [Azure Policy - NIST SP 800-53 Rev. 5 IA-5: Authenticator Management](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#authenticator-management) -- [Azure Policy - NIST SP 800-53 Rev. 5 IA-5 (1): Password-based Authentication](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#password-based-authentication) -- [Azure Policy - NIST SP 800-53 Rev. 4 IA-5 (7): No Embedded Unencrypted Static Authenticators](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#no-embedded-unencrypted-static-authenticators) -- [Azure Policy - NIST SP 800-53 Rev. 4 IA-5 (13): Expiration of Cached Authenticators](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#expiration-of-cached-authenticators) -- [Azure Policy - NIST SP 800-53 Rev. 4 IA-6: Authenticator Feedback](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#authenticator-feedback) -- [Azure Policy - NIST SP 800-53 Rev. 4 IA-8: Identification and Authentication (non-organizational Users)](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#identification-and-authentication-non-organizational-users) +- [Azure Policy - NIST SP 800-53 Rev. 5 AC-2: Account Management](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#account-management) +- [Azure Policy - NIST SP 800-53 Rev. 5 AC-2 (1): Automated System Account Management](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#automated-system-account-management) +- [Azure Policy - NIST SP 800-53 Rev. 5 AC-3: Access Enforcement](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#access-enforcement) +- [Azure Policy - NIST SP 800-53 Rev. 5 AC-5: Separation of Duties](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#separation-of-duties) +- [Azure Policy - NIST SP 800-53 Rev. 5 AC-6: Least Privilege](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#least-privilege) +- [Azure Policy - NIST SP 800-53 Rev. 4 AC-6 (5): Account Management](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#account-management) +- [Azure Policy - NIST SP 800-53 Rev. 4 AC-6 (10): Prohibit Non-privileged Users from Executing Privileged Functions](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#prohibit-non-privileged-users-from-executing-privileged-functions) +- [Azure Policy - NIST SP 800-53 Rev. 4 AC-7: Unsuccessful Logon Attempts](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#unsuccessful-logon-attempts) +- [Azure Policy - NIST SP 800-53 Rev. 4 AC-19: Access Control for Mobile Devices](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#access-control-for-mobile-devices) +- [Azure Policy - NIST SP 800-53 Rev. 5 IA-2: Identification and Authentication (organizational Users)](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#identification-and-authentication-organizational-users) +- [Azure Policy - NIST SP 800-53 Rev. 5 IA-2 (1): Multi-factor Authentication to Privileged Accounts](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#access-control-for-mobile-devices) +- [Azure Policy - NIST SP 800-53 Rev. 5 IA-2 (2): Multi-factor Authentication to Non-privileged Accounts](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#multi-factor-authentication-to-non-privileged-accounts) +- [Azure Policy - NIST SP 800-53 Rev. 4 IA-2 (11): Remote Access - Separate Device](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#remote-access---separate-device) +- [Azure Policy - NIST SP 800-53 Rev. 5 IA-4: Identifier Management](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#identifier-management) +- [Azure Policy - NIST SP 800-53 Rev. 5 IA-5: Authenticator Management](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#authenticator-management) +- [Azure Policy - NIST SP 800-53 Rev. 5 IA-5 (1): Password-based Authentication](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#password-based-authentication) +- [Azure Policy - NIST SP 800-53 Rev. 4 IA-5 (7): No Embedded Unencrypted Static Authenticators](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#no-embedded-unencrypted-static-authenticators) +- [Azure Policy - NIST SP 800-53 Rev. 4 IA-5 (13): Expiration of Cached Authenticators](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#expiration-of-cached-authenticators) +- [Azure Policy - NIST SP 800-53 Rev. 4 IA-6: Authenticator Feedback](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#authenticator-feedback) +- [Azure Policy - NIST SP 800-53 Rev. 4 IA-8: Identification and Authentication (non-organizational Users)](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#identification-and-authentication-non-organizational-users) ## Guardrails @@ -120,28 +120,28 @@ The following policies related to identity management are enabled by default in #### 1.1 Implement multi-factor authentication (MFA) mechanism for root/master account. -This consideration can be met by appropriately configuring your Azure AD instance. Review the following tutorial: [Secure user sign-in events with Azure AD Multi-Factor Authentication](https://docs.microsoft.com/azure/active-directory/authentication/tutorial-enable-azure-mfa) +This consideration can be met by appropriately configuring your Azure AD instance. Review the following tutorial: [Secure user sign-in events with Azure AD Multi-Factor Authentication](https://learn.microsoft.com/azure/active-directory/authentication/tutorial-enable-azure-mfa) [Azure AD Conditional Access](#azure-ad-conditional-access-azure-ad-p1-required) is a native solution that can help to meet this consideration. Multi-factor authentication controls are implemented as listed in [ALZCPS Identity Management Policies](#alzcps-identity-management-policies). Relevant Links: -- [Conditional Access: Require MFA for administrators](https://docs.microsoft.com/azure/active-directory/conditional-access/howto-conditional-access-policy-admin-mfa) +- [Conditional Access: Require MFA for administrators](https://learn.microsoft.com/azure/active-directory/conditional-access/howto-conditional-access-policy-admin-mfa) #### 1.2 Document a break glass emergency account management procedure. Including names of users with root or master account access. Documentation exercises are out of scope. GC intranet users can reference the [break-glass emergency account procedure document](https://gcconnex.gc.ca/file/view/55010566/break-glass-emergency-account-procedure-departments-can-use-to-develop-their-emergency-access-management-controls-for-cloud?language=en). Relevant Links: -- [Manage emergency access accounts in Azure AD](https://docs.microsoft.com/azure/active-directory/roles/security-emergency-access) +- [Manage emergency access accounts in Azure AD](https://learn.microsoft.com/azure/active-directory/roles/security-emergency-access) #### 1.3 Obtain signature from Departmental Chief Information Officer (CIO) and Chief Security Officer (CSO) to confirm acknowledgement and approval of the break glass emergency account management procedures. Documentation exercises are out of scope. GC intranet users can reference the [break-glass emergency account procedure document](https://gcconnex.gc.ca/file/view/55010566/break-glass-emergency-account-procedure-departments-can-use-to-develop-their-emergency-access-management-controls-for-cloud?language=en). Relevant Links: -- [Manage emergency access accounts in Azure AD](https://docs.microsoft.com/azure/active-directory/roles/security-emergency-access) +- [Manage emergency access accounts in Azure AD](https://learn.microsoft.com/azure/active-directory/roles/security-emergency-access) #### 1.4 Implement a mechanism for enforcing access authorizations. @@ -155,9 +155,9 @@ The following native solutions can help to meet this consideration: Access authorization controls are implemented as listed in [ALZCPS Identity Management Policies](#alzcps-identity-management-policies). Relevant Links: -- [Authorization with Azure AD](https://docs.microsoft.com/azure/architecture/framework/security/design-identity-authorization) -- [What is Azure role-based access control (Azure RBAC)?](https://docs.microsoft.com/azure/role-based-access-control/overview) -- [Steps to assign an Azure role](https://docs.microsoft.com/azure/role-based-access-control/role-assignments-steps) +- [Authorization with Azure AD](https://learn.microsoft.com/azure/architecture/framework/security/design-identity-authorization) +- [What is Azure role-based access control (Azure RBAC)?](https://learn.microsoft.com/azure/role-based-access-control/overview) +- [Steps to assign an Azure role](https://learn.microsoft.com/azure/role-based-access-control/role-assignments-steps) #### 1.5 Configure appropriate alerts on root/master accounts to detect a potential compromise, in accordance with the GC Event Logging Guidance. @@ -168,8 +168,8 @@ The following native solutions can help to meet this consideration: - [User and Entity Behavioral Analytics (UEBA)](#azure-ad-privileged-identity-management-azure-ad-p2-required) Related Links: -- [Azure Active Directory Identity Protection notifications](https://docs.microsoft.com/azure/active-directory/identity-protection/howto-identity-protection-configure-notifications) -- [Identity Protection - How To: Export risk data](https://docs.microsoft.com/azure/active-directory/identity-protection/howto-export-risk-data) +- [Azure Active Directory Identity Protection notifications](https://learn.microsoft.com/azure/active-directory/identity-protection/howto-identity-protection-configure-notifications) +- [Identity Protection - How To: Export risk data](https://learn.microsoft.com/azure/active-directory/identity-protection/howto-export-risk-data) --- @@ -187,7 +187,7 @@ The following native solutions can help to meet this consideration: Relevant Links: - [SPIN 2017-01 Subsection 6.2.3](https://www.canada.ca/en/government/system/digital-government/digital-government-innovations/cloud-services/direction-secure-use-commercial-cloud-services-spin.html#toc6-2-3) -- [Enhance security with the principle of least privilege](https://docs.microsoft.com/azure/active-directory/develop/secure-least-privileged-access) +- [Enhance security with the principle of least privilege](https://learn.microsoft.com/azure/active-directory/develop/secure-least-privileged-access) #### 2.2 Implement a mechanism for enforcing access authorizations. @@ -201,9 +201,9 @@ The following native solutions can help to meet this consideration: Access authorization controls are implemented as listed in [ALZCPS Identity Management Policies](#alzcps-identity-management-policies). Relevant Links: -- [Authorization with Azure AD](https://docs.microsoft.com/azure/architecture/framework/security/design-identity-authorization) -- [What is Azure role-based access control (Azure RBAC)?](https://docs.microsoft.com/azure/role-based-access-control/overview) -- [Steps to assign an Azure role](https://docs.microsoft.com/azure/role-based-access-control/role-assignments-steps) +- [Authorization with Azure AD](https://learn.microsoft.com/azure/architecture/framework/security/design-identity-authorization) +- [What is Azure role-based access control (Azure RBAC)?](https://learn.microsoft.com/azure/role-based-access-control/overview) +- [Steps to assign an Azure role](https://learn.microsoft.com/azure/role-based-access-control/role-assignments-steps) #### 2.3 Implement a mechanism for uniquely identifying and authenticating organizational users, non-organizational users (if applicable), and processes (for example, username and password). @@ -212,20 +212,20 @@ This consideration can be met by appropriately configuring your Azure AD instanc Controls for authenticating organizational users, non-organizational users, and processes are implemented as listed in [ALZCPS Identity Management Policies](#alzcps-identity-management-policies). Relevant Links: -- [Azure Active Directory Authentication management operations reference guide](https://docs.microsoft.com/azure/active-directory/fundamentals/active-directory-ops-guide-auth) -- [B2B collaboration overview](https://docs.microsoft.com/azure/active-directory/external-identities/what-is-b2b) (Guest Accounts) -- [Application and service principal objects in Azure Active Directory](https://docs.microsoft.com/azure/active-directory/develop/app-objects-and-service-principals) (Apps) +- [Azure Active Directory Authentication management operations reference guide](https://learn.microsoft.com/azure/active-directory/fundamentals/active-directory-ops-guide-auth) +- [B2B collaboration overview](https://learn.microsoft.com/azure/active-directory/external-identities/what-is-b2b) (Guest Accounts) +- [Application and service principal objects in Azure Active Directory](https://learn.microsoft.com/azure/active-directory/develop/app-objects-and-service-principals) (Apps) #### 2.4 Implement a multi-factor authentication mechanism for privileged accounts (for example, username, password and one-time password) and for external facing interfaces. -This consideration can be met by appropriately configuring your Azure AD instance. Review the following tutorial: [Secure user sign-in events with Azure AD Multi-Factor Authentication](https://docs.microsoft.com/azure/active-directory/authentication/tutorial-enable-azure-mfa) +This consideration can be met by appropriately configuring your Azure AD instance. Review the following tutorial: [Secure user sign-in events with Azure AD Multi-Factor Authentication](https://learn.microsoft.com/azure/active-directory/authentication/tutorial-enable-azure-mfa) [Azure AD Conditional Access](#azure-ad-conditional-access-azure-ad-p1-required) is a native solution that can help to meet this consideration. Multi-factor authentication controls are implemented as listed in [ALZCPS Identity Management Policies](#alzcps-identity-management-policies). Relevant Links: -- [Conditional Access: Require MFA for administrators](https://docs.microsoft.com/azure/active-directory/conditional-access/howto-conditional-access-policy-admin-mfa) +- [Conditional Access: Require MFA for administrators](https://learn.microsoft.com/azure/active-directory/conditional-access/howto-conditional-access-policy-admin-mfa) #### 2.5 Change default passwords. @@ -234,11 +234,11 @@ This consideration can be met by appropriately configuring your Azure AD instanc Password controls are implemented as listed in [ALZCPS Identity Management Policies](#alzcps-identity-management-policies). Relevant Links: -- [Combined password policy and weak password check in Azure Active Directory](https://docs.microsoft.com/azure/active-directory/authentication/concept-password-ban-bad-combined-policy) +- [Combined password policy and weak password check in Azure Active Directory](https://learn.microsoft.com/azure/active-directory/authentication/concept-password-ban-bad-combined-policy) #### 2.6 Ensure that no custom subscription owner roles are created. -As described in the [Microsoft Cloud Adoption Framework design recommendations](https://docs.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/design-area/identity-access#prerequisites-for-a-landing-zonedesign-recommendations), there is one custom owner role created: +As described in the [Microsoft Cloud Adoption Framework design recommendations](https://learn.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/design-area/identity-access#prerequisites-for-a-landing-zonedesign-recommendations), there is one custom owner role created: - Custom - Landing Zone Subscription Owner However, this is not truly a "subscription owner", as it has limited permissions and is unable to manage RBAC and networking. @@ -253,14 +253,14 @@ Password controls are implemented as listed in [ALZCPS Identity Management Polic Relevant Links: - [GC Password Guidance](https://www.canada.ca/en/government/system/digital-government/online-security-privacy/password-guidance.html) -- [Password policies and account restrictions in Azure Active Directory](https://docs.microsoft.com/azure/active-directory/authentication/concept-sspr-policy) +- [Password policies and account restrictions in Azure Active Directory](https://learn.microsoft.com/azure/active-directory/authentication/concept-sspr-policy) #### 2.8 Minimize number of guest users; add only if needed. Out of scope. Relevant Links: -- [B2B collaboration overview](https://docs.microsoft.com/azure/active-directory/external-identities/what-is-b2b) (Guest Accounts) +- [B2B collaboration overview](https://learn.microsoft.com/azure/active-directory/external-identities/what-is-b2b) (Guest Accounts) #### 2.9 Determine access restrictions and configuration requirements for GC-issued endpoint devices, including those of non-privileged and privileged users, and configure access restrictions for endpoint devices accordingly. Note: Some service providers may offer configuration options to restrict endpoint device access. Alternatively, organizational policy and procedural instruments can be implemented to restrict access. @@ -281,14 +281,14 @@ Access restriction controls are implemented as listed in [ALZCPS Identity Manage #### 3.1 Implement multi-factor authentication mechanism for privileged accounts and remote network (cloud) access. -This consideration can be met by appropriately configuring your Azure AD instance. Review the following tutorial: [Secure user sign-in events with Azure AD Multi-Factor Authentication](https://docs.microsoft.com/azure/active-directory/authentication/tutorial-enable-azure-mfa) +This consideration can be met by appropriately configuring your Azure AD instance. Review the following tutorial: [Secure user sign-in events with Azure AD Multi-Factor Authentication](https://learn.microsoft.com/azure/active-directory/authentication/tutorial-enable-azure-mfa) [Azure AD Conditional Access](#azure-ad-conditional-access-azure-ad-p1-required) is a native solution that can help to meet this consideration. Multi-factor authentication controls are implemented as listed in [ALZCPS Identity Management Policies](#alzcps-identity-management-policies). Relevant Links: -- [Conditional Access: Require MFA for administrators](https://docs.microsoft.com/azure/active-directory/conditional-access/howto-conditional-access-policy-admin-mfa) +- [Conditional Access: Require MFA for administrators](https://learn.microsoft.com/azure/active-directory/conditional-access/howto-conditional-access-policy-admin-mfa) #### 3.2 Determine access restrictions and configuration requirements for GC managed devices, including those of non-privileged and privileged users, and configure access restrictions for endpoint devices accordingly. @@ -312,7 +312,7 @@ For logging and monitoring, see [Azure AD Logging and Monitoring](#azure-ad-logg Access-control controls are implemented as listed in [ALZCPS Identity Management Policies](#alzcps-identity-management-policies). Relevant Links: -- [Conditional Access: Require compliant or hybrid Azure AD joined device](https://docs.microsoft.com/azure/active-directory/conditional-access/howto-conditional-access-policy-compliant-device) +- [Conditional Access: Require compliant or hybrid Azure AD joined device](https://learn.microsoft.com/azure/active-directory/conditional-access/howto-conditional-access-policy-compliant-device) #### 3.4 Implement a mechanism for enforcing access authorizations. @@ -326,21 +326,21 @@ The following native solutions can help to meet this consideration: Access authorization controls are implemented as listed in [ALZCPS Identity Management Policies](#alzcps-identity-management-policies). Relevant Links: -- [Authorization with Azure AD](https://docs.microsoft.com/azure/architecture/framework/security/design-identity-authorization) -- [What is Azure role-based access control (Azure RBAC)?](https://docs.microsoft.com/azure/role-based-access-control/overview) -- [Steps to assign an Azure role](https://docs.microsoft.com/azure/role-based-access-control/role-assignments-steps) +- [Authorization with Azure AD](https://learn.microsoft.com/azure/architecture/framework/security/design-identity-authorization) +- [What is Azure role-based access control (Azure RBAC)?](https://learn.microsoft.com/azure/role-based-access-control/overview) +- [Steps to assign an Azure role](https://learn.microsoft.com/azure/role-based-access-control/role-assignments-steps) #### 3.5 Implement password protection mechanisms to protect against password brute force attacks. -This consideration can be met by appropriately configuring your Azure AD instance. Specifically, [configuring Azure AD smart lockout](https://docs.microsoft.com/azure/active-directory/authentication/howto-password-smart-lockout) or by implementing a [passwordless authentication deployment](https://docs.microsoft.com/azure/active-directory/authentication/howto-authentication-passwordless-deployment). +This consideration can be met by appropriately configuring your Azure AD instance. Specifically, [configuring Azure AD smart lockout](https://learn.microsoft.com/azure/active-directory/authentication/howto-password-smart-lockout) or by implementing a [passwordless authentication deployment](https://learn.microsoft.com/azure/active-directory/authentication/howto-authentication-passwordless-deployment). Password controls are implemented as listed in [ALZCPS Identity Management Policies](#alzcps-identity-management-policies). Relevant Links: -- [What authentication and verification methods are available in Azure Active Directory?](https://docs.microsoft.com/azure/active-directory/authentication/concept-authentication-methods) +- [What authentication and verification methods are available in Azure Active Directory?](https://learn.microsoft.com/azure/active-directory/authentication/concept-authentication-methods) - [Forget passwords, go passwordless](https://www.microsoft.com/security/business/identity-access-management/passwordless-authentication) -- [Plan and deploy on-premises Azure Active Directory Password Protection](https://docs.microsoft.com/azure/active-directory/authentication/howto-password-ban-bad-on-premises-deploy) -- [Combined password policy and weak password check in Azure Active Directory](https://docs.microsoft.com/azure/active-directory/authentication/concept-password-ban-bad-combined-policy) +- [Plan and deploy on-premises Azure Active Directory Password Protection](https://learn.microsoft.com/azure/active-directory/authentication/howto-password-ban-bad-on-premises-deploy) +- [Combined password policy and weak password check in Azure Active Directory](https://learn.microsoft.com/azure/active-directory/authentication/concept-password-ban-bad-combined-policy) --- @@ -350,21 +350,21 @@ Relevant Links: #### 4.1 Assign roles to approved GC stakeholders to enable enterprise visibility. Roles include billing reader, policy contributor/reader, security reader, and global reader. -This consideration can be met by appropriately configuring your Azure AD instance. Specifically, by assigning the appropriate [RBAC roles](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles). +This consideration can be met by appropriately configuring your Azure AD instance. Specifically, by assigning the appropriate [RBAC roles](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles). Role-related controls are implemented as listed in [ALZCPS Identity Management Policies](#alzcps-identity-management-policies). Relevant Links: -- [Steps to assign an Azure role](https://docs.microsoft.com/azure/role-based-access-control/role-assignments-steps) +- [Steps to assign an Azure role](https://learn.microsoft.com/azure/role-based-access-control/role-assignments-steps) #### 4.2 Ensure that multi-factor authentication mechanism for enterprise monitoring accounts is enabled. -This consideration can be met by appropriately configuring your Azure AD instance. Review the following tutorial: [Secure user sign-in events with Azure AD Multi-Factor Authentication](https://docs.microsoft.com/azure/active-directory/authentication/tutorial-enable-azure-mfa) +This consideration can be met by appropriately configuring your Azure AD instance. Review the following tutorial: [Secure user sign-in events with Azure AD Multi-Factor Authentication](https://learn.microsoft.com/azure/active-directory/authentication/tutorial-enable-azure-mfa) [Azure AD Conditional Access](#azure-ad-conditional-access-azure-ad-p1-required) is a native solution that can help to meet this consideration. Relevant Links: -- [Tutorial: Secure user sign-in events with Azure AD Multi-Factor Authentication](https://docs.microsoft.com/azure/active-directory/authentication/tutorial-enable-azure-mfa) +- [Tutorial: Secure user sign-in events with Azure AD Multi-Factor Authentication](https://learn.microsoft.com/azure/active-directory/authentication/tutorial-enable-azure-mfa) --- @@ -381,8 +381,8 @@ The following policies related to data location are enabled by default in ALZCPS Relevant Links: - [ALZCPS location parameters](../policy/builtin/assignments/location.parameters.json) -- [Azure built-in policies](https://docs.microsoft.com/azure/governance/policy/samples/built-in-policies) - - [General built-in policies](https://docs.microsoft.com/azure/governance/policy/samples/built-in-policies#general) +- [Azure built-in policies](https://learn.microsoft.com/azure/governance/policy/samples/built-in-policies) + - [General built-in policies](https://learn.microsoft.com/azure/governance/policy/samples/built-in-policies#general) --- @@ -396,51 +396,51 @@ Institutional policy guidance exercises are out of scope. #### 6.2 Implement an encryption mechanism to protect the confidentiality and integrity of data when data are at rest in your solution's storage. -[Most Azure services that support encryption at rest](https://docs.microsoft.com/azure/security/fundamentals/encryption-models#supporting-services) typically support offloading the management of encryption keys to Azure. The Azure resource provider creates the keys, places them in secure storage, and retrieves them when needed. This means that the service has full access to the keys and the service has full control over the credential lifecycle management. However, there are various supported encryption models, including: +[Most Azure services that support encryption at rest](https://learn.microsoft.com/azure/security/fundamentals/encryption-models#supporting-services) typically support offloading the management of encryption keys to Azure. The Azure resource provider creates the keys, places them in secure storage, and retrieves them when needed. This means that the service has full access to the keys and the service has full control over the credential lifecycle management. However, there are various supported encryption models, including: -- [Server-side encryption using Service-Managed keys](https://docs.microsoft.com/azure/security/fundamentals/encryption-models#server-side-encryption-using-service-managed-keys) -- [Server-side encryption using customer-managed keys in Azure Key Vault](https://docs.microsoft.com/azure/security/fundamentals/encryption-models#server-side-encryption-using-customer-managed-keys-in-azure-key-vault) -- [Server-side encryption using customer-managed keys in customer-controlled hardware](https://docs.microsoft.com/azure/security/fundamentals/encryption-models#server-side-encryption-using-customer-managed-keys-in-customer-controlled-hardware) -- [Client-side encryption](https://docs.microsoft.com/azure/security/fundamentals/encryption-models#client-encryption-model) +- [Server-side encryption using Service-Managed keys](https://learn.microsoft.com/azure/security/fundamentals/encryption-models#server-side-encryption-using-service-managed-keys) +- [Server-side encryption using customer-managed keys in Azure Key Vault](https://learn.microsoft.com/azure/security/fundamentals/encryption-models#server-side-encryption-using-customer-managed-keys-in-azure-key-vault) +- [Server-side encryption using customer-managed keys in customer-controlled hardware](https://learn.microsoft.com/azure/security/fundamentals/encryption-models#server-side-encryption-using-customer-managed-keys-in-customer-controlled-hardware) +- [Client-side encryption](https://learn.microsoft.com/azure/security/fundamentals/encryption-models#client-encryption-model) -Refer to [this list](https://docs.microsoft.com/azure/security/fundamentals/encryption-models#client-encryption-model) to see encryption models are supported by each service. +Refer to [this list](https://learn.microsoft.com/azure/security/fundamentals/encryption-models#client-encryption-model) to see encryption models are supported by each service. The following policies related to protection of information at rest are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-28: Protection of Information at Rest](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#protection-of-information-at-rest) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-28: Protection of Information at Rest](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#protection-of-information-at-rest) Relevant Links: -- [Azure encryption overview](https://docs.microsoft.com/azure/security/fundamentals/encryption-overview) -- [Azure Data Encryption at rest](https://docs.microsoft.com/azure/security/fundamentals/encryption-atrest) -- [Azure Storage encryption for data at rest](https://docs.microsoft.com/azure/storage/common/storage-service-encryption) -- [Data encryption models](https://docs.microsoft.com/azure/security/fundamentals/encryption-models) -- [Azure data security and encryption best practices](https://docs.microsoft.com/azure/security/fundamentals/data-encryption-best-practices) +- [Azure encryption overview](https://learn.microsoft.com/azure/security/fundamentals/encryption-overview) +- [Azure Data Encryption at rest](https://learn.microsoft.com/azure/security/fundamentals/encryption-atrest) +- [Azure Storage encryption for data at rest](https://learn.microsoft.com/azure/storage/common/storage-service-encryption) +- [Data encryption models](https://learn.microsoft.com/azure/security/fundamentals/encryption-models) +- [Azure data security and encryption best practices](https://learn.microsoft.com/azure/security/fundamentals/data-encryption-best-practices) #### 6.3 Use CSE-approved cryptographic algorithms and protocols, in accordance with ITSP.40.111 and ITSP.40.062. -Azure provides the ability to use CSE-approved algorithms and protocols. However, _policy enforcement_ is not possible across all use-cases as it is dependant upon the individual application architecture. For example, the [default certificate signing algorithm within Azure AD](https://docs.microsoft.com/azure/active-directory/manage-apps/certificate-signing-options#certificate-signing-algorithms) is SHA-256. However, if an application only supports SHA-1, Azure AD can be manually configured to sign SAML responses using SHA-1 for that application. +Azure provides the ability to use CSE-approved algorithms and protocols. However, _policy enforcement_ is not possible across all use-cases as it is dependant upon the individual application architecture. For example, the [default certificate signing algorithm within Azure AD](https://learn.microsoft.com/azure/active-directory/manage-apps/certificate-signing-options#certificate-signing-algorithms) is SHA-256. However, if an application only supports SHA-1, Azure AD can be manually configured to sign SAML responses using SHA-1 for that application. The following policies related to approved cryptographic algorithms are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 4 SC-13: Cryptographic Protection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#cryptographic-protection-3) -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-28 (1): Cryptographic Protection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#cryptographic-protection-1) +- [Azure Policy - NIST SP 800-53 Rev. 4 SC-13: Cryptographic Protection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#cryptographic-protection-3) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-28 (1): Cryptographic Protection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#cryptographic-protection-1) Relevant Links: - [ITSP.40.111: Cryptographic Algorithms for UNCLASSIFIED, PROTECTED A, and PROTECTED B Information](https://cyber.gc.ca/guidance/cryptographic-algorithms-unclassified-protected-and-protected-b-information-itsp40111) - [ITSP.40.062: Guidance on Securely Configuring Network Protocols](https://cyber.gc.ca/guidance/guidance-securely-configuring-network-protocols-itsp40062) -- [Azure encryption overview](https://docs.microsoft.com/azure/security/fundamentals/encryption-overview) +- [Azure encryption overview](https://learn.microsoft.com/azure/security/fundamentals/encryption-overview) #### 6.4 Implement key management procedures. -[Most Azure services that support encryption at rest](https://docs.microsoft.com/azure/security/fundamentals/encryption-models#supporting-services) typically support offloading the management of encryption keys to Azure. The Azure resource provider creates the keys, places them in secure storage, and retrieves them when needed. This means that the service has full access to the keys and the service has full control over the credential lifecycle management. [Customer-managed key](https://docs.microsoft.com/azure/security/fundamentals/encryption-models#server-side-encryption-using-customer-managed-keys-in-azure-key-vault) scenarios are supported within ALZCPS in the [Healthcare](./archetypes/healthcare.md) and [Machine Learning](./docs/archetypes/machinelearning.md) archetypes. See [Key management in Azure](https://docs.microsoft.com/azure/security/fundamentals/key-management) for more details on platform-managed and customer-managed keys. +[Most Azure services that support encryption at rest](https://learn.microsoft.com/azure/security/fundamentals/encryption-models#supporting-services) typically support offloading the management of encryption keys to Azure. The Azure resource provider creates the keys, places them in secure storage, and retrieves them when needed. This means that the service has full access to the keys and the service has full control over the credential lifecycle management. [Customer-managed key](https://learn.microsoft.com/azure/security/fundamentals/encryption-models#server-side-encryption-using-customer-managed-keys-in-azure-key-vault) scenarios are supported within ALZCPS in the [Healthcare](./archetypes/healthcare.md) and [Machine Learning](./docs/archetypes/machinelearning.md) archetypes. See [Key management in Azure](https://learn.microsoft.com/azure/security/fundamentals/key-management) for more details on platform-managed and customer-managed keys. The following policies related to key management are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-12: Cryptographic Key Establishment and Management](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#cryptographic-key-establishment-and-management) -- [Azure Policy - NIST SP 800-53 Rev. 4 SC-17: Public Key Infrastructure Certificates](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#public-key-infrastructure-certificates) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-12: Cryptographic Key Establishment and Management](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#cryptographic-key-establishment-and-management) +- [Azure Policy - NIST SP 800-53 Rev. 4 SC-17: Public Key Infrastructure Certificates](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#public-key-infrastructure-certificates) Relevant Links: - [Government of Canada Considerations for the Use of Cryptography in Commercial Cloud Services: Key Management](https://www.canada.ca/en/government/system/digital-government/digital-government-innovations/cloud-services/government-canada-consideration-use-cryptography-in-cloud.html#toc3) -- [Key management in Azure](https://docs.microsoft.com/azure/security/fundamentals/key-management) -- [Data encryption models](https://docs.microsoft.com/azure/security/fundamentals/encryption-models) -- [About Azure Key Vault](https://docs.microsoft.com/azure/key-vault/general/overview) +- [Key management in Azure](https://learn.microsoft.com/azure/security/fundamentals/key-management) +- [Data encryption models](https://learn.microsoft.com/azure/security/fundamentals/encryption-models) +- [About Azure Key Vault](https://learn.microsoft.com/azure/key-vault/general/overview) --- @@ -450,28 +450,28 @@ Relevant Links: #### 7.1 Implement an encryption mechanism to protect the confidentiality and integrity of data when data are in transit to and from your solution. -For client applications, this is specific to the application architecture and determined risk profiles. Azure PaaS services can be audited for compliance via Azure Policy. [Azure offers many mechanisms for keeping data private as it moves from one location to another.](https://docs.microsoft.com/azure/security/fundamentals/encryption-overview#encryption-of-data-in-transit). +For client applications, this is specific to the application architecture and determined risk profiles. Azure PaaS services can be audited for compliance via Azure Policy. [Azure offers many mechanisms for keeping data private as it moves from one location to another.](https://learn.microsoft.com/azure/security/fundamentals/encryption-overview#encryption-of-data-in-transit). The following policies related to protection of data in transit are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-8: Transmission Confidentiality and Integrity](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#transmission-confidentiality-and-integrity) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-8: Transmission Confidentiality and Integrity](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#transmission-confidentiality-and-integrity) Relevant Links: -- [Azure encryption overview](https://docs.microsoft.com/azure/security/fundamentals/encryption-overview) -- [Encryption of data in transit](https://docs.microsoft.com/azure/security/fundamentals/encryption-overview#encryption-of-data-in-transit) -- [Data encryption models](https://docs.microsoft.com/azure/security/fundamentals/encryption-models) -- [Azure data security and encryption best practices](https://docs.microsoft.com/azure/security/fundamentals/data-encryption-best-practices) +- [Azure encryption overview](https://learn.microsoft.com/azure/security/fundamentals/encryption-overview) +- [Encryption of data in transit](https://learn.microsoft.com/azure/security/fundamentals/encryption-overview#encryption-of-data-in-transit) +- [Data encryption models](https://learn.microsoft.com/azure/security/fundamentals/encryption-models) +- [Azure data security and encryption best practices](https://learn.microsoft.com/azure/security/fundamentals/data-encryption-best-practices) #### 7.2 Use CSE-approved cryptographic algorithms and protocols. -Azure provides the ability to use CSE-approved algorithms and protocols. However, _policy enforcement_ is not possible across all use-cases as it is dependant upon the individual application architecture. For example, the [default certificate signing algorithm within Azure AD](https://docs.microsoft.com/azure/active-directory/manage-apps/certificate-signing-options#certificate-signing-algorithms) is SHA-256. However, if an application only supports SHA-1, Azure AD can be manually configured to sign SAML responses using SHA-1 for that application. +Azure provides the ability to use CSE-approved algorithms and protocols. However, _policy enforcement_ is not possible across all use-cases as it is dependant upon the individual application architecture. For example, the [default certificate signing algorithm within Azure AD](https://learn.microsoft.com/azure/active-directory/manage-apps/certificate-signing-options#certificate-signing-algorithms) is SHA-256. However, if an application only supports SHA-1, Azure AD can be manually configured to sign SAML responses using SHA-1 for that application. The following policies related to approved cryptographic algorithms are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 4 SC-13: Cryptographic Protection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#cryptographic-protection-3) -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-28 (1): Cryptographic Protection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#cryptographic-protection-1) +- [Azure Policy - NIST SP 800-53 Rev. 4 SC-13: Cryptographic Protection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#cryptographic-protection-3) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-28 (1): Cryptographic Protection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#cryptographic-protection-1) Relevant Links: -- [Azure encryption overview](https://docs.microsoft.com/azure/security/fundamentals/encryption-overview) +- [Azure encryption overview](https://learn.microsoft.com/azure/security/fundamentals/encryption-overview) #### 7.3 Encryption of data in transit by default (e.g. TLS v1.2, etc.) for all publicly accessible sites and external communications as per the direction on Implementing HTTPS for Secure Web Connections (ITPIN 2018-01). @@ -481,51 +481,51 @@ TLS 1.2 is set as the minimum TLS version in the following deployed resources: - Storage The following policies related to encryption of data in transit for publicly accessible sites and external communications are enabled by default in ALZCPS deployments: -- [Azure Policy - Canada Federal PBMM SC8(1): Transmission Confidentiality and Integrity | Cryptographic or Alternate Physical Protection](https://docs.microsoft.com/azure/governance/policy/samples/canada-federal-pbmm#transmission-confidentiality-and-integrity--cryptographic-or-alternate-physical-protection) -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-8(1): Cryptographic Protection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#cryptographic-protection) +- [Azure Policy - Canada Federal PBMM SC8(1): Transmission Confidentiality and Integrity | Cryptographic or Alternate Physical Protection](https://learn.microsoft.com/azure/governance/policy/samples/canada-federal-pbmm#transmission-confidentiality-and-integrity--cryptographic-or-alternate-physical-protection) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-8(1): Cryptographic Protection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#cryptographic-protection) Relevant Links: - [ITPIN 2018-01: Implementing HTTPS for Secure Web Connections](https://www.canada.ca/en/government/system/digital-government/modern-emerging-technologies/policy-implementation-notices/implementing-https-secure-web-connections-itpin.html) -- [Azure encryption overview](https://docs.microsoft.com/azure/security/fundamentals/encryption-overview) -- [Encryption of data in transit](https://docs.microsoft.com/azure/security/fundamentals/encryption-overview#encryption-of-data-in-transit) +- [Azure encryption overview](https://learn.microsoft.com/azure/security/fundamentals/encryption-overview) +- [Encryption of data in transit](https://learn.microsoft.com/azure/security/fundamentals/encryption-overview#encryption-of-data-in-transit) #### 7.4 Encryption for all access to cloud services (e.g. Cloud storage, Key Management systems, etc.). The following policies related to encryption for access to cloud services are enabled by default in ALZCPS deployments: -- [Azure Policy - Canada Federal PBMM SC8(1): Transmission Confidentiality and Integrity | Cryptographic or Alternate Physical Protection](https://docs.microsoft.com/azure/governance/policy/samples/canada-federal-pbmm#transmission-confidentiality-and-integrity--cryptographic-or-alternate-physical-protection) -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-8(1): Cryptographic Protection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#cryptographic-protection) +- [Azure Policy - Canada Federal PBMM SC8(1): Transmission Confidentiality and Integrity | Cryptographic or Alternate Physical Protection](https://learn.microsoft.com/azure/governance/policy/samples/canada-federal-pbmm#transmission-confidentiality-and-integrity--cryptographic-or-alternate-physical-protection) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-8(1): Cryptographic Protection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#cryptographic-protection) Relevant Links: -- [Azure encryption overview](https://docs.microsoft.com/azure/security/fundamentals/encryption-overview) +- [Azure encryption overview](https://learn.microsoft.com/azure/security/fundamentals/encryption-overview) #### 7.5 Consider encryption for internal zone communication in the cloud based on risk profile and as per the direction in CCCS network security zoning guidance in ITSG-22 and ITSG-38. -For client applications, this is specific to the application architecture and determined risk profiles. Azure PaaS services can be audited for compliance via Azure Policy. [Azure offers many mechanisms for keeping data private as it moves from one location to another](https://docs.microsoft.com/azure/security/fundamentals/encryption-overview#encryption-of-data-in-transit). +For client applications, this is specific to the application architecture and determined risk profiles. Azure PaaS services can be audited for compliance via Azure Policy. [Azure offers many mechanisms for keeping data private as it moves from one location to another](https://learn.microsoft.com/azure/security/fundamentals/encryption-overview#encryption-of-data-in-transit). -As an additional layer of protection, [Azure Private Link](https://docs.microsoft.com/azure/private-link/private-link-overview) enables access to Azure PaaS Services (for example, Azure Storage and SQL Database) and Azure hosted customer-owned/partner services over a private endpoint in your virtual network. Azure Private Link is enabled on all supported PaaS services in an ALZCPS deployment. +As an additional layer of protection, [Azure Private Link](https://learn.microsoft.com/azure/private-link/private-link-overview) enables access to Azure PaaS Services (for example, Azure Storage and SQL Database) and Azure hosted customer-owned/partner services over a private endpoint in your virtual network. Azure Private Link is enabled on all supported PaaS services in an ALZCPS deployment. The following policies related to information flow enforcement are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 5 AC-4: Information Flow Enforcement](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#information-flow-enforcement) -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-28 (1): Cryptographic Protection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#cryptographic-protection-1) +- [Azure Policy - NIST SP 800-53 Rev. 5 AC-4: Information Flow Enforcement](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#information-flow-enforcement) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-28 (1): Cryptographic Protection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#cryptographic-protection-1) Relevant Links: - [ITSG-22: Baseline Security Requirements for Network Security Zones in the Government of Canada](https://www.cyber.gc.ca/sites/default/files/publications/itsg-22-eng.pdf) - [ITSG-38: Network Security Zoning - Design Considerations for Placement of Services within Zones](https://cyber.gc.ca/guidance/network-security-zoning-design-considerations-placement-services-within-zones-itsg-38) -- [What is Azure Private Link?](https://docs.microsoft.com/azure/private-link/private-link-overview) +- [What is Azure Private Link?](https://learn.microsoft.com/azure/private-link/private-link-overview) #### 7.6 Implement key management procedures. -See [Key management in Azure](https://docs.microsoft.com/azure/security/fundamentals/key-management) for details on platform-managed and customer-managed keys. +See [Key management in Azure](https://learn.microsoft.com/azure/security/fundamentals/key-management) for details on platform-managed and customer-managed keys. The following policies related to key management are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-12: Cryptographic Key Establishment and Management](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#cryptographic-key-establishment-and-management) -- [Azure Policy - NIST SP 800-53 Rev. 4 SC-17: Public Key Infrastructure Certificates](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#public-key-infrastructure-certificates) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-12: Cryptographic Key Establishment and Management](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#cryptographic-key-establishment-and-management) +- [Azure Policy - NIST SP 800-53 Rev. 4 SC-17: Public Key Infrastructure Certificates](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#public-key-infrastructure-certificates) Relevant Links: - [Government of Canada Considerations for the Use of Cryptography in Commercial Cloud Services: Key Management](https://www.canada.ca/en/government/system/digital-government/digital-government-innovations/cloud-services/government-canada-consideration-use-cryptography-in-cloud.html#toc3) -- [Data encryption models](https://docs.microsoft.com/azure/security/fundamentals/encryption-models) -- [About Azure Key Vault](https://docs.microsoft.com/azure/key-vault/general/overview) +- [Data encryption models](https://learn.microsoft.com/azure/security/fundamentals/encryption-models) +- [About Azure Key Vault](https://learn.microsoft.com/azure/key-vault/general/overview) --- @@ -538,9 +538,9 @@ Relevant Links: The [ALZCPS network design](./architecture.md#5-network) implements separate hub virtual networks that allow for segmenting management operations. However, it is up to the implementer to determine how these networks should be enhanced to meet their specific security needs. The following policies related to network security are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 5 AC-4: Information Flow Enforcement](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#information-flow-enforcement) -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-7: Boundary Protection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#boundary-protection) -- [Azure Policy - NIST SP 800-53 Rev. 4 SC-7 (5): Deny by Default / Allow by Exception](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#deny-by-default--allow-by-exception) +- [Azure Policy - NIST SP 800-53 Rev. 5 AC-4: Information Flow Enforcement](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#information-flow-enforcement) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-7: Boundary Protection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#boundary-protection) +- [Azure Policy - NIST SP 800-53 Rev. 4 SC-7 (5): Deny by Default / Allow by Exception](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#deny-by-default--allow-by-exception) Relevant Links: - [ITSG-22: Baseline Security Requirements for Network Security Zones in the Government of Canada](https://www.cyber.gc.ca/sites/default/files/publications/itsg-22-eng.pdf) @@ -550,12 +550,12 @@ Relevant Links: #### 8.2 Implement increased levels of protection for management interfaces. -ALZCPS adheres to boundary protection policies for management interfaces. This includes the use of [Azure Private Link](https://docs.microsoft.com/azure/private-link/private-link-overview), routing traffic to a [deployed firewall](./architecture.md#topology), and disabling public network access to sensitive resources. +ALZCPS adheres to boundary protection policies for management interfaces. This includes the use of [Azure Private Link](https://learn.microsoft.com/azure/private-link/private-link-overview), routing traffic to a [deployed firewall](./architecture.md#topology), and disabling public network access to sensitive resources. For custom applications, it is up to the implementer to identify management interfaces which may need increased levels of protection. The following policies related to protection for management interfaces are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-7: Boundary Protection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#boundary-protection) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-7: Boundary Protection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#boundary-protection) --- @@ -573,55 +573,55 @@ For logging and monitoring, review the [Azure Firewall Archetype Log Analytics I The following policies related to boundary protection are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-7: Boundary Protection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#boundary-protection) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-7: Boundary Protection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#boundary-protection) Relevant Links: -- [What is Azure Firewall?](https://docs.microsoft.com/azure/firewall/overview) -- [Monitor Azure Firewall logs and metrics](https://docs.microsoft.com/azure/firewall/firewall-diagnostics) -- [Azure Firewall Premium features](https://docs.microsoft.com/azure/firewall/premium-features) -- [Monitor logs using Azure Firewall Workbook](https://docs.microsoft.com/azure/firewall/firewall-workbook) +- [What is Azure Firewall?](https://learn.microsoft.com/azure/firewall/overview) +- [Monitor Azure Firewall logs and metrics](https://learn.microsoft.com/azure/firewall/firewall-diagnostics) +- [Azure Firewall Premium features](https://learn.microsoft.com/azure/firewall/premium-features) +- [Monitor logs using Azure Firewall Workbook](https://learn.microsoft.com/azure/firewall/firewall-workbook) #### 9.2 Implement network boundary protection mechanisms for all external facing interfaces that enforce a deny-all or allow-by-exception policy. When using the Azure Firewall Archetype, please review the pre-configured [Azure Firewall Rules](./archetypes/hubnetwork-azfw.md#azure-firewall-rules). The following policies related to boundary protection are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-7: Boundary Protection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#boundary-protection) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-7: Boundary Protection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#boundary-protection) #### 9.3 Perimeter security services such as boundary protection, intrusion prevention services, proxy services, TLS traffic inspection, etc. must be enabled based on risk profile, in alignment with GC Secure Connectivity Requirements and ITSG-22 and ITSG-38. The required/available security services will depend on the deployed workload, such as the firewall used, and any additional requirements of the workload based on risk profile. When deploying using ALZCPS: - Microsoft Defender for Cloud is enabled [via a custom policy](../policy/custom/definitions/policyset/DefenderForCloud.bicep) on all supported resources. -- Microsoft Sentinel is enabled, but requires further [configuration and management](https://docs.microsoft.com/azure/sentinel/best-practices). +- Microsoft Sentinel is enabled, but requires further [configuration and management](https://learn.microsoft.com/azure/sentinel/best-practices). - Azure Private DNS Zones [are used to enable Private Link](./architecture.md#private-dns-zones) for Azure PaaS services. - Azure DDoS Standard can optionally be enabled during [hub networking configuration](./onboarding/azure-devops-pipelines.md#step-7---configure-hub-networking). -- [TLS Inspection](https://docs.microsoft.com/azure/firewall/premium-features#tls-inspection) can be enabled on Azure Firewall Premium. +- [TLS Inspection](https://learn.microsoft.com/azure/firewall/premium-features#tls-inspection) can be enabled on Azure Firewall Premium. The following policies related to perimeter security services are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-5: Denial-of-service Protection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#denial-of-service-protection) -- [Azure Policy - NIST SP 800-53 Rev. 5 SC-7: Boundary Protection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#boundary-protection) -- [Azure Policy - NIST SP 800-53 Rev. 5 SI-3: Malicious Code Protection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#malicious-code-protection) -- [Azure Policy - NIST SP 800-53 Rev. 4 SI-3 (7): Nonsignature-based Detection](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#nonsignature-based-detection) -- [Azure Policy - NIST SP 800-53 Rev. 4 SI-4: System Monitoring](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#system-monitoring) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-5: Denial-of-service Protection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#denial-of-service-protection) +- [Azure Policy - NIST SP 800-53 Rev. 5 SC-7: Boundary Protection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#boundary-protection) +- [Azure Policy - NIST SP 800-53 Rev. 5 SI-3: Malicious Code Protection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#malicious-code-protection) +- [Azure Policy - NIST SP 800-53 Rev. 4 SI-3 (7): Nonsignature-based Detection](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#nonsignature-based-detection) +- [Azure Policy - NIST SP 800-53 Rev. 4 SI-4: System Monitoring](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#system-monitoring) Relevant Links: - [ITSG-22: Baseline Security Requirements for Network Security Zones in the Government of Canada](https://www.cyber.gc.ca/sites/default/files/publications/itsg-22-eng.pdf) - [ITSG-38: Network Security Zoning - Design Considerations for Placement of Services within Zones](https://cyber.gc.ca/guidance/network-security-zoning-design-considerations-placement-services-within-zones-itsg-38) -- [What is Microsoft Defender for Cloud?](https://docs.microsoft.com/azure/defender-for-cloud/defender-for-cloud-introduction) -- [What is Microsoft Sentinel?](https://docs.microsoft.com/azure/sentinel/overview) -- [Azure DDoS Protection Standard overview](https://docs.microsoft.com/azure/ddos-protection/ddos-protection-overview) -- [Azure Firewall Premium features](https://docs.microsoft.com/azure/firewall/premium-features) +- [What is Microsoft Defender for Cloud?](https://learn.microsoft.com/azure/defender-for-cloud/defender-for-cloud-introduction) +- [What is Microsoft Sentinel?](https://learn.microsoft.com/azure/sentinel/overview) +- [Azure DDoS Protection Standard overview](https://learn.microsoft.com/azure/ddos-protection/ddos-protection-overview) +- [Azure Firewall Premium features](https://learn.microsoft.com/azure/firewall/premium-features) #### 9.4 Ensure that access to cloud storage services is protected and restricted to authorized users and services. For the archetypes provided in ALZCPS, we provide Private Endpoints for storage accounts. Further controls may be required to limit access to specific users and groups as needed. The following policies related to access to cloud storage services are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 5 AC-4: Information Flow Enforcement](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#information-flow-enforcement) +- [Azure Policy - NIST SP 800-53 Rev. 5 AC-4: Information Flow Enforcement](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#information-flow-enforcement) Relevant Links: -- [Authorize access to data in Azure Storage](https://docs.microsoft.com/azure/storage/common/authorize-data-access) +- [Authorize access to data in Azure Storage](https://learn.microsoft.com/azure/storage/common/authorize-data-access) --- @@ -641,19 +641,19 @@ Relevant Links: The required/available cyber defense services will depend on the deployed workload, such as the firewall used, and any additional requirements of the workload based on risk profile. When deploying using ALZCPS: - Microsoft Defender for Cloud is enabled [via a custom policy](../policy/custom/definitions/policyset/DefenderForCloud.bicep) on all supported resources. -- Microsoft Sentinel is enabled, but requires further [configuration and management](https://docs.microsoft.com/azure/sentinel/best-practices). +- Microsoft Sentinel is enabled, but requires further [configuration and management](https://learn.microsoft.com/azure/sentinel/best-practices). - Azure DDoS Standard can optionally be enabled during [hub networking configuration](./onboarding/azure-devops-pipelines.md#step-7---configure-hub-networking). -- [TLS Inspection](https://docs.microsoft.com/azure/firewall/premium-features#tls-inspection) can be enabled on Azure Firewall Premium. +- [TLS Inspection](https://learn.microsoft.com/azure/firewall/premium-features#tls-inspection) can be enabled on Azure Firewall Premium. The following policies related to to cyber defense services are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 4 SI-2: Flaw Remediation](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#flaw-remediation) -- [Azure Policy - NIST SP 800-53 Rev. 5 SI-4: System Monitoring](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#system-monitoring) +- [Azure Policy - NIST SP 800-53 Rev. 4 SI-2: Flaw Remediation](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#flaw-remediation) +- [Azure Policy - NIST SP 800-53 Rev. 5 SI-4: System Monitoring](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#system-monitoring) Relevant Links: -- [What is Microsoft Defender for Cloud?](https://docs.microsoft.com/azure/defender-for-cloud/defender-for-cloud-introduction) -- [What is Microsoft Sentinel?](https://docs.microsoft.com/azure/sentinel/overview) -- [Azure DDoS Protection Standard overview](https://docs.microsoft.com/azure/ddos-protection/ddos-protection-overview) -- [Azure Firewall Premium features](https://docs.microsoft.com/azure/firewall/premium-features) +- [What is Microsoft Defender for Cloud?](https://learn.microsoft.com/azure/defender-for-cloud/defender-for-cloud-introduction) +- [What is Microsoft Sentinel?](https://learn.microsoft.com/azure/sentinel/overview) +- [Azure DDoS Protection Standard overview](https://learn.microsoft.com/azure/ddos-protection/ddos-protection-overview) +- [Azure Firewall Premium features](https://learn.microsoft.com/azure/firewall/premium-features) --- @@ -685,17 +685,17 @@ For VMs, diagnostic logs are collected using the Microsoft Monitoring Agent whic For PaaS services, diagnostics settings are turned on. The following policies related to to logging and reporting are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 4 AU-2: Audit Events](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#audit-events) -- [Azure Policy - NIST SP 800-53 Rev. 4 AU-3: Audit Events](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#content-of-audit-records) -- [Azure Policy - NIST SP 800-53 Rev. 5 AU-6: Audit Record Review, Analysis, and Reporting](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#audit-record-review-analysis-and-reporting) -- [Azure Policy - NIST SP 800-53 Rev. 4 AU-9: Protection of Audit Information](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#protection-of-audit-information) -- [Azure Policy - NIST SP 800-53 Rev. 4 AU-9 (4): Access by Subset of Privileged Users](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#access-by-subset-of-privileged-users) -- [Azure Policy - NIST SP 800-53 Rev. 5 AU-12: Audit Record Generation](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#audit-record-generation) -- [Azure Policy - NIST SP 800-53 Rev. 5 SI-4: System Monitoring](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#system-monitoring) +- [Azure Policy - NIST SP 800-53 Rev. 4 AU-2: Audit Events](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#audit-events) +- [Azure Policy - NIST SP 800-53 Rev. 4 AU-3: Audit Events](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#content-of-audit-records) +- [Azure Policy - NIST SP 800-53 Rev. 5 AU-6: Audit Record Review, Analysis, and Reporting](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#audit-record-review-analysis-and-reporting) +- [Azure Policy - NIST SP 800-53 Rev. 4 AU-9: Protection of Audit Information](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#protection-of-audit-information) +- [Azure Policy - NIST SP 800-53 Rev. 4 AU-9 (4): Access by Subset of Privileged Users](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#access-by-subset-of-privileged-users) +- [Azure Policy - NIST SP 800-53 Rev. 5 AU-12: Audit Record Generation](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#audit-record-generation) +- [Azure Policy - NIST SP 800-53 Rev. 5 SI-4: System Monitoring](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#system-monitoring) Relevant Links: -- [Azure Monitor: Log Analytics workspaces](https://docs.microsoft.com/azure/azure-monitor/logs/data-platform-logs#log-analytics-workspaces) -- [Monitoring solutions in Azure Monitor](https://docs.microsoft.com/azure/azure-monitor/insights/solutions) +- [Azure Monitor: Log Analytics workspaces](https://learn.microsoft.com/azure/azure-monitor/logs/data-platform-logs#log-analytics-workspaces) +- [Monitoring solutions in Azure Monitor](https://learn.microsoft.com/azure/azure-monitor/insights/solutions) #### 11.2 Identify the events within the solution that must be audited in accordance with GC Event Logging. @@ -710,8 +710,8 @@ ALZCPS sets up email notifications for the following alerts by default: Further configuration is required to set up appropriate alerts and notifications for any deployment. Relevant Links: -- [Microsoft Defender for Cloud: Configure email notifications for security alerts](https://docs.microsoft.com/azure/defender-for-cloud/configure-email-notifications) -- [Microsoft Sentinel: Automate incident handling in Microsoft Sentinel with automation rules](https://docs.microsoft.com/azure/sentinel/automate-incident-handling-with-automation-rules) +- [Microsoft Defender for Cloud: Configure email notifications for security alerts](https://learn.microsoft.com/azure/defender-for-cloud/configure-email-notifications) +- [Microsoft Sentinel: Automate incident handling in Microsoft Sentinel with automation rules](https://learn.microsoft.com/azure/sentinel/automate-incident-handling-with-automation-rules) #### 11.4 Configure or use an authoritative time source for the time-stamp of the audit records generated by your solution components. @@ -720,15 +720,15 @@ Azure PaaS Services and Azure Marketplace Windows VMs use _time.windows.com_ as Since early 2021, Azure Marketplace Linux VMs use the _chronyd_ service to synchronize with the host time (_time.windows.com_). There are two standard time-stamp columns within Azure Monitor logs: -- [TimeGenerated](https://docs.microsoft.com/azure/azure-monitor/logs/log-standard-columns#timegenerated), which contains the date and time that the record was created by the data source. -- [_TimeReceived](https://docs.microsoft.com/azure/azure-monitor/logs/log-standard-columns#_timereceived), which contains the date and time that the record was received by the Azure Monitor ingestion point in the Azure cloud. +- [TimeGenerated](https://learn.microsoft.com/azure/azure-monitor/logs/log-standard-columns#timegenerated), which contains the date and time that the record was created by the data source. +- [_TimeReceived](https://learn.microsoft.com/azure/azure-monitor/logs/log-standard-columns#_timereceived), which contains the date and time that the record was received by the Azure Monitor ingestion point in the Azure cloud. The following policies related to to time stamps are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 4 AU-8: Time Stamps](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#time-stamps) +- [Azure Policy - NIST SP 800-53 Rev. 4 AU-8: Time Stamps](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#time-stamps) Relevant Links: -- [Time sync for Linux VMs in Azure](https://docs.microsoft.com/azure/virtual-machines/linux/time-sync) -- [Time sync for Windows VMs in Azure](https://docs.microsoft.com/azure/virtual-machines/windows/time-sync) +- [Time sync for Linux VMs in Azure](https://learn.microsoft.com/azure/virtual-machines/linux/time-sync) +- [Time sync for Windows VMs in Azure](https://learn.microsoft.com/azure/virtual-machines/windows/time-sync) #### 11.5 Continuously monitor system events and performance. @@ -755,18 +755,18 @@ For PaaS services, diagnostics settings are turned on. Additionally, Microsoft Defender for Cloud is enabled by default on all supported solutions. The following policies related to to logging and reporting are enabled by default in ALZCPS deployments: -- [Azure Policy - NIST SP 800-53 Rev. 4 AU-2: Audit Events](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#audit-events) -- [Azure Policy - NIST SP 800-53 Rev. 4 AU-3: Audit Events](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#content-of-audit-records) -- [Azure Policy - NIST SP 800-53 Rev. 5 AU-6: Audit Record Review, Analysis, and Reporting](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#audit-record-review-analysis-and-reporting) -- [Azure Policy - NIST SP 800-53 Rev. 4 AU-9: Protection of Audit Information](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#protection-of-audit-information) -- [Azure Policy - NIST SP 800-53 Rev. 4 AU-9 (4): Access by Subset of Privileged Users](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#access-by-subset-of-privileged-users) -- [Azure Policy - NIST SP 800-53 Rev. 5 AU-12: Audit Record Generation](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#audit-record-generation) -- [Azure Policy - NIST SP 800-53 Rev. 5 SI-4: System Monitoring](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#system-monitoring) +- [Azure Policy - NIST SP 800-53 Rev. 4 AU-2: Audit Events](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#audit-events) +- [Azure Policy - NIST SP 800-53 Rev. 4 AU-3: Audit Events](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#content-of-audit-records) +- [Azure Policy - NIST SP 800-53 Rev. 5 AU-6: Audit Record Review, Analysis, and Reporting](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#audit-record-review-analysis-and-reporting) +- [Azure Policy - NIST SP 800-53 Rev. 4 AU-9: Protection of Audit Information](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#protection-of-audit-information) +- [Azure Policy - NIST SP 800-53 Rev. 4 AU-9 (4): Access by Subset of Privileged Users](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4#access-by-subset-of-privileged-users) +- [Azure Policy - NIST SP 800-53 Rev. 5 AU-12: Audit Record Generation](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#audit-record-generation) +- [Azure Policy - NIST SP 800-53 Rev. 5 SI-4: System Monitoring](https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5#system-monitoring) Relevant Links: -- [Azure Monitor: Log Analytics workspaces](https://docs.microsoft.com/azure/azure-monitor/logs/data-platform-logs#log-analytics-workspaces) -- [Monitoring solutions in Azure Monitor](https://docs.microsoft.com/azure/azure-monitor/insights/solutions) -- [What is Microsoft Defender for Cloud?](https://docs.microsoft.com/azure/defender-for-cloud/defender-for-cloud-introduction) +- [Azure Monitor: Log Analytics workspaces](https://learn.microsoft.com/azure/azure-monitor/logs/data-platform-logs#log-analytics-workspaces) +- [Monitoring solutions in Azure Monitor](https://learn.microsoft.com/azure/azure-monitor/insights/solutions) +- [What is Microsoft Defender for Cloud?](https://learn.microsoft.com/azure/defender-for-cloud/defender-for-cloud-introduction) --- @@ -779,7 +779,7 @@ Relevant Links: The private marketplace is not enabled by default. Once enabled, only approved public marketplace offerings are allowed. Relevant Links: -- [Create and manage Private Azure Marketplace collections in the Azure portal](https://docs.microsoft.com/marketplace/create-manage-private-azure-marketplace-new) +- [Create and manage Private Azure Marketplace collections in the Azure portal](https://learn.microsoft.com/marketplace/create-manage-private-azure-marketplace-new) #### 12.2 Submit requests to add third-party products to marketplace to SSC Cloud Broker. diff --git a/docs/media/architecture/archetype-identity.jpg b/docs/media/architecture/archetype-identity.jpg new file mode 100644 index 0000000000000000000000000000000000000000..56d1745fe0f2ceb93392b5e8ced0687be25e4a1a GIT binary patch literal 140799 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 literal 0 HcmV?d00001 diff --git a/docs/onboarding/azure-devops-pipelines.md b/docs/onboarding/azure-devops-pipelines.md index 38a8e61d..fc17f7e0 100644 --- a/docs/onboarding/azure-devops-pipelines.md +++ b/docs/onboarding/azure-devops-pipelines.md @@ -4,6 +4,8 @@ This document provides steps required to onboard to the Azure Landing Zones desi > There are scripts available to help simplify the onboarding process to Azure Landing Zones design using Azure DevOps Pipelines. The [Azure DevOps Scripts](./azure-devops-scripts.md) document contains more detailed information on the those scripts. +> There are scripts available to help simplify the configuration process of the Azure Landing Zones design. The [Configuration Scripts](./configuration-scripts.md) document contains more detailed information on the those scripts. + **All steps will need to be repeated per Azure AD tenant.** --- @@ -12,7 +14,7 @@ This document provides steps required to onboard to the Azure Landing Zones desi > Telemetry is introduced on November 11, 2021. -Microsoft can identify the deployments of the Azure Resource Manager and Bicep templates with the deployed Azure resources. Microsoft can correlate these resources used to support the deployments. Microsoft collects this information to provide the best experiences with their products and to operate their business. The telemetry is collected through [customer usage attribution](https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution). The data is collected and governed by Microsoft's privacy policies, located at [https://www.microsoft.com/trustcenter](https://www.microsoft.com/trustcenter). +Microsoft can identify the deployments of the Azure Resource Manager and Bicep templates with the deployed Azure resources. Microsoft can correlate these resources used to support the deployments. Microsoft collects this information to provide the best experiences with their products and to operate their business. The telemetry is collected through [customer usage attribution](https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution). The data is collected and governed by Microsoft's privacy policies, located at [https://www.microsoft.com/trustcenter](https://www.microsoft.com/trustcenter). The automation is instrumented to identify the modules that are being deployed. At this time, we don't differentiate the deployments and tracked under a single GUID (`a83f6385-f514-415f-991b-2d9bd7aed658`). @@ -63,6 +65,7 @@ This deployment diagram describes the steps for deploying one, many or all modul Logging: Logging Policy: Azure Policy HubNetworking: Hub Networking (NVAs or Azure Firewall) + Identity: Identity Archetypes: Archetypes (Spokes) [*] --> ManagementGroups @@ -73,10 +76,13 @@ This deployment diagram describes the steps for deploying one, many or all modul Policy --> HubNetworking Policy --> Archetypes + HubNetworking --> Identity + HubNetworking --> Archetypes Policy --> [*] + Identity --> [*] HubNetworking --> [*] Archetypes --> [*] ``` @@ -103,6 +109,10 @@ This deployment diagram describes the steps for deploying one, many or all modul AssignDDOSPolicy: [Optional] Assign Azure Policy for linking DDoS Standard Plan to virtual network AssignPrivateDNSZonesPolicy: [Optional] Assign Azure Policies for centrally managing private DNS zones + Identity: Identity + DeployVirtualNetwork: Deploy Virtual Network + DeployDNSResolver: Deploy DNS Resolver (optional) + Archetypes: Archetypes (Spokes) DeployGenericSubscriptionArchetype: Generic Subscription DeployMachineLearningArchetype: Machine Learning @@ -126,6 +136,7 @@ This deployment diagram describes the steps for deploying one, many or all modul } Policy --> HubNetworking: When Hub Networking is required + HubNetworking --> Archetypes: When archetypes are deployed in spoke subscriptions Policy --> Archetypes: When existing Hub Networking is in place state HubNetworking { @@ -150,7 +161,13 @@ This deployment diagram describes the steps for deploying one, many or all modul AssignPrivateDNSZonesPolicy --> [*] } - HubNetworking --> Archetypes: When archetypes are deployed in spoke subscriptions + HubNetworking --> Identity: When Identity Sub is required + + state Identity { + DeployVirtualNetwork --> DeployDNSResolver + DeployDNSResolver --> [*] + } + state Archetypes { state ArchetypeChoice <> @@ -163,7 +180,8 @@ This deployment diagram describes the steps for deploying one, many or all modul } Policy --> [*]: MVP deployment and enables Microsoft Sentinel & Log Analytics - HubNetworking --> [*] + HubNetworking --> [*]: Identity Sub is NOT required + Identity --> [*] Archetypes --> [*] ``` @@ -178,12 +196,15 @@ This deployment diagram describes the steps for deploying one, many or all modul * [Step 5 - Configure Logging](#step-5---configure-logging) * [Step 6 - Configure Azure Policies](#step-6---configure-azure-policies) * [Step 7 - Configure Hub Networking](#step-7---configure-hub-networking) -* [Step 8 - Configure Subscription Archetypes](#step-8---configure-subscription-archetypes) +* [Step 8 - Configure Identity subscription](#step-8---configure-identity-subscription) +* [Step 9 - Configure Subscription Archetypes](#step-9---configure-subscription-archetypes) * [Appendix](#appendix) * [Populate management group hierarchy from your environment](#populate-management-group-hierarchy-from-your-environment) * [Migrate Logging configuration from Azure DevOps variables to JSON parameters file](#migrate-logging-configuration-from-azure-devops-variables-to-json-parameters-file) * [Migrate Hub Networking configuration from Azure DevOps variables to JSON parameters file](#migrate-hub-networking-configuration-from-azure-devops-variables-to-json-parameters-file) +>Note: For steps #3 - #9 above, there are scripts available to automate generating the JSON and YAML configuration files for environments. Refer to the [Configuration Scripts](./configuration-scripts.md) documentation for more information. + --- ## Step 1 - Create Service Principal Account & Assign RBAC @@ -196,9 +217,9 @@ An Azure service principal is an identity created for use with applications, hos * **Scope:** Tenant Root Group (this is a management group in the Azure environment) - * **Role:** [Owner](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#owner) (Grants full access to manage all resources, including the ability to assign roles in [Azure RBAC](https://docs.microsoft.com/azure/role-based-access-control/overview). Owner permission is required so that the Azure DevOps Pipelines can create resources and role assignments.) + * **Role:** [Owner](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#owner) (Grants full access to manage all resources, including the ability to assign roles in [Azure RBAC](https://learn.microsoft.com/azure/role-based-access-control/overview). Owner permission is required so that the Azure DevOps Pipelines can create resources and role assignments.) - * **Instructions**: [Create an Azure service principal with the Azure CLI | Microsoft Docs](https://docs.microsoft.com/cli/azure/create-an-azure-service-principal-azure-cli) + * **Instructions**: [Create an Azure service principal with the Azure CLI | Microsoft Docs](https://learn.microsoft.com/cli/azure/create-an-azure-service-principal-azure-cli) To create the service principal account and role assignment through Azure CLI: @@ -263,11 +284,11 @@ Note down the `appId`, `tenant` and `password`. These will be required to for s * Verify and save -* **Reference**: [Service connections in Azure Pipelines - Azure Pipelines | Microsoft Docs](https://docs.microsoft.com/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml). Use the settings described above when following the instructions. +* **Reference**: [Service connections in Azure Pipelines - Azure Pipelines | Microsoft Docs](https://learn.microsoft.com/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml). Use the settings described above when following the instructions. ### Step 2.2: Configure Azure DevOps Pipeline Environment -An environment is a collection of resources that you can target with deployments from a pipeline. Typical examples of environment names are Dev, Test, QA, Staging, and Production. More information such as benefits of using Environments can be found in [Azure Docs](https://docs.microsoft.com/azure/devops/pipelines/process/environments). +An environment is a collection of resources that you can target with deployments from a pipeline. Typical examples of environment names are Dev, Test, QA, Staging, and Production. More information such as benefits of using Environments can be found in [Azure Docs](https://learn.microsoft.com/azure/devops/pipelines/process/environments). Azure DevOps may create an environment if it doesn't exist, however, it's recommended to explicitly create an empty environment and reference it from deployment jobs. This lets you record the deployment history against the environment. @@ -325,7 +346,7 @@ Instructions: >**Note**: The ID of the default parent management group 'Tenant Root Group' is the same as the Azure Active Directory (AAD) Tenant ID (GUID). -2. Create/edit `./config/variables/-.yml` in Git (i.e. CanadaESLZ-main.yml). This file name is automatically inferred by the pipeline based on the Azure DevOps organization name and the branch name. +2. Create/edit `./config/variables/-.yml` in Git (i.e. CanadaPubSecALZ-main.yml). This file name is automatically inferred by the pipeline based on the Azure DevOps organization name and the branch name. **Sample environment YAML (v0.9.0 or later)** @@ -386,7 +407,7 @@ Instructions: * Specify the `id` and `name` of your existing Azure AD tenant for the topmost management group definition. The `id` attribute for this element is mapped into the `var-parentManagementGroupId` pipeline variable for backward compatibility. * Specify only 1 child management group definition for the topmost management group definition. You can specify more than 1 child of the topmost management group definition, but it is the first child of the topmost level that will be considered the root of of your management group hierarchy, and is the scope that the `policy-ci` pipeline will use to deploy built-in and custom policies. The `id` attribute for this element is mapped into the `var-topLevelManagementGroupName` pipeline variable for backward compatibility. - * The `id` attribute for management group elements can only be an ASCII letter, digit, `-`, `_`, `(`, `)`, `.` and cannot end with a period. In the sample environment configuration file (`CanadaESLZ-main.yml`), we illustrate a convention that prepends the id of the parent management group to the id of each child management group. This is an example only and not a requirement. You are welcome to choose any management group id convention that best suits your needs. + * The `id` attribute for management group elements can only be an ASCII letter, digit, `-`, `_`, `(`, `)`, `.` and cannot end with a period. In the sample environment configuration file (`CanadaPubSecALZ-main.yml`), we illustrate a convention that prepends the id of the parent management group to the id of each child management group. This is an example only and not a requirement. You are welcome to choose any management group id convention that best suits your needs. * If you are using **CanadaPubSecALZ v0.9.0 or later** and **do not** include a `var-managementgroup-hierarchy` variable setting in your configuration, it will fallback to using the pipeline variables `var-parentManagementGroupId` and `var-topLevelManagementGroupName`. This is to ensure backward compatibility, enabling newer versions of the code to run with older environment configurations. * If you are using **CanadaPubSecALZ v0.9.0** or later and **do** include a `var-managementgroup-hierarchy` variable setting in your configuration, it will override any pipeline variables `var-parentManagementGroupId` and `var-topLevelManagementGroupName` also present. @@ -468,8 +489,8 @@ This role assignment is used to grant users access to the logging subscription b > **The deployment automation will update the existing resources instead of creating new.** 1. Create directory `./config/logging`. -2. Create subdirectory based on the syntax: `-` (e.g. `CanadaESLZ-main` to create path `./config/logging/CanadaESLZ-main/`). -3. Create JSON parameters file with name `logging.parameters.json` (any name can be used) in directory created on step 2 (i.e. `./config/logging/CanadaESLZ-main/logging.parameters.json`). +2. Create subdirectory based on the syntax: `-` (e.g. `CanadaPubSecALZ-main` to create path `./config/logging/CanadaPubSecALZ-main/`). +3. Create JSON parameters file with name `logging.parameters.json` (any name can be used) in directory created on step 2 (i.e. `./config/logging/CanadaPubSecALZ-main/logging.parameters.json`). 4. Define deployment parameters based on example below. * Set valid contact information for the Azure Service Health Alerts: email and phone number. @@ -502,8 +523,8 @@ This role assignment is used to grant users access to the logging subscription b "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -514,7 +535,7 @@ This role assignment is used to grant users access to the logging subscription b "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { @@ -688,7 +709,7 @@ In order to configure audit stream for Azure Monitor, identify the following inf 3. Navigate to Agents Management 4. Select either Primary or Secondary Key -**Instructions**: [Create an audit stream in Azure DevOps for Azure Monitor](https://docs.microsoft.com/azure/devops/organizations/audit/auditing-streaming?view=azure-devops#create-a-stream). +**Instructions**: [Create an audit stream in Azure DevOps for Azure Monitor](https://learn.microsoft.com/azure/devops/organizations/audit/auditing-streaming?view=azure-devops#create-a-stream). --- @@ -765,11 +786,11 @@ In order to configure audit stream for Azure Monitor, identify the following inf > * [Hub Networking with Fortigate Firewall (NVA)](../../docs/archetypes/hubnetwork-nva-fortigate.md) 1. Create directory `./config/networking`. -1. Create subdirectory based on the syntax: `-` (i.e. `CanadaESLZ-main` to create path `./config/networking/CanadaESLZ-main/`). +1. Create subdirectory based on the syntax: `-` (i.e. `CanadaPubSecALZ-main` to create path `./config/networking/CanadaPubSecALZ-main/`). 1. When using Hub Networking with Azure Firewall - 1. Create subdirectory: `hub-azfw-policy` (i.e. `./config/networking/CanadaESLZ-main/hub-azfw-policy`) - 1. Create JSON parameters file with name `azure-firewall-policy.parameters.json` (any name can be used) in the directory (i.e. `./config/networking/CanadaESLZ-main/hub-azfw-policy/azure-firewall-policy.parameters.json`). + 1. Create subdirectory: `hub-azfw-policy` (i.e. `./config/networking/CanadaPubSecALZ-main/hub-azfw-policy`) + 1. Create JSON parameters file with name `azure-firewall-policy.parameters.json` (any name can be used) in the directory (i.e. `./config/networking/CanadaPubSecALZ-main/hub-azfw-policy/azure-firewall-policy.parameters.json`). 1. Define deployment parameters based on example below. * Set the values for the Azure tags that would be applied to the logging resources. @@ -803,8 +824,8 @@ In order to configure audit stream for Azure Monitor, identify the following inf } ``` - 1. Create subdirectory: `hub-azfw` (i.e. `./config/networking/CanadaESLZ-main/hub-azfw`) - 1. Create JSON parameters file with name `hub-network.parameters.json` (any name can be used) in the directory (i.e. `./config/networking/CanadaESLZ-main/hub-azfw/hub-network.json`). + 1. Create subdirectory: `hub-azfw` (i.e. `./config/networking/CanadaPubSecALZ-main/hub-azfw`) + 1. Create JSON parameters file with name `hub-network.parameters.json` (any name can be used) in the directory (i.e. `./config/networking/CanadaPubSecALZ-main/hub-azfw/hub-network.json`). 1. Define deployment parameters based on example below. * Set valid contact information for the Azure Service Health Alerts: email and phone number. @@ -837,8 +858,8 @@ In order to configure audit stream for Azure Monitor, identify the following inf "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -849,7 +870,7 @@ In order to configure audit stream for Azure Monitor, identify the following inf "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { @@ -1041,8 +1062,8 @@ In order to configure audit stream for Azure Monitor, identify the following inf 1. When using Hub Networking with Fortigate Firewall (NVA) - 1. Create subdirectory: `hub-nva` (i.e. `./config/networking/CanadaESLZ-main/hub-nva`) - 1. Create JSON parameters file with name `hub-network.parameters.json` (any name can be used) in the directory (i.e. `./config/networking/CanadaESLZ-main/hub-nva/hub-network.parameters.json`). + 1. Create subdirectory: `hub-nva` (i.e. `./config/networking/CanadaPubSecALZ-main/hub-nva`) + 1. Create JSON parameters file with name `hub-network.parameters.json` (any name can be used) in the directory (i.e. `./config/networking/CanadaPubSecALZ-main/hub-nva/hub-network.parameters.json`). 1. Define deployment parameters based on example below. * Set valid contact information for the Azure Service Health Alerts: email and phone number. @@ -1075,8 +1096,8 @@ In order to configure audit stream for Azure Monitor, identify the following inf "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -1087,7 +1108,7 @@ In order to configure audit stream for Azure Monitor, identify the following inf "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { @@ -1479,8 +1500,168 @@ In order to configure audit stream for Azure Monitor, identify the following inf * When using Hub Networking with Azure Firewall, run `platform-connectivity-hub-azfw-policy-ci` pipeline first. This ensures that the Azure Firewall Policy is deployed and can be used as a reference for Azure Firewall. This approach allows for Azure Firewall Policies (such as allow/deny rules) to be managed independently from the Hub Networking components. --- +## Step 8 - Configure Identity Subscription + +1. Configure Pipeline definition for Identity + + > Pipelines are stored as YAML definitions in Git and imported into Azure DevOps Pipelines. This approach allows for portability and change tracking. + + 1. Go to Pipelines + 1. New Pipeline + 1. Choose Azure Repos Git + 1. Select Repository + 1. Select Existing Azure Pipeline YAML file + 1. Identify the pipeline in `.pipelines/platform-identity.yml`. + 1. Save the pipeline (don't run it yet) + 1. Rename the pipeline to `identity-ci` + +1. Create a subscription configuration file (JSON) + + 1. Create directory ./config/identity. + + 1. Create subdirectory based on the syntax: `-` (i.e. `CanadaPubSecALZ-main` to create path `./config/identity/CanadaPubSecALZ-main/`). + + 1. Make a copy of an existing subscription configuration file under `config/identity/CanadaPubSecALZ-main` as a starting point + + 1. Define deployment parameters based on example below. + + * Set the values for the Azure tags that would be applied to the identity subscription and the Identity resources. + * Set resource group names for the Identity resources. + > **Note:** Each resource group is created if the associated resource's Enabled flag is set to `true`. + + * Example deployment parameters file: + + ```json + "subscriptionTags": { + "value": { + "ISSO": "isso-tbd", + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "automation", + "networking": "networking", + "networkWatcher": "NetworkWatcherRG", + "backupRecoveryVault": "backup", + "domainControllers": "DomainControllersRG", + "dnsResolver": "dns-resolverRG", + "dnsCondionalForwarders": "dns-CondionalForwardersRG", + "privateDnsZones": "pubsec-dns" + } + }, + ``` + * Configure Azure DNS Private Resolver + + ```json + "privateDnsResolver": { + "value": { + "enabled": true, + "name": "dns-resolver", + "inboundEndpointName": "dns-resolver-Inbound", + "outboundEndpointName": "dns-resolver-Outbound" + } + }, -## Step 8 - Configure Subscription Archetypes + "privateDnsResolverRuleset": { + "value": { + "enabled": true, + "name": "dns-resolver-ruleset", + "linkRuleSetToVnet": true, + "linkRuleSetToVnetName": "dns-resolver-vnet-link", + "forwardingRules": [ + { + "name": "default", + "domain": "dontMakeMeThink.local", + "state": "Enabled", + "targetDnsServers": [ + { + "ipAddress": "10.99.99.100" + }, + { + "ipAddress": "10.99.99.99" + } + ] + } + ] + } + }, + ``` + * Configure the Identity network & it's peering connection to the hub network + > **Note:** If the PrivateDnsResolver Enabled setting is set to `false` the associated DNS Resolver subnets will not be deployed. + + ```json + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/db8a3c31-7dbb-4368-8883-f9e6333ff23a/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4" + } + }, + + "network": { + "value": { + "deployVnet": true, + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "id-vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.15.0.0/24" + ], + "subnets": { + "domainControllers": { + "comments": "Identity Subnet for Domain Controllers and VM-Based DNS Servers", + "name": "DomainControllers", + "addressPrefix": "10.15.0.0/27" + }, + "dnsResolverInbound": { + "comments": "Azure DNS Resolver Inbound Requests subnet", + "name": "AzureDNSResolver-Inbound", + "addressPrefix": "10.15.0.32/27" + }, + "dnsResolverOutbound": { + "comments": "Azure DNS Resolver Outbound Requests subnet", + "name": "AzureDNSResolver-Outbound", + "addressPrefix": "10.15.0.64/27" + }, + "optional": [] + } + } + } + ``` + +1. Configure the Azure DevOps pipeline for Identity + + 1. In Azrue DevOps, go to Pipelines + 1. New Pipeline + 1. Select Existing Azrue Pipline YAML file + 1. Identify the pipeline in `.pipelines/identity.yml` + 1. save the pipeline (don't run it yet) + 1. Rename the pipeline to `identity-ci` + +1. Run pipeline and wait for completion + +--- +## Step 9 - Configure Subscription Archetypes 1. Configure Pipeline definition for subscription archetypes @@ -1499,7 +1680,7 @@ In order to configure audit stream for Azure Monitor, identify the following inf *Review the [README.md under `/config/subscriptions`](../../config/subscriptions/README.md) to create the folder structure required for subscriptions deployments.* - 1. Make a copy of an existing subscription configuration file under `config/subscriptions/CanadaESLZ-main` as a starting point + 1. Make a copy of an existing subscription configuration file under `config/subscriptions/CanadaPubSecALZ-main` as a starting point 2. Be sure to rename the file in one of the following formats: * `[GUID]_[TYPE].json` @@ -1534,7 +1715,7 @@ In order to configure audit stream for Azure Monitor, identify the following inf You can migrate to the management group hierarchy implemented in v0.9.0 by populating the hierarchy from your existing Azure environment. By migrating to the hierarchy, you can take advantage of simplified configuration without modifying Bicep templates. To generate the hierarchy: -1. Install [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli) & [jq](https://stedolan.github.io/jq/download/) on your environment. +1. Install [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) & [jq](https://stedolan.github.io/jq/download/) on your environment. 2. Login to Azure CLI. @@ -1690,7 +1871,7 @@ As of `v0.10.0`, logging configuration have been migrated to JSON parameters fil * Separates Azure DevOps pipeline variables from ARM deployment parameters. * Simplifies support for multiple Log Analytics Workspaces in an Azure tenant (i.e. LAWs deployed by region or workload) -We added a new parameter to `common.yml` to set the folder for logging configuration. This folder is used by Azure DevOps Pipelines to create a fully qualified file path for logging configuration. A fully qualified path will have the following structure: ``/`-`/`logging.parameters.json`. For example: `config/logging/CanadaESLZ-main/logging.parameters.json` +We added a new parameter to `common.yml` to set the folder for logging configuration. This folder is used by Azure DevOps Pipelines to create a fully qualified file path for logging configuration. A fully qualified path will have the following structure: ``/`-`/`logging.parameters.json`. For example: `config/logging/CanadaPubSecALZ-main/logging.parameters.json` ```yaml loggingPathFromRoot: 'config/logging' @@ -1699,8 +1880,8 @@ We added a new parameter to `common.yml` to set the folder for logging configura Migration process: 1. Create directory `./config/logging`. -2. Create subdirectory based on the syntax: `-` (i.e. `CanadaESLZ-main` to create path `./config/logging/CanadaESLZ-main/`). -3. Create JSON parameters file with name `logging.parameters.json` (any name can be used) in the directory (i.e. `./config/logging/CanadaESLZ-main/logging.parameters.json`). +2. Create subdirectory based on the syntax: `-` (i.e. `CanadaPubSecALZ-main` to create path `./config/logging/CanadaPubSecALZ-main/`). +3. Create JSON parameters file with name `logging.parameters.json` (any name can be used) in the directory (i.e. `./config/logging/CanadaPubSecALZ-main/logging.parameters.json`). 4. Define deployment parameters based on example below. **Template to use for logging.parameters.json** @@ -1844,7 +2025,7 @@ As of `v0.10.0`, hub networking configuration have been migrated to JSON paramet * Separates Azure DevOps pipeline variables from ARM deployment parameters. * Simplifies support for multiple Hub Networks in an Azure tenant (i.e. Hub Network deployed by region) -We added a new parameter to `common.yml` to set the folder for networking configuration. This folder is used by Azure DevOps Pipelines to create a fully qualified file path for networking configuration. A fully qualified path will have the following structure: ``/`-`/`hub-capability`/`hub-network.parameters.json`. For example: `config/networking/CanadaESLZ-main/hub-azfw/hub-network.parameters.json` +We added a new parameter to `common.yml` to set the folder for networking configuration. This folder is used by Azure DevOps Pipelines to create a fully qualified file path for networking configuration. A fully qualified path will have the following structure: ``/`-`/`hub-capability`/`hub-network.parameters.json`. For example: `config/networking/CanadaPubSecALZ-main/hub-azfw/hub-network.parameters.json` ```yaml networkPathFromRoot: 'config/networking' @@ -1853,11 +2034,11 @@ We added a new parameter to `common.yml` to set the folder for networking config Migration process: 1. Create directory `./config/networking`. -2. Create subdirectory based on the syntax: `-` (i.e. `CanadaESLZ-main` to create path `./config/networking/CanadaESLZ-main/`). +2. Create subdirectory based on the syntax: `-` (i.e. `CanadaPubSecALZ-main` to create path `./config/networking/CanadaPubSecALZ-main/`). 3. When using Hub Networking with Azure Firewall - 1. Create subdirectory: `hub-azfw-policy` (i.e. `./config/networking/CanadaESLZ-main/hub-azfw-policy`) - 1. Create JSON parameters file with name `azure-firewall-policy.parameters.json` (any name can be used) in the directory (i.e. `./config/networking/CanadaESLZ-main/hub-azfw-policy/azure-firewall-policy.parameters.json`). + 1. Create subdirectory: `hub-azfw-policy` (i.e. `./config/networking/CanadaPubSecALZ-main/hub-azfw-policy`) + 1. Create JSON parameters file with name `azure-firewall-policy.parameters.json` (any name can be used) in the directory (i.e. `./config/networking/CanadaPubSecALZ-main/hub-azfw-policy/azure-firewall-policy.parameters.json`). 1. Define deployment parameters based on example below. **Template to use for azure-firewall-policy.parameters.json** @@ -1880,8 +2061,8 @@ Migration process: } ``` - 1. Create subdirectory: `hub-azfw` (i.e. `./config/networking/CanadaESLZ-main/hub-azfw`) - 1. Create JSON parameters file with name `hub-network.parameters.json` (any name can be used) in the directory (i.e. `./config/networking/CanadaESLZ-main/hub-azfw/hub-network.json`). + 1. Create subdirectory: `hub-azfw` (i.e. `./config/networking/CanadaPubSecALZ-main/hub-azfw`) + 1. Create JSON parameters file with name `hub-network.parameters.json` (any name can be used) in the directory (i.e. `./config/networking/CanadaPubSecALZ-main/hub-azfw/hub-network.json`). 1. Define deployment parameters based on example below. **Template to use for hub-network.parameters.json** @@ -2066,8 +2247,8 @@ Migration process: 4. When using Hub Networking with Network Virtual Appliance - 1. Create subdirectory: `hub-nva` (i.e. `./config/networking/CanadaESLZ-main/hub-nva`) - 1. Create JSON parameters file with name `hub-network.parameters.json` (any name can be used) in the directory (i.e. `./config/networking/CanadaESLZ-main/hub-nva/hub-network.parameters.json`). + 1. Create subdirectory: `hub-nva` (i.e. `./config/networking/CanadaPubSecALZ-main/hub-nva`) + 1. Create JSON parameters file with name `hub-network.parameters.json` (any name can be used) in the directory (i.e. `./config/networking/CanadaPubSecALZ-main/hub-nva/hub-network.parameters.json`). 1. Define deployment parameters based on example below. **Template to use for hub-network.parameters.json** diff --git a/docs/onboarding/azure-devops-scripts.md b/docs/onboarding/azure-devops-scripts.md index e06d95a7..e00d1470 100644 --- a/docs/onboarding/azure-devops-scripts.md +++ b/docs/onboarding/azure-devops-scripts.md @@ -26,23 +26,23 @@ The instructions in this document and scripts in the `/scripts/onboarding` folde Install instructions: -- +- After installation: - Sign in with `az login` - + ### Azure CLI devops extension Install instructions: -- +- After installation: - Sign-in with a Personal Access Token (PAT): - . For example: + . For example: `az devops login --organization https://dev.azure.com/[DEVOPS-ORG]` Optionally, you may also want to perform the following steps: @@ -55,7 +55,7 @@ Optionally, you may also want to perform the following steps: These additional steps are optional since the scripts use the `DEVOPS_ORG` and `DEVOPS_PROJECT_NAME` environment variables. Setting the default Azure DevOps organization and project may be useful when you are invoking the `az devops` commands directly. -- For other `az devops` commands, refer to the following documentation: +- For other `az devops` commands, refer to the following documentation: ### jq.exe @@ -87,7 +87,7 @@ If you need to create Azure DevOps project(s) or manage organization-wide policy If you don't need to create Azure DevOps project(s) or manage organization-wide policy settings, then your user account will only need to be a member of the `Project Administrators` group in an existing Azure DevOps project. -Detailed instructions on how to configure security & usage settings for Azure DevOps are outside the scope of this documentation. For additional information on these topics, refer to the following: [Settings, Security & Usage documentation](https://docs.microsoft.com/azure/devops/organizations). +Detailed instructions on how to configure security & usage settings for Azure DevOps are outside the scope of this documentation. For additional information on these topics, refer to the following: [Settings, Security & Usage documentation](https://learn.microsoft.com/azure/devops/organizations). ### Azure Active Directory @@ -102,7 +102,7 @@ Perform the following steps to verify your administrative access level: 1. Verify your account is assigned the `Global administrator` role Next, ensure your account has elevated access at Azure AD tenant root scope, so that you are able to manage management groups: - Reference: + Reference: Here are some sample Azure CLI commands you can use: @@ -119,7 +119,7 @@ Here are some sample Azure CLI commands you can use: ### Azure Subscriptions -You will need to either have the ability to create new Azure subscriptions or have Azure subscriptions created for you and ready for use. If you need to create Azure subscriptions, then review the following documentation that discusses the role requirements depending on whether your Azure subscriptions are procured through an Azure Enterprise Agreement, Microsoft Customer Agreement, Microsoft Partner Agreement, or Microsoft Online Service Program billing account: [Create an additional Azure subscription](https://docs.microsoft.com/azure/cost-management-billing/manage/create-subscription). +You will need to either have the ability to create new Azure subscriptions or have Azure subscriptions created for you and ready for use. If you need to create Azure subscriptions, then review the following documentation that discusses the role requirements depending on whether your Azure subscriptions are procured through an Azure Enterprise Agreement, Microsoft Customer Agreement, Microsoft Partner Agreement, or Microsoft Online Service Program billing account: [Create an additional Azure subscription](https://learn.microsoft.com/azure/cost-management-billing/manage/create-subscription). --- @@ -143,7 +143,7 @@ There are two options for hosting the repository code, outlined in the following > Choose this option if you are already using (or more comfortable working with) Azure DevOps as a location for maintaining your repository files. This option is also a good choice if you want to simplify Azure DevOps pipelines creation by referencing Git repositories in Azure DevOps instead of GitHub Enterprise. -Follow the instructions in the documentation [Import a Git repo](https://docs.microsoft.com/azure/devops/repos/git/import-git-repository?view=azure-devops). +Follow the instructions in the documentation [Import a Git repo](https://learn.microsoft.com/azure/devops/repos/git/import-git-repository?view=azure-devops). In the instructions above, you will use the following Clone URL value: `https://github.com/Azure/CanadaPubSecALZ.git`, and the process will look similar to the following screenshot at the import stage: @@ -260,6 +260,7 @@ Run the `create-pipelines.bat` script to create the landing zone pipelines: - platform-connectivity-hub-nva-ci - platform-connectivity-hub-azfw-ci - platform-connectivity-hub-azfw-policy-ci +- platform-identity-ci - subscriptions-ci If you would rather perform these steps manually, detailed guidance is available in the following sections of the [Azure DevOps Pipelines Onboarding Guide](./azure-devops-pipelines.md): @@ -269,7 +270,8 @@ If you would rather perform these steps manually, detailed guidance is available - [Step 5 - Configure Logging](./azure-devops-pipelines.md#step-5--configure-logging) - [Step 6 - Configure Azure Policies](./azure-devops-pipelines.md#step-6---configure-azure-policies) - [Step 7 - Configure Hub Networking](./azure-devops-pipelines.md#step-7---configure-hub-networking) -- [Step 8 - Configure Subscription Archetypes](./azure-devops-pipelines.md#step-8---configure-subscription-archetypes) +- [Step 8 - Configure Identity Subscription](./azure-devops-pipelines.md#step-8---configure-identity-subscription) +- [Step 9 - Configure Subscription Archetypes](./azure-devops-pipelines.md#step-9---configure-subscription-archetypes) ### Give pipelines access to service endpoint diff --git a/docs/onboarding/azure-devops-setup.md b/docs/onboarding/azure-devops-setup.md index 1941b16c..02facc44 100644 --- a/docs/onboarding/azure-devops-setup.md +++ b/docs/onboarding/azure-devops-setup.md @@ -20,7 +20,7 @@ This section introduces some terminology, and examines logical boundaries at the - **`Organization`**: an Azure DevOps organization may have the same or different Azure AD tenant backing user authentication. Generally speaking, there little to no visibility between `Projects` (and `Teams`) that are located in different Azure DevOps organizations. Furthermore, it is difficult to track work efforts (e.g. using Agile Portfolio Management techniques) for `Projects` and `Teams` that are located in different Azure DevOps organizations. Where these boundaries are desirable, perhaps at higher levels in a large organization, having a separate Azure DevOps organization may be desirable. - **`Project`**: a project within an Azure DevOps organization shares `Organization`-level administrative capabilities, such as work item process customization, access to the same set of extensions, a common billing source (Azure subscription), a common Azure AD tenant for identity/authentication, auditing, and a common set of security policies. Additionally, there is visibility between projects on elements such as work items, repositories. - - **`Team`**: a team within an Azure DevOps project shares `Project`-level administrative capabilities, but may customize many aspects of the interface to their teams requirements, for example: boards, repositories, wikis, etc. Pipelines do not have team-specific views, however it is possible to have multiple Git repositories per `Project` and organize them in a folder hierarchy by `Team`. Read more about this topic in [When to add a team or project](https://docs.microsoft.com/azure/devops/pipelines/create-first-pipeline) and [When to add another project](https://docs.microsoft.com/azure/devops/organizations/projects/about-projects#when-to-add-another-project). + - **`Team`**: a team within an Azure DevOps project shares `Project`-level administrative capabilities, but may customize many aspects of the interface to their teams requirements, for example: boards, repositories, wikis, etc. Pipelines do not have team-specific views, however it is possible to have multiple Git repositories per `Project` and organize them in a folder hierarchy by `Team`. Read more about this topic in [When to add a team or project](https://learn.microsoft.com/azure/devops/pipelines/create-first-pipeline) and [When to add another project](https://learn.microsoft.com/azure/devops/organizations/projects/about-projects#when-to-add-another-project). Generally, it is recommended to strive for fewer Azure DevOps organizations and projects where possible. For ideas on how to determine the right mix of `Organizations`, `Projects` and `Teams` for your business, refer to the Q&A in the next section. @@ -42,7 +42,7 @@ These activities should be completed once per Azure Active Directory tenant that | # | | Task | :-: | - | -------------------- -| 1 | [Link](https://docs.microsoft.com/azure/devops/organizations/accounts/azure-ad-tenant-policy-restrict-org-creation#prerequisites) | Enable the Azure AD prerequisites for the `Restrict organization creation` policy. +| 1 | [Link](https://learn.microsoft.com/azure/devops/organizations/accounts/azure-ad-tenant-policy-restrict-org-creation#prerequisites) | Enable the Azure AD prerequisites for the `Restrict organization creation` policy. | 2 | | Add users to the Azure AD group `Azure DevOps Administrators` who should have the ability to create new Azure DevOps organizations that are linked to your Azure AD tenant. ## Creating an Azure DevOps organization @@ -51,14 +51,14 @@ These activities should be completed once per new Azure DevOps organization crea | # | | Task | :-: | - | -------------------- -| 1 | [Link](https://docs.microsoft.com/azure/devops/organizations/accounts/create-organization) | Create a new Azure DevOps organization. Ensure you are signed in with the identity from the Azure AD tenant you want associated with the new Azure DevOps organization. During the creation process, ensure you select the Canada geography for [data location](https://docs.microsoft.com/azure/devops/organizations/security/data-location) - it should default to this setting based on nearest geography, but it's good to be aware of this configuration option just in case. -| 2 | [Link](https://docs.microsoft.com/azure/devops/organizations/security/set-project-collection-level-permissions) | Add one or more secondary users to the `Project Collection Administrators` group. This ensures continuity in the event the original creator (Owner) of the Azure DevOps organization is unavailable. Limit the total number of users assigned this role to the minium needed. -| 3 | [Link](https://docs.microsoft.com/azure/devops/organizations/accounts/azure-ad-tenant-policy-restrict-org-creation#turn-on-the-policy) | Turn on the `Restrict organization creation` policy. -| 4 | [Link](https://docs.microsoft.com/azure/devops/organizations/billing/set-up-billing-for-your-organization-vs) | Set up billing for your organization. This step associates your Azure DevOps organization with an Azure subscription as a means of payment for things like additional `Basic` access licensed users, additional parallel jobs for Azure Pipelines, and additional storage for Azure Artifacts. -| 5 | [Link](https://docs.microsoft.com/azure/devops/organizations/accounts/add-organization-users) | Add users who will need access to projects within this organization. As part of adding users you will specify an Access Level (Stakeholder, Basic, or Visual Studio Subscriber). Optionally, you may want to consider implementing one or more [Group Rules](https://docs.microsoft.com/azure/devops/organizations/accounts/assign-access-levels-by-group-membership) to automate Access Level and Project permission assignments based on user membership in either an Azure AD group or an Azure DevOps Services group. -| 6 | [Link](https://docs.microsoft.com/azure/devops/organizations/settings/work/manage-process#set-the-default-process) | Set/change the default process to one of the following values, based on your organization's process template standard or the process you anticipate will be most often used during Azure DevOps project creation: `Agile`, `Scrum`, `Basic`, or `CMMI`. You may also customize an existing process (inheritance) and set that to be the default process selected during Azure DevOps project creation. +| 1 | [Link](https://learn.microsoft.com/azure/devops/organizations/accounts/create-organization) | Create a new Azure DevOps organization. Ensure you are signed in with the identity from the Azure AD tenant you want associated with the new Azure DevOps organization. During the creation process, ensure you select the Canada geography for [data location](https://learn.microsoft.com/azure/devops/organizations/security/data-location) - it should default to this setting based on nearest geography, but it's good to be aware of this configuration option just in case. +| 2 | [Link](https://learn.microsoft.com/azure/devops/organizations/security/set-project-collection-level-permissions) | Add one or more secondary users to the `Project Collection Administrators` group. This ensures continuity in the event the original creator (Owner) of the Azure DevOps organization is unavailable. Limit the total number of users assigned this role to the minium needed. +| 3 | [Link](https://learn.microsoft.com/azure/devops/organizations/accounts/azure-ad-tenant-policy-restrict-org-creation#turn-on-the-policy) | Turn on the `Restrict organization creation` policy. +| 4 | [Link](https://learn.microsoft.com/azure/devops/organizations/billing/set-up-billing-for-your-organization-vs) | Set up billing for your organization. This step associates your Azure DevOps organization with an Azure subscription as a means of payment for things like additional `Basic` access licensed users, additional parallel jobs for Azure Pipelines, and additional storage for Azure Artifacts. +| 5 | [Link](https://learn.microsoft.com/azure/devops/organizations/accounts/add-organization-users) | Add users who will need access to projects within this organization. As part of adding users you will specify an Access Level (Stakeholder, Basic, or Visual Studio Subscriber). Optionally, you may want to consider implementing one or more [Group Rules](https://learn.microsoft.com/azure/devops/organizations/accounts/assign-access-levels-by-group-membership) to automate Access Level and Project permission assignments based on user membership in either an Azure AD group or an Azure DevOps Services group. +| 6 | [Link](https://learn.microsoft.com/azure/devops/organizations/settings/work/manage-process#set-the-default-process) | Set/change the default process to one of the following values, based on your organization's process template standard or the process you anticipate will be most often used during Azure DevOps project creation: `Agile`, `Scrum`, `Basic`, or `CMMI`. You may also customize an existing process (inheritance) and set that to be the default process selected during Azure DevOps project creation. | 7 | [Link](https://aka.ms/vsts-anon-access) | Turn off the option allowing public projects. -| 8 | [Link](https://docs.microsoft.com/azure/devops/organizations/settings/timezone-settings-usage) | Configure time zone settings. +| 8 | [Link](https://learn.microsoft.com/azure/devops/organizations/settings/timezone-settings-usage) | Configure time zone settings. ## Creating an Azure DevOps project @@ -66,12 +66,12 @@ These activities should be completed once per new Azure DevOps project created i | # | | Task | :-: | - | -------------------- -| 1 | [Link](https://docs.microsoft.com/azure/devops/organizations/projects/create-project) | Create a new Azure DevOps project. You will need to have the following information available to configure the newly created project: project name, visibility (typically Private), version control (typically Git), and work item process (e.g. Agile, Scrum, Basic, or CMMI). You should develop and follow an standard naming convention for projects. This allows users to easily find and identify projects based on their usage within the organization. While you may use spaces in your project names, this is discouraged as it leads to special encoding requirements in reference URLs. E.g. the URL-encoded sequence for a space character is `%20`, which has the effect of visually cluttering URL references to elements of the project, such as pipelines, and generally makes it more difficult to write automation scripts. -| 2 | [Link](https://docs.microsoft.com/azure/devops/organizations/security/add-users-team-project) | Add users to the newly created project. When adding users to a project, you will place them into one of three roles: 1) Readers, 2) Contributors, or 3) Project Administrators. The list of users and their roles should be acquired from the group requesting the project as part of the intake process. Once one or more Project Administrators have been added to a project, they can complete any additional configuration for the project (e.g. the following steps in this list), or you may continue and do so on their behalf. -| 3 | [Link](https://docs.microsoft.com/azure/devops/organizations/settings/set-services) | **Optionally** disable visibility of any Azure DevOps services that are not required by the project members. For example, if no project members are using source control (Azure Repos), you can turn off its visibility in the menu at the project scope so it does not appear in the sidebar menu. -| 4 | [Link](https://docs.microsoft.com/azure/devops/organizations/projects/create-project#add-a-repository-to-your-project) | Add a repository to the project. Additionally you may also: [Clone an existing Git repo](https://docs.microsoft.com/azure/devops/repos/git/clone), [Import a Git repo](https://docs.microsoft.com/azure/devops/repos/git/import-git-repository), and [Import a repo from TFVC](https://docs.microsoft.com/azure/devops/repos/git/import-from-tfvc). -| 5 | [Link](https://docs.microsoft.com/azure/devops/boards/work-items/view-add-work-items) | Add new work items to the project. For example, populate an initial set of User Stories. -| 6 | [Link](https://docs.microsoft.com/azure/devops/organizations/settings/about-areas-iterations) | Configure `Area` and `Iteration` (sprint) paths. +| 1 | [Link](https://learn.microsoft.com/azure/devops/organizations/projects/create-project) | Create a new Azure DevOps project. You will need to have the following information available to configure the newly created project: project name, visibility (typically Private), version control (typically Git), and work item process (e.g. Agile, Scrum, Basic, or CMMI). You should develop and follow an standard naming convention for projects. This allows users to easily find and identify projects based on their usage within the organization. While you may use spaces in your project names, this is discouraged as it leads to special encoding requirements in reference URLs. E.g. the URL-encoded sequence for a space character is `%20`, which has the effect of visually cluttering URL references to elements of the project, such as pipelines, and generally makes it more difficult to write automation scripts. +| 2 | [Link](https://learn.microsoft.com/azure/devops/organizations/security/add-users-team-project) | Add users to the newly created project. When adding users to a project, you will place them into one of three roles: 1) Readers, 2) Contributors, or 3) Project Administrators. The list of users and their roles should be acquired from the group requesting the project as part of the intake process. Once one or more Project Administrators have been added to a project, they can complete any additional configuration for the project (e.g. the following steps in this list), or you may continue and do so on their behalf. +| 3 | [Link](https://learn.microsoft.com/azure/devops/organizations/settings/set-services) | **Optionally** disable visibility of any Azure DevOps services that are not required by the project members. For example, if no project members are using source control (Azure Repos), you can turn off its visibility in the menu at the project scope so it does not appear in the sidebar menu. +| 4 | [Link](https://learn.microsoft.com/azure/devops/organizations/projects/create-project#add-a-repository-to-your-project) | Add a repository to the project. Additionally you may also: [Clone an existing Git repo](https://learn.microsoft.com/azure/devops/repos/git/clone), [Import a Git repo](https://learn.microsoft.com/azure/devops/repos/git/import-git-repository), and [Import a repo from TFVC](https://learn.microsoft.com/azure/devops/repos/git/import-from-tfvc). +| 5 | [Link](https://learn.microsoft.com/azure/devops/boards/work-items/view-add-work-items) | Add new work items to the project. For example, populate an initial set of User Stories. +| 6 | [Link](https://learn.microsoft.com/azure/devops/organizations/settings/about-areas-iterations) | Configure `Area` and `Iteration` (sprint) paths. ## Creating an Azure DevOps team in existing project @@ -79,8 +79,8 @@ These activities should be completed once per new `Team` created in an existing | # | | Task | :-: | - | -------------------- -| 1 | [Link](https://docs.microsoft.com/azure/devops/organizations/settings/add-teams) | Add a new team. -| 2 | [Link](https://docs.microsoft.com/azure/devops/organizations/settings/add-team-administrator) | Add one or more team administrators. -| 3 | [Link](https://docs.microsoft.com/en-us/azure/devops/organizations/security/add-users-team-project) | Add users to the team. -| 4 | [Link](https://docs.microsoft.com/azure/devops/project/navigation/set-favorites) | Set team favorites. -| 5 | [Link](https://docs.microsoft.com/azure/devops/boards/plans/portfolio-management) | Determine if this team is part of a larger portfolio management effort, and if so follow the guidance in [Configure a hierarchy of team](https://docs.microsoft.com/azure/devops/boards/plans/configure-hierarchical-teams) to ensure the newly created team is configured in the hierarchy. +| 1 | [Link](https://learn.microsoft.com/azure/devops/organizations/settings/add-teams) | Add a new team. +| 2 | [Link](https://learn.microsoft.com/azure/devops/organizations/settings/add-team-administrator) | Add one or more team administrators. +| 3 | [Link](https://learn.microsoft.com/en-us/azure/devops/organizations/security/add-users-team-project) | Add users to the team. +| 4 | [Link](https://learn.microsoft.com/azure/devops/project/navigation/set-favorites) | Set team favorites. +| 5 | [Link](https://learn.microsoft.com/azure/devops/boards/plans/portfolio-management) | Determine if this team is part of a larger portfolio management effort, and if so follow the guidance in [Configure a hierarchy of team](https://learn.microsoft.com/azure/devops/boards/plans/configure-hierarchical-teams) to ensure the newly created team is configured in the hierarchy. diff --git a/docs/onboarding/configuration-scripts.md b/docs/onboarding/configuration-scripts.md new file mode 100644 index 00000000..e92fbe92 --- /dev/null +++ b/docs/onboarding/configuration-scripts.md @@ -0,0 +1,663 @@ +# Configuration Scripts + +## Introduction + +This document discusses the scripts available to help simplify creating and using configuration files for a CanadaPubSecALZ deployment. + +## Table of Contents + +- [Prerequisites](#prerequisites) +- [Creating the Service Principal](#creating-the-service-principal) +- [Working with Configuration Files](#working-with-configuration-files) +- [Working with Deployments](#working-with-deployments) + +--- + +## Prerequisites + +The instructions in this document and scripts in the `/scripts/configuration` folder have a number of prerequisites. Please review the prerequisites below and complete any missing prerequisites before proceeding. + +### Azure PowerShell + +Install the latest version of the Azure PowerShell module. Review the [Azure PowerShell documentation](https://docs.microsoft.com/powershell/azure/install-az-ps) for installation instructions. + +### Azure CLI + +Install the latest version of the Azure CLI. Review the [Azure CLI documentation](https://docs.microsoft.com/cli/azure/install-azure-cli) for installation instructions. + +### Required Permissions + +Review the [Required Permissions](./azure-devops-scripts.md#required-permissions) section of the [Azure DevOps Scripts](./azure-devops-scripts.md) document for the required permissions to run the scripts in the `/scripts/configuration` folder. + +--- + +## Overview + +The scripts in the `/scripts/configuration` folder can be used to simplify part of the Azure Landing Zones onboarding process related to creating the configuration files (`.yml` and `.json`) for your environment. + +Using these scripts is optional.If you choose not to use them, you can still follow the manual steps in the [Azure DevOps Pipelines Onboarding Guide](./azure-devops-pipelines.md) document to create the configuration files. + +These scripts consolidate most, but not all, of the common settings available in the configuration files used to for a CanadaPubSecALZ deployment. Notable examples of configuration values you will need to update directly in the finished configuration files include network settings such as IP addresses and CIDR ranges. Therefore, you will still need to review and update the configuration files after they are created by these scripts. + +Benefits of using these scripts: + +- Simplifies the process of creating a new service principal and credential file. +- Simplifies the process of creating a new set of configuration files based on an existing set of configuration files. +- Automatically copies an existing set of configuration files to a new set of configuration files, putting the new files in their correct folder locations, retaining common configuration settings, and overriding select configuration settings using a single YAML file stored outside of the repository. +- Provide detailed logging for all credential, configuration, and deployment operations in datetime-stamped log files located in your home directory for easier troubleshooting. +- Provide a foundation for automating the creation and management of configuration files using Azure DevOps pipelines or GitHub Actions. + +The following scripts are available: + +Script | Category | Description +---- | -------- | ------------ +Connect-AlzCredential.ps1 | Credentials | Connects to Azure using one of the following methods: Credential file, Service Principal, or Interactive login. +Get-AlzConfiguration.ps1 | Configuration | Gets the ALZ configuration file. Used primarily by other scripts in this folder. +Get-AlzSubscriptions.ps1 | Configuration | Gets an array of ALZ subscription identifiers. Used primarily by other scripts in this folder. +Install-Prerequisites.ps1 | Prerequisites | Installs the PowerShell module prerequisites. +New-AlzConfiguration.ps1 | Configuration | Creates the ALZ configuration files in a specified Target Environment from existing configuration files in a specified Source Environment. +New-AlzCredential.ps1 | Credentials | Creates an ALZ credential file in your home directory. +New-AlzDeployment.ps1 | Deployment | Deploys the ALZ configuration files to the specified Target Environment. Uses the `../deployments/RunWorkflows.ps1` script to deploy the configuration files. +Remove-AlzConfiguration.ps1 | Configuration | Removes the ALZ configuration file. +Remove-AlzCredential.ps1 | Credentials | Removes all elements of an ALZ credential created using the `New-AlzCredential.ps1` script. This includes: the credential file, the service principal, and the app registration. +Test-AlzCredential.ps1 | Credentials | Tests the ALZ credential file. + +>Note: The scripts in this folder are designed to be run from the folder they are located in (`/scripts/configuration`). Running them from any other location may result in errors. + +The configuration scripts take some common parameters. These parameters are used to specify the location of the ALZ configuration files. The default values for these parameters are as follows: + +```powershell +[string]$UserRootPath = "$HOME" +[string]$UserLogsPath = "$UserRootPath/ALZ/logs" +[string]$UserCredsPath = "$UserRootPath/ALZ/credentials" +[string]$UserConfigPath = "$UserRootPath/ALZ/config" +``` + +These parameters can be overridden by specifying the parameters when running the scripts. By default, they are set to the current user's home directory, which is usually outside of the repository. This is done to prevent the ALZ configuration files from being committed to the repository since some of these configuration files contain sensitive information such as credentials that should not be shared. + +The `logs` folder contains log output from the scripts. The `credentials` folder contains the ALZ credential files. The `config` folder contains the ALZ configuration input files. + +The following sections of this document will outline the end-to-end process of creating the ALZ credentials, creating the ALZ configuration files, and then using the ALZ configuration files to deploy the Azure Landing Zones design using either PowerShell or the Azure DevOps pipelines. + +--- + +## Creating the Service Principal + +This section deals with creating the ALZ credential, which is used by the CanadaPubSecALZ configuration scripts to authenticate to Azure during deployment. + +You must be signed in to Azure via the Azure PowerShell SDK before running the scripts in this section. You can do this by either running `Connect-AzAccount` or by using the `Connect-AlzCredential.ps1` script with the `-TenantId` parameter. + +For example: + +```powershell +PS> $TenantId = '10000000-0000-0000-0000-000000000000' +PS> .\Connect-AlzCredential.ps1 -TenantId $TenantId +``` + +You will need certain permissions to run the scripts in this section. Review the [Required Permissions](./azure-devops-scripts.md#required-permissions) section of the [Azure DevOps Scripts](./azure-devops-scripts.md) document for the required permissions to run the scripts in this folder. + +### The ALZ Credential + +The ALZ credential file is a JSON file that contains the following information: `appId`, `displayName`, `password`, and `tenant`. The `appId` and `password` are used to authenticate to Azure, and the `tenant` is used to identify the tenant to which the service principal belongs. The `displayName` is used to identify the service principal. + +### Generate the Credential + +The first step is to generate the credential. This is done by running the `New-AlzCredential.ps1` script. This script will create a service principal in the tenant specified by the `TenantId` parameter. The service principal will be created with the `Owner` role in the tenant. The script will also create the ALZ credential file in the `$HOME/ALZ/credentials` folder. The name of the ALZ credential file will be the same as the name of the environment specified by the `Environment` parameter. + +```powershell +PS> $Environment = 'MyAzureDevOpsOrg-main' +PS> .\New-AlzCredential.ps1 -Environment $Environment +``` + +```text +Creating Service Principal for environment (MyAzureDevOpsOrg-main) in tenant (domain.onmicrosoft.com) + appId: 20000000-0000-0000-0000-000000000000 + displayName: MyAzureDevOpsOrg-main + password: ********** + tenant: 10000000-0000-0000-0000-000000000000 + +Saving Service Principal to file (C:\Users\username\ALZ\credentials/MyAzureDevOpsOrg-main.json) + +Elapsed time: 00:00:05.3650544 +``` + +### Check Credential File Creation + +The next step is to check that the credential file was created successfully. The credential file will be located in the `$HOME/ALZ/credentials` folder. The name of the credential file will be the same as the name of the environment specified by the `Environment` parameter. + +```powershell +PS> $Environment = 'MyAzureDevOpsOrg-main' +PS> Get-ChildItem -Path $HOME/ALZ/credentials +``` + +```text + Directory: C:\Users\username\ALZ\credentials + +Mode LastWriteTime Length Name +---- ------------- ------ ---- +-a--- 7/1/2023 10:40 AM 206 MyAzureDevOpsOrg-main.json +``` + +### Check Credential File Contents + +The next step is to check that the credential file contains the correct information. The credential file will be a JSON file that contains the following information: `appId`, `displayName`, `password`, and `tenant`. The `appId` and `password` are used to authenticate to Azure, and the `tenant` is used to identify the tenant to which the service principal belongs. The `displayName` is used to identify the service principal. + +```powershell +PS> $Environment = 'MyAzureDevOpsOrg-main' +PS> Get-Content -Raw -Path $HOME/ALZ/credentials/$Environment.json +``` + +```text +{ + "appId": "20000000-0000-0000-0000-000000000000", + "displayName": "MyAzureDevOpsOrg-main", + "password": "this-is-not-a-real-password", + "tenant": "10000000-0000-0000-0000-000000000000" +} +``` + +### Test Credentials + +The next step is to test the credentials. This is done by running the `Test-AlzCredential.ps1` script. This script will test the credentials by logging in to Azure using the service principal specified by the `Environment` parameter. The script will check the following: + +- the service principal has the `Owner` role in the tenant +- the service principal can be used to log in to Azure +- the Azure context is set to the correct environment + +```powershell +PS> $Environment = 'MyAzureDevOpsOrg-main' +PS> .\Test-AlzCredential.ps1 -Environment $Environment +``` + +```text +Service Principal (MyAzureDevOpsOrg-main) for environment (MyAzureDevOpsOrg-main) from tenant (10000000-0000-0000-0000-000000000000) is an Owner of the tenant. + +Current Azure context: + +Name Account SubscriptionName Environment TenantId +---- ------- ---------------- ----------- -------- +ALZ-Workload (00000000-… user@domain ALZ-Workload AzureCloud 10000000-0000-0000-0000-000… + +Logging in to Azure using service principal... + +Service Principal Azure context: + +Id : 00000000-0000-0000-0000-000000000000 +Type : ServicePrincipal +Tenants : {10000000-0000-0000-0000-000000000000} +AccessToken : +Credential : +TenantMap : {} +CertificateThumbprint : + +Original Azure context: + +Name Account SubscriptionName Environment TenantId +---- ------- ---------------- ----------- -------- +ALZ-Workload (00000000-… user@domain ALZ-Workload AzureCloud 10000000-0000-0000-0000-000… + +Elapsed time: 00:00:04.9138939 +``` + +--- + +## Working with Configuration Files + +### Configuration File Structure + +Configurations for the CanadaPubSecALZ implementation are stored under the `config` folder in the root of the repository. There are four subfolders under the `config` folder: + +- The `logging` subfolder contains the configuration files for the logging and monitoring components of the implementation. +- The `networking` subfolder contains the configuration files for the networking components of the implementation. +- The `identity` subfolder contains the configuration files for the identity components of the implementation. +- The `subscriptions` subfolder contains the configuration files for the subscriptions used by the implementation. +- The `variables` subfolder contains the configuration files for the variables used by the implementation. + +The `logging`, `networking`, `identity`, and `subscriptions` subfolders contain JSON configuration files for each environment. An environment is named using the following convention: `-`. For example, the `CanadaPubSecALZ` environment corresponding to the `CanadaPubSecALZ` organization name (for Azure DevOps) or repository name (for GitHub) and the `main` branch of the repository is named `CanadaPubSecALZ-main`. + +The `variables` subfolder contains a YAML configuration file for each environment. An environment is named using the following convention: `-`. For example, the `CanadaPubSecALZ` environment corresponding to the `CanadaPubSecALZ` organization name (for Azure DevOps) or repository name (for GitHub) and the `main` branch of the repository is named `CanadaPubSecALZ-main`. + +Take a moment to familiarize yourself with the contents of the `config` folder, its subfolders, and the JSON and YAML configuration files therein. The configuration files in the main repository are provided as a starting point for your implementation. You will need to make copies of and modify the configuration files to suit your implementation. + +In order to streamline the process of creating configuration files tailored for your environment(s), the `New-AlzConfiguration.ps1` script has been provided. This script will create the configuration files for your implementation. It uses configuration files for an existing environment in the main repository as a starting point, combining information from a YAML file you create in your `$HOME/ALZ/config` folder that contains a subset of the most commonly updated configuration values. The script will then create the configuration files for your environment(s) in the correct location. + +### Using a Configuration Template + +Create a YAML file in your `$HOME/ALZ/config` folder that contains the configuration values for your implementation. The file name should be the name of your environment, followed by the `.yml` extension. For example, if your environment is named `MyAzureDevOpsOrg-main`, the file name should be `MyAzureDevOpsOrg-main.yml`. + +Some notes about the values shown in the sample YAML template below: + +- Some sections in the sample YAML file are commented out. You can uncomment these sections and provide values for them if you need to override the default values. + +- You may comment out sections that you do not need to override the existing values from the source environment configuration. + +- Most of the values you are likely to change are represented in the YAML template, with the exception of the network CIDR blocks. These are defined in the `networking` configuration files, and are discussed in more detail in the [Azure DevOps Pipelines Onboarding Guide](./azure-devops-pipelines.md). + +- The `Source` attribute is the name of the environment you are copying the configuration from. This environment must already exist in your repository's `config` folder. + +- The `Target` attribute is empty in the sample YAML file. When no value is specified, the base name of the YAML template file will be used to form your new environment name. For example, if your YAML template file is named `MyAzureDevOpsOrg-main.yml`, the `Target` attribute will be set to `MyAzureDevOpsOrg-main` by default. + +- The following GUID is provided as a placeholder for the Azure AD tenant identifier. You must replace them with the actual GUID value of the Azure AD tenant in your environment: + - `10000000-0000-0000-0000-000000000000`: Tenant + +- The following GUIDs are provided as placeholders for Azure subscription identifiers. You must replace them with the actual GUID values of the subscriptions in your environment: + - `20000000-0000-0000-0000-000000000000`: Logging subscription + - `30000000-0000-0000-0000-000000000000`: Network Hub subscription + - `40000000-0000-0000-0000-000000000000`: Identity subscription + - `70000000-0000-0000-0000-000000000000`: Generic subscription + - `80000000-0000-0000-0000-000000000000`: Machine Learning subscription + - `90000000-0000-0000-0000-000000000000`: Healthcare subscription + + Note that the last 3 subscriptions correspond to each of the 3 archetypes provided in the reference implementation: Generic, Machine Learning, and Healthcare. These are optional and one or more of these can be removed if not needed. + +- The following GUIDs are provided as placeholders for Azure AD security group identifiers. You must replace them with the actual GUID values of the security groups in your environment: + - `00000000-1000-0000-0000-000000000000`: Azure AD group to assign Contributor role in Logging subscription + - `00000000-2000-0000-0000-000000000000`: Azure AD group to assign Contributor role in Network subscription + - `00000000-3000-0000-0000-000000000000`: Azure AD group to assign Contributor role in Identity subscription + - `00000000-4000-0000-0000-000000000000`: Azure AD group to assign Contributor role in Generic subscription + - `00000000-5000-0000-0000-000000000000`: Azure AD group to assign custom landing zone application owners role in Generic subscription + - `00000000-6000-0000-0000-000000000000`: Azure AD group to assign Contributor role in Machine Learning subscription + - `00000000-7000-0000-0000-000000000000`: Azure AD group to assign custom landing zone application owners role in Machine Learning subscription + - `00000000-8000-0000-0000-000000000000`: Azure AD group to assign Contributor role in Healthcare subscription + - `00000000-9000-0000-0000-000000000000`: Azure AD group to assign custom landing zone application owners role in Healthcare subscription + +- The following partial GUIDs (8-character prefix) are provided as placeholders for the Azure subscription identifiers for 3 of the archetype subscriptions in the `CanadaPubSecALZ-main` configuration: Generic, Machine Learning, and Healthcare. You can replace them with partial GUIDs of alternate subscription configurations from a different source environment, or you can leave them as-is to use the subscription configurations provided in the `CanadaPubSecALZ-main` configuration: + - `8422552f`: The Generic subscription from the `CanadaPubSecALZ-main` configuration + - `f881fccb`: One of the Machine Learning subscriptions from the `CanadaPubSecALZ-main` configuration + - `1f519216`: The Healthcare subscription from the `CanadaPubSecALZ-main` configuration + +- The `Subscriptions:` element in the YAML template is a list of subscriptions from an existing source environment that will be used to create corresponding subscriptions in your target (new) environment. You may add or remove (delete or comment out) items in this list as needed. + +Copy the following sample into your YAML file, and update the values to suit your implementation. The values you provide will be used to create the configuration files for your environment(s). + +```yaml +Environment: + Source: CanadaPubSecALZ-main + Target: + +DeployRegion: canadacentral + +ManagementGroupHierarchy: + name: Tenant Root Group + id: 10000000-0000-0000-0000-000000000000 + children: + - name: Azure Landing Zones for Canadian Public Sector + id: pubsec + children: + - name: Platform + id: Platform + children: + - name: Management + id: Management + children: [] + - name: Connectivity + id: Connectivity + children: [] + - name: Identity + id: Identity + children: [] + - name: LandingZones + id: LandingZones + children: + - name: DevTest + id: DevTest + children: [] + - name: QA + id: QA + children: [] + - name: Prod + id: Prod + children: [] + - name: Sandbox + id: Sandbox + children: [] + +Logging: + SubscriptionId: 20000000-0000-0000-0000-000000000000 + ManagementGroupId: Management + SecurityCenter: + email: 'security@example.com' + phone: '6135555555' + ServiceHealthAlerts: + resourceGroupName: service-health-alerts-rg + incidentTypes: ['Incident', 'Security'] + regions: ['Global', 'Canada Central', 'Canada East'] + receivers: + app: ['logging@example.com'] + email: ['logging@example.com'] + sms: [{ countryCode: '1', phoneNumber: '6135555555' }] + voice: [{ countryCode: '1', phoneNumber: '6135555555' }] + actionGroupName: 'Logging Alerts' + actionGroupShortName: 'logging-ag' + alertRuleName: 'Logging Alerts' + alertRuleDescription: 'Logging Alerts for Incidents and Security' + RoleAssignments: + - comments: 'Built-in Contributor Role' + roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + securityGroupObjectIds: ['00000000-1000-0000-0000-000000000000'] + # SubscriptionTags: + # ISSO: isso-tag + # ResourceTags: + # ClientOrganization: client-organization-tag + # CostCenter: cost-center-tag + # DataSensitivity: data-sensitivity-tag + # ProjectContact: project-contact-tag + # ProjectName: project-name-tag + # TechnicalContact: technical-contact-tag + DataCollectionRule: + Enabled: false + +HubNetwork: + SubscriptionId: 30000000-0000-0000-0000-000000000000 + ManagementGroupId: Connectivity + SecurityCenter: + email: 'security@example.com' + phone: '6135555555' + ServiceHealthAlerts: + resourceGroupName: service-health-alerts-rg + incidentTypes: ['Incident', 'Security'] + regions: ['Global', 'Canada Central', 'Canada East'] + receivers: + app: ['networking@example.com'] + email: ['networking@example.com'] + sms: [{ countryCode: '1', phoneNumber: '6135555555' }] + voice: [{ countryCode: '1', phoneNumber: '6135555555' }] + actionGroupName: 'Networking Alerts' + actionGroupShortName: 'network-ag' + alertRuleName: 'Networking Alerts' + alertRuleDescription: 'Networking Alerts for Incidents and Security' + RoleAssignments: + - comments: 'Built-in Contributor Role' + roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + securityGroupObjectIds: ['00000000-2000-0000-0000-000000000000'] + # SubscriptionTags: + # ISSO: isso-tag + # ResourceTags: + # ClientOrganization: client-organization-tag + # CostCenter: cost-center-tag + # DataSensitivity: data-sensitivity-tag + # ProjectContact: project-contact-tag + # ProjectName: project-name-tag + # TechnicalContact: technical-contact-tag + PrivateDNS: + enabled: true + resourceGroupName: private-dns-rg + DDoS: + # https://learn.microsoft.com/azure/ddos-protection/ddos-faq#are-services-unsafe-in-azure-without-the-service- + enabled: false + resourceGroupName: ddos-rg + planName: ddos-plan + +Identity: + SubscriptionId: 40000000-0000-0000-0000-000000000000 + ManagementGroupId: Identity + SecurityCenter: + email: 'security@example.com' + phone: '6135555555' + ServiceHealthAlerts: + resourceGroupName: service-health-alerts-rg + incidentTypes: ['Incident', 'Security'] + regions: ['Global', 'Canada Central', 'Canada East'] + receivers: + app: ['identity@example.com'] + email: ['identity@example.com'] + sms: [{ countryCode: '1', phoneNumber: '6135555555' }] + voice: [{ countryCode: '1', phoneNumber: '6135555555' }] + actionGroupName: 'Identity Alerts' + actionGroupShortName: 'identity-ag' + alertRuleName: 'Identity Alerts' + alertRuleDescription: 'Identity Alerts for Incidents and Security' + RoleAssignments: + - comments: 'Built-in Contributor Role' + roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + securityGroupObjectIds: ['00000000-3000-0000-0000-000000000000'] + # SubscriptionTags: + # ISSO: isso-tag + # ResourceTags: + # ClientOrganization: client-organization-tag + # CostCenter: cost-center-tag + # DataSensitivity: data-sensitivity-tag + # ProjectContact: project-contact-tag + # ProjectName: project-name-tag + # TechnicalContact: technical-contact-tag + +Subscriptions: +- '8422552f': # Generic subscription ID prefix from {$Environment.Source} + SubscriptionId: 70000000-0000-0000-0000-000000000000 + ManagementGroupId: DevTest + Location: canadacentral + SecurityCenter: + email: 'security@example.com' + phone: '6135555555' + ServiceHealthAlerts: + resourceGroupName: service-health-alerts-rg + incidentTypes: ['Incident', 'Security'] + regions: ['Global', 'Canada Central', 'Canada East'] + receivers: + app: ['subscription-owners@example.com'] + email: ['subscription-owners@example.com'] + sms: [{ countryCode: '1', phoneNumber: '6135555555' }] + voice: [{ countryCode: '1', phoneNumber: '6135555555' }] + actionGroupName: 'Subscription Owners Alerts' + actionGroupShortName: 'sub-own-ag' + alertRuleName: 'Subscription Owners Alerts' + alertRuleDescription: 'Subscription Owners Alerts for Incidents and Security' + RoleAssignments: + - comments: 'Built-in Contributor Role' + roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + securityGroupObjectIds: ['00000000-4000-0000-0000-000000000000'] + - comments: 'Custom Role: Landing Zone Application Owner' + roleDefinitionId: 'b4c87314-c1a1-5320-9c43-779585186bcc' + securityGroupObjectIds: ['00000000-5000-0000-0000-000000000000'] + # SubscriptionTags: + # ISSO: isso-tag + # ResourceTags: + # ClientOrganization: client-organization-tag + # CostCenter: cost-center-tag + # DataSensitivity: data-sensitivity-tag + # ProjectContact: project-contact-tag + # ProjectName: project-name-tag + # TechnicalContact: technical-contact-tag + +- 'f881fccb': # Machine Learning 1 subscription ID prefix from {$Environment.Source} + SubscriptionId: 80000000-0000-0000-0000-000000000000 + ManagementGroupId: DevTest + Location: canadacentral + SecurityCenter: + email: 'security@example.com' + phone: '6135555555' + ServiceHealthAlerts: + resourceGroupName: service-health-alerts-rg + incidentTypes: ['Incident', 'Security'] + regions: ['Global', 'Canada Central', 'Canada East'] + receivers: + app: ['subscription-owners@example.com'] + email: ['subscription-owners@example.com'] + sms: [{ countryCode: '1', phoneNumber: '6135555555' }] + voice: [{ countryCode: '1', phoneNumber: '6135555555' }] + actionGroupName: 'Subscription Owners Alerts' + actionGroupShortName: 'sub-own-ag' + alertRuleName: 'Subscription Owners Alerts' + alertRuleDescription: 'Subscription Owners Alerts for Incidents and Security' + RoleAssignments: + - comments: 'Built-in Contributor Role' + roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + securityGroupObjectIds: ['00000000-6000-0000-0000-000000000000'] + - comments: 'Custom Role: Landing Zone Application Owner' + roleDefinitionId: 'b4c87314-c1a1-5320-9c43-779585186bcc' + securityGroupObjectIds: ['00000000-7000-0000-0000-000000000000'] + # SubscriptionTags: + # ISSO: isso-tag + # ResourceTags: + # ClientOrganization: client-organization-tag + # CostCenter: cost-center-tag + # DataSensitivity: data-sensitivity-tag + # ProjectContact: project-contact-tag + # ProjectName: project-name-tag + # TechnicalContact: technical-contact-tag + +- '1f519216': # Healthcare subscription ID prefix from {$Environment.Source} + SubscriptionId: 90000000-0000-0000-0000-000000000000 + ManagementGroupId: DevTest + Location: canadacentral + SecurityCenter: + email: 'security@example.com' + phone: '6135555555' + ServiceHealthAlerts: + resourceGroupName: service-health-alerts-rg + incidentTypes: ['Incident', 'Security'] + regions: ['Global', 'Canada Central', 'Canada East'] + receivers: + app: ['subscription-owners@example.com'] + email: ['subscription-owners@example.com'] + sms: [{ countryCode: '1', phoneNumber: '6135555555' }] + voice: [{ countryCode: '1', phoneNumber: '6135555555' }] + actionGroupName: 'Subscription Owners Alerts' + actionGroupShortName: 'sub-own-ag' + alertRuleName: 'Subscription Owners Alerts' + alertRuleDescription: 'Subscription Owners Alerts for Incidents and Security' + RoleAssignments: + - comments: 'Built-in Contributor Role' + roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + securityGroupObjectIds: ['00000000-8000-0000-0000-000000000000'] + - comments: 'Custom Role: Landing Zone Application Owner' + roleDefinitionId: 'b4c87314-c1a1-5320-9c43-779585186bcc' + securityGroupObjectIds: ['00000000-9000-0000-0000-000000000000'] + # SubscriptionTags: + # ISSO: isso-tag + # ResourceTags: + # ClientOrganization: client-organization-tag + # CostCenter: cost-center-tag + # DataSensitivity: data-sensitivity-tag + # ProjectContact: project-contact-tag + # ProjectName: project-name-tag + # TechnicalContact: technical-contact-tag +``` + +### Creating Configuration Files from a Template + +The next step is to create the configuration files for the target environment. The following command will create a new set of configuration files in the `config` folder, based on the settings in the YAML template file created in the previous section. + +```powershell +PS> $Environment = 'MyAzureDevOpsOrg-main' +PS> .\New-AlzConfiguration.ps1 -Environment $Environment +``` + +Running the `New-AlzConfiguration.ps1` script as shown above will create the new set of configuration files and produce output similar to the following: + +```text +This script creates a new set of configuration files, using an existing CanadaPubSecALZ configuration. Select configuration elements are replaced with values specific to the target environment. + +Reading parameters from file (C:\Users\username\ALZ\config\MyAzureDevOpsOrg-main.yml) +Checking configuration path (P:\CanadaPubSecALZ\CanadaPubSecALZ\config) + Source environment: CanadaPubSecALZ-main + Target environment: MyAzureDevOpsOrg-main + +Generating Variables configurations + + Updating variables configuration + Writing variables configuration file: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/variables/MyAzureDevOpsOrg-main.yml + +Generating Logging configurations + + Reading source environment logging configuration file: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/logging/CanadaPubSecALZ-main/logging.parameters.json + Updating logging configuration + Writing logging configuration file: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/logging/MyAzureDevOpsOrg-main/logging.parameters.json + +Generating Network Azure Firewall configurations + + Reading source environment network Azure Firewall configuration file: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/networking/CanadaPubSecALZ-main/hub-azfw/hub-network.parameters.json + Updating network Azure Firewall configuration + Writing network Azure Firewall configuration file: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/networking/MyAzureDevOpsOrg-main/hub-azfw/hub-network.parameters.json + +Generating Network Azure Firewall Policy configurations + + Reading source environment network Azure Firewall Policy configuration file: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/networking/CanadaPubSecALZ-main/hub-azfw-policy/azure-firewall-policy.parameters.json + Updating network Azure Firewall Policy configuration + Writing network Azure Firewall Policy configuration file: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/networking/MyAzureDevOpsOrg-main/hub-azfw-policy/azure-firewall-policy.parameters.json + +Generating Network NVA configurations + + Reading source environment network NVA configuration file: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/networking/CanadaPubSecALZ-main/hub-nva/hub-network.parameters.json + Updating network NVA configuration + Writing network NVA configuration file: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/networking/MyAzureDevOpsOrg-main/hub-nva/hub-network.parameters.json + +Generating Identity configurations + + Reading source environment identity configuration file: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/identity/CanadaPubSecALZ-main/identity.parameters.json + Updating identity configuration + Writing identity configuration file: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/identity/MyAzureDevOpsOrg-main/identity.parameters.json + +Generating subscription configurations + + Looking for source environment subscription configuration file(s) matching specified pattern (8422552f) + Reading subscription configuration (8422552f-3840-4934-a971-6ee349ffbb05_generic-subscription_canadacentral.json) + Updating subscription configuration + Writing new subscription configuration (P:\CanadaPubSecALZ\CanadaPubSecALZ\config/subscriptions/MyAzureDevOpsOrg-main/DevTest/70000000-0000-0000-0000-000000000000_generic-subscription_canadacentral.json) + + Looking for source environment subscription configuration file(s) matching specified pattern (f881fccb) + Reading subscription configuration (f881fccb-2598-4b9c-b87c-b392f5e16f12_machinelearning_canadacentral.json) + Updating subscription configuration + Writing new subscription configuration (P:\CanadaPubSecALZ\CanadaPubSecALZ\config/subscriptions/MyAzureDevOpsOrg-main/DevTest/80000000-0000-0000-0000-000000000000_machinelearning_canadacentral.json) + + Looking for source environment subscription configuration file(s) matching specified pattern (1f519216) + Reading subscription configuration (1f519216-5e39-4b51-a9b6-10cc2b79b6c7_healthcare_canadacentral.json) + Updating subscription configuration + Writing new subscription configuration (P:\CanadaPubSecALZ\CanadaPubSecALZ\config/subscriptions/MyAzureDevOpsOrg-main/DevTest/90000000-0000-0000-0000-000000000000_healthcare_canadacentral.json) + +Elapsed time: 00:00:00.5295725 +``` + +### Deleting Configuration Files + +If you create a set of configuration files and decide you no longer need them, you can delete them using the `Remove-AlzConfiguration.ps1` script. The following command will delete the configuration files for the target environment. + +```powershell +PS> $Environment = 'MyAzureDevOpsOrg-main' +PS> .\Remove-AlzConfiguration.ps1 -Environment $Environment +``` + +Running the `Remove-AlzConfiguration.ps1` script as shown above will delete the configuration files for the target environment and produce output similar to the following: + +```text +This script removes an existing set of configuration files. + +Checking configuration path (P:\CanadaPubSecALZ\CanadaPubSecALZ\config) +Removing variables configuration file: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/variables/MyAzureDevOpsOrg-main.yml +Removing logging configuration directory: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/logging/MyAzureDevOpsOrg-main +Removing network configuration directory: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/networking/MyAzureDevOpsOrg-main +Removing identity configuration directory: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/identity/MyAzureDevOpsOrg-main +Removing subscription configuration directory: P:\CanadaPubSecALZ\CanadaPubSecALZ\config/subscriptions/MyAzureDevOpsOrg-main + +Elapsed time: 00:00:00.1053294 +``` + +If you have deleted the configuration files for an environment and want to recreate them, you can do so by running the `New-AlzConfiguration.ps1` script as described in the previous section, assuming you have not deleted the source environment configuration files and the YAML template file is still available. + +--- + +## Working with Deployments + +### Deploying a Configuration + +Once you have created a set of configuration files for an environment, you can deploy the configuration using the `New-AlzDeployment.ps1` script. + +>Note: The `New-AlzDeployment.ps1` script will deploy all stages of the specified environment configuration, including: management groups, logging, policies, networking, identity, and subscriptions. If you only want to deploy a subset of the configuration, you can use the `/scripts/deployments/RunWorkflows.ps1` script or invoke the individual Azure DevOps pipelines / GitHub workflows for each stage. + +The following invocation of the `New-AlzDeployment.ps1` script will deploy all stages of the configuration files for the target environment using the previously created credential file and the `AzFW` (Azure Firewall) network hub type: + +```powershell +PS> $Environment = 'MyAzureDevOpsOrg-main' +PS> .\New-AlzDeployment.ps1 -Environment $Environment -CredentialFile $Environment -NetworkType 'AzFW' +``` + +The following invocation of the `New-AlzDeployment.ps1` script will deploy all stages of the configuration files for the target environment using the previously created credential file and the `NVA` (Network Virtual Appliance) network hub type: + +```powershell +PS> $Environment = 'MyAzureDevOpsOrg-main' +PS> .\New-AlzDeployment.ps1 -Environment $Environment -CredentialFile $Environment -NetworkType 'NVA' +``` + +The `.\New-AlzDeployment.ps1` script also provides for alternate authentication methods to the credential file. You may also use a service principal or interactive authentication. + +When you deploy from local configuration files, remember that detailed log output is available in `$HOME/ALZ/logs` (by default) and the log files generated for deployment operations can be very useful for troubleshooting. diff --git a/docs/policy/authoring-guide.md b/docs/policy/authoring-guide.md index 86c773d1..16d421f7 100644 --- a/docs/policy/authoring-guide.md +++ b/docs/policy/authoring-guide.md @@ -114,7 +114,7 @@ The built-in policy sets are used as-is to ensure future improvements from Azure Once the permissions are identified, click the **Cancel** button to discard the changes. - Use [Azure Built-In Roles table](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles) to map the permission name to it's Resource ID. Resource ID will be used when defining the role assignments. + Use [Azure Built-In Roles table](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles) to map the permission name to it's Resource ID. Resource ID will be used when defining the role assignments. 4. Click on the **Duplicate initiative** button. We will not be duplicating the policy set definition, but use this step to identify the parameter names that will need to be populated during policy assignment. @@ -336,7 +336,7 @@ The built-in policy sets are used as-is to ensure future improvements from Azure Execute `Azure DevOps Policy pipeline` to deploy. The policy set assignment will be deployed to the `top level management group` (i.e. `pubsec`). -> It takes approximately 30 minutes for the assignment to be applied to the defined scope. Once it's applied, the evaluation cycle begins for resources within that scope against the newly assigned policy or initiative, and depending on the effects defined by the policy or initiative, resources are marked as compliant, non-compliant, or exempt. A large policy or initiative evaluated against a large scope of resources can take time. As such, there's no pre-defined expectation of when the evaluation cycle completes. Once evaluation completes, the updated compliance results are available in the portal and to SDKs. See [Azure Docs for more information](https://docs.microsoft.com/azure/governance/policy/how-to/get-compliance-data). +> It takes approximately 30 minutes for the assignment to be applied to the defined scope. Once it's applied, the evaluation cycle begins for resources within that scope against the newly assigned policy or initiative, and depending on the effects defined by the policy or initiative, resources are marked as compliant, non-compliant, or exempt. A large policy or initiative evaluated against a large scope of resources can take time. As such, there's no pre-defined expectation of when the evaluation cycle completes. Once evaluation completes, the updated compliance results are available in the portal and to SDKs. See [Azure Docs for more information](https://learn.microsoft.com/azure/governance/policy/how-to/get-compliance-data). #### **Step 5: Verify policy set assignment** @@ -388,7 +388,7 @@ You may want to evaluate the compliance in your environment without any automati Please review guidance and expected behaviour of these settings prior to making any modification: -* [Cloud Adoption Framework - Adopting policy driven guardrails](https://docs.microsoft.com/azure/cloud-adoption-framework/ready/enterprise-scale/dine-guidance) +* [Cloud Adoption Framework - Adopting policy driven guardrails](https://learn.microsoft.com/azure/cloud-adoption-framework/ready/enterprise-scale/dine-guidance) * [Azure Policy Enforcement Mode](https://aka.ms/enforcementMode) You can switch between these modes per-policy set based on your Azure Policy adoption strategy. @@ -445,13 +445,13 @@ Custom policies and policy sets enable an organization to expand their governanc * all: evaluate resource groups, subscriptions, and all resource types * indexed: only evaluate resource types that support tags and location - See [Azure Policy Reference](https://docs.microsoft.com/azure/governance/policy/concepts/definition-structure#mode) for more information. + See [Azure Policy Reference](https://learn.microsoft.com/azure/governance/policy/concepts/definition-structure#mode) for more information. 4. Edit `azurepolicy.parameters.json`. Define parameters that are required by the policy definition. Using parameters enable the policy to be used with different configuration. - See [Azure Parameter Reference](https://docs.microsoft.com/azure/governance/policy/concepts/definition-structure#parameters) for more information. + See [Azure Parameter Reference](https://learn.microsoft.com/azure/governance/policy/concepts/definition-structure#parameters) for more information. **Example** @@ -472,7 +472,7 @@ Custom policies and policy sets enable an organization to expand their governanc Describes the policy rule that will be evaluated by Azure Policy. The rule can have any effect such as Audit, Deny, DeployIfNotExists. - See [Azure Policy docs for more information on creating custom policies](https://docs.microsoft.com/azure/governance/policy/concepts/definition-structure). + See [Azure Policy docs for more information on creating custom policies](https://learn.microsoft.com/azure/governance/policy/concepts/definition-structure). **Example** @@ -879,7 +879,7 @@ When there are deployment errors: Execute `Azure DevOps Policy pipeline` to deploy. The policy set definition and assignment will be deployed to the `top level management group` (i.e. `pubsec`). -> It takes around 30 minutes for the assignment to be applied to the defined scope. Once it's applied, the evaluation cycle begins for resources within that scope against the newly assigned policy or initiative and depending on the effects used by the policy or initiative, resources are marked as compliant, non-compliant, or exempt. A large policy or initiative evaluated against a large scope of resources can take time. As such, there's no pre-defined expectation of when the evaluation cycle completes. Once it completes, updated compliance results are available in the portal and SDKs. See [Azure Docs for more information](https://docs.microsoft.com/azure/governance/policy/how-to/get-compliance-data). +> It takes around 30 minutes for the assignment to be applied to the defined scope. Once it's applied, the evaluation cycle begins for resources within that scope against the newly assigned policy or initiative and depending on the effects used by the policy or initiative, resources are marked as compliant, non-compliant, or exempt. A large policy or initiative evaluated against a large scope of resources can take time. As such, there's no pre-defined expectation of when the evaluation cycle completes. Once it completes, updated compliance results are available in the portal and SDKs. See [Azure Docs for more information](https://learn.microsoft.com/azure/governance/policy/how-to/get-compliance-data). #### **Step 5: Verify policy set definition and assignment deployment** @@ -911,7 +911,7 @@ Execute `Azure DevOps Policy pipeline` to automatically deploy the policy defini Navigate to [Azure Policy Definitions][portalAzurePolicyDefinition] to verify that the policy has been updated. -> It takes around 30 minutes for the update to be applied. Once it's applied, the evaluation cycle begins for resources within that scope against the newly assigned policy or initiative and depending on the effects used by the policy or initiative, resources are marked as compliant, non-compliant, or exempt. A large policy or initiative evaluated against a large scope of resources can take time. As such, there's no pre-defined expectation of when the evaluation cycle completes. Once it completes, updated compliance results are available in the portal and SDKs. See [Azure Docs for more information](https://docs.microsoft.com/azure/governance/policy/how-to/get-compliance-data). +> It takes around 30 minutes for the update to be applied. Once it's applied, the evaluation cycle begins for resources within that scope against the newly assigned policy or initiative and depending on the effects used by the policy or initiative, resources are marked as compliant, non-compliant, or exempt. A large policy or initiative evaluated against a large scope of resources can take time. As such, there's no pre-defined expectation of when the evaluation cycle completes. Once it completes, updated compliance results are available in the portal and SDKs. See [Azure Docs for more information](https://learn.microsoft.com/azure/governance/policy/how-to/get-compliance-data). When there are deployment errors: @@ -1025,7 +1025,7 @@ You may want to evaluate the compliance in your environment without any automati Please review guidance and expected behaviour of these settings prior to making any modification: -* [Cloud Adoption Framework - Adopting policy driven guardrails](https://docs.microsoft.com/azure/cloud-adoption-framework/ready/enterprise-scale/dine-guidance) +* [Cloud Adoption Framework - Adopting policy driven guardrails](https://learn.microsoft.com/azure/cloud-adoption-framework/ready/enterprise-scale/dine-guidance) * [Azure Policy Enforcement Mode](https://aka.ms/enforcementMode) You can switch between these modes per-policy set based on your Azure Policy adoption strategy. @@ -1052,13 +1052,13 @@ The Diagnostic Settings policies in this reference implementation were created u * Create `azurepolicy.config.json` with policy name and mode. 4. Delete the instance created in Step 1. -[nist80053r4Policyset]: https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4 -[nist80053r5Policyset]: https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5 -[pbmmPolicyset]: https://docs.microsoft.com/azure/governance/policy/samples/canada-federal-pbmm -[asbPolicySet]: https://docs.microsoft.com/security/benchmark/azure/overview -[cisMicrosoftAzureFoundationPolicySet]: https://docs.microsoft.com/azure/governance/policy/samples/cis-azure-1-3-0 -[fedrampmPolicySet]: https://docs.microsoft.com/azure/governance/policy/samples/fedramp-moderate -[hipaaHitrustPolicySet]: https://docs.microsoft.com/azure/governance/policy/samples/hipaa-hitrust-9-2 +[nist80053r4Policyset]: https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4 +[nist80053r5Policyset]: https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5 +[pbmmPolicyset]: https://learn.microsoft.com/azure/governance/policy/samples/canada-federal-pbmm +[asbPolicySet]: https://learn.microsoft.com/security/benchmark/azure/overview +[cisMicrosoftAzureFoundationPolicySet]: https://learn.microsoft.com/azure/governance/policy/samples/cis-azure-1-3-0 +[fedrampmPolicySet]: https://learn.microsoft.com/azure/governance/policy/samples/fedramp-moderate +[hipaaHitrustPolicySet]: https://learn.microsoft.com/azure/governance/policy/samples/hipaa-hitrust-9-2 [portalAzurePolicyDefinition]: https://portal.azure.com/#blade/Microsoft_Azure_Policy/PolicyMenuBlade/Definitions [portalAzurePolicyAssignment]: https://portal.azure.com/#blade/Microsoft_Azure_Policy/PolicyMenuBlade/Assignments [portalAzurePolicyCompliance]: https://portal.azure.com/#blade/Microsoft_Azure_Policy/PolicyMenuBlade/Compliance diff --git a/docs/policy/readme.md b/docs/policy/readme.md index 4e686392..9fdeeba0 100644 --- a/docs/policy/readme.md +++ b/docs/policy/readme.md @@ -13,7 +13,7 @@ ## Overview -Guardrails in Azure are deployed through [Azure Policy](https://docs.microsoft.com/azure/governance/policy/overview). Azure Policy helps to enforce organizational standards and to assess compliance at-scale. Through its compliance dashboard, it provides an aggregated view to evaluate the overall state of the environment, with the ability to drill down to the per-resource, per-policy granularity. It also helps to bring your resources to compliance through bulk remediation for existing resources and automatic remediation for new resources. +Guardrails in Azure are deployed through [Azure Policy](https://learn.microsoft.com/azure/governance/policy/overview). Azure Policy helps to enforce organizational standards and to assess compliance at-scale. Through its compliance dashboard, it provides an aggregated view to evaluate the overall state of the environment, with the ability to drill down to the per-resource, per-policy granularity. It also helps to bring your resources to compliance through bulk remediation for existing resources and automatic remediation for new resources. Common use cases for Azure Policy include implementing governance for resource consistency, regulatory compliance, security, cost, and management. Policy definitions for these common use cases are already available in your Azure environment as built-ins to help you get started. @@ -154,12 +154,12 @@ Parameters can be templated using the syntax `{{PARAMETER_NAME}}`. Following pa | Templated Parameter | Source Value | Example | | --- | --- | --- | -| {{var-topLevelManagementGroupName}} | Environment configuration file such as [config/variables/CanadaESLZ-main.yml](../../config/variables/CanadaESLZ-main.yml) | `pubsec` -| {{var-logging-logAnalyticsWorkspaceResourceId}} | Resource ID is inferred using Log Analytics settings in environment configuration file such as [config/variables/CanadaESLZ-main.yml](../../config/variables/CanadaESLZ-main.yml) | `/subscriptions/bc0a4f9f-07fa-4284-b1bd-fbad38578d3a/resourcegroups/pubsec-central-logging/providers/microsoft.operationalinsights/workspaces/log-analytics-workspace` -| {{var-logging-logAnalyticsWorkspaceId}} | Workspace ID is inferred using Log Analytics settings in environment configuration file such as [config/variables/CanadaESLZ-main.yml](../../config/variables/CanadaESLZ-main.yml) | `fcce3f30-158a-4561-a714-361623f42168` -| {{var-logging-logAnalyticsResourceGroupName}} | Environment configuration file such as [config/variables/CanadaESLZ-main.yml](../../config/variables/CanadaESLZ-main.yml) | `pubsec-central-logging` -| {{var-logging-logAnalyticsRetentionInDays}} | Environment configuration file such as [config/variables/CanadaESLZ-main.yml](../../config/variables/CanadaESLZ-main.yml) | `730` -| {{var-logging-diagnosticSettingsforNetworkSecurityGroupsStoragePrefix}} | Environment configuration file such as [config/variables/CanadaESLZ-main.yml](../../config/variables/CanadaESLZ-main.yml) | `pubsecnsg` +| {{var-topLevelManagementGroupName}} | Environment configuration file such as [config/variables/CanadaPubSecALZ-main.yml](../../config/variables/CanadaPubSecALZ-main.yml) | `pubsec` +| {{var-logging-logAnalyticsWorkspaceResourceId}} | Resource ID is inferred using Log Analytics settings in environment configuration file such as [config/variables/CanadaPubSecALZ-main.yml](../../config/variables/CanadaPubSecALZ-main.yml) | `/subscriptions/bc0a4f9f-07fa-4284-b1bd-fbad38578d3a/resourcegroups/pubsec-central-logging/providers/microsoft.operationalinsights/workspaces/log-analytics-workspace` +| {{var-logging-logAnalyticsWorkspaceId}} | Workspace ID is inferred using Log Analytics settings in environment configuration file such as [config/variables/CanadaPubSecALZ-main.yml](../../config/variables/CanadaPubSecALZ-main.yml) | `fcce3f30-158a-4561-a714-361623f42168` +| {{var-logging-logAnalyticsResourceGroupName}} | Environment configuration file such as [config/variables/CanadaPubSecALZ-main.yml](../../config/variables/CanadaPubSecALZ-main.yml) | `pubsec-central-logging` +| {{var-logging-logAnalyticsRetentionInDays}} | Environment configuration file such as [config/variables/CanadaPubSecALZ-main.yml](../../config/variables/CanadaPubSecALZ-main.yml) | `730` +| {{var-logging-diagnosticSettingsforNetworkSecurityGroupsStoragePrefix}} | Environment configuration file such as [config/variables/CanadaPubSecALZ-main.yml](../../config/variables/CanadaPubSecALZ-main.yml) | `pubsecnsg` | {{var-policyAssignmentManagementGroupId}} | The management group scope for policy assignment. | `pubsec` --- @@ -170,10 +170,10 @@ See [Azure Policy Authoring Guide](authoring-guide.md) for step-by-step instruct -[nist80053r4Policyset]: https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4 -[nist80053r5Policyset]: https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5 -[pbmmPolicyset]: https://docs.microsoft.com/azure/governance/policy/samples/canada-federal-pbmm -[asbPolicySet]: https://docs.microsoft.com/security/benchmark/azure/overview -[cisMicrosoftAzureFoundationPolicySet]: https://docs.microsoft.com/azure/governance/policy/samples/cis-azure-1-3-0 -[fedrampmPolicySet]: https://docs.microsoft.com/azure/governance/policy/samples/fedramp-moderate -[hipaaHitrustPolicySet]: https://docs.microsoft.com/azure/governance/policy/samples/hipaa-hitrust-9-2 +[nist80053r4Policyset]: https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4 +[nist80053r5Policyset]: https://learn.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r5 +[pbmmPolicyset]: https://learn.microsoft.com/azure/governance/policy/samples/canada-federal-pbmm +[asbPolicySet]: https://learn.microsoft.com/security/benchmark/azure/overview +[cisMicrosoftAzureFoundationPolicySet]: https://learn.microsoft.com/azure/governance/policy/samples/cis-azure-1-3-0 +[fedrampmPolicySet]: https://learn.microsoft.com/azure/governance/policy/samples/fedramp-moderate +[hipaaHitrustPolicySet]: https://learn.microsoft.com/azure/governance/policy/samples/hipaa-hitrust-9-2 diff --git a/landingzones/lz-generic-subscription/main.bicep b/landingzones/lz-generic-subscription/main.bicep index cf4f835c..314fdf58 100644 --- a/landingzones/lz-generic-subscription/main.bicep +++ b/landingzones/lz-generic-subscription/main.bicep @@ -95,7 +95,7 @@ param hubNetwork object param network object // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../azresources/telemetry/customer-usage-attribution-subscription.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.archetypes.genericSubscription}' diff --git a/landingzones/lz-generic-subscription/networking.bicep b/landingzones/lz-generic-subscription/networking.bicep index 4023c99d..1eb87dad 100644 --- a/landingzones/lz-generic-subscription/networking.bicep +++ b/landingzones/lz-generic-subscription/networking.bicep @@ -297,7 +297,8 @@ module vnetPeeringHubToSpoke '../../azresources/network/vnet-peering.bicep' = if peeringName: 'Spoke-${last(hubVnetIdSplit)}-to-${vnet.name}-${uniqueString(vnet.id)}' allowForwardedTraffic: true allowVirtualNetworkAccess: true - sourceVnetName: last(hubVnetIdSplit) + allowGatewayTransit: true + sourceVnetName: last(hubVnetIdSplit)! targetVnetId: vnet.id useRemoteGateways: false } diff --git a/landingzones/lz-healthcare/main.bicep b/landingzones/lz-healthcare/main.bicep index 6ca0e2aa..0a44a107 100644 --- a/landingzones/lz-healthcare/main.bicep +++ b/landingzones/lz-healthcare/main.bicep @@ -80,7 +80,7 @@ param hubNetwork object param network object // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../azresources/telemetry/customer-usage-attribution-subscription.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.archetypes.healthcare}' diff --git a/landingzones/lz-healthcare/networking.bicep b/landingzones/lz-healthcare/networking.bicep index 982440ed..1e50ef22 100644 --- a/landingzones/lz-healthcare/networking.bicep +++ b/landingzones/lz-healthcare/networking.bicep @@ -229,7 +229,7 @@ module nsgDatabricks '../../azresources/network/nsg/nsg-databricks.bicep' = { // Network security groups (NSGs): You can block outbound traffic with an NSG that's placed on your integration subnet. // The inbound rules don't apply because you can't use VNet Integration to provide inbound access to your app. // At the moment, there are no outbound rules to block outbound traffic -// See https://docs.microsoft.com/azure/app-service/web-sites-integrate-with-vnet#regional-vnet-integration +// See https://learn.microsoft.com/azure/app-service/web-sites-integrate-with-vnet#regional-vnet-integration module nsgWebApp '../../azresources/network/nsg/nsg-empty.bicep' = { name: 'deploy-nsg-webapp' params: { @@ -265,7 +265,7 @@ module udrDatabricksPrivate '../../azresources/network/udr/udr-databricks-privat // Route tables (UDRs): You can place a route table on the integration subnet to send outbound traffic where you want. // At the moment, the route table is empty but rules can be added to force tunnel. -// See https://docs.microsoft.com/azure/app-service/web-sites-integrate-with-vnet#regional-vnet-integration +// See https://learn.microsoft.com/azure/app-service/web-sites-integrate-with-vnet#regional-vnet-integration module udrWebApp '../../azresources/network/udr/udr-custom.bicep' = { name: 'deploy-route-table-web-app' params: { @@ -413,7 +413,8 @@ module vnetPeeringHubToSpoke '../../azresources/network/vnet-peering.bicep' = if peeringName: 'Spoke-${last(hubVnetIdSplit)}-to-${vnet.name}-${uniqueString(vnet.id)}' allowForwardedTraffic: true allowVirtualNetworkAccess: true - sourceVnetName: last(hubVnetIdSplit) + allowGatewayTransit: true + sourceVnetName: last(hubVnetIdSplit)! targetVnetId: vnet.id useRemoteGateways: false } diff --git a/landingzones/lz-machinelearning/main.bicep b/landingzones/lz-machinelearning/main.bicep index 3b3c5553..54cd086c 100644 --- a/landingzones/lz-machinelearning/main.bicep +++ b/landingzones/lz-machinelearning/main.bicep @@ -93,7 +93,7 @@ param hubNetwork object param network object // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../azresources/telemetry/customer-usage-attribution-subscription.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.archetypes.machineLearning}' diff --git a/landingzones/lz-machinelearning/networking.bicep b/landingzones/lz-machinelearning/networking.bicep index 0c7ffc89..3adc7d98 100644 --- a/landingzones/lz-machinelearning/networking.bicep +++ b/landingzones/lz-machinelearning/networking.bicep @@ -480,7 +480,8 @@ module vnetPeeringHubToSpoke '../../azresources/network/vnet-peering.bicep' = if peeringName: 'Spoke-${last(hubVnetIdSplit)}-to-${vnet.name}-${uniqueString(vnet.id)}' allowForwardedTraffic: true allowVirtualNetworkAccess: true - sourceVnetName: last(hubVnetIdSplit) + allowGatewayTransit: true + sourceVnetName: last(hubVnetIdSplit)! targetVnetId: vnet.id useRemoteGateways: false } diff --git a/landingzones/lz-platform-connectivity-hub-azfw/azfw-policy/azure-firewall-policy.bicep b/landingzones/lz-platform-connectivity-hub-azfw/azfw-policy/azure-firewall-policy.bicep index 044b3694..15eeac22 100644 --- a/landingzones/lz-platform-connectivity-hub-azfw/azfw-policy/azure-firewall-policy.bicep +++ b/landingzones/lz-platform-connectivity-hub-azfw/azfw-policy/azure-firewall-policy.bicep @@ -390,7 +390,7 @@ resource policy 'Microsoft.Network/firewallPolicies@2021-02-01' = { targetFqdns: [ 'adf.azure.com' - // https://docs.microsoft.com/en-us/azure/data-factory/data-factory-ux-troubleshoot-guide + // https://learn.microsoft.com/en-us/azure/data-factory/data-factory-ux-troubleshoot-guide 'dpcanadacentral.svc.datafactory.azure.com' 'dpcanadaeast.svc.datafactory.azure.com' ] @@ -474,7 +474,7 @@ resource policy 'Microsoft.Network/firewallPolicies@2021-02-01' = { type: 'Allow' } rules: [ - // Reference: https://docs.microsoft.com/azure/security-center/deploy-vulnerability-assessment-vm#deploy-the-integrated-scanner-to-your-azure-and-hybrid-machines + // Reference: https://learn.microsoft.com/azure/security-center/deploy-vulnerability-assessment-vm#deploy-the-integrated-scanner-to-your-azure-and-hybrid-machines { ruleType: 'ApplicationRule' name: 'US Data Center' @@ -739,8 +739,8 @@ resource policy 'Microsoft.Network/firewallPolicies@2021-02-01' = { ] } { - // https://docs.microsoft.com/azure/firewall/fqdn-tags - // https://docs.microsoft.com/mem/configmgr/sum/get-started/install-a-software-update-point + // https://learn.microsoft.com/azure/firewall/fqdn-tags + // https://learn.microsoft.com/mem/configmgr/sum/get-started/install-a-software-update-point name: 'Windows Update' ruleCollectionType: 'FirewallPolicyFilterRuleCollection' priority: 1000 @@ -771,7 +771,7 @@ resource policy 'Microsoft.Network/firewallPolicies@2021-02-01' = { } // AKS required FQDNs - // https://docs.microsoft.com/en-us/azure/aks/limit-egress-traffic + // https://learn.microsoft.com/en-us/azure/aks/limit-egress-traffic resource AKSCollectionGroup 'ruleCollectionGroups@2021-02-01' = { dependsOn: [ windowsCollectionGroup @@ -904,7 +904,7 @@ resource policy 'Microsoft.Network/firewallPolicies@2021-02-01' = { priority: 2000 ruleCollections: [ { - // https://docs.microsoft.com/azure/virtual-machines/workloads/redhat/redhat-rhui#the-ips-for-the-rhui-content-delivery-servers + // https://learn.microsoft.com/azure/virtual-machines/workloads/redhat/redhat-rhui#the-ips-for-the-rhui-content-delivery-servers name: 'RedHat Update Infrastructure' ruleCollectionType: 'FirewallPolicyFilterRuleCollection' priority: 100 diff --git a/landingzones/lz-platform-connectivity-hub-azfw/main-azfw-policy.bicep b/landingzones/lz-platform-connectivity-hub-azfw/main-azfw-policy.bicep index f6742a69..b962083d 100644 --- a/landingzones/lz-platform-connectivity-hub-azfw/main-azfw-policy.bicep +++ b/landingzones/lz-platform-connectivity-hub-azfw/main-azfw-policy.bicep @@ -47,7 +47,7 @@ param resourceGroupName string param policyName string // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../azresources/telemetry/customer-usage-attribution-subscription.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.networking.azureFirewall}' diff --git a/landingzones/lz-platform-connectivity-hub-azfw/main.bicep b/landingzones/lz-platform-connectivity-hub-azfw/main.bicep index 06ca4a32..9ba9ecb0 100644 --- a/landingzones/lz-platform-connectivity-hub-azfw/main.bicep +++ b/landingzones/lz-platform-connectivity-hub-azfw/main.bicep @@ -44,7 +44,7 @@ param logAnalyticsWorkspaceResourceId string // "securityCenter": { // "value": { // "email": "alzcanadapubsec@microsoft.com", -// "phone": "5555555555" +// "phone": "6045555555" // } // } @@ -52,7 +52,7 @@ param logAnalyticsWorkspaceResourceId string // ----------------------------- // { // email: 'alzcanadapubsec@microsoft.com' -// phone: '5555555555' +// phone: '6045555555' // } @description('Microsoft Defender for Cloud configuration. It includes email and phone.') param securityCenter object @@ -179,7 +179,7 @@ param managementRestrictedZone object param publicAccessZone object // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../azresources/telemetry/customer-usage-attribution-subscription.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.networking.azureFirewall}' diff --git a/landingzones/lz-platform-connectivity-hub-azfw/mrz/mrz.bicep b/landingzones/lz-platform-connectivity-hub-azfw/mrz/mrz.bicep index 3e30bf1d..01e5ba02 100644 --- a/landingzones/lz-platform-connectivity-hub-azfw/mrz/mrz.bicep +++ b/landingzones/lz-platform-connectivity-hub-azfw/mrz/mrz.bicep @@ -81,6 +81,7 @@ module vnetPeeringHubToSpoke '../../../azresources/network/vnet-peering.bicep' = peeringName: '${hubVnetName}-to-${mrzVnet.outputs.vnetName}' allowForwardedTraffic: true allowVirtualNetworkAccess: true + allowGatewayTransit: true sourceVnetName: hubVnetName targetVnetId: mrzVnet.outputs.vnetId useRemoteGateways: false diff --git a/landingzones/lz-platform-connectivity-hub-nva/main.bicep b/landingzones/lz-platform-connectivity-hub-nva/main.bicep index 56725f4f..c77002c6 100644 --- a/landingzones/lz-platform-connectivity-hub-nva/main.bicep +++ b/landingzones/lz-platform-connectivity-hub-nva/main.bicep @@ -60,7 +60,7 @@ param logAnalyticsWorkspaceResourceId string // "securityCenter": { // "value": { // "email": "alzcanadapubsec@microsoft.com", -// "phone": "5555555555" +// "phone": "6045555555" // } // } @@ -68,7 +68,7 @@ param logAnalyticsWorkspaceResourceId string // ----------------------------- // { // email: 'alzcanadapubsec@microsoft.com' -// phone: '5555555555' +// phone: '6045555555' // } @description('Microsoft Defender for Cloud configuration. It includes email and phone.') param securityCenter object @@ -204,7 +204,7 @@ param fwUsername string param fwPassword string // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../azresources/telemetry/customer-usage-attribution-subscription.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.networking.nvaFortinet}' diff --git a/landingzones/lz-platform-connectivity-hub-nva/mrz/mrz.bicep b/landingzones/lz-platform-connectivity-hub-nva/mrz/mrz.bicep index 958bd12c..71114048 100644 --- a/landingzones/lz-platform-connectivity-hub-nva/mrz/mrz.bicep +++ b/landingzones/lz-platform-connectivity-hub-nva/mrz/mrz.bicep @@ -81,6 +81,7 @@ module vnetPeeringHubToSpoke '../../../azresources/network/vnet-peering.bicep' = peeringName: '${hubVnetName}-to-${mrzVnet.outputs.vnetName}' allowForwardedTraffic: true allowVirtualNetworkAccess: true + allowGatewayTransit: true sourceVnetName: hubVnetName targetVnetId: mrzVnet.outputs.vnetId useRemoteGateways: false diff --git a/landingzones/lz-platform-identity/dnsResolver.bicep b/landingzones/lz-platform-identity/dnsResolver.bicep new file mode 100644 index 00000000..a1a16eae --- /dev/null +++ b/landingzones/lz-platform-identity/dnsResolver.bicep @@ -0,0 +1,112 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + + +@description('Location for the deployment.') +param location string = deployment().location + + +// Example (JSON) +// ----------------------------- +// "resourceTags": { +// "value": { +// "ClientOrganization": "client-organization-tag", +// "CostCenter": "cost-center-tag", +// "DataSensitivity": "data-sensitivity-tag", +// "ProjectContact": "project-contact-tag", +// "ProjectName": "project-name-tag", +// "TechnicalContact": "technical-contact-tag" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// ClientOrganization: 'client-organization-tag' +// CostCenter: 'cost-center-tag' +// DataSensitivity: 'data-sensitivity-tag' +// ProjectContact: 'project-contact-tag' +// ProjectName: 'project-name-tag' +// TechnicalContact: 'technical-contact-tag' +// } +@description('A set of key/value pairs of tags assigned to the resource group and resources.') +param resourceTags object + +@description('Network configuration for the spoke virtual network. It includes name, dnsServers, address spaces, vnet peering and subnets.') +param network object + +// Private DNS Resolver +@description('Private DNS Resolver configuration for Inbound connections.') +param privateDnsResolver object + +// Private DNS Resolver Ruleset +@description('Private DNS Resolver Default Ruleset Configuration') +param privateDnsResolverRuleset object + +// Private DNS Resolver Ruleset +@description('Private DNS Resolver Default Ruleset Configuration') +param dnsResolverRG string + +// vnet resource group +@description('virtual network resource group name') +param rgVnet string + +// vnet +@description('virtual network ID') +param vnetId string + +// vnet +@description('virtual network Name') +param vnetName string + + + + +// Create Private DNS Resolver Resource Group +resource rgPrivateDnsResolver 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: dnsResolverRG + location: location + tags: resourceTags +} + +//create Private DNS Resolver +module dnsResolver '../../azresources/network/dnsresolver.bicep' ={ + name:'deploy-private-dns-resolver' + scope: rgPrivateDnsResolver + params:{ + name: privateDnsResolver.name + location: location + inboundEndpointName: privateDnsResolver.inboundEndpointName + inboundSubnetName: network.subnets.dnsResolverInbound.name + outboundEndpointName: privateDnsResolver.outboundEndpointName + outboundSubnetName: network.subnets.dnsResolverOutbound.name + vnetResourceGroupName: rgVnet + vnetId: vnetId + vnetName: vnetName + } +} + +module dnsResolverFwRuleset '../../azresources/network/dns-forwarding-ruleset.bicep' = if (privateDnsResolverRuleset.enabled) { + name:'deploy-private-dns-resolver-fw-ruleset' + scope: rgPrivateDnsResolver + + params:{ + name: privateDnsResolverRuleset.name + location: location + outEndpointId: dnsResolver.outputs.outboundEndpointId + + forwardingRuleSet: privateDnsResolverRuleset.forwardingRules + + linkRuleSetToVnet: privateDnsResolverRuleset.linkRuleSetToVnet + linkName: privateDnsResolverRuleset.linkRuleSetToVnetName + vnetId: vnetId + } +} diff --git a/landingzones/lz-platform-identity/main.bicep b/landingzones/lz-platform-identity/main.bicep new file mode 100644 index 00000000..1631cc5d --- /dev/null +++ b/landingzones/lz-platform-identity/main.bicep @@ -0,0 +1,340 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + + +/* + +Identity Landing Zone to support ESLZ topology. This architeype will provide: + +* Azure Automation Account +* Azure Virtual Network +* Azure Recovery Services Vault +* Role-based access control for Owner, Contributor & Reader +* Integration with Azure Cost Management for Subscription-scoped budget +* Integration with Microsoft Defender for Cloud +* Azure Private DNS Resolver & Conditional Forwarder Zone (optional). +* Enables Azure Private DNS Zones (optional). + +*/ + +targetScope = 'subscription' + +@description('Location for the deployment.') +param location string = deployment().location + +// Service Health +// Example (JSON) +// ----------------------------- +// "serviceHealthAlerts": { +// "value": { +// "incidentTypes": [ "Incident", "Security", "Maintenance", "Information", "ActionRequired" ], +// "regions": [ "Global", "Canada East", "Canada Central" ], +// "receivers": { +// "app": [ "email-1@company.com", "email-2@company.com" ], +// "email": [ "email-1@company.com", "email-3@company.com", "email-4@company.com" ], +// "sms": [ { "countryCode": "1", "phoneNumber": "1234567890" }, { "countryCode": "1", "phoneNumber": "0987654321" } ], +// "voice": [ { "countryCode": "1", "phoneNumber": "1234567890" } ] +// }, +// "actionGroupName": "ALZ action group", +// "actionGroupShortName": "alz-alert", +// "alertRuleName": "ALZ alert rule", +// "alertRuleDescription": "Alert rule for Azure Landing Zone" +// } +// } +@description('Service Health alerts') +param serviceHealthAlerts object = {} + +// Log Analytics +@description('Log Analytics Resource Id to integrate Microsoft Defender for Cloud.') +param logAnalyticsWorkspaceResourceId string + +// Microsoft Defender for Cloud +// Example (JSON) +// ----------------------------- +// "securityCenter": { +// "value": { +// "email": "alzcanadapubsec@microsoft.com", +// "phone": "5555555555" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// email: 'alzcanadapubsec@microsoft.com' +// phone: '5555555555' +// } +@description('Microsoft Defender for Cloud configuration. It includes email and phone.') +param securityCenter object + +// Subscription Role Assignments +// Example (JSON) +// ----------------------------- +// [ +// { +// "comments": "Built-in Contributor Role", +// "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", +// "securityGroupObjectIds": [ +// "38f33f7e-a471-4630-8ce9-c6653495a2ee" +// ] +// } +// ] + +// Example (Bicep) +// ----------------------------- +// [ +// { +// comments: 'Built-In Contributor Role' +// roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c' +// securityGroupObjectIds: [ +// '38f33f7e-a471-4630-8ce9-c6653495a2ee' +// ] +// } +// ] +@description('Array of role assignments at subscription scope. The array will contain an object with comments, roleDefinitionId and array of securityGroupObjectIds.') +param subscriptionRoleAssignments array = [] + +// Subscription Budget +// Example (JSON) +// --------------------------- +// "subscriptionBudget": { +// "value": { +// "createBudget": false, +// "name": "MonthlySubscriptionBudget", +// "amount": 1000, +// "timeGrain": "Monthly", +// "contactEmails": [ "alzcanadapubsec@microsoft.com" ] +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// createBudget: true +// name: 'MonthlySubscriptionBudget' +// amount: 1000 +// timeGrain: 'Monthly' +// contactEmails: [ +// 'alzcanadapubsec@microsoft.com' +// ] +// } +@description('Subscription budget configuration containing createBudget flag, name, amount, timeGrain and array of contactEmails') +param subscriptionBudget object + +// Tags +// Example (JSON) +// ----------------------------- +// "subscriptionTags": { +// "value": { +// "ISSO": "isso-tag" +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// ISSO: 'isso-tag' +// } +@description('A set of key/value pairs of tags assigned to the subscription.') +param subscriptionTags object + +// Example (JSON) +// ----------------------------- +// "resourceTags": { +// "value": { +// "ClientOrganization": "client-organization-tag", +// "CostCenter": "cost-center-tag", +// "DataSensitivity": "data-sensitivity-tag", +// "ProjectContact": "project-contact-tag", +// "ProjectName": "project-name-tag", +// "TechnicalContact": "technical-contact-tag" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// ClientOrganization: 'client-organization-tag' +// CostCenter: 'cost-center-tag' +// DataSensitivity: 'data-sensitivity-tag' +// ProjectContact: 'project-contact-tag' +// ProjectName: 'project-name-tag' +// TechnicalContact: 'technical-contact-tag' +// } +@description('A set of key/value pairs of tags assigned to the resource group and resources.') +param resourceTags object + +// Resource Groups +@description('Resource groups required for the archetype. It includes automation, networking and networkWatcher.') +param resourceGroups object + +// RecoveryVault +@description('Azure recovery vault configuration containing enabled flag, and name') +param backupRecoveryVault object + +// Azure Automation Account +@description('Azure Automation Account configuration. Includes name.') +param automation object + +// Networking +@description('Hub Network configuration that includes virtualNetworkId, rfc1918IPRange, rfc6598IPRange and egressVirtualApplianceIp.') +param hubNetwork object + +@description('Network configuration for the spoke virtual network. It includes name, dns services, address spaces, vnet peering and subnets.') +param network object + +// Private Dns Zones +@description('Private DNS Zones configuration. See docs/archetypes/identity.md for configuration settings.') +param privateDnsZones object + +// Private DNS Resolver +@description('Private DNS Resolver configuration for Inbound connections.') +param privateDnsResolver object + +// Private DNS Resolver Ruleset +@description('Private DNS Resolver Default Ruleset Configuration') +param privateDnsResolverRuleset object + + +// Telemetry - Azure customer usage attribution +// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +var telemetry = json(loadTextContent('../../config/telemetry.json')) +module telemetryCustomerUsageAttribution '../../azresources/telemetry/customer-usage-attribution-subscription.bicep' = if (telemetry.customerUsageAttribution.enabled) { + name: 'pid-${telemetry.customerUsageAttribution.modules.identity}' +} + +/* + Scaffold the subscription which includes: + * Microsoft Defender for Cloud - Enable Azure Defender (all available options) + * Microsoft Defender for Cloud - Configure Log Analytics Workspace + * Microsoft Defender for Cloud - Configure Security Alert Contact + * Service Health Alerts + * Role Assignments to Security Groups + * Subscription Budget + * Subscription Tags +*/ +module subScaffold '../scaffold-subscription.bicep' = { + name: 'configure-subscription' + scope: subscription() + params: { + location: location + + serviceHealthAlerts: serviceHealthAlerts + + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + securityCenter: securityCenter + + subscriptionBudget: subscriptionBudget + + subscriptionTags: subscriptionTags + resourceTags: resourceTags + + subscriptionRoleAssignments: subscriptionRoleAssignments + } +} + +// Create Network Watcher Resource Group +resource rgNetworkWatcher 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.networkWatcher + location: location + tags: resourceTags +} + +// Create Virtual Network Resource Group - only if Virtual Network is being deployed +resource rgVnet 'Microsoft.Resources/resourceGroups@2020-06-01' = if (network.deployVnet) { + name: network.deployVnet ? resourceGroups.networking : 'placeholder' + location: location + tags: resourceTags +} + +// Create Azure Automation Resource Group +resource rgAutomation 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.automation + location: location + tags: resourceTags +} + +// Create Azure backup RecoveryVault Resource Group +resource rgBackupVault 'Microsoft.Resources/resourceGroups@2020-06-01' =if (backupRecoveryVault.enabled) { + name: resourceGroups.backupRecoveryVault + location: location + tags: resourceTags +} + +// Create Private DNS Zones Resource Group +resource rgPrivateDnsZones 'Microsoft.Resources/resourceGroups@2020-06-01' =if (privateDnsZones.enabled) { + name: resourceGroups.privateDnsZones + location: location + tags: resourceTags +} + +// Create automation account +module automationAccount '../../azresources/automation/automation-account.bicep' = { + name: 'deploy-automation-account' + scope: rgAutomation + params: { + automationAccountName: automation.name + tags: resourceTags + location: location + } +} + +//create recovery vault for backup of vms +module backupVault '../../azresources/management/backup-recovery-vault.bicep'= if(backupRecoveryVault.enabled){ + name:'deploy-backup-recoveryvault' + scope: rgBackupVault + params:{ + vaultName: backupRecoveryVault.name + tags: resourceTags + location: location + } +} + +// Create & configure virtaual network - only if Virtual Network is being deployed +module vnet 'networking.bicep' = if (network.deployVnet) { + name: 'deploy-networking' + scope: resourceGroup(rgVnet.name) + params: { + hubNetwork: hubNetwork + network: network + location: location + deployDNSResolver: privateDnsResolver + } +} + +module dnsResolver 'dnsResolver.bicep' = if (privateDnsResolver.enabled) { + name: 'deploy-dns-resolver' + scope: subscription() + params: { + privateDnsResolver: privateDnsResolver + location: location + rgVnet: rgVnet.name + vnetId: vnet.outputs.vnetId + vnetName: vnet.outputs.vnetName + network: network + resourceTags: resourceTags + privateDnsResolverRuleset: privateDnsResolverRuleset + dnsResolverRG: resourceGroups.dnsResolver + } +} + +// Private DNS Zones +module privatelinkDnsZones '../../azresources/network/private-dns-zone-privatelinks.bicep' = if (privateDnsZones.enabled) { + name: 'deploy-privatelink-private-dns-zones' + scope: resourceGroup(resourceGroups.privateDnsZones) + params: { + vnetId: vnet.outputs.vnetId + dnsCreateNewZone: true + dnsLinkToVirtualNetwork: true + + // Not required since the private dns zones will be created and linked to hub virtual network. + dnsExistingZoneSubscriptionId: '' + dnsExistingZoneResourceGroupName: '' + } +} diff --git a/landingzones/lz-platform-identity/networking.bicep b/landingzones/lz-platform-identity/networking.bicep new file mode 100644 index 00000000..06c61bd7 --- /dev/null +++ b/landingzones/lz-platform-identity/networking.bicep @@ -0,0 +1,404 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Location for the deployment.') +param location string = resourceGroup().location + +// Networking +// Example (JSON) +// ----------------------------- +// "hubNetwork": { +// "value": { +// "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet", +// "rfc1918IPRange": "10.18.0.0/22", +// "rfc6598IPRange": "100.60.0.0/16", +// "egressVirtualApplianceIp": "10.18.0.36", +// "privateDnsManagedByHub": true, +// "privateDnsManagedByHubSubscriptionId": "ed7f4eed-9010-4227-b115-2a5e37728f27", +// "privateDnsManagedByHubResourceGroupName": "pubsec-dns" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// virtualNetworkId: '/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet' +// rfc1918IPRange: '10.18.0.0/22' +// rfc6598IPRange: '100.60.0.0/16' +// egressVirtualApplianceIp: '10.18.0.36' +// privateDnsManagedByHub: true, +// privateDnsManagedByHubSubscriptionId: 'ed7f4eed-9010-4227-b115-2a5e37728f27', +// privateDnsManagedByHubResourceGroupName: 'pubsec-dns' +// } +@description('Hub Network configuration that includes virtualNetworkId, rfc1918IPRange, rfc6598IPRange, egressVirtualApplianceIp, privateDnsManagedByHub flag, privateDnsManagedByHubSubscriptionId and privateDnsManagedByHubResourceGroupName.') +param hubNetwork object + +// Example (JSON) +// ----------------------------- +// "network": { +// "value": { +// "peerToHubVirtualNetwork": true, +// "useRemoteGateway": false, +// "name": "vnet", +// "dnsServers": [ +// "10.18.1.4" +// ], +// "addressPrefixes": [ +// "10.2.0.0/16" +// ], +// "subnets": { +// "privateEndpoints": { +// "comments": "Private Endpoints Subnet", +// "name": "privateendpoints", +// "addressPrefix": "10.2.5.0/25" +// }, +// "sqlmi": { +// "comments": "SQL Managed Instances Delegated Subnet", +// "name": "sqlmi", +// "addressPrefix": "10.2.6.0/25" +// }, +// "databricksPublic": { +// "comments": "Databricks Public Delegated Subnet", +// "name": "databrickspublic", +// "addressPrefix": "10.2.7.0/25" +// }, +// "databricksPrivate": { +// "comments": "Databricks Private Delegated Subnet", +// "name": "databricksprivate", +// "addressPrefix": "10.2.8.0/25" +// }, +// "aks": { +// "comments": "AKS Subnet", +// "name": "aks", +// "addressPrefix": "10.2.9.0/25" +// }, +// "appService": { +// "comments": "App Service Subnet", +// "name": "appService", +// "addressPrefix": "10.2.10.0/25" +// } +// "optional": [ +// { +// "comments": "Optional Subnet 1", +// "name": "virtualMachines", +// "addressPrefix": "10.6.11.0/25", +// "nsg": { +// "enabled": true +// }, +// "udr": { +// "enabled": true +// } +// }, +// { +// "comments": "Optional Subnet 2 with delegation for NetApp Volumes", +// "name": "NetappVolumes", +// "addressPrefix": "10.6.12.0/25", +// "nsg": { +// "enabled": false +// }, +// "udr": { +// "enabled": false +// }, +// "delegations": { +// "serviceName": "Microsoft.NetApp/volumes" +// } +// } +// ] +// } +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// peerToHubVirtualNetwork: true +// useRemoteGateway: false +// name: 'vnet' +// dnsServers: [ +// '10.18.1.4' +// ] +// addressPrefixes: [ +// '10.2.0.0/16' +// ] +// subnets: { +// privateEndpoints: { +// comments: 'Private Endpoints Subnet' +// name: 'privateendpoints' +// addressPrefix: '10.2.5.0/25' +// } +// sqlmi: { +// comments: 'SQL Managed Instances Delegated Subnet' +// name: 'sqlmi' +// addressPrefix: '10.2.6.0/25' +// } +// databricksPublic: { +// comments: 'Databricks Public Delegated Subnet' +// name: 'databrickspublic' +// addressPrefix: '10.2.7.0/25' +// } +// databricksPrivate: { +// comments: 'Databricks Private Delegated Subnet' +// name: 'databricksprivate' +// addressPrefix: '10.2.8.0/25' +// } +// aks: { +// comments: 'AKS Subnet' +// name: 'aks' +// addressPrefix: '10.2.9.0/25' +// } +// appService: { +// comments: 'App Service Subnet' +// name: 'appService' +// addressPrefix: '10.2.10.0/25' +// } +// optional: [ +// { +// comments: 'Optional Subnet 1' +// name: 'virtualMachines' +// addressPrefix: '10.6.11.0/25' +// nsg: { +// enabled: true +// }, +// udr: { +// enabled: true +// } +// }, +// { +// comments: 'Optional Subnet 2 with delegation for NetApp Volumes', +// name: 'NetappVolumes' +// addressPrefix: '10.6.12.0/25' +// nsg: { +// enabled: false +// }, +// udr: { +// enabled: false +// }, +// delegations: { +// serviceName: 'Microsoft.NetApp/volumes' +// } +// } +// ] +// } +// } +@description('Network configuration for the spoke virtual network. It includes name, dnsServers, address spaces, vnet peering and subnets.') +param network object + +@description('Get the DNS Private Resolver enabled/disabled setting so the associated subnets can be optionally deployed based on the value.') +param deployDNSResolver object + +var hubVnetIdSplit = split(hubNetwork.virtualNetworkId, '/') + + +var routesToHub = [ + // Force Routes to Hub IPs (RFC1918 range) via FW despite knowing that route via peering + { + name: 'SpokeUdrHubRFC1918FWRoute' + properties: { + addressPrefix: hubNetwork.rfc1918IPRange + nextHopType: 'VirtualAppliance' + nextHopIpAddress: hubNetwork.egressVirtualApplianceIp + } + } + // Force Routes to Hub IPs (CGNAT range) via FW despite knowing that route via peering + { + name: 'SpokeUdrHubRFC6598FWRoute' + properties: { + addressPrefix: hubNetwork.rfc6598IPRange + nextHopType: 'VirtualAppliance' + nextHopIpAddress: hubNetwork.egressVirtualApplianceIp + } + } + { + name: 'RouteToEgressFirewall' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'VirtualAppliance' + nextHopIpAddress: hubNetwork.egressVirtualApplianceIp + } + } +] + +// Network Security Groups +resource nsg 'Microsoft.Network/networkSecurityGroups@2021-02-01' = [for subnet in network.subnets.optional: if (subnet.nsg.enabled) { + name: '${subnet.name}Nsg' + location: location + properties: { + securityRules: [] + } +}] + +module nsgDomainControllers '../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-DomainControllers' + params: { + name: '${network.subnets.domainControllers.name}Nsg' + location: location + } +} + +module nsgDnsResolverInbound '../../azresources/network/nsg/nsg-empty.bicep' = if (deployDNSResolver.enabled) { + name: 'deploy-nsg-dnsResolverInbound' + params: { + name: '${network.subnets.dnsResolverInbound.name}Nsg' + location: location + } +} + +module nsgDnsResolverOutbound '../../azresources/network/nsg/nsg-empty.bicep' = if (deployDNSResolver.enabled) { + name: 'deploy-nsg-dnsResolverOutbound' + params: { + name: '${network.subnets.dnsResolverOutbound.name}Nsg' + location: location + } +} + +// Route Tables +resource udr 'Microsoft.Network/routeTables@2021-02-01' = { + name: 'RouteTable' + location: location + properties: { + routes: network.peerToHubVirtualNetwork ? routesToHub : null + } +} + +// Virtual Network +var requiredSubnets = [ + { + name: network.subnets.domainControllers.name + properties: { + addressPrefix: network.subnets.domainControllers.addressPrefix + privateEndpointNetworkPolicies: 'Enabled' + routeTable: { + id: udr.id + } + networkSecurityGroup: { + id: nsgDomainControllers.outputs.nsgId + } + } + } +] + +var dnsResolverSubnets = [ + { + name: network.subnets.dnsResolverInbound.name + properties: { + addressPrefix: network.subnets.dnsResolverInbound.addressPrefix + privateEndpointNetworkPolicies: 'Enabled' + routeTable: { + id: udr.id + } + networkSecurityGroup: { + id: nsgDnsResolverInbound.outputs.nsgId + } + delegations: [ + { + name: 'delAzureDNSResolverInbound' + properties: { + serviceName: 'Microsoft.Network/dnsResolvers' + } + } + ] + } + } + + { + name: network.subnets.dnsResolverOutbound.name + properties: { + addressPrefix: network.subnets.dnsResolverOutbound.addressPrefix + privateEndpointNetworkPolicies: 'Enabled' + routeTable: { + id: udr.id + } + networkSecurityGroup: { + id: nsgDnsResolverOutbound.outputs.nsgId + } + delegations: [ + { + name: 'delAzureDNSResolverOutbound' + properties: { + serviceName: 'Microsoft.Network/dnsResolvers' + } + } + ] + } + } +] + +var optionalSubnets = [for (subnet, i) in network.subnets.optional: { + name: subnet.name + properties: { + addressPrefix: subnet.addressPrefix + networkSecurityGroup: (subnet.nsg.enabled) ? { + id: nsg[i].id + } : null + routeTable: (subnet.udr.enabled) ? { + id: udr.id + } : null + delegations: contains(subnet, 'delegations') ? [ + { + name: replace(subnet.delegations.serviceName, '/', '.') + properties: { + serviceName: subnet.delegations.serviceName + } + } + ] : null + } +}] + +//Optionally add DNS Resolver subnets based on if the deployDNSResolver parameter is set to true +var allSubnets = deployDNSResolver.enabled ? union(requiredSubnets, optionalSubnets, dnsResolverSubnets) : union(requiredSubnets, optionalSubnets) + + +resource vnet 'Microsoft.Network/virtualNetworks@2021-02-01' = { + name: network.name + location: location + properties: { + dhcpOptions: { + dnsServers: network.dnsServers + } + addressSpace: { + addressPrefixes: network.addressPrefixes + } + subnets: allSubnets + } +} + +module vnetPeeringSpokeToHub '../../azresources/network/vnet-peering.bicep' = if (network.peerToHubVirtualNetwork) { + name: 'deploy-vnet-peering-spoke-to-hub' + scope: resourceGroup() + params: { + peeringName: 'Hub-${vnet.name}-to-${last(hubVnetIdSplit)}' + allowForwardedTraffic: true + allowVirtualNetworkAccess: true + sourceVnetName: vnet.name + targetVnetId: hubNetwork.virtualNetworkId + useRemoteGateways: network.useRemoteGateway + } +} + +// For Hub to Spoke vnet peering, we must rescope the deployment to the subscription id & resource group of where the Hub VNET is located. +module vnetPeeringHubToSpoke '../../azresources/network/vnet-peering.bicep' = if (network.peerToHubVirtualNetwork) { + name: 'deploy-vnet-peering-${subscription().subscriptionId}' + scope: resourceGroup(network.peerToHubVirtualNetwork ? hubVnetIdSplit[2] : '', network.peerToHubVirtualNetwork ? hubVnetIdSplit[4] : '') + params: { + peeringName: 'Spoke-${last(hubVnetIdSplit)}-to-${vnet.name}-${uniqueString(vnet.id)}' + allowForwardedTraffic: true + allowVirtualNetworkAccess: true + allowGatewayTransit: true + sourceVnetName: last(hubVnetIdSplit)! + targetVnetId: vnet.id + useRemoteGateways: false + } +} + + + + + +output vnetId string = vnet.id +output vnetName string = vnet.name diff --git a/landingzones/lz-platform-logging/main.bicep b/landingzones/lz-platform-logging/main.bicep index 2297ce71..b1ecb2fa 100644 --- a/landingzones/lz-platform-logging/main.bicep +++ b/landingzones/lz-platform-logging/main.bicep @@ -66,7 +66,7 @@ param serviceHealthAlerts object = {} // "securityCenter": { // "value": { // "email": "alzcanadapubsec@microsoft.com", -// "phone": "5555555555" +// "phone": "6045555555" // } // } @@ -74,7 +74,7 @@ param serviceHealthAlerts object = {} // ----------------------------- // { // email: 'alzcanadapubsec@microsoft.com' -// phone: '5555555555' +// phone: '6045555555' // } @description('Microsoft Defender for Cloud. It includes email and phone.') param securityCenter object @@ -196,7 +196,7 @@ param dataCollectionRule object param enableDeleteLockOnResourceGroup bool = true // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../azresources/telemetry/customer-usage-attribution-subscription.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.logging}-${uniqueString(location)}' diff --git a/landingzones/scaffold-subscription.bicep b/landingzones/scaffold-subscription.bicep index f62406a6..cd90ef54 100644 --- a/landingzones/scaffold-subscription.bicep +++ b/landingzones/scaffold-subscription.bicep @@ -44,7 +44,7 @@ param logAnalyticsWorkspaceResourceId string // "securityCenter": { // "value": { // "email": "alzcanadapubsec@microsoft.com", -// "phone": "5555555555" +// "phone": "6045555555" // } // } @@ -52,7 +52,7 @@ param logAnalyticsWorkspaceResourceId string // ----------------------------- // { // 'email': 'alzcanadapubsec@microsoft.com' -// 'phone': '5555555555' +// 'phone': '6045555555' // } @description('Microsoft Defender for Cloud configuration. It includes email and phone.') param securityCenter object diff --git a/management-groups/structure-v2.bicep b/management-groups/structure-v2.bicep index cbf1b926..a5b26437 100644 --- a/management-groups/structure-v2.bicep +++ b/management-groups/structure-v2.bicep @@ -22,7 +22,7 @@ param childManagementGroupId string param childManagementGroupName string // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../config/telemetry.json')) module telemetryCustomerUsageAttribution '../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled && (childManagementGroupName == topLevelManagementGroupName)) { name: 'pid-${telemetry.customerUsageAttribution.modules.managementGroups}' diff --git a/management-groups/structure.bicep b/management-groups/structure.bicep index a5f64220..26b921bc 100644 --- a/management-groups/structure.bicep +++ b/management-groups/structure.bicep @@ -18,7 +18,7 @@ param topLevelManagementGroupName string param parentManagementGroupId string // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../config/telemetry.json')) module telemetryCustomerUsageAttribution '../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.managementGroups}' diff --git a/policy/builtin/assignments/asb.bicep b/policy/builtin/assignments/asb.bicep index 6ebaf502..5b6e927b 100644 --- a/policy/builtin/assignments/asb.bicep +++ b/policy/builtin/assignments/asb.bicep @@ -29,7 +29,7 @@ var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssi var policyScopedId = resourceId('Microsoft.Authorization/policySetDefinitions', policyId) // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-asb' diff --git a/policy/builtin/assignments/cis-msft-130.bicep b/policy/builtin/assignments/cis-msft-130.bicep index bfb1ddef..d4b5e2a7 100644 --- a/policy/builtin/assignments/cis-msft-130.bicep +++ b/policy/builtin/assignments/cis-msft-130.bicep @@ -41,7 +41,7 @@ var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssi var policyScopedId = resourceId('Microsoft.Authorization/policySetDefinitions', policyId) // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-cis-msft-130' diff --git a/policy/builtin/assignments/fedramp-moderate.bicep b/policy/builtin/assignments/fedramp-moderate.bicep index f64d0f90..537efe56 100644 --- a/policy/builtin/assignments/fedramp-moderate.bicep +++ b/policy/builtin/assignments/fedramp-moderate.bicep @@ -32,7 +32,7 @@ var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssi var policyScopedId = resourceId('Microsoft.Authorization/policySetDefinitions', policyId) // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-fedramp-m' @@ -64,7 +64,7 @@ resource policySetRoleAssignmentContributor 'Microsoft.Authorization/roleAssignm name: guid(policyAssignmentManagementGroupId, 'fedramp-moderate-Contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions','b24988ac-6180-42a0-ab88-20f7382dd24c') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } diff --git a/policy/builtin/assignments/hitrust-hipaa.bicep b/policy/builtin/assignments/hitrust-hipaa.bicep index 2642dadd..08c0bba5 100644 --- a/policy/builtin/assignments/hitrust-hipaa.bicep +++ b/policy/builtin/assignments/hitrust-hipaa.bicep @@ -44,7 +44,7 @@ var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssi var policyScopedId = resourceId('Microsoft.Authorization/policySetDefinitions', policyId) // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-hitrust-hipaa' @@ -157,7 +157,7 @@ resource policySetRoleAssignmentContributor 'Microsoft.Authorization/roleAssignm name: guid(policyAssignmentManagementGroupId, 'hitrust-hipaa-contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions','b24988ac-6180-42a0-ab88-20f7382dd24c') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } @@ -167,7 +167,7 @@ resource policySetRoleAssignmentVMContributor 'Microsoft.Authorization/roleAssig name: guid(policyAssignmentManagementGroupId, 'hitrust-hipaa-virtual-machine-contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/9980e02c-c2be-4d73-94e8-173b1dc7cf3c' + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions','9980e02c-c2be-4d73-94e8-173b1dc7cf3c') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } @@ -177,7 +177,7 @@ resource policySetRoleAssignmentNetworkContributor 'Microsoft.Authorization/role name: guid(policyAssignmentManagementGroupId, 'hitrust-hipaa-network-contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7' + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions','4d97b98b-1d4f-4787-a291-c67834d212e7') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } @@ -187,7 +187,7 @@ resource policySetRoleAssignmentMonitoringContributor 'Microsoft.Authorization/r name: guid(policyAssignmentManagementGroupId, 'hitrust-hipaa-monitoring-contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa' + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions','749f88d5-cbae-40b8-bcfc-e573ddc772fa') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } @@ -197,7 +197,7 @@ resource policySetRoleAssignmentStorageAccountContributor 'Microsoft.Authorizati name: guid(policyAssignmentManagementGroupId, 'hitrust-hipaa-storage-account-contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/17d1049b-9a84-46fb-8f53-869881c3d3ab' + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions','17d1049b-9a84-46fb-8f53-869881c3d3ab') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } diff --git a/policy/builtin/assignments/location.bicep b/policy/builtin/assignments/location.bicep index ef309656..bdd5d2ba 100644 --- a/policy/builtin/assignments/location.bicep +++ b/policy/builtin/assignments/location.bicep @@ -28,7 +28,7 @@ param allowedLocations array var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-location' @@ -38,7 +38,7 @@ resource rgLocationAssignment 'Microsoft.Authorization/policyAssignments@2020-03 name: 'locrg-${uniqueString('rg-location-', policyAssignmentManagementGroupId)}' properties: { displayName: 'Restrict to Canada Central and Canada East regions for Resource Groups' - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/e765b5de-1225-4ba3-bd56-1ac6695af988' + policyDefinitionId: resourceId('Microsoft.Authorization/policyDefinitions','e765b5de-1225-4ba3-bd56-1ac6695af988') scope: scope notScopes: [] parameters: { @@ -55,7 +55,7 @@ resource resourceLocationAssignment 'Microsoft.Authorization/policyAssignments@2 name: 'locr-${uniqueString('resource-location-', policyAssignmentManagementGroupId)}' properties: { displayName: 'Restrict to Canada Central and Canada East regions for Resources' - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/e56962a6-4747-49cd-b67b-bf8b01975c4c' + policyDefinitionId: resourceId('Microsoft.Authorization/policyDefinitions','e56962a6-4747-49cd-b67b-bf8b01975c4c') scope: scope notScopes: [] parameters: { diff --git a/policy/builtin/assignments/nist80053r4.bicep b/policy/builtin/assignments/nist80053r4.bicep index 54df5ecc..80cd7c30 100644 --- a/policy/builtin/assignments/nist80053r4.bicep +++ b/policy/builtin/assignments/nist80053r4.bicep @@ -38,7 +38,7 @@ var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssi var policyScopedId = resourceId('Microsoft.Authorization/policySetDefinitions', policyId) // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-nist-80053-r4' @@ -76,7 +76,7 @@ resource policySetRoleAssignmentContributor 'Microsoft.Authorization/roleAssignm name: guid(policyAssignmentManagementGroupId, 'nist-sp-800-53-r4-contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions','b24988ac-6180-42a0-ab88-20f7382dd24c') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } diff --git a/policy/builtin/assignments/nist80053r5.bicep b/policy/builtin/assignments/nist80053r5.bicep index ba16fbfd..0cfab389 100644 --- a/policy/builtin/assignments/nist80053r5.bicep +++ b/policy/builtin/assignments/nist80053r5.bicep @@ -32,7 +32,7 @@ var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssi var policyScopedId = resourceId('Microsoft.Authorization/policySetDefinitions', policyId) // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-nist-80053-r5' @@ -64,7 +64,7 @@ resource policySetRoleAssignmentContributor 'Microsoft.Authorization/roleAssignm name: guid(policyAssignmentManagementGroupId, 'nist-sp-800-53-r5-contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions','b24988ac-6180-42a0-ab88-20f7382dd24c') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } diff --git a/policy/builtin/assignments/pbmm.bicep b/policy/builtin/assignments/pbmm.bicep index 2e6d3ec4..13501fb3 100644 --- a/policy/builtin/assignments/pbmm.bicep +++ b/policy/builtin/assignments/pbmm.bicep @@ -38,7 +38,7 @@ var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssi var policyScopedId = resourceId('Microsoft.Authorization/policySetDefinitions', policyId) // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-pbmm' @@ -84,7 +84,7 @@ resource policySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03- 'Microsoft.DataFactory/factories' 'Microsoft.DataLakeAnalytics/accounts' 'Microsoft.DataLakeStore/accounts' - 'Microsoft.EventGrid/eventSubscriptions' + // 'Microsoft.EventGrid/eventSubscriptions' # Removed since it doesn't have any logs 'Microsoft.EventGrid/topics' 'Microsoft.EventHub/namespaces' 'Microsoft.Network/expressRouteCircuits' @@ -132,7 +132,7 @@ resource policySetRoleAssignmentContributor 'Microsoft.Authorization/roleAssignm name: guid(policyAssignmentManagementGroupId, 'pbmm-Contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions','b24988ac-6180-42a0-ab88-20f7382dd24c') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } diff --git a/policy/custom/assignments/AKS.bicep b/policy/custom/assignments/AKS.bicep index 7876f745..892909a1 100644 --- a/policy/custom/assignments/AKS.bicep +++ b/policy/custom/assignments/AKS.bicep @@ -12,7 +12,8 @@ targetScope = 'managementGroup' @description('Location for the deployment.') param location string = deployment().location -@description('Management Group scope for the policy definition.') + +//@description('Management Group scope for the policy definition.') param policyDefinitionManagementGroupId string @description('Management Group scope for the policy assignment.') @@ -29,10 +30,13 @@ var policyId = 'custom-aks' var assignmentName = 'Custom - Azure Kubernetes Service' var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) -var policyScopedId = '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${policyId}' +var policyDefinitionScope = resourceId('Microsoft.Management/managementGroups', policyDefinitionManagementGroupId) +var policyScopedId = extensionResourceId(policyDefinitionScope, 'Microsoft.Authorization/policySetDefinitions', policyId) + +output PolicyDefinitionId string = policyScopedId // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-aks' @@ -58,7 +62,7 @@ resource podSecurityRestrictedStandardsPolicySetAssignment 'Microsoft.Authorizat name: 'aks-res-${uniqueString(policyAssignmentManagementGroupId)}' properties: { displayName: 'Kubernetes cluster pod security restricted standards for Linux-based workloads' - policyDefinitionId: '/providers/Microsoft.Authorization/policySetDefinitions/42b8ef37-b724-4e24-bbc8-7a7708edfe00' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policySetDefinitions','42b8ef37-b724-4e24-bbc8-7a7708edfe00') scope: scope notScopes: [] parameters: {} @@ -74,7 +78,7 @@ resource podSecurityBaselineStandardsPolicySetAssignment 'Microsoft.Authorizatio name: 'aks-std-${uniqueString(policyAssignmentManagementGroupId)}' properties: { displayName: 'Kubernetes cluster pod security baseline standards for Linux-based workloads' - policyDefinitionId: '/providers/Microsoft.Authorization/policySetDefinitions/a8640138-9b0a-4a28-b8cb-1666c838647d' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policySetDefinitions','a8640138-9b0a-4a28-b8cb-1666c838647d') scope: scope notScopes: [] parameters: {} @@ -93,7 +97,7 @@ resource policySetRoleAssignmentContributor 'Microsoft.Authorization/roleAssignm name: guid(policyAssignmentManagementGroupId, 'aks', 'Contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + roleDefinitionId: tenantResourceId('Microsoft.Authorization/roleDefinitions','b24988ac-6180-42a0-ab88-20f7382dd24c') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } diff --git a/policy/custom/assignments/DDoS.bicep b/policy/custom/assignments/DDoS.bicep index 06d1edf1..134a2cc3 100644 --- a/policy/custom/assignments/DDoS.bicep +++ b/policy/custom/assignments/DDoS.bicep @@ -32,10 +32,12 @@ var policyId = 'Network-Deploy-DDoS-Standard' var assignmentName = 'Custom - Enable DDoS Standard on Virtual Networks' var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) -var policyScopedId = '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policyDefinitions/${policyId}' +var policyDefinitionScope = tenantResourceId('Microsoft.Management/managementGroups', policyDefinitionManagementGroupId) +var policyScopedId = extensionResourceId(policyDefinitionScope, 'Microsoft.Authorization/policyDefinitions', policyId) + // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-ddos' @@ -66,7 +68,7 @@ resource policySetRoleAssignmentNetworkContributor 'Microsoft.Authorization/role name: guid(policyAssignmentManagementGroupId, 'ddos-standard', 'Network Contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7' + roleDefinitionId: tenantResourceId('Microsoft.Authorization/roleDefinitions','4d97b98b-1d4f-4787-a291-c67834d212e7') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } diff --git a/policy/custom/assignments/DNSPrivateEndpoints.bicep b/policy/custom/assignments/DNSPrivateEndpoints.bicep index 9504865d..8cce678c 100644 --- a/policy/custom/assignments/DNSPrivateEndpoints.bicep +++ b/policy/custom/assignments/DNSPrivateEndpoints.bicep @@ -35,10 +35,12 @@ var policyId = 'custom-central-dns-private-endpoints' var assignmentName = 'Custom - Central DNS for Private Endpoints' var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) -var policyScopedId = '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${policyId}' +var policyDefinitionScope = tenantResourceId('Microsoft.Management/managementGroups', policyDefinitionManagementGroupId) +var policyScopedId = extensionResourceId(policyDefinitionScope, 'Microsoft.Authorization/policySetDefinitions', policyId) + // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-dns-pe' @@ -76,7 +78,7 @@ resource policySetRoleAssignmentNetworkContributor 'Microsoft.Authorization/role name: guid(policyAssignmentManagementGroupId, 'dns-private-endpoint', 'Network Contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7' + roleDefinitionId: tenantResourceId('Microsoft.Authorization/roleDefinitions','4d97b98b-1d4f-4787-a291-c67834d212e7') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } diff --git a/policy/custom/assignments/DefenderForCloud.bicep b/policy/custom/assignments/DefenderForCloud.bicep index cec8691b..bf8664fc 100644 --- a/policy/custom/assignments/DefenderForCloud.bicep +++ b/policy/custom/assignments/DefenderForCloud.bicep @@ -29,10 +29,11 @@ var policyId = 'custom-enable-azure-defender' var assignmentName = 'Custom - Microsoft Defender for Cloud' var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) -var policyScopedId = '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${policyId}' +var policyDefinitionScope = tenantResourceId('Microsoft.Management/managementGroups', policyDefinitionManagementGroupId) +var policyScopedId = extensionResourceId(policyDefinitionScope, 'Microsoft.Authorization/policySetDefinitions', policyId) // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-mdfc' @@ -61,7 +62,7 @@ resource policySetRoleAssignmentSecurityAdmin 'Microsoft.Authorization/roleAssig name: guid(policyAssignmentManagementGroupId, 'asc', 'Security Admin') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/fb1c8493-542b-48eb-b624-b4c8fea62acd' + roleDefinitionId: tenantResourceId('Microsoft.Authorization/roleDefinitions','fb1c8493-542b-48eb-b624-b4c8fea62acd') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } @@ -71,7 +72,7 @@ resource policySetRoleAssignmentVirtualMachineContributor 'Microsoft.Authorizati name: guid(policyAssignmentManagementGroupId, 'asc', 'Virtual Machine Contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/9980e02c-c2be-4d73-94e8-173b1dc7cf3c' + roleDefinitionId: tenantResourceId('Microsoft.Authorization/roleDefinitions','9980e02c-c2be-4d73-94e8-173b1dc7cf3c') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } diff --git a/policy/custom/assignments/LogAnalytics.bicep b/policy/custom/assignments/LogAnalytics.bicep index 6acd534d..7c6c8b1f 100644 --- a/policy/custom/assignments/LogAnalytics.bicep +++ b/policy/custom/assignments/LogAnalytics.bicep @@ -35,10 +35,12 @@ var policyId = 'custom-enable-logging-to-loganalytics' var assignmentName = 'Custom - Log Analytics for Azure Services' var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) -var policyScopedId = '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${policyId}' +var policyDefinitionScope = tenantResourceId('Microsoft.Management/managementGroups', policyDefinitionManagementGroupId) +var policyScopedId = extensionResourceId(policyDefinitionScope, 'Microsoft.Authorization/policySetDefinitions', policyId) + // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-logging' @@ -73,7 +75,7 @@ resource policySetRoleAssignmentLogAnalyticsContributor 'Microsoft.Authorization name: guid(policyAssignmentManagementGroupId, 'loganalytics', 'Log Analytics Contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293' + roleDefinitionId: tenantResourceId('Microsoft.Authorization/roleDefinitions','92aaf0da-9dab-42b6-94a3-d43ce8d16293') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } @@ -83,7 +85,7 @@ resource policySetRoleAssignmentVirtualMachineContributor 'Microsoft.Authorizati name: guid(policyAssignmentManagementGroupId, 'loganalytics', 'Virtual Machine Contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/9980e02c-c2be-4d73-94e8-173b1dc7cf3c' + roleDefinitionId: tenantResourceId('Microsoft.Authorization/roleDefinitions','9980e02c-c2be-4d73-94e8-173b1dc7cf3c') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } @@ -93,7 +95,7 @@ resource policySetRoleAssignmentMonitoringContributor 'Microsoft.Authorization/r name: guid(policyAssignmentManagementGroupId, 'loganalytics', 'Monitoring Contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa' + roleDefinitionId: tenantResourceId('Microsoft.Authorization/roleDefinitions','749f88d5-cbae-40b8-bcfc-e573ddc772fa') principalId: policySetAssignment.identity.principalId principalType: 'ServicePrincipal' } diff --git a/policy/custom/assignments/Network.bicep b/policy/custom/assignments/Network.bicep index bfcc4c93..ea937730 100644 --- a/policy/custom/assignments/Network.bicep +++ b/policy/custom/assignments/Network.bicep @@ -29,10 +29,12 @@ var policyId = 'custom-network' var assignmentName = 'Custom - Network' var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) -var policyScopedId = '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${policyId}' +var policyDefinitionScope = tenantResourceId('Microsoft.Management/managementGroups', policyDefinitionManagementGroupId) +var policyScopedId = extensionResourceId(policyDefinitionScope, 'Microsoft.Authorization/policySetDefinitions', policyId) + // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-network' diff --git a/policy/custom/assignments/Tags.bicep b/policy/custom/assignments/Tags.bicep index 820f1ea4..709eba09 100644 --- a/policy/custom/assignments/Tags.bicep +++ b/policy/custom/assignments/Tags.bicep @@ -26,9 +26,11 @@ param policyAssignmentManagementGroupId string param enforcementMode string = 'Default' var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) +var policyDefinitionScope = tenantResourceId('Microsoft.Management/managementGroups', policyDefinitionManagementGroupId) + // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../../../config/telemetry.json')) module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.policy}-tags' @@ -42,7 +44,7 @@ resource rgInheritedPolicySetFromSubscriptionToResourceGroupAssignment 'Microsof name: 'tags-torg-${uniqueString('tags-torg-', policyAssignmentManagementGroupId)}' properties: { displayName: rgInheritedAssignmentFromSubscriptionToResourceGroupName - policyDefinitionId: '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${rgInheritedPolicyFromSubscriptionToResourceGroupId}' + policyDefinitionId: extensionResourceId(policyDefinitionScope, 'Microsoft.Authorization/policySetDefinitions', rgInheritedPolicyFromSubscriptionToResourceGroupId) scope: scope notScopes: [] parameters: {} @@ -58,7 +60,7 @@ resource rgPolicySetRoleAssignmentFromSubscriptionToResourceGroupContributor 'Mi name: guid(rgInheritedPolicyFromSubscriptionToResourceGroupId, 'RgRemediation', 'Contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions','b24988ac-6180-42a0-ab88-20f7382dd24c') principalId: rgInheritedPolicySetFromSubscriptionToResourceGroupAssignment.identity.principalId principalType: 'ServicePrincipal' } @@ -72,7 +74,7 @@ resource rgInheritedPolicySetAssignment 'Microsoft.Authorization/policyAssignmen name: 'tags-rg-${uniqueString('tags-from-rg-', policyAssignmentManagementGroupId)}' properties: { displayName: rgInheritedAssignmentName - policyDefinitionId: '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${rgInheritedPolicyId}' + policyDefinitionId: extensionResourceId(policyDefinitionScope, 'Microsoft.Authorization/policySetDefinitions', rgInheritedPolicyId) scope: scope notScopes: [] parameters: {} @@ -88,7 +90,7 @@ resource rgPolicySetRoleAssignmentContributor 'Microsoft.Authorization/roleAssig name: guid(policyAssignmentManagementGroupId, 'RgRemediation', 'Contributor') scope: managementGroup() properties: { - roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions','b24988ac-6180-42a0-ab88-20f7382dd24c') principalId: rgInheritedPolicySetAssignment.identity.principalId principalType: 'ServicePrincipal' } @@ -102,7 +104,7 @@ resource rgRequiredPolicySetAssignment 'Microsoft.Authorization/policyAssignment name: 'tags-rg-${uniqueString('tags-required-', policyAssignmentManagementGroupId)}' properties: { displayName: rgRequiredAssignmentName - policyDefinitionId: '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${rgRequiredPolicyId}' + policyDefinitionId: extensionResourceId(policyDefinitionScope, 'Microsoft.Authorization/policySetDefinitions', rgRequiredPolicyId) scope: scope notScopes: [] parameters: {} @@ -122,7 +124,7 @@ resource resourcesAuditPolicySetAssignment 'Microsoft.Authorization/policyAssign name: 'tags-r-${uniqueString('tags-missing-', policyAssignmentManagementGroupId)}' properties: { displayName: resourcesAssignmentName - policyDefinitionId: '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${resourcesPolicyId}' + policyDefinitionId: extensionResourceId(policyDefinitionScope, 'Microsoft.Authorization/policySetDefinitions', resourcesPolicyId) scope: scope notScopes: [] parameters: {} diff --git a/policy/custom/definitions/policyset/AKS.bicep b/policy/custom/definitions/policyset/AKS.bicep index 55e74372..087ca498 100644 --- a/policy/custom/definitions/policyset/AKS.bicep +++ b/policy/custom/definitions/policyset/AKS.bicep @@ -9,6 +9,8 @@ targetScope = 'managementGroup' +@description('Management Group scope for the policy definition.') + resource aksPolicySet 'Microsoft.Authorization/policySetDefinitions@2020-03-01' = { name: 'custom-aks' properties: { @@ -24,7 +26,7 @@ resource aksPolicySet 'Microsoft.Authorization/policySetDefinitions@2020-03-01' groupNames: [ 'AKS' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/a8eff44f-8c92-45c3-a3fb-9880802d67a7' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', 'a8eff44f-8c92-45c3-a3fb-9880802d67a7') policyDefinitionReferenceId: toLower(replace('Deploy Azure Policy Add-on to Azure Kubernetes Service clusters', ' ', '-')) parameters: {} } @@ -32,7 +34,7 @@ resource aksPolicySet 'Microsoft.Authorization/policySetDefinitions@2020-03-01' groupNames: [ 'AKS' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/3fc4dc25-5baf-40d8-9b05-7fe74c1bc64e' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '3fc4dc25-5baf-40d8-9b05-7fe74c1bc64e') policyDefinitionReferenceId: toLower(replace('Kubernetes clusters should use internal load balancers', ' ', '-')) parameters: {} } diff --git a/policy/custom/definitions/policyset/AKS.parameters.json b/policy/custom/definitions/policyset/AKS.parameters.json index 1be78a2d..14a94b2a 100644 --- a/policy/custom/definitions/policyset/AKS.parameters.json +++ b/policy/custom/definitions/policyset/AKS.parameters.json @@ -1,5 +1,7 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", - "parameters": {} + "parameters": { + + } } \ No newline at end of file diff --git a/policy/custom/definitions/policyset/DNSPrivateEndpoints.parameters.json b/policy/custom/definitions/policyset/DNSPrivateEndpoints.parameters.json index ca4ac38b..197bf2c2 100644 --- a/policy/custom/definitions/policyset/DNSPrivateEndpoints.parameters.json +++ b/policy/custom/definitions/policyset/DNSPrivateEndpoints.parameters.json @@ -241,6 +241,15 @@ "privatelink.mysql.database.azure.com" ] }, + { + "privateLinkServiceNamespace": "Microsoft.DBforMySQL/flexibleServers", + "zone": "privatelink.mysql.database.azure.com", + "filterLocationLike": "*", + "groupId": "mysqlServer", + "privateDnsZoneConfigs": [ + "privatelink.mysql.database.azure.com" + ] + }, { "privateLinkServiceNamespace": "Microsoft.DBforMariaDB/servers", "zone": "privatelink.mariadb.database.azure.com", diff --git a/policy/custom/definitions/policyset/DefenderForCloud.bicep b/policy/custom/definitions/policyset/DefenderForCloud.bicep index 853dbee4..e20984df 100644 --- a/policy/custom/definitions/policyset/DefenderForCloud.bicep +++ b/policy/custom/definitions/policyset/DefenderForCloud.bicep @@ -24,7 +24,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/361c2074-3595-4e5d-8cab-4f21dffc835c' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '361c2074-3595-4e5d-8cab-4f21dffc835c') policyDefinitionReferenceId: toLower(replace('Deploy Advanced Threat Protection on Storage Accounts', ' ', '-')) parameters: {} } @@ -32,7 +32,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/36d49e87-48c4-4f2e-beed-ba4ed02b71f5' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '36d49e87-48c4-4f2e-beed-ba4ed02b71f5') policyDefinitionReferenceId: toLower(replace('Deploy Threat Detection on SQL servers', ' ', '-')) parameters: {} } @@ -40,7 +40,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/feedbf84-6b99-488c-acc2-71c829aa5ffc' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', 'feedbf84-6b99-488c-acc2-71c829aa5ffc') policyDefinitionReferenceId: toLower(replace('Vulnerabilities on your SQL databases should be remediated', ' ', '-')) parameters: {} } @@ -48,7 +48,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/6134c3db-786f-471e-87bc-8f479dc890f6' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '6134c3db-786f-471e-87bc-8f479dc890f6') policyDefinitionReferenceId: toLower(replace('Deploy Advanced Data Security on SQL servers', ' ', '-')) parameters: {} } @@ -56,7 +56,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/3c735d8a-a4ba-4a3a-b7cf-db7754cf57f4' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '3c735d8a-a4ba-4a3a-b7cf-db7754cf57f4') policyDefinitionReferenceId: toLower(replace('Vulnerabilities in security configuration on your virtual machine scale sets should be remediated', ' ', '-')) parameters: {} } @@ -64,7 +64,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/e1e5fd5d-3e4c-4ce1-8661-7d1873ae6b15' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', 'e1e5fd5d-3e4c-4ce1-8661-7d1873ae6b15') policyDefinitionReferenceId: toLower(replace('Vulnerabilities in security configuration on your machines should be remediated', ' ', '-')) parameters: {} } @@ -72,7 +72,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/501541f7-f7e7-4cd6-868c-4190fdad3ac9' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '501541f7-f7e7-4cd6-868c-4190fdad3ac9') policyDefinitionReferenceId: toLower(replace('vulnerability assessment solution should be enabled on your virtual machines', ' ', '-')) parameters: {} } @@ -80,7 +80,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/13ce0167-8ca6-4048-8e6b-f996402e3c1b' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '13ce0167-8ca6-4048-8e6b-f996402e3c1b') policyDefinitionReferenceId: toLower(replace('Configure machines to receive the Qualys vulnerability assessment agent', ' ', '-')) parameters: {} } @@ -88,7 +88,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/1f725891-01c0-420a-9059-4fa46cb770b7' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '1f725891-01c0-420a-9059-4fa46cb770b7') policyDefinitionReferenceId: toLower(replace('Configure Azure Defender for Key Vaults to be enabled', ' ', '-')) parameters: {} } @@ -96,7 +96,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/b40e7bcd-a1e5-47fe-b9cf-2f534d0bfb7d' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', 'b40e7bcd-a1e5-47fe-b9cf-2f534d0bfb7d') policyDefinitionReferenceId: toLower(replace('Configure Azure Defender for App Service to be enabled', ' ', '-')) parameters: {} } @@ -104,7 +104,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/b7021b2b-08fd-4dc0-9de7-3c6ece09faf9' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', 'b7021b2b-08fd-4dc0-9de7-3c6ece09faf9') policyDefinitionReferenceId: toLower(replace('Configure Azure Defender for Resource Manager to be enabled', ' ', '-')) parameters: {} } @@ -112,7 +112,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/2370a3c1-4a25-4283-a91a-c9c1a145fb2f' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '2370a3c1-4a25-4283-a91a-c9c1a145fb2f') policyDefinitionReferenceId: toLower(replace('Configure Azure Defender for DNS to be enabled', ' ', '-')) parameters: {} } @@ -120,7 +120,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/44433aa3-7ec2-4002-93ea-65c65ff0310a' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '44433aa3-7ec2-4002-93ea-65c65ff0310a') policyDefinitionReferenceId: toLower(replace('Configure Azure Defender for open-source relational databases to be enabled', ' ', '-')) parameters: {} } @@ -128,7 +128,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/b99b73e7-074b-4089-9395-b7236f094491' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', 'b99b73e7-074b-4089-9395-b7236f094491') policyDefinitionReferenceId: toLower(replace('Configure Azure Defender for Azure SQL database to be enabled', ' ', '-')) parameters: {} } @@ -136,7 +136,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/50ea7265-7d8c-429e-9a7d-ca1f410191c3' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '50ea7265-7d8c-429e-9a7d-ca1f410191c3') policyDefinitionReferenceId: toLower(replace('Configure Azure Defender for SQL servers on machines to be enabled', ' ', '-')) parameters: {} } @@ -144,7 +144,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/74c30959-af11-47b3-9ed2-a26e03f427a3' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '74c30959-af11-47b3-9ed2-a26e03f427a3') policyDefinitionReferenceId: toLower(replace('Configure Azure Defender for Storage to be enabled', ' ', '-')) parameters: {} } @@ -152,7 +152,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/8e86a5b6-b9bd-49d1-8e21-4bb8a0862222' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '8e86a5b6-b9bd-49d1-8e21-4bb8a0862222') policyDefinitionReferenceId: toLower(replace('Configure Azure Defender for servers to be enabled', ' ', '-')) parameters: {} } @@ -160,7 +160,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/c9ddb292-b203-4738-aead-18e2716e858f' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', 'c9ddb292-b203-4738-aead-18e2716e858f') policyDefinitionReferenceId: toLower(replace('Configure Microsoft Defender for Containers to be enabled', ' ', '-')) parameters: {} } @@ -168,7 +168,7 @@ resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'EXTRA' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/82bf5b87-728b-4a74-ba4d-6123845cf542' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '82bf5b87-728b-4a74-ba4d-6123845cf542') policyDefinitionReferenceId: toLower(replace('Configure Microsoft Defender for Azure Cosmos DB to be enabled', ' ', '-')) parameters: {} } diff --git a/policy/custom/definitions/policyset/DefenderForCloud.parameters.json b/policy/custom/definitions/policyset/DefenderForCloud.parameters.json index 1be78a2d..14a94b2a 100644 --- a/policy/custom/definitions/policyset/DefenderForCloud.parameters.json +++ b/policy/custom/definitions/policyset/DefenderForCloud.parameters.json @@ -1,5 +1,7 @@ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", - "parameters": {} + "parameters": { + + } } \ No newline at end of file diff --git a/policy/custom/definitions/policyset/LogAnalytics.bicep b/policy/custom/definitions/policyset/LogAnalytics.bicep index d0e45764..2d3f5338 100644 --- a/policy/custom/definitions/policyset/LogAnalytics.bicep +++ b/policy/custom/definitions/policyset/LogAnalytics.bicep @@ -56,7 +56,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 'Microsoft.DataLakeAnalytics/accounts' 'Microsoft.DataLakeStore/accounts' 'Microsoft.EventGrid/systemTopics' - 'Microsoft.EventGrid/eventSubscriptions' + //'Microsoft.EventGrid/eventSubscriptions' # Removed since it doesn't have any logs 'Microsoft.EventGrid/topics' 'Microsoft.EventHub/namespaces' 'Microsoft.Network/expressRouteCircuits' @@ -111,7 +111,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/5ee9e9ed-0b42-41b7-8c9c-3cfb2fbe2069' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '5ee9e9ed-0b42-41b7-8c9c-3cfb2fbe2069') policyDefinitionReferenceId: toLower(replace('Deploy Log Analytics agent for Linux virtual machine scale sets', ' ', '-')) parameters: { logAnalytics: { @@ -123,7 +123,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/3c1b3629-c8f8-4bf6-862c-037cb9094038' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '3c1b3629-c8f8-4bf6-862c-037cb9094038') policyDefinitionReferenceId: toLower(replace('Deploy Log Analytics agent for Windows virtual machine scale sets', ' ', '-')) parameters: { logAnalytics: { @@ -135,7 +135,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/4da21710-ce6f-4e06-8cdb-5cc4c93ffbee' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '4da21710-ce6f-4e06-8cdb-5cc4c93ffbee') policyDefinitionReferenceId: toLower(replace('Deploy Dependency agent for Linux virtual machines', ' ', '-')) parameters: {} } @@ -143,7 +143,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/1c210e94-a481-4beb-95fa-1571b434fb04' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '1c210e94-a481-4beb-95fa-1571b434fb04') policyDefinitionReferenceId: toLower(replace('Deploy Dependency agent for Windows virtual machines', ' ', '-')) parameters: {} } @@ -151,7 +151,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/765266ab-e40e-4c61-bcb2-5a5275d0b7c0' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '765266ab-e40e-4c61-bcb2-5a5275d0b7c0') policyDefinitionReferenceId: toLower(replace('Deploy Dependency agent for Linux virtual machine scale sets', ' ', '-')) parameters: {} } @@ -159,7 +159,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/3be22e3b-d919-47aa-805e-8985dbeb0ad9' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '3be22e3b-d919-47aa-805e-8985dbeb0ad9') policyDefinitionReferenceId: toLower(replace('Deploy Dependency agent for Windows virtual machine scale sets', ' ', '-')) parameters: {} } @@ -167,7 +167,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/6f8f98a4-f108-47cb-8e98-91a0d85cd474' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '6f8f98a4-f108-47cb-8e98-91a0d85cd474') policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic settings for storage accounts to Log Analytics workspace', ' ', '-')) parameters: { logAnalytics: { @@ -191,7 +191,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/c84e5349-db6d-4769-805e-e14037dab9b5' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', 'c84e5349-db6d-4769-805e-e14037dab9b5') policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Batch Account to Log Analytics workspace', ' ', '-')) parameters: { logAnalytics: { @@ -203,7 +203,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/d56a5a7c-72d7-42bc-8ceb-3baf4c0eae03' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', 'd56a5a7c-72d7-42bc-8ceb-3baf4c0eae03') policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Data Lake Analytics to Log Analytics workspace', ' ', '-')) parameters: { logAnalytics: { @@ -215,7 +215,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/25763a0a-5783-4f14-969e-79d4933eb74b' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '25763a0a-5783-4f14-969e-79d4933eb74b') policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Data Lake Storage Gen1 to Log Analytics workspace', ' ', '-')) parameters: { logAnalytics: { @@ -227,7 +227,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/bef3f64c-5290-43b7-85b0-9b254eef4c47' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', 'bef3f64c-5290-43b7-85b0-9b254eef4c47') policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Key Vault to Log Analytics workspace', ' ', '-')) parameters: { logAnalytics: { @@ -239,7 +239,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/b889a06c-ec72-4b03-910a-cb169ee18721' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', 'b889a06c-ec72-4b03-910a-cb169ee18721') policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Logic Apps to Log Analytics workspace', ' ', '-')) parameters: { logAnalytics: { @@ -251,7 +251,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/08ba64b8-738f-4918-9686-730d2ed79c7d' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '08ba64b8-738f-4918-9686-730d2ed79c7d') policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Search Services to Log Analytics workspace', ' ', '-')) parameters: { logAnalytics: { @@ -263,7 +263,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/04d53d87-841c-4f23-8a5b-21564380b55e' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '04d53d87-841c-4f23-8a5b-21564380b55e') policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Service Bus to Log Analytics workspace', ' ', '-')) parameters: { logAnalytics: { @@ -275,7 +275,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/237e0f7e-b0e8-4ec4-ad46-8c12cb66d673' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '237e0f7e-b0e8-4ec4-ad46-8c12cb66d673') policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Stream Analytics to Log Analytics workspace', ' ', '-')) parameters: { logAnalytics: { @@ -287,7 +287,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/6c66c325-74c8-42fd-a286-a74b0e2939d8' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '6c66c325-74c8-42fd-a286-a74b0e2939d8') policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Azure Kubernetes Service to Log Analytics workspace', ' ', '-')) parameters: { logAnalytics: { @@ -302,7 +302,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/053d3325-282c-4e5c-b944-24faffd30d77' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '053d3325-282c-4e5c-b944-24faffd30d77') policyDefinitionReferenceId: toLower(replace('Deploy Log Analytics agent for Linux VMs', ' ', '-')) parameters: { logAnalytics: { @@ -314,7 +314,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/0868462e-646c-4fe3-9ced-a733534b6a2c' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '0868462e-646c-4fe3-9ced-a733534b6a2c') policyDefinitionReferenceId: toLower(replace('Deploy Log Analytics agent for Windows VMs', ' ', '-')) parameters: { logAnalytics: { @@ -326,7 +326,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/32133ab0-ee4b-4b44-98d6-042180979d50' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '32133ab0-ee4b-4b44-98d6-042180979d50') policyDefinitionReferenceId: toLower(replace('Audit Log Analytics Agent Deployment - VM Image (OS) unlisted', ' ', '-')) parameters: {} } @@ -334,7 +334,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/5c3bc7b8-a64c-4e08-a9cd-7ff0f31e1138' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '5c3bc7b8-a64c-4e08-a9cd-7ff0f31e1138') policyDefinitionReferenceId: toLower(replace('Audit Log Analytics agent deployment in virtual machine scale sets - VM Image (OS) unlisted', ' ', '-')) parameters: {} } @@ -342,7 +342,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/f47b5582-33ec-4c5c-87c0-b010a6b2e917' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', 'f47b5582-33ec-4c5c-87c0-b010a6b2e917') policyDefinitionReferenceId: toLower(replace('Audit Log Analytics Workspace for VM - Report Mismatch', ' ', '-')) parameters: { logAnalyticsWorkspaceId: { @@ -354,7 +354,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/752154a7-1e0f-45c6-a880-ac75a7e4f648' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '752154a7-1e0f-45c6-a880-ac75a7e4f648') policyDefinitionReferenceId: toLower(replace('Public IP addresses should have resource logs enabled for Azure DDoS Protection Standard', ' ', '-')) parameters: { effect: { @@ -372,7 +372,7 @@ resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01 groupNames: [ 'BUILTIN' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/7f89b1eb-583c-429a-8828-af049802c1d9' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '7f89b1eb-583c-429a-8828-af049802c1d9') policyDefinitionReferenceId: toLower(replace('Audit diagnostic setting', ' ', '-')) parameters: { listOfResourceTypes: { diff --git a/policy/custom/definitions/policyset/Network.bicep b/policy/custom/definitions/policyset/Network.bicep index a65db6d2..8d8471a9 100644 --- a/policy/custom/definitions/policyset/Network.bicep +++ b/policy/custom/definitions/policyset/Network.bicep @@ -29,7 +29,7 @@ resource networkPolicySet 'Microsoft.Authorization/policySetDefinitions@2020-03- groupNames: [ 'NETWORK' ] - policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/83a86a26-fd1f-447c-b59d-e51f44264114' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', '83a86a26-fd1f-447c-b59d-e51f44264114') policyDefinitionReferenceId: toLower(replace('Network interfaces should not have public IPs', ' ', '-')) parameters: {} } diff --git a/roles/la-vminsights-readonly.bicep b/roles/la-vminsights-readonly.bicep index 326664f3..ab466460 100644 --- a/roles/la-vminsights-readonly.bicep +++ b/roles/la-vminsights-readonly.bicep @@ -17,7 +17,7 @@ var roleName = 'Custom - Log Analytics - Read Only for VM Insights' var roleDescription = 'Read only access to Log Analytics for VM Insights.' // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../config/telemetry.json')) module telemetryCustomerUsageAttribution '../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.roles}-lavminsightsreadonly' diff --git a/roles/lz-appowner.bicep b/roles/lz-appowner.bicep index bdcd6910..ba364d1d 100644 --- a/roles/lz-appowner.bicep +++ b/roles/lz-appowner.bicep @@ -17,13 +17,13 @@ var roleName = 'Custom - Landing Zone Application Owner' var roleDescription = 'Contributor role granted for application/operations team at resource group level.' // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../config/telemetry.json')) module telemetryCustomerUsageAttribution '../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.roles}-lzappowner' } -// Reference: https://docs.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/design-area/identity-access +// Reference: https://learn.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/design-area/identity-access resource roleDefn 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' = { name: guid(roleName) scope: managementGroup() diff --git a/roles/lz-netops.bicep b/roles/lz-netops.bicep index 066a3bbe..af7d0be0 100644 --- a/roles/lz-netops.bicep +++ b/roles/lz-netops.bicep @@ -17,13 +17,13 @@ var roleName = 'Custom - Network Operations (NetOps)' var roleDescription = 'Platform-wide global connectivity management: virtual networks, UDRs, NSGs, NVAs, VPN, Azure ExpressRoute, and others.' // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../config/telemetry.json')) module telemetryCustomerUsageAttribution '../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.roles}-lznetops' } -// Reference: https://docs.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/design-area/identity-access +// Reference: https://learn.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/design-area/identity-access resource roleDefn 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' = { name: guid(roleName) scope: managementGroup() diff --git a/roles/lz-secops.bicep b/roles/lz-secops.bicep index 9ed990b0..c948a9f9 100644 --- a/roles/lz-secops.bicep +++ b/roles/lz-secops.bicep @@ -17,13 +17,13 @@ var roleName = 'Custom - Security Operations (SecOps)' var roleDescription = 'Security Administrator role with a horizontal view across the entire Azure estate and the Azure Key Vault purge policy.' // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../config/telemetry.json')) module telemetryCustomerUsageAttribution '../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.roles}-lzsecops' } -// Reference: https://docs.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/design-area/identity-access +// Reference: https://learn.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/design-area/identity-access resource roleDefn 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' = { name: guid(roleName) scope: managementGroup() diff --git a/roles/lz-subowner.bicep b/roles/lz-subowner.bicep index a863b814..b8a47f3e 100644 --- a/roles/lz-subowner.bicep +++ b/roles/lz-subowner.bicep @@ -17,13 +17,13 @@ var roleName = 'Custom - Landing Zone Subscription Owner' var roleDescription = 'Delegated role for subscription owner generated from subscription Owner role.' // Telemetry - Azure customer usage attribution -// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution +// Reference: https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution var telemetry = json(loadTextContent('../config/telemetry.json')) module telemetryCustomerUsageAttribution '../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) { name: 'pid-${telemetry.customerUsageAttribution.modules.roles}-lzsubowner' } -// Reference: https://docs.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/design-area/identity-access +// Reference: https://learn.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/design-area/identity-access resource roleDefn 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' = { name: guid(roleName) scope: managementGroup() diff --git a/schemas/latest/landingzones/lz-platform-identity.json b/schemas/latest/landingzones/lz-platform-identity.json new file mode 100644 index 00000000..b18e318d --- /dev/null +++ b/schemas/latest/landingzones/lz-platform-identity.json @@ -0,0 +1,455 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "$ref": "#/definitions/PlatformIdentityArchetypeDefinition", + "definitions": { + "PlatformIdentityArchetypeDefinition": { + "type": "object", + "additionalProperties": false, + "properties": { + "$schema": { + "type": "string", + "format": "uri", + "qt-uri-protocols": [ + "https" + ], + "qt-uri-extensions": [ + ".json" + ] + }, + "contentVersion": { + "type": "string" + }, + "parameters": { + "$ref": "#/definitions/Parameters" + } + }, + "required": [ + "$schema", + "contentVersion", + "parameters" + ], + "title": "PlatformIdentityArchetypeDefinition" + }, + "Parameters": { + "type": "object", + "additionalProperties": false, + "properties": { + "location": { + "$ref": "types/location.json#/definitions/Location" + }, + "logAnalyticsWorkspaceResourceId": { + "$ref": "types/logAnalyticsWorkspaceId.json#/definitions/LogAnalyticsWorkspaceId" + }, + "serviceHealthAlerts": { + "$ref": "types/serviceHealthAlerts.json#/definitions/ServiceHealthAlerts" + }, + "securityCenter": { + "$ref": "types/securityCenter.json#/definitions/SecurityCenter" + }, + "subscriptionRoleAssignments": { + "$ref": "types/subscriptionRoleAssignments.json#/definitions/SubscriptionRoleAssignments" + }, + "subscriptionBudget": { + "$ref": "types/subscriptionBudget.json#/definitions/SubscriptionBudget" + }, + "subscriptionTags": { + "$ref": "types/subscriptionTags.json#/definitions/SubscriptionTags" + }, + "resourceTags": { + "$ref": "types/resourceTags.json#/definitions/ResourceTags" + }, + "resourceGroups": { + "$ref": "#/definitions/ResourceGroups" + }, + "automation": { + "$ref": "types/automation.json#/definitions/Automation" + }, + "backupRecoveryVault": { + "$ref": "types/backupRecoveryVault.json#/definitions/RecoveryVault" + }, + "privateDnsZones": { + "$ref": "#/definitions/PrivateDNSZones" + }, + "privateDnsResolver": { + "$ref": "#/definitions/PrivateDNSResolver" + }, + "privateDnsResolverRuleset": { + "$ref": "#/definitions/PrivateDNSResolverRuleset" + }, + "hubNetwork": { + "$ref": "#/definitions/HubNetwork" + }, + "network": { + "$ref": "#/definitions/Network" + } + }, + "required": [ + "automation", + "backupRecoveryVault", + "hubNetwork", + "network", + "privateDnsResolver", + "privateDnsResolverRuleset", + "privateDnsZones", + "resourceGroups", + "resourceTags", + "securityCenter", + "serviceHealthAlerts", + "subscriptionBudget", + "subscriptionRoleAssignments", + "subscriptionTags" + ], + "title": "Parameters" + }, + "HubNetwork": { + "type": "object", + "additionalProperties": false, + "properties": { + "value": { + "$ref": "#/definitions/HubNetworkValue" + } + }, + "required": [ + "value" + ], + "title": "HubNetwork" + }, + "HubNetworkValue": { + "type": "object", + "additionalProperties": false, + "properties": { + "virtualNetworkId": { + "type": "string" + }, + "rfc1918IPRange": { + "type": "string" + }, + "rfc6598IPRange": { + "type": "string" + }, + "egressVirtualApplianceIp": { + "type": "string" + } + }, + "required": [ + "egressVirtualApplianceIp", + "rfc1918IPRange", + "rfc6598IPRange", + "virtualNetworkId" + ], + "title": "HubNetworkValue" + }, + "DNSResolverInbound": { + "type": "object", + "additionalProperties": false, + "properties": { + "comments": { + "type": "string" + }, + "name": { + "type": "string" + }, + "addressPrefix": { + "type": "string" + } + }, + "required": [ + "addressPrefix", + "comments", + "name" + ], + "title": "DNSResolverInbound" + }, + "PrivateDNSResolver": { + "type": "object", + "additionalProperties": false, + "properties": { + "value": { + "$ref": "#/definitions/PrivateDNSResolverValue" + } + }, + "required": [ + "value" + ], + "title": "PrivateDNSResolver" + }, + "PrivateDNSResolverValue": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "inboundEndpointName": { + "type": "string" + }, + "outboundEndpointName": { + "type": "string" + } + }, + "required": [ + "enabled", + "inboundEndpointName", + "name", + "outboundEndpointName" + ], + "title": "PrivateDNSResolverValue" + }, + "PrivateDNSResolverRuleset": { + "type": "object", + "additionalProperties": false, + "properties": { + "value": { + "$ref": "#/definitions/PrivateDNSResolverRulesetValue" + } + }, + "required": [ + "value" + ], + "title": "PrivateDNSResolverRuleset" + }, + "PrivateDNSResolverRulesetValue": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "linkRuleSetToVnet": { + "type": "boolean" + }, + "linkRuleSetToVnetName": { + "type": "string" + }, + "forwardingRules": { + "type": "array", + "items": { + "$ref": "#/definitions/ForwardingRule" + } + } + }, + "required": [ + "enabled", + "forwardingRules", + "linkRuleSetToVnet", + "linkRuleSetToVnetName", + "name" + ], + "title": "PrivateDNSResolverRulesetValue" + }, + "ForwardingRule": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "state": { + "type": "string" + }, + "targetDnsServers": { + "type": "array", + "items": { + "$ref": "#/definitions/TargetDNSServer" + } + } + }, + "required": [ + "domain", + "name", + "state", + "targetDnsServers" + ], + "title": "ForwardingRule" + }, + "TargetDNSServer": { + "type": "object", + "additionalProperties": false, + "properties": { + "ipAddress": { + "type": "string" + } + }, + "required": [ + "ipAddress" + ], + "title": "TargetDNSServer" + }, + "PrivateDNSZones": { + "type": "object", + "additionalProperties": false, + "properties": { + "value": { + "$ref": "#/definitions/PrivateDNSZonesValue" + } + }, + "required": [ + "value" + ], + "title": "PrivateDNSZones" + }, + "PrivateDNSZonesValue": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "resourceGroupName": { + "type": "string" + } + }, + "required": [ + "enabled", + "resourceGroupName" + ], + "title": "PrivateDNSZonesValue" + }, + "ResourceGroups": { + "type": "object", + "additionalProperties": false, + "properties": { + "value": { + "$ref": "#/definitions/ResourceGroupsValue" + } + }, + "required": [ + "value" + ], + "title": "ResourceGroups" + }, + "ResourceGroupsValue": { + "type": "object", + "additionalProperties": false, + "properties": { + "automation": { + "type": "string" + }, + "networking": { + "type": "string" + }, + "networkWatcher": { + "type": "string" + }, + "backupRecoveryVault": { + "type": "string" + }, + "domainControllers": { + "type": "string" + }, + "dnsResolver": { + "type": "string" + }, + "dnsCondionalForwarders": { + "type": "string" + }, + "privateDnsZones": { + "type": "string" + } + }, + "required": [ + "automation", + "backupRecoveryVault", + "dnsCondionalForwarders", + "dnsResolver", + "domainControllers", + "networkWatcher", + "networking", + "privateDnsZones" + ], + "title": "ResourceGroupsValue" + }, + + "Network": { + "type": "object", + "additionalProperties": false, + "properties": { + "value": { + "$ref": "#/definitions/NetworkValue" + } + }, + "required": [ + "value" + ], + "title": "Network" + }, + "NetworkValue": { + "type": "object", + "additionalProperties": false, + "properties": { + "deployVnet": { + "type": "boolean" + }, + "peerToHubVirtualNetwork": { + "type": "boolean" + }, + "useRemoteGateway": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "dnsServers": { + "type": "array", + "items": { + "type": "string" + } + }, + "addressPrefixes": { + "type": "array", + "items": { + "type": "string" + } + }, + "subnets": { + "$ref": "#/definitions/Subnets" + } + }, + "required": [ + "addressPrefixes", + "deployVnet", + "dnsServers", + "name", + "peerToHubVirtualNetwork", + "subnets", + "useRemoteGateway" + ], + "title": "NetworkValue" + }, + "Subnets": { + "type": "object", + "additionalProperties": false, + "properties": { + "domainControllers": { + "$ref": "#/definitions/DNSResolverInbound" + }, + "dnsResolverInbound": { + "$ref": "#/definitions/DNSResolverInbound" + }, + "dnsResolverOutbound": { + "$ref": "#/definitions/DNSResolverInbound" + }, + "optional": { + "type": "array", + "items": {} + } + }, + "required": [ + "dnsResolverInbound", + "dnsResolverOutbound", + "domainControllers", + "optional" + ], + "title": "Subnets" + } + } +} \ No newline at end of file diff --git a/schemas/latest/readme.md b/schemas/latest/readme.md index f7e9315f..ca8fa578 100644 --- a/schemas/latest/readme.md +++ b/schemas/latest/readme.md @@ -105,13 +105,13 @@ "sms": [ { "countryCode": "1", - "phoneNumber": "5555555555" + "phoneNumber": "6045555555" } ], "voice": [ { "countryCode": "1", - "phoneNumber": "5555555555" + "phoneNumber": "6045555555" } ] }, @@ -124,7 +124,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { @@ -340,13 +340,13 @@ "sms": [ { "countryCode": "1", - "phoneNumber": "5555555555" + "phoneNumber": "6045555555" } ], "voice": [ { "countryCode": "1", - "phoneNumber": "5555555555" + "phoneNumber": "6045555555" } ] }, @@ -359,7 +359,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/schemas/v0.5.0/readme.md b/schemas/v0.5.0/readme.md index e3dd01b5..a2fb1b14 100644 --- a/schemas/v0.5.0/readme.md +++ b/schemas/v0.5.0/readme.md @@ -36,13 +36,13 @@ "sms": [ { "countryCode": "1", - "phoneNumber": "5555555555" + "phoneNumber": "6045555555" } ], "voice": [ { "countryCode": "1", - "phoneNumber": "5555555555" + "phoneNumber": "6045555555" } ] }, @@ -55,7 +55,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { @@ -271,13 +271,13 @@ "sms": [ { "countryCode": "1", - "phoneNumber": "5555555555" + "phoneNumber": "6045555555" } ], "voice": [ { "countryCode": "1", - "phoneNumber": "5555555555" + "phoneNumber": "6045555555" } ] }, @@ -290,7 +290,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/schemas/v0.6.0/readme.md b/schemas/v0.6.0/readme.md index f7e9315f..ca8fa578 100644 --- a/schemas/v0.6.0/readme.md +++ b/schemas/v0.6.0/readme.md @@ -105,13 +105,13 @@ "sms": [ { "countryCode": "1", - "phoneNumber": "5555555555" + "phoneNumber": "6045555555" } ], "voice": [ { "countryCode": "1", - "phoneNumber": "5555555555" + "phoneNumber": "6045555555" } ] }, @@ -124,7 +124,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { @@ -340,13 +340,13 @@ "sms": [ { "countryCode": "1", - "phoneNumber": "5555555555" + "phoneNumber": "6045555555" } ], "voice": [ { "countryCode": "1", - "phoneNumber": "5555555555" + "phoneNumber": "6045555555" } ] }, @@ -359,7 +359,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/scripts/configuration/Connect-AlzCredential.ps1 b/scripts/configuration/Connect-AlzCredential.ps1 new file mode 100644 index 00000000..277b6958 --- /dev/null +++ b/scripts/configuration/Connect-AlzCredential.ps1 @@ -0,0 +1,70 @@ +<# +---------------------------------------------------------------------------------- +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. + +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +---------------------------------------------------------------------------------- +#> + +<# + .SYNOPSIS + This script connects to Azure using a service principal stored in a credential file, a service principal stored in a SecureString, or interactively. + + .DESCRIPTION + This script connects to Azure using a service principal stored in a credential file, a service principal stored in a SecureString, or interactively. + + .PARAMETER CredentialFile + The path to the credential file to use for login. + + .PARAMETER SecureServicePrincipal + The service principal to use for login. + + .PARAMETER TenantId + The tenant ID to use for interactive login. + + .EXAMPLE + PS> .\Connect-AlzCredential.ps1 -CredentialFile '$HOME/CanadaALZ.json' + + .EXAMPLE + PS> .\Connect-AlzCredential.ps1 -SecureServicePrincipal $SecureSP + + .EXAMPLE + PS> .\Connect-AlzCredential.ps1 -TenantId '00000000-0000-0000-0000-000000000000' +#> + +[CmdletBinding()] +Param( + [Parameter(Mandatory = $true, ParameterSetName = "CredentialFile")] + [string]$CredentialFile, + + [Parameter(Mandatory = $true, ParameterSetName = "ServicePrincipal")] + [SecureString]$SecureServicePrincipal, + + [Parameter(Mandatory = $true, ParameterSetName = "Interactive")] + [string]$TenantId +) + +switch ($PSCmdlet.ParameterSetName) { + "CredentialFile" { + $ServicePrincipalCredentials = Get-Content -Raw -Path $CredentialFile + $SecureSP = ConvertTo-SecureString -String $ServicePrincipalCredentials -AsPlainText -Force + .\Connect-AlzCredential.ps1 -SecureServicePrincipal $SecureSP + } + "ServicePrincipal" { + Write-Output "Logging in to Azure using service principal..." + $ServicePrincipal = ($SecureServicePrincipal | ConvertFrom-SecureString -AsPlainText) | ConvertFrom-Json + $Password = ConvertTo-SecureString $ServicePrincipal.password -AsPlainText -Force + $Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $ServicePrincipal.appId, $Password + Connect-AzAccount -ServicePrincipal -TenantId $ServicePrincipal.tenant -Credential $Credential + } + "Interactive" { + $context = Get-AzContext + if ($context -eq $null) { + Write-Output "Logging in to Azure using interactive login..." + Connect-AzAccount -Tenant $TenantId + } + } +} diff --git a/scripts/configuration/Get-AlzConfiguration.ps1 b/scripts/configuration/Get-AlzConfiguration.ps1 new file mode 100644 index 00000000..04742ec0 --- /dev/null +++ b/scripts/configuration/Get-AlzConfiguration.ps1 @@ -0,0 +1,53 @@ +<# +---------------------------------------------------------------------------------- +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. + +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +---------------------------------------------------------------------------------- +#> + +<# + .SYNOPSIS + This script gets the main YAML configuration for a CanadaPubSecALZ deployment. + + .DESCRIPTION + This script gets the main YAML configuration for a CanadaPubSecALZ deployment. + + .PARAMETER Environment + The name of the environment. + + .PARAMETER RepoRootPath + The path to the repository directory. + + .PARAMETER ConfigVariablesByRef + The reference to the configuration variables hashtable. + + .EXAMPLE + PS> $ConfigVariablesYaml = @{} + PS> .\Get-AlzConfiguration.ps1 -Environment 'CanadaALZ-main' -ConfigVariablesByRef ([ref]$ConfigVariablesYaml) +#> + +[CmdletBinding()] +Param( + [Parameter(Mandatory = $true)] + [string]$Environment, + + [string]$RepoRootPath = "../..", + + [ref]$ConfigVariablesByRef +) + +$ErrorActionPreference = "Stop" + +$RepoConfigPath = (Resolve-Path -Path "$RepoRootPath/config/variables/$Environment.yml").Path + +Write-Output "Getting environment configuration ($RepoConfigPath)" + +if (Test-Path -PathType Leaf -Path $RepoConfigPath) { + $ConfigVariablesByRef.value = Get-Content -Path $RepoConfigPath -Raw | ConvertFrom-Yaml +} else { + throw "Environment file not found ($RepoConfigPath)" +} diff --git a/scripts/configuration/Get-AlzSubscriptions.ps1 b/scripts/configuration/Get-AlzSubscriptions.ps1 new file mode 100644 index 00000000..0e9f9c9d --- /dev/null +++ b/scripts/configuration/Get-AlzSubscriptions.ps1 @@ -0,0 +1,55 @@ +<# +---------------------------------------------------------------------------------- +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. + +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +---------------------------------------------------------------------------------- +#> + +<# + .SYNOPSIS + This script gets an array of subscription ids for a CanadaPubSecALZ deployment. + + .DESCRIPTION + This script gets an array of subscription ids for a CanadaPubSecALZ deployment. + + .PARAMETER Environment + The name of the environment. + + .PARAMETER RepoRootPath + The path to the repository directory. + + .PARAMETER SubscriptionIdsByRef + The reference to the subscription IDs array. + + .EXAMPLE + PS> $SubscriptionIds = @() + PS> .\Get-AlzSubscriptions.ps1 -Environment 'CanadaALZ-main' -SubscriptionIdsByRef ([ref]$SubscriptionIds) +#> + +[CmdletBinding()] +Param( + [Parameter(Mandatory = $true)] + [string]$Environment, + + [string]$RepoRootPath = "../..", + + [ref]$SubscriptionIdsByRef +) + +$ErrorActionPreference = "Stop" + +Write-Output "Getting subscription configurations for environment ($Environment)" + +$SubscriptionIdsByRef.value = @() + +$Pattern = "^[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}(_.*)(_.*)?\.json" + +$Subscriptions = @(Get-ChildItem -Path "$RepoRootPath/config/subscriptions/$Environment" -File -Recurse | ? { $_.Name -match $Pattern }) + +foreach ($Subscription in $Subscriptions) { + $SubscriptionIdsByRef.value += $Subscription.Name.Split('_')[0] +} diff --git a/scripts/configuration/Install-Prerequisites.ps1 b/scripts/configuration/Install-Prerequisites.ps1 new file mode 100644 index 00000000..aef06f5e --- /dev/null +++ b/scripts/configuration/Install-Prerequisites.ps1 @@ -0,0 +1,14 @@ +<# +---------------------------------------------------------------------------------- +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. + +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +---------------------------------------------------------------------------------- +#> + +Install-Module Az -Repository PSGallery -Force +Install-Module powershell-yaml -Repository PSGallery -Force +Install-Module PSPasswordGenerator -Repository PSGallery -Force diff --git a/scripts/configuration/New-AlzConfiguration.ps1 b/scripts/configuration/New-AlzConfiguration.ps1 new file mode 100644 index 00000000..15d6268f --- /dev/null +++ b/scripts/configuration/New-AlzConfiguration.ps1 @@ -0,0 +1,473 @@ +<# +---------------------------------------------------------------------------------- +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. + +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +---------------------------------------------------------------------------------- +#> + +<# + .SYNOPSIS + Creates a new configuration for the CanadaPubSecALZ deployment. + + .DESCRIPTION + This script creates a new set of configuration files, using an existing CanadaPubSecALZ configuration. Select configuration elements are replaced with values specific to the target environment. + + .PARAMETER Environment + The base name of the YAML environment configuration file. + + .PARAMETER SourceEnvironment + The name of the source environment. If not specified, the source environment attribute in the environment configuration file is used. If the environment configuration file does not specify a source environment, the environment configuration file base name is used. + + .PARAMETER TargetEnvironment + The name of the target environment. If not specified, the target environment attribute in the environment configuration file is used. If the environment configuration file does not specify a target environment, the environment configuration file base name is used. + + .PARAMETER RepoRootPath + The path to the repository directory. Defaults to ../.. + + .PARAMETER Force + If specified, the script will overwrite existing configuration files. + + .PARAMETER UserRootPath + The path to the user directory. Defaults to $HOME. + + .PARAMETER UserLogsPath + The path to the user logs directory. Defaults to $UserRootPath/ALZ/logs. + + .PARAMETER UserCredsPath + The path to the user credentials directory. Defaults to $UserRootPath/ALZ/credentials. + + .PARAMETER UserConfigPath + The path to the user configuration directory. Defaults to $UserRootPath/ALZ/config. + + .EXAMPLE + PS> .\New-AlzConfiguration.ps1 -Environment 'CanadaALZ-main' + + .EXAMPLE + PS> .\New-AlzConfiguration.ps1 -Environment 'CanadaALZ-main' -Force +#> + +[CmdletBinding()] +Param( + [Parameter(Mandatory = $true)] + [string]$Environment, + + [string]$SourceEnvironment = $null, + + [string]$TargetEnvironment = $null, + + [string]$RepoRootPath = "../..", + + [switch]$Force = $false, + + [string]$UserRootPath = "$HOME", + + [string]$UserLogsPath = "$UserRootPath/ALZ/logs", + + [string]$UserCredsPath = "$UserRootPath/ALZ/credentials", + + [string]$UserConfigPath = "$UserRootPath/ALZ/config" +) + +$ErrorActionPreference = "Stop" + +function ValidateParameters { + param ( + [Parameter(Mandatory = $true)] + [object]$Parameters, + [Parameter(Mandatory = $true)] + [string]$ParameterFile + ) + Write-Output "Checking configuration path ($RepoConfigPath)" + if (-not (Test-Path -PathType Container -Path $RepoConfigPath)) { + throw "Configuration path does not exist." + } + + # How we determine the source environment name: + # 1. Use the '$SourceEnvironment' parameter if specified + # 2. Otherwise, use the 'Environment.Source' attribute in the parameter file if specified + # 3. Otherwise, use the parameter file (base) name + if (-not ([string]::IsNullOrEmpty($SourceEnvironment))) { + $Parameters.Environment.Source = $SourceEnvironment + } elseif (-not ([string]::IsNullOrEmpty($Parameters.Environment.Source))) { + $Parameters.Environment.Source = $Parameters.Environment.Source + } else { + $Parameters.Environment.Source = $ParameterFile | Split-Path -LeafBase + } + + # How we determine the target environment name: + # 1. Use the '$TargetEnvironment' parameter if specified + # 2. Otherwise, use the 'Environment.Target' attribute in the parameter file if specified + # 3. Otherwise, use the parameter file (base) name + if (-not ([string]::IsNullOrEmpty($TargetEnvironment))) { + $Parameters.Environment.Target = $TargetEnvironment + } elseif (-not ([string]::IsNullOrEmpty($Parameters.Environment.Target))) { + $Parameters.Environment.Target = $Parameters.Environment.Target + } else { + $Parameters.Environment.Target = $ParameterFile | Split-Path -LeafBase + } + + if ($Parameters.Environment.Source -eq $Parameters.Environment.Target) { + throw "Source ($Parameters.Environment.Source) and target ($Parameters.Environment.Target) environments cannot be the same." + } + + if (-not (Test-Path -PathType Leaf -Path "$RepoConfigPath/variables/$($Parameters.Environment.Source).yml")) { + throw "Source environment does not exist ($($Parameters.Environment.Source))" + } else { + Write-Output " Source environment: $($Parameters.Environment.Source)" + } + + if ((Test-Path -PathType Leaf -Path "$RepoConfigPath/variables/$($Parameters.Environment.Target).yml") -and (-not $Force)) { + throw "Target environment already exists ($($Parameters.Environment.Target)). Use the '-Force' parameter to overwrite it." + } else { + Write-Output " Target environment: $($Parameters.Environment.Target)" + } +} + +function VariablesConfiguration { + param ( + [Parameter(Mandatory = $true)] + [object]$Parameters, + [ref]$ConfigVariablesByRef + ) + Write-Output "" + Write-Output "Generating Variables configurations" + Write-Output "" + + $file = "$RepoConfigPath/variables/$($Parameters.Environment.Source).yml" + if (Test-Path -PathType Leaf -Path $file) { + $ConfigVariablesByRef.value = Get-Content -Path $file -Raw | ConvertFrom-Yaml + } else { + throw "Source environment file not found ($file)" + } + + Write-Output " Updating variables configuration" + + # Deployment variables + $ConfigVariablesByRef.value.variables['deploymentRegion'] = $Parameters.DeployRegion ?? $ConfigVariablesByRef.value.variables['deploymentRegion'] + + # Management Group Hierarchy variables + $ConfigVariablesByRef.value.variables['var-managementgroup-hierarchy'] = ($Parameters.ManagementGroupHierarchy | ConvertTo-Json -Depth 100) ?? $ConfigVariablesByRef.value.variables['var-managementgroup-hierarchy'] + + # Logging variables + $ConfigVariablesByRef.value.variables['var-logging-region'] = $Parameters.DeployRegion ?? $ConfigVariablesByRef.value.variables['var-logging-region'] + $ConfigVariablesByRef.value.variables['var-logging-managementGroupId'] = $Parameters.Logging.ManagementGroupId ?? $ConfigVariablesByRef.value.variables['var-logging-managementGroupId'] + $ConfigVariablesByRef.value.variables['var-logging-subscriptionId'] = $Parameters.Logging.SubscriptionId ?? $ConfigVariablesByRef.value.variables['var-logging-subscriptionId'] + $ConfigVariablesByRef.value.variables['var-logging-diagnosticSettingsforNetworkSecurityGroupsStoragePrefix'] = $Parameters.ManagementGroupHierarchy.children[0].id + 'nsg' + + # Identity variables + $ConfigVariablesByRef.value.variables['var-identity-region'] = $Parameters.DeployRegion ?? $ConfigVariablesByRef.value.variables['var-identity-region'] + $ConfigVariablesByRef.value.variables['var-identity-managementGroupId'] = $Parameters.Identity.ManagementGroupId ?? $ConfigVariablesByRef.value.variables['var-identity-managementGroupId'] + $ConfigVariablesByRef.value.variables['var-identity-subscriptionId'] = $Parameters.Identity.SubscriptionId ?? $ConfigVariablesByRef.value.variables['var-identity-subscriptionId'] + + # Hub Network variables + $ConfigVariablesByRef.value.variables['var-hubnetwork-region'] = $Parameters.DeployRegion ?? $ConfigVariablesByRef.value.variables['var-hubnetwork-region'] + $ConfigVariablesByRef.value.variables['var-hubnetwork-managementGroupId'] = $Parameters.HubNetwork.ManagementGroupId ?? $ConfigVariablesByRef.value.variables['var-hubnetwork-managementGroupId'] + $ConfigVariablesByRef.value.variables['var-hubnetwork-subscriptionId'] = $Parameters.HubNetwork.SubscriptionId ?? $ConfigVariablesByRef.value.variables['var-hubnetwork-subscriptionId'] + + # Write the variables configuration file + $ConfigVariablesFile = "$RepoConfigPath/variables/$($Parameters.Environment.Target).yml" + Write-Output " Writing variables configuration file: $ConfigVariablesFile" + New-Item -ItemType Directory -Path (Split-Path -Parent -Path $ConfigVariablesFile) -Force | Out-Null + $ConfigVariablesYaml | ConvertTo-Yaml | Set-Content -Path $ConfigVariablesFile | Out-Null +} + +function LoggingConfiguration { + param ( + [Parameter(Mandatory = $true)] + [object]$Parameters, + [Parameter(Mandatory = $true)] + [object]$ConfigVariablesYaml + ) + Write-Output "" + Write-Output "Generating Logging configurations" + Write-Output "" + + $file = "$RepoConfigPath/logging/$($Parameters.Environment.Source)/$($ConfigVariablesYaml.variables['var-logging-configurationFileName'])" + if (Test-Path -PathType Leaf -Path $file) { + Write-Output " Reading source environment logging configuration file: $file" + $ConfigLoggingJson = Get-Content -Path $file -Raw | ConvertFrom-Json + + Write-Output " Updating logging configuration" + $ConfigLoggingJson.{$schema} = 'https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-platform-logging.json#' + $ConfigLoggingJson.parameters.securityCenter.value = $Parameters.Logging.SecurityCenter ?? $ConfigLoggingJson.parameters.securityCenter.value + $ConfigLoggingJson.parameters.serviceHealthAlerts.value = $Parameters.Logging.ServiceHealthAlerts ?? $ConfigLoggingJson.parameters.serviceHealthAlerts.value + $ConfigLoggingJson.parameters.subscriptionRoleAssignments.value = $Parameters.Logging.RoleAssignments ?? $ConfigLoggingJson.parameters.subscriptionRoleAssignments.value + $ConfigLoggingJson.parameters.subscriptionTags.value = $Parameters.values.Logging.SubscriptionTags ?? $ConfigLoggingJson.parameters.subscriptionTags.value + $ConfigLoggingJson.parameters.resourceTags.value = $Parameters.values.Logging.ResourceTags ?? $ConfigLoggingJson.parameters.resourceTags.value + $ConfigLoggingJson.parameters.dataCollectionRule.value.enabled = $Parameters.Logging.DataCollectionRule.Enabled ?? $ConfigLoggingJson.parameters.dataCollectionRule.value.enabled + + $ConfigLoggingFile = "$RepoConfigPath/logging/$($Parameters.Environment.Target)/$($ConfigVariablesYaml.variables['var-logging-configurationFileName'])" + Write-Output " Writing logging configuration file: $ConfigLoggingFile" + New-Item -ItemType Directory -Path (Split-Path -Parent -Path $ConfigLoggingFile) -Force | Out-Null + $ConfigLoggingJson | ConvertTo-Json -Depth 100 | Set-Content -Path $ConfigLoggingFile | Out-Null + } else { + Write-Output " Source environment logging configuration file not found: $file" + } +} + +function NetworkAzfwConfiguration { + param ( + [Parameter(Mandatory = $true)] + [object]$Parameters, + [Parameter(Mandatory = $true)] + [object]$ConfigVariablesYaml, + [ref]$ConfigNetworkAzfwByRef + ) + Write-Output "" + Write-Output "Generating Network Azure Firewall configurations" + Write-Output "" + + $file = "$RepoConfigPath/networking/$($Parameters.Environment.Source)/$($ConfigVariablesYaml.variables['var-hubnetwork-azfw-configurationFileName'])" + if (Test-Path -PathType Leaf -Path $file) { + Write-Output " Reading source environment network Azure Firewall configuration file: $file" + $ConfigNetworkAzfwByRef.value = Get-Content -Path $file -Raw | ConvertFrom-Json + + Write-Output " Updating network Azure Firewall configuration" + $ConfigNetworkAzfwByRef.value.{$schema} = 'https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-platform-connectivity-hub-azfw.json#' + $ConfigNetworkAzfwByRef.value.parameters.securityCenter.value = $Parameters.HubNetwork.SecurityCenter ?? $ConfigNetworkAzfwByRef.value.parameters.securityCenter.value + $ConfigNetworkAzfwByRef.value.parameters.serviceHealthAlerts.value = $Parameters.HubNetwork.ServiceHealthAlerts ?? $ConfigNetworkAzfwByRef.value.parameters.serviceHealthAlerts.value + $ConfigNetworkAzfwByRef.value.parameters.subscriptionRoleAssignments.value = $Parameters.HubNetwork.RoleAssignments ?? $ConfigNetworkAzfwByRef.value.parameters.subscriptionRoleAssignments.value + $ConfigNetworkAzfwByRef.value.parameters.subscriptionTags.value = $Parameters.values.HubNetwork.SubscriptionTags ?? $ConfigNetworkAzfwByRef.value.parameters.subscriptionTags.value + $ConfigNetworkAzfwByRef.value.parameters.resourceTags.value = $Parameters.values.HubNetwork.ResourceTags ?? $ConfigNetworkAzfwByRef.value.parameters.resourceTags.value + $ConfigNetworkAzfwByRef.value.parameters.privateDnsZones.value = $Parameters.HubNetwork.PrivateDNS ?? $ConfigNetworkAzfwByRef.value.parameters.privateDnsZones.value + $ConfigNetworkAzfwByRef.value.parameters.ddosStandard.value = $Parameters.HubNetwork.DDoS ?? $ConfigNetworkAzfwByRef.value.parameters.ddosStandard.value + + $ConfigNetworkAzfwFile = "$RepoConfigPath/networking/$($Parameters.Environment.Target)/$($ConfigVariablesYaml.variables['var-hubnetwork-azfw-configurationFileName'])" + Write-Output " Writing network Azure Firewall configuration file: $ConfigNetworkAzfwFile" + New-Item -ItemType Directory -Path (Split-Path -Parent -Path $ConfigNetworkAzfwFile) -Force | Out-Null + $ConfigNetworkAzfwByRef.value | ConvertTo-Json -Depth 100 | Set-Content -Path $ConfigNetworkAzfwFile | Out-Null + } else { + Write-Output " Source environment network Azure Firewall configuration file not found: $file" + } +} + +function NetworkAzfwPolicyConfiguration { + param ( + [Parameter(Mandatory = $true)] + [object]$Parameters, + [Parameter(Mandatory = $true)] + [object]$ConfigVariablesYaml + ) + Write-Output "" + Write-Output "Generating Network Azure Firewall Policy configurations" + Write-Output "" + + $file = "$RepoConfigPath/networking/$($Parameters.Environment.Source)/$($ConfigVariablesYaml.variables['var-hubnetwork-azfwPolicy-configurationFileName'])" + if (Test-Path -PathType Leaf -Path $file) { + Write-Output " Reading source environment network Azure Firewall Policy configuration file: $file" + $ConfigNetworkAzfwPolicyJson = Get-Content -Path $file -Raw | ConvertFrom-Json + + Write-Output " Updating network Azure Firewall Policy configuration" + $ConfigNetworkAzfwPolicyJson.{$schema} = 'https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-platform-connectivity-hub-azfw-policy.json#' + $ConfigNetworkAzfwPolicyJson.parameters.resourceTags.value = $Parameters.values.HubNetwork.ResourceTags ?? $ConfigNetworkAzfwPolicyJson.parameters.resourceTags.value + + $ConfigNetworkAzfwPolicyFile = "$RepoConfigPath/networking/$($Parameters.Environment.Target)/$($ConfigVariablesYaml.variables['var-hubnetwork-azfwPolicy-configurationFileName'])" + Write-Output " Writing network Azure Firewall Policy configuration file: $ConfigNetworkAzfwPolicyFile" + New-Item -ItemType Directory -Path (Split-Path -Parent -Path $ConfigNetworkAzfwPolicyFile) -Force | Out-Null + $ConfigNetworkAzfwPolicyJson | ConvertTo-Json -Depth 100 | Set-Content -Path $ConfigNetworkAzfwPolicyFile | Out-Null + } else { + Write-Output " Source environment network Azure Firewall Policy configuration file not found: $file" + } +} + +function NetworkNvaConfiguration { + param ( + [Parameter(Mandatory = $true)] + [object]$Parameters, + [Parameter(Mandatory = $true)] + [object]$ConfigVariablesYaml + ) + Write-Output "" + Write-Output "Generating Network NVA configurations" + Write-Output "" + + $file = "$RepoConfigPath/networking/$($Parameters.Environment.Source)/$($ConfigVariablesYaml.variables['var-hubnetwork-nva-configurationFileName'])" + if (Test-Path -PathType Leaf -Path $file) { + Write-Output " Reading source environment network NVA configuration file: $file" + $ConfigNetworkNvaJson = Get-Content -Path $file -Raw | ConvertFrom-Json + + Write-Output " Updating network NVA configuration" + $ConfigNetworkNvaJson.{$schema} = 'https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-platform-connectivity-hub-nva.json#' + $ConfigNetworkNvaJson.parameters.securityCenter.value = $Parameters.HubNetwork.SecurityCenter ?? $ConfigNetworkNvaJson.parameters.securityCenter.value + $ConfigNetworkNvaJson.parameters.serviceHealthAlerts.value = $Parameters.HubNetwork.ServiceHealthAlerts ?? $ConfigNetworkNvaJson.parameters.serviceHealthAlerts.value + $ConfigNetworkNvaJson.parameters.subscriptionRoleAssignments.value = $Parameters.HubNetwork.RoleAssignments ?? $ConfigNetworkNvaJson.parameters.subscriptionRoleAssignments.value + $ConfigNetworkNvaJson.parameters.subscriptionTags.value = $Parameters.values.HubNetwork.SubscriptionTags ?? $ConfigNetworkNvaJson.parameters.subscriptionTags.value + $ConfigNetworkNvaJson.parameters.resourceTags.value = $Parameters.values.HubNetwork.ResourceTags ?? $ConfigNetworkNvaJson.parameters.resourceTags.value + $ConfigNetworkNvaJson.parameters.privateDnsZones.value = $Parameters.HubNetwork.PrivateDNS ?? $ConfigNetworkNvaJson.parameters.privateDnsZones.value + $ConfigNetworkNvaJson.parameters.ddosStandard.value = $Parameters.HubNetwork.DDoS ?? $ConfigNetworkNvaJson.parameters.ddosStandard.value + + $ConfigNetworkNvaFile = "$RepoConfigPath/networking/$($Parameters.Environment.Target)/$($ConfigVariablesYaml.variables['var-hubnetwork-nva-configurationFileName'])" + Write-Output " Writing network NVA configuration file: $ConfigNetworkNvaFile" + New-Item -ItemType Directory -Path (Split-Path -Parent -Path $ConfigNetworkNvaFile) -Force | Out-Null + $ConfigNetworkNvaJson | ConvertTo-Json -Depth 100 | Set-Content -Path $ConfigNetworkNvaFile | Out-Null + } else { + Write-Output " Source environment network NVA configuration file not found: $file" + } +} + +function IdentityConfiguration { + param ( + [Parameter(Mandatory = $true)] + [object]$Parameters, + [Parameter(Mandatory = $true)] + [object]$ConfigVariablesYaml + ) + Write-Output "" + Write-Output "Generating Identity configurations" + Write-Output "" + + $file = "$RepoConfigPath/identity/$($Parameters.Environment.Source)/$($ConfigVariablesYaml.variables['var-identity-configurationFileName'])" + if (Test-Path -PathType Leaf -Path $file) { + Write-Output " Reading source environment identity configuration file: $file" + $ConfigIdentityJson = Get-Content -Path $file -Raw | ConvertFrom-Json + + Write-Output " Updating identity configuration" + $ConfigIdentityJson.{$schema} = 'https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-platform-identity.json#' + $ConfigIdentityJson.parameters.securityCenter.value = $Parameters.Identity.SecurityCenter ?? $ConfigIdentityJson.parameters.securityCenter.value + $ConfigIdentityJson.parameters.serviceHealthAlerts.value = $Parameters.Identity.ServiceHealthAlerts ?? $ConfigIdentityJson.parameters.serviceHealthAlerts.value + $ConfigIdentityJson.parameters.subscriptionRoleAssignments.value = $Parameters.Identity.RoleAssignments ?? $ConfigIdentityJson.parameters.subscriptionRoleAssignments.value + $ConfigIdentityJson.parameters.subscriptionTags.value = $Parameters.values.Identity.SubscriptionTags ?? $ConfigIdentityJson.parameters.subscriptionTags.value + $ConfigIdentityJson.parameters.resourceTags.value = $Parameters.values.Identity.ResourceTags ?? $ConfigIdentityJson.parameters.resourceTags.value + $ConfigIdentityJson.parameters.hubNetwork.value.virtualNetworkId = "/subscriptions/$($ConfigVariablesYaml.variables['var-hubnetwork-subscriptionId'])/resourceGroups/$($ConfigNetworkAzfwJson.parameters.hub.value.resourceGroupName)/providers/Microsoft.Network/virtualNetworks/$($ConfigNetworkAzfwJson.parameters.hub.value.network.name)" + + $ConfigIdentityFile = "$RepoConfigPath/identity/$($Parameters.Environment.Target)/$($ConfigVariablesYaml.variables['var-identity-configurationFileName'])" + Write-Output " Writing identity configuration file: $ConfigIdentityFile" + New-Item -ItemType Directory -Path (Split-Path -Parent -Path $ConfigIdentityFile) -Force | Out-Null + $ConfigIdentityJson | ConvertTo-Json -Depth 100 | Set-Content -Path $ConfigIdentityFile | Out-Null + } else { + Write-Output " Source environment identity configuration file not found: $file" + } +} + +function SubscriptionConfiguration { + param ( + [Parameter(Mandatory = $true)] + [object]$Parameters, + [object]$ConfigNetworkAzfwJson + ) + Write-Output "" + Write-Output "Generating subscription configurations" + + foreach ($subscription in $Parameters.Subscriptions) { + $pattern = $subscription.keys[0] + + Write-Output "" + Write-Output " Looking for source environment subscription configuration file(s) matching specified pattern ($pattern)" + $templates = @(Get-ChildItem -Path "$RepoConfigPath/subscriptions/$($Parameters.Environment.Source)/*" -File -Recurse | ? { $_.Name -match $pattern }) + if ($templates.Count -gt 0) { + if ($templates.Count -gt 1) { + Write-Output " More than 1 source environment subscription configuration file(s) matching specified pattern found ($pattern); using the first one found" + } + $ConfigSubscriptionFile = $templates[0] + Write-Output " Reading subscription configuration ($($ConfigSubscriptionFile.Name))" + $ConfigSubscriptionJson = Get-Content -Path $ConfigSubscriptionFile.FullName -Raw | ConvertFrom-Json + } else { + Write-Output " Source environment subscription configuration file(s) matching specified pattern not found ($pattern)" + continue + } + + Write-Output " Updating subscription configuration" + $ConfigSubscriptionArchetype = $ConfigSubscriptionFile.Name.Split('_')[1] + $ConfigSubscriptionJson.{$schema} = "https://raw.githubusercontent.com/Azure/CanadaPubSecALZ/main/schemas/latest/landingzones/lz-$($ConfigSubscriptionArchetype).json#" + # Not all subscription configuration files have a location parameter + if ($ConfigSubscriptionJson.parameters.location -ne $null) { + $ConfigSubscriptionJson.parameters.location.value = $subscription.values.Location ?? $ConfigSubscriptionJson.parameters.location.value + } + # Not all subscription configuration files have a privateDnsManagedByHub parameter + if ($ConfigSubscriptionJson.parameters.hubNetwork.value.privateDnsManagedByHub -ne $null) { + $ConfigSubscriptionJson.parameters.hubNetwork.value.privateDnsManagedByHub = $Parameters.HubNetwork.PrivateDNS.Enabled ?? $ConfigSubscriptionJson.parameters.hubNetwork.value.privateDnsManagedByHub + } + # Not all subscription configuration files have a privateDnsManagedByHubSubscriptionId parameter + if ($ConfigSubscriptionJson.parameters.hubNetwork.value.privateDnsManagedByHubSubscriptionId -ne $null) { + $ConfigSubscriptionJson.parameters.hubNetwork.value.privateDnsManagedByHubSubscriptionId = $Parameters.HubNetwork.SubscriptionId ?? $ConfigSubscriptionJson.parameters.hubNetwork.value.privateDnsManagedByHubSubscriptionId + } + # Not all subscription configuration files have a privateDnsManagedByHub parameter + if ($ConfigSubscriptionJson.parameters.hubNetwork.value.privateDnsManagedByHubResourceGroupName -ne $null) { + $ConfigSubscriptionJson.parameters.hubNetwork.value.privateDnsManagedByHubResourceGroupName = $Parameters.HubNetwork.PrivateDNS.ResourceGroupName ?? $ConfigSubscriptionJson.parameters.hubNetwork.value.privateDnsManagedByHubResourceGroupName + } + # All subscription configuration files have the following parameters + $ConfigSubscriptionJson.parameters.securityCenter.value = $subscription.values.SecurityCenter ?? $ConfigSubscriptionJson.parameters.securityCenter.value + $ConfigSubscriptionJson.parameters.serviceHealthAlerts.value = $subscription.values.ServiceHealthAlerts ?? $ConfigSubscriptionJson.parameters.serviceHealthAlerts.value + $ConfigSubscriptionJson.parameters.subscriptionRoleAssignments.value = $subscription.values.RoleAssignments ?? $ConfigSubscriptionJson.parameters.subscriptionRoleAssignments.value + $ConfigSubscriptionJson.parameters.subscriptionTags.value = $subscription.values.SubscriptionTags ?? $ConfigSubscriptionJson.parameters.subscriptionTags.value + $ConfigSubscriptionJson.parameters.resourceTags.value = $subscription.values.ResourceTags ?? $ConfigSubscriptionJson.parameters.resourceTags.value + $ConfigSubscriptionJson.parameters.hubNetwork.value.virtualNetworkId = "/subscriptions/$($ConfigVariablesYaml.variables['var-hubnetwork-subscriptionId'])/resourceGroups/$($ConfigNetworkAzfwJson.parameters.hub.value.resourceGroupName)/providers/Microsoft.Network/virtualNetworks/$($ConfigNetworkAzfwJson.parameters.hub.value.network.name)" + + $NewConfigSubscriptionFile = "$RepoConfigPath/subscriptions/$($Parameters.Environment.Target)/$($subscription.values.ManagementGroupId)/$($subscription.values.SubscriptionId)_$($ConfigSubscriptionArchetype)_$($subscription.values.Location).json" + Write-Output " Writing new subscription configuration ($($NewConfigSubscriptionFile))" + New-Item -ItemType Directory -Path (Split-Path -Parent -Path $NewConfigSubscriptionFile) -Force | Out-Null + $ConfigSubscriptionJson | ConvertTo-Json -Depth 100 | Set-Content -Path $NewConfigSubscriptionFile | Out-Null + } + + Write-Output "" +} + +# Set script variables +$RepoConfigPath = (Resolve-Path -Path "$RepoRootPath/config").Path +$ParameterFile = (Resolve-Path -Path "$UserConfigPath/$Environment.yml").Path + +# Ensure paths exist and are normalized to the OS path format +New-Item -ItemType Directory -Path $UserCredsPath -Force | Out-Null +$UserCredsPath = (Resolve-Path -Path $UserCredsPath).Path +New-Item -ItemType Directory -Path $UserLogsPath -Force | Out-Null +$UserLogsPath = (Resolve-Path -Path $UserLogsPath).Path +New-Item -ItemType Directory -Path $UserConfigPath -Force | Out-Null +$UserConfigPath = (Resolve-Path -Path $UserConfigPath).Path + +# Local variables +$date = Get-Date -Format "yyMMdd-HHmmss-fff" +$script = $(Split-Path -Path $PSCommandPath -LeafBase) +$logFile = "$UserLogsPath/$date-$script-$Environment.log" +$stopWatch = [System.Diagnostics.Stopwatch]::New() + +try { + $stopWatch.Restart() + + Write-Output "" | Tee-Object -FilePath $logFile -Append + Write-Output "This script creates a new set of configuration files, using an existing CanadaPubSecALZ configuration. Select configuration elements are replaced with values specific to the target environment." | Tee-Object -FilePath $logFile -Append + Write-Output "" | Tee-Object -FilePath $logFile -Append + + Write-Output "Reading parameters from file ($ParameterFile)" + if (-not (Test-Path $ParameterFile)) { + throw "Parameter file '$ParameterFile' does not exist." + } + $Parameters = Get-Content $ParameterFile -Raw | ConvertFrom-Yaml + + ValidateParameters -Parameters $Parameters -ParameterFile $ParameterFile ` + | Tee-Object -FilePath $logFile -Append + + $ConfigVariablesYaml = @{} + VariablesConfiguration -Parameters $Parameters -ConfigVariablesByRef ([ref]$ConfigVariablesYaml) ` + | Tee-Object -FilePath $logFile -Append + + LoggingConfiguration -Parameters $Parameters -ConfigVariablesYaml $ConfigVariablesYaml ` + | Tee-Object -FilePath $logFile -Append + + $ConfigNetworkAzfwJson = @{} + NetworkAzfwConfiguration -Parameters $Parameters -ConfigVariablesYaml $ConfigVariablesYaml -ConfigNetworkAzfwByRef ([ref]$ConfigNetworkAzfwJson) ` + | Tee-Object -FilePath $logFile -Append + + NetworkAzfwPolicyConfiguration -Parameters $Parameters -ConfigVariablesYaml $ConfigVariablesYaml ` + | Tee-Object -FilePath $logFile -Append + + NetworkNvaConfiguration -Parameters $Parameters -ConfigVariablesYaml $ConfigVariablesYaml ` + | Tee-Object -FilePath $logFile -Append + + IdentityConfiguration -Parameters $Parameters -ConfigVariablesYaml $ConfigVariablesYaml ` + | Tee-Object -FilePath $logFile -Append + + SubscriptionConfiguration -Parameters $Parameters -ConfigNetworkAzfwJson $ConfigNetworkAzfwJson ` + | Tee-Object -FilePath $logFile -Append + +} catch { + Write-Output $_ | Tee-Object -FilePath $logFile -Append + Write-Output $_.Exception | Tee-Object -FilePath $logFile -Append + throw +} finally { + Write-Output "Elapsed time: $($stopWatch.Elapsed)" ` + | Tee-Object -FilePath $logFile -Append +} diff --git a/scripts/configuration/New-AlzCredential.ps1 b/scripts/configuration/New-AlzCredential.ps1 new file mode 100644 index 00000000..58e64d40 --- /dev/null +++ b/scripts/configuration/New-AlzCredential.ps1 @@ -0,0 +1,130 @@ +<# +---------------------------------------------------------------------------------- +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. + +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +---------------------------------------------------------------------------------- +#> + +<# + .SYNOPSIS + Creates a Service Principal for the specified environment. + + .DESCRIPTION + Creates a Service Principal for the specified environment. + + .PARAMETER Environment + The name of the environment. This is typically the repo/org name. + + .PARAMETER UserRootPath + The root path for the log, credential, and configuration files. Defaults to $HOME. + + .PARAMETER UserLogsPath + The path for the log files. Defaults to $UserRootPath/ALZ/logs. + + .PARAMETER UserCredsPath + The path for the credential files. Defaults to $UserRootPath/ALZ/credentials. + + .PARAMETER UserConfigPath + The path for the configuration files. Defaults to $UserRootPath/ALZ/config. + + .EXAMPLE + PS> .\New-AlzCredential.ps1 -Environment 'CanadaALZ' + + .EXAMPLE + PS> .\New-AlzCredential.ps1 -Environment 'CanadaALZ' -UserRootPath 'C:\Users\me\ALZ' + + .EXAMPLE + PS> .\New-AlzCredential.ps1 -Environment 'CanadaALZ' -UserLogsPath 'C:\Users\me\ALZ\logs' + + .EXAMPLE + PS> .\New-AlzCredential.ps1 -Environment 'CanadaALZ' -UserCredsPath 'C:\Users\me\ALZ\credentials' + + .EXAMPLE + PS> .\New-AlzCredential.ps1 -Environment 'CanadaALZ' -UserConfigPath 'C:\Users\me\ALZ\config' +#> + +[CmdletBinding()] +Param( + [Parameter(Mandatory = $true)] + [string]$Environment, + + [string]$UserRootPath = "$HOME", + + [string]$UserLogsPath = "$UserRootPath/ALZ/logs", + + [string]$UserCredsPath = "$UserRootPath/ALZ/credentials", + + [string]$UserConfigPath = "$UserRootPath/ALZ/config" +) + +$ErrorActionPreference = "Stop" + +function CreateServicePrincipal { + param( + [string]$Environment = $Environment, + [string]$UserCredsPath = $UserCredsPath + ) + <# Create JSON representation of the service principal using the Azure CLI + if ((az account show) -eq $null) { + throw "You must be logged in to Azure via the Azure CLI to create a service principal." + } + $json = (az ad sp create-for-rbac --name $Environment --role "Owner" --scopes "/") + #> + + # Create JSON representation of the service principal using Azure PowerShell + $context = Get-AzContext + if ($context -eq $null) { + throw "You must be logged in to Azure via Azure PowerShell to create a service principal." + } + + $tenant = Get-AzTenant -TenantId $context.Tenant.Id + Write-Output "Creating Service Principal for environment ($Environment) in tenant ($($tenant.DefaultDomain)))" + $sp = New-AzADServicePrincipal -DisplayName $Environment -Role Owner -Scope "/" + Write-Output " appId: $($sp.AppId)" + Write-Output " displayName: $($sp.DisplayName)" + Write-Output " password: **********" + Write-Output " tenant: $($context.Tenant.Id)" + $json = @{ + appId = $sp.AppId + displayName = $sp.DisplayName + password = $sp.PasswordCredentials.SecretText + tenant = $context.Tenant.Id + } | ConvertTo-Json + + # Save the service principal to a file + $credentialFile = "$UserCredsPath/$Environment.json" + Write-Output "Saving Service Principal to file ($credentialFile)" + Set-Content -Value $json -Path $credentialFile +} + +# Ensure paths exist and are normalized to the OS path format +New-Item -ItemType Directory -Path $UserCredsPath -Force | Out-Null +$UserCredsPath = (Resolve-Path -Path $UserCredsPath).Path +New-Item -ItemType Directory -Path $UserLogsPath -Force | Out-Null +$UserLogsPath = (Resolve-Path -Path $UserLogsPath).Path +New-Item -ItemType Directory -Path $UserConfigPath -Force | Out-Null +$UserConfigPath = (Resolve-Path -Path $UserConfigPath).Path + +# Local variables +$date = Get-Date -Format "yyMMdd-HHmmss-fff" +$script = $(Split-Path -Path $PSCommandPath -LeafBase) +$logFile = "$UserLogsPath/$date-$script-$Environment.log" +$stopWatch = [System.Diagnostics.Stopwatch]::New() + +# Create the service principal +try { + $stopWatch.Restart() + CreateServicePrincipal -Environment $Environment -CredsPath $UserCredsPath ` + | Tee-Object -FilePath $logFile -Append +} catch { + Write-Output $_ | Tee-Object -FilePath $logFile -Append + Write-Output $_.Exception | Tee-Object -FilePath $logFile -Append + throw +} finally { + Write-Output "Elapsed time: $($stopWatch.Elapsed)" ` + | Tee-Object -FilePath $logFile -Append +} diff --git a/scripts/configuration/New-AlzDeployment.ps1 b/scripts/configuration/New-AlzDeployment.ps1 new file mode 100644 index 00000000..9cf4c740 --- /dev/null +++ b/scripts/configuration/New-AlzDeployment.ps1 @@ -0,0 +1,215 @@ +<# +---------------------------------------------------------------------------------- +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. + +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +---------------------------------------------------------------------------------- +#> + +<# + .SYNOPSIS + This script creates a CanadaPubSecALZ deployment, based on information present in the configuration files. + + .DESCRIPTION + This script creates a CanadaPubSecALZ deployment, based on information present in the configuration files. + + .PARAMETER Environment + The name of the environment to deploy. + + .PARAMETER NetworkType + The type of network to deploy. Valid values are "AzFW" and "NVA". Default is "AzFW". + + .PARAMETER CredentialFile + The path to the credential file to use for login. + + .PARAMETER SecureServicePrincipal + The service principal to use for login. + + .PARAMETER TenantId + The tenant ID to use for interactive login. + + .PARAMETER RepoRootPath + The path to the repository directory. + + .PARAMETER UserRootPath + The path to the user directory. + + .PARAMETER UserLogsPath + The path to the user logs directory. + + .PARAMETER UserCredsPath + The path to the user credentials directory. + + .PARAMETER UserConfigPath + The path to the user configuration directory. + + .EXAMPLE + PS> .\New-AlzDeployment.ps1 -Environment 'CanadaALZ-main' -CredentialFile 'CanadaALZ' -NetworkType 'AzFW' + + Deploy the CanadaALZ-main environment with Azure Firewall hub network using a credential file. + + .EXAMPLE + PS> .\New-AlzDeployment.ps1 -Environment 'CanadaALZ-main' -SecureServicePrincipal $SecureSP -NetworkType 'NVA' + + Deploy the CanadaALZ-main environment with NVA hub network using a service principal. +#> + +[CmdletBinding()] +Param( + [Parameter(Mandatory = $true)] + [string]$Environment, + + [Parameter(Mandatory = $true)] + [ValidateSet("AzFW", "NVA")] + [string]$NetworkType, + + [Parameter(Mandatory = $true, ParameterSetName = "CredentialFile")] + [string]$CredentialFile, + + [Parameter(Mandatory = $true, ParameterSetName = "ServicePrincipal")] + [SecureString]$SecureServicePrincipal, + + [Parameter(Mandatory = $true, ParameterSetName = "Interactive")] + [string]$TenantId, + + [string]$RepoRootPath = "../..", + + [string]$UserRootPath = "$HOME", + + [string]$UserLogsPath = "$UserRootPath/ALZ/logs", + + [string]$UserCredsPath = "$UserRootPath/ALZ/credentials", + + [string]$UserConfigPath = "$UserRootPath/ALZ/config" +) + +$ErrorActionPreference = "Stop" + +#region Functions + +function CreateDeployment { + param( + [string]$Environment, + [string]$RepoRootPath, + [string]$NetworkType, + [string[]]$SubscriptionIds + ) + try { + Push-Location -Path "$RepoRootPath/scripts/deployments" + if ($NetworkType -ieq "AzFW") { + Write-Output "Deploying environment ($Environment) with Azure Firewall" + .\RunWorkflows.ps1 ` + -EnvironmentName $Environment ` + -DeployManagementGroups ` + -DeployRoles ` + -DeployLogging ` + -DeployCustomPolicyDefinitions ` + -DeployCustomPolicySetDefinitions ` + -DeployCustomPolicySetAssignments ` + -DeployBuiltinPolicySetAssignments ` + -DeployAzureFirewallPolicy ` + -DeployHubNetworkWithAzureFirewall ` + -DeployIdentity ` + -DeploySubscriptionIds $SubscriptionIds + } elseif ($NetworkType -ieq "NVA") { + Write-Output "Generating temporary NVA credentials" + $nvaUsername = ConvertTo-SecureString -String ($env:USER ?? $env:USERNAME) -AsPlainText + $nvaPassword = Get-RandomPassword -Length 16 -StartWithLetter + + Write-Output "Deploying environment ($Environment) with NVA firewall" + Write-Output "NVA credentials (save these in a secure location" + Write-Output " Username: $(ConvertFrom-SecureString -SecureString $nvaUsername -AsPlainText)" + Write-Output " Password: $(ConvertFrom-SecureString -SecureString $nvaPassword -AsPlainText)" + + .\RunWorkflows.ps1 ` + -EnvironmentName $Environment ` + -DeployManagementGroups ` + -DeployRoles ` + -DeployLogging ` + -DeployCustomPolicyDefinitions ` + -DeployCustomPolicySetDefinitions ` + -DeployCustomPolicySetAssignments ` + -DeployBuiltinPolicySetAssignments ` + -DeployHubNetworkWithNVA ` + -NvaUserName $nvaUsername ` + -NvaPassword $nvaPassword ` + -DeployIdentity ` + -DeploySubscriptionIds $SubscriptionIds + + } else { + throw "Invalid network type ($NetworkType)" + } + } catch { + throw + } finally { + Pop-Location + } +} + +#endregion Functions + +# Ensure paths exist and are normalized to the OS path format +New-Item -ItemType Directory -Path $UserCredsPath -Force | Out-Null +$UserCredsPath = (Resolve-Path -Path $UserCredsPath).Path +New-Item -ItemType Directory -Path $UserLogsPath -Force | Out-Null +$UserLogsPath = (Resolve-Path -Path $UserLogsPath).Path +New-Item -ItemType Directory -Path $UserConfigPath -Force | Out-Null +$UserConfigPath = (Resolve-Path -Path $UserConfigPath).Path + +# Local variables +$date = Get-Date -Format "yyMMdd-HHmmss-fff" +$script = $(Split-Path -Path $PSCommandPath -LeafBase) +$logFile = "$UserLogsPath/$date-$script-$Environment.log" +$stopWatch = [System.Diagnostics.Stopwatch]::New() + +try { + $stopWatch.Restart() + + Write-Output "" | Tee-Object -FilePath $logFile -Append + Write-Output "This script creates a new deployment, using an existing CanadaPubSecALZ configuration ($Environment)." | Tee-Object -FilePath $logFile -Append + Write-Output "" | Tee-Object -FilePath $logFile -Append + + $ConfigVariablesYaml = @{} + .\Get-AlzConfiguration.ps1 -Environment $Environment -RepoRootPath $RepoRootPath -ConfigVariablesByRef ([ref]$ConfigVariablesYaml) ` + | Tee-Object -FilePath $logFile -Append + + $mgh = ($ConfigVariablesYaml.variables['var-managementgroup-hierarchy'] | ConvertFrom-Json) + + switch ($PSCmdlet.ParameterSetName) { + "CredentialFile" { + .\Connect-AlzCredential.ps1 -CredentialFile "$UserCredsPath/$CredentialFile.json" ` + | Tee-Object -FilePath $logFile -Append + } + "ServicePrincipal" { + .\Connect-AlzCredential.ps1 -SecureServicePrincipal $SecureServicePrincipal ` + | Tee-Object -FilePath $logFile -Append + } + "Interactive" { + .\Connect-AlzCredential.ps1 -TenantId $mgh.id ` + | Tee-Object -FilePath $logFile -Append + } + } + + $context = Get-AzContext + if ($context.Tenant.Id -ne $mgh.id) { + throw "You are not logged in to the correct tenant. You are logged in to $($context.Tenant.Id), but you should be logged in to $($mgh.id)." + } + + $SubscriptionIds = @() + .\Get-AlzSubscriptions.ps1 -Environment $Environment -RepoRootPath $RepoRootPath -SubscriptionIdsByRef ([ref]$SubscriptionIds) ` + | Tee-Object -FilePath $logFile -Append + + CreateDeployment -Environment $Environment -RepoRootPath $RepoRootPath -NetworkType $NetworkType -SubscriptionIds $SubscriptionIds ` + | Tee-Object -FilePath $logFile -Append + +} catch { + Write-Output $_ | Tee-Object -FilePath $logFile -Append + Write-Output $_.Exception | Tee-Object -FilePath $logFile -Append + throw +} finally { + Write-Output "Elapsed time: $($stopWatch.Elapsed)" ` + | Tee-Object -FilePath $logFile -Append +} diff --git a/scripts/configuration/Remove-AlzConfiguration.ps1 b/scripts/configuration/Remove-AlzConfiguration.ps1 new file mode 100644 index 00000000..5fceb8d7 --- /dev/null +++ b/scripts/configuration/Remove-AlzConfiguration.ps1 @@ -0,0 +1,150 @@ +<# +---------------------------------------------------------------------------------- +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. + +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +---------------------------------------------------------------------------------- +#> + +<# + .SYNOPSIS + Removes an existing environment configuration for a CanadaPubSecALZ deployment. + + .DESCRIPTION + This script removes an existing set of environment configuration files. + + .PARAMETER Environment + The name of the environment to remove. + + .PARAMETER RepoRootPath + The path to the repository directory. Defaults to ..\.. + + .PARAMETER UserRootPath + The path to the user directory. Defaults to $HOME. + + .PARAMETER UserLogsPath + The path to the user logs directory. Defaults to $UserRootPath/ALZ/logs. + + .PARAMETER UserCredsPath + The path to the user credentials directory. Defaults to $UserRootPath/ALZ/credentials. + + .PARAMETER UserConfigPath + The path to the user configuration directory. Defaults to $UserRootPath/ALZ/config. + + .EXAMPLE + PS> .\Remove-AlzConfiguration.ps1 -Environment 'DevOpsOrg-branch' +#> + +[CmdletBinding()] +Param( + [Parameter(Mandatory = $true)] + [string]$Environment, + + [string]$RepoRootPath = "../..", + + [string]$UserRootPath = "$HOME", + + [string]$UserLogsPath = "$UserRootPath/ALZ/logs", + + [string]$UserCredsPath = "$UserRootPath/ALZ/credentials", + + [string]$UserConfigPath = "$UserRootPath/ALZ/config" +) + +$ErrorActionPreference = "Stop" + +function RemovePaths { + param( + [string]$Environment, + [string]$RepoRootPath + ) + # Validate Parameters + $RepoConfigPath = (Resolve-Path -Path "$RepoRootPath/config").Path + Write-Output "Checking configuration path ($RepoConfigPath)" + if (-not (Test-Path -PathType Container -Path $RepoConfigPath)) { + throw "Configuration path does not exist." + } + + # Remove variables configuration file + $path = "$RepoConfigPath/variables/$Environment.yml" + if (Test-Path -PathType Leaf -Path $path) { + Write-Output "Removing variables configuration file: $path" + Remove-Item -Path $path + } else { + Write-Output "Variables configuration file not found ($path)" + } + + # Remove logging configuration file(s) + $path = "$RepoConfigPath/logging/$Environment" + if (Test-Path -PathType Container -Path $path) { + Write-Output "Removing logging configuration directory: $path" + Remove-Item -Path $path -Recurse + } else { + Write-Output "Logging configuration directory not found ($path)" + } + + # Remove identity configuration file(s) + $path = "$RepoConfigPath/identity/$Environment" + if (Test-Path -PathType Container -Path $path) { + Write-Output "Removing identity configuration directory: $path" + Remove-Item -Path $path -Recurse + } else { + Write-Output "Identity configuration directory not found ($path)" + } + + # Remove network configuration file(s) + $path = "$RepoConfigPath/networking/$Environment" + if (Test-Path -PathType Container -Path $path) { + Write-Output "Removing network configuration directory: $path" + Remove-Item -Path $path -Recurse + } else { + Write-Output "Network configuration directory not found ($path)" + } + + # Remove subscription configuration file(s) + $path = "$RepoConfigPath/subscriptions/$Environment" + if (Test-Path -PathType Container -Path $path) { + Write-Output "Removing subscription configuration directory: $path" + Remove-Item -Path $path -Recurse + } else { + Write-Output "Subscription configuration directory not found ($path)" + } + + Write-Output "" +} + +# Ensure paths exist and are normalized to the OS path format +New-Item -ItemType Directory -Path $UserCredsPath -Force | Out-Null +$UserCredsPath = (Resolve-Path -Path $UserCredsPath).Path +New-Item -ItemType Directory -Path $UserLogsPath -Force | Out-Null +$UserLogsPath = (Resolve-Path -Path $UserLogsPath).Path +New-Item -ItemType Directory -Path $UserConfigPath -Force | Out-Null +$UserConfigPath = (Resolve-Path -Path $UserConfigPath).Path + +# Local variables +$date = Get-Date -Format "yyMMdd-HHmmss-fff" +$script = $(Split-Path -Path $PSCommandPath -LeafBase) +$logFile = "$UserLogsPath/$date-$script-$Environment.log" +$stopWatch = [System.Diagnostics.Stopwatch]::New() + +try { + $stopWatch.Restart() + + Write-Output "" | Tee-Object -FilePath $logFile -Append + Write-Output "This script removes an existing set of configuration files." | Tee-Object -FilePath $logFile -Append + Write-Output "" | Tee-Object -FilePath $logFile -Append + + RemovePaths -Environment $Environment -RepoRootPath $RepoRootPath ` + | Tee-Object -FilePath $logFile -Append + +} catch { + Write-Output $_ | Tee-Object -FilePath $logFile -Append + Write-Output $_.Exception | Tee-Object -FilePath $logFile -Append + throw +} finally { + Write-Output "Elapsed time: $($stopWatch.Elapsed)" ` + | Tee-Object -FilePath $logFile -Append +} diff --git a/scripts/configuration/Remove-AlzCredential.ps1 b/scripts/configuration/Remove-AlzCredential.ps1 new file mode 100644 index 00000000..239dc6b0 --- /dev/null +++ b/scripts/configuration/Remove-AlzCredential.ps1 @@ -0,0 +1,121 @@ +<# +---------------------------------------------------------------------------------- +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. + +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +---------------------------------------------------------------------------------- +#> + +<# + .SYNOPSIS + Removes a Service Principal for the specified environment. + + .DESCRIPTION + Removes a Service Principal for the specified environment. + + .PARAMETER Environment + The name of the environment. This is typically the repo/org name. + + .PARAMETER RootPath + The root path for the log, credential, and configuration files. Defaults to $HOME. + + .PARAMETER LogsPath + The path for the log files. Defaults to $UserRootPath/ALZ/logs. + + .PARAMETER CredsPath + The path for the credential files. Defaults to $UserRootPath/ALZ/credentials. + + .PARAMETER ConfigPath + The path for the configuration files. Defaults to $UserRootPath/ALZ/config. + + .EXAMPLE + PS> .\Remove-AlzCredential.ps1 -Environment 'CanadaALZ' + + .EXAMPLE + PS> .\Remove-AlzCredential.ps1 -Environment 'CanadaALZ' -UserRootPath 'C:\Users\me\ALZ' + + .EXAMPLE + PS> .\Remove-AlzCredential.ps1 -Environment 'CanadaALZ' -UserLogsPath 'C:\Users\me\ALZ\logs' + + .EXAMPLE + PS> .\Remove-AlzCredential.ps1 -Environment 'CanadaALZ' -UserCredsPath 'C:\Users\me\ALZ\credentials' + + .EXAMPLE + PS> .\Remove-AlzCredential.ps1 -Environment 'CanadaALZ' -UserConfigPath 'C:\Users\me\ALZ\config' +#> + +[CmdletBinding()] +Param( + [Parameter(Mandatory = $true)] + [string]$Environment, + + [string]$UserRootPath = "$HOME", + + [string]$UserLogsPath = "$UserRootPath/ALZ/logs", + + [string]$UserCredsPath = "$UserRootPath/ALZ/credentials", + + [string]$UserConfigPath = "$UserRootPath/ALZ/config" +) + +$ErrorActionPreference = "Stop" + +function RemoveServicePrincipal { + param( + [string]$Environment = $Environment, + [string]$UserCredsPath = $UserCredsPath + ) + + $credentialFile = "$UserCredsPath/$Environment.json" + if (!(Test-Path -Path $credentialFile)) { + throw "Service principal file ($credentialFile) does not exist." + } + + $context = Get-AzContext + if ($context -eq $null) { + throw "You must be logged in to Azure via Azure PowerShell to remove a service principal." + } + + try { + $sp = (Get-Content -Raw -Path $credentialFile | ConvertFrom-Json -Depth 100) + Write-Output "Removing Service Principal ($($sp.displayName)) for environment ($Environment) from tenant ($($sp.tenant))))" + Remove-AzADServicePrincipal -ApplicationId $sp.appId + Remove-AzADApplication -DisplayName $sp.displayName + } catch { + throw "Failed to remove Service Principal ($($sp.displayName)) for environment ($Environment) from tenant ($($sp.tenant))): $($_.Exception.Message)" + } + + Write-Output "Removing Service Principal file ($credentialFile)" + Remove-Item -Path $credentialFile -Force +} + +# Ensure paths exist and are normalized to the OS path format +New-Item -ItemType Directory -Path $UserCredsPath -Force | Out-Null +$UserCredsPath = (Resolve-Path -Path $UserCredsPath).Path +New-Item -ItemType Directory -Path $UserLogsPath -Force | Out-Null +$UserLogsPath = (Resolve-Path -Path $UserLogsPath).Path +New-Item -ItemType Directory -Path $UserConfigPath -Force | Out-Null +$UserConfigPath = (Resolve-Path -Path $UserConfigPath).Path + +# Local variables +$date = Get-Date -Format "yyMMdd-HHmmss-fff" +$script = $(Split-Path -Path $PSCommandPath -LeafBase) +$logFile = "$UserLogsPath/$date-$script-$Environment.log" +$stopWatch = [System.Diagnostics.Stopwatch]::New() + +# Create the service principal +try { + $stopWatch.Restart() + RemoveServicePrincipal -Environment $Environment -CredsPath $UserCredsPath ` + | Tee-Object -FilePath $logFile -Append +} catch { + Write-Output $_ | Tee-Object -FilePath $logFile -Append + Write-Output $_.Exception | Tee-Object -FilePath $logFile -Append + throw +} finally { + Write-Output "Elapsed time: $($stopWatch.Elapsed)" ` + | Tee-Object -FilePath $logFile -Append +} diff --git a/scripts/configuration/Test-AlzCredential.ps1 b/scripts/configuration/Test-AlzCredential.ps1 new file mode 100644 index 00000000..fa8ee5ff --- /dev/null +++ b/scripts/configuration/Test-AlzCredential.ps1 @@ -0,0 +1,136 @@ +<# +---------------------------------------------------------------------------------- +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. + +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +---------------------------------------------------------------------------------- +#> + +<# + .SYNOPSIS + Tests a Service Principal for the specified environment. + + .DESCRIPTION + Tests a Service Principal for the specified environment. + + .PARAMETER Environment + The name of the environment. This is typically the repo/org name. + + .PARAMETER UserRootPath + The root path for the log, credential, and configuration files. Defaults to $HOME. + + .PARAMETER UserLogsPath + The path for the log files. Defaults to $UserRootPath/ALZ/logs. + + .PARAMETER UserCredsPath + The path for the credential files. Defaults to $UserRootPath/ALZ/credentials. + + .PARAMETER UserConfigPath + The path for the configuration files. Defaults to $UserRootPath/ALZ/config. + + .EXAMPLE + PS> .\Test-AlzCredential.ps1 -Environment 'CanadaALZ' + + .EXAMPLE + PS> .\Test-AlzCredential.ps1 -Environment 'CanadaALZ' -UserRootPath 'C:\Users\me\ALZ' + + .EXAMPLE + PS> .\Test-AlzCredential.ps1 -Environment 'CanadaALZ' -UserLogsPath 'C:\Users\me\ALZ\logs' + + .EXAMPLE + PS> .\Test-AlzCredential.ps1 -Environment 'CanadaALZ' -UserCredsPath 'C:\Users\me\ALZ\credentials' + + .EXAMPLE + PS> .\Test-AlzCredential.ps1 -Environment 'CanadaALZ' -UserConfigPath 'C:\Users\me\ALZ\config' +#> + +[CmdletBinding()] +Param( + [Parameter(Mandatory = $true)] + [string]$Environment, + + [string]$UserRootPath = "$HOME", + + [string]$UserLogsPath = "$UserRootPath/ALZ/logs", + + [string]$UserCredsPath = "$UserRootPath/ALZ/credentials", + + [string]$UserConfigPath = "$UserRootPath/ALZ/config" +) + +$ErrorActionPreference = "Stop" + +function TestServicePrincipal { + param( + [string]$Environment = $Environment, + [string]$UserCredsPath = $UserCredsPath + ) + + $credentialFile = "$UserCredsPath/$Environment.json" + if (!(Test-Path -Path $credentialFile)) { + throw "Service principal file ($credentialFile) does not exist." + } + + $context = Get-AzContext + if ($context -eq $null) { + throw "You must be logged in to Azure via Azure PowerShell to test a service principal." + } + + $sp = (Get-Content -Raw -Path $credentialFile | ConvertFrom-Json -Depth 100) + + $role = Get-AzRoleAssignment -ServicePrincipalName $sp.appId + + Write-Output "" + if (($role | where { $_.RoleDefinitionName -eq 'Owner' -and $_.Scope -eq '/' }).Count -lt 1) { + throw "Service Principal ($($sp.displayName)) for environment ($Environment) from tenant ($($sp.tenant)) is not an Owner of the tenant." + } else { + Write-Output "Service Principal ($($sp.displayName)) for environment ($Environment) from tenant ($($sp.tenant)) is an Owner of the tenant." + } + + try { + Write-Output "" + Write-Output "Current Azure context:" + Get-AzContext + .\Connect-AlzCredential.ps1 -CredentialFile $credentialFile + Write-Output "" + Write-Output "Service Principal Azure context:" + Get-AzContext + Disconnect-AzAccount + Write-Output "" + Write-Output "Original Azure context:" + Get-AzContext + } catch { + throw + } +} + +# Ensure paths exist and are normalized to the OS path format +New-Item -ItemType Directory -Path $UserCredsPath -Force | Out-Null +$UserCredsPath = (Resolve-Path -Path $UserCredsPath).Path +New-Item -ItemType Directory -Path $UserLogsPath -Force | Out-Null +$UserLogsPath = (Resolve-Path -Path $UserLogsPath).Path +New-Item -ItemType Directory -Path $UserConfigPath -Force | Out-Null +$UserConfigPath = (Resolve-Path -Path $UserConfigPath).Path + +# Local variables +$date = Get-Date -Format "yyMMdd-HHmmss-fff" +$script = $(Split-Path -Path $PSCommandPath -LeafBase) +$logFile = "$UserLogsPath/$date-$script-$Environment.log" +$stopWatch = [System.Diagnostics.Stopwatch]::New() + +# Create the service principal +try { + $stopWatch.Restart() + TestServicePrincipal -Environment $Environment -CredsPath $UserCredsPath ` + | Tee-Object -FilePath $logFile -Append +} catch { + Write-Output $_ | Tee-Object -FilePath $logFile -Append + Write-Output $_.Exception | Tee-Object -FilePath $logFile -Append + throw +} finally { + Write-Output "Elapsed time: $($stopWatch.Elapsed)" ` + | Tee-Object -FilePath $logFile -Append +} diff --git a/scripts/deployments/Functions/EnvironmentContext.ps1 b/scripts/deployments/Functions/EnvironmentContext.ps1 index 347af7d7..54ff4447 100644 --- a/scripts/deployments/Functions/EnvironmentContext.ps1 +++ b/scripts/deployments/Functions/EnvironmentContext.ps1 @@ -8,8 +8,6 @@ EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. ---------------------------------------------------------------------------------- #> - -#Requires -Modules powershell-yaml Import-Module powershell-yaml @@ -22,7 +20,7 @@ function New-EnvironmentContext { [string] $Environment ) - $EnvironmentConfigurationYamlFilePath = "$WorkingDirectory/config/variables/$Environment.yml" + $EnvironmentConfigurationYamlFilePath = (Resolve-Path -Path "$WorkingDirectory/config/variables/$Environment.yml").Path # Load main environment variables file as YAML $EnvironmentConfiguration = Get-Content $EnvironmentConfigurationYamlFilePath | ConvertFrom-Yaml @@ -31,24 +29,25 @@ function New-EnvironmentContext { # Retrieve the management group hierarchy variable as JSON $ManagementGroupHierarchy = $Variables['var-managementgroup-hierarchy'] | ConvertFrom-Json - $PolicyDirectory = "$WorkingDirectory/policy" + $PolicyDirectory = (Resolve-Path -Path "$WorkingDirectory/policy").Path # Create a new context object return [PSCustomObject]@{ - WorkingDirectory = $WorkingDirectory + WorkingDirectory = (Resolve-Path -Path $WorkingDirectory).Path - RolesDirectory = "$WorkingDirectory/roles" + RolesDirectory = (Resolve-Path -Path "$WorkingDirectory/roles").Path - PolicyCustomDefinitionDirectory = "$PolicyDirectory/custom/definitions/policy" - PolicySetCustomDefinitionDirectory = "$PolicyDirectory/custom/definitions/policyset" - PolicySetCustomAssignmentsDirectory = "$PolicyDirectory/custom/assignments" - PolicySetBuiltInAssignmentsDirectory = "$PolicyDirectory/builtin/assignments" + PolicyCustomDefinitionDirectory = (Resolve-Path -Path "$PolicyDirectory/custom/definitions/policy").Path + PolicySetCustomDefinitionDirectory = (Resolve-Path -Path "$PolicyDirectory/custom/definitions/policyset").Path + PolicySetCustomAssignmentsDirectory = (Resolve-Path -Path "$PolicyDirectory/custom/assignments").Path + PolicySetBuiltInAssignmentsDirectory = (Resolve-Path -Path "$PolicyDirectory/builtin/assignments").Path - SchemaDirectory = "$WorkingDirectory/schemas/latest" + SchemaDirectory = (Resolve-Path -Path "$WorkingDirectory/schemas/latest").Path - LoggingDirectory = "$WorkingDirectory/config/logging/$Environment" - NetworkingDirectory = "$WorkingDirectory/config/networking/$Environment" - SubscriptionsDirectory = "$WorkingDirectory/config/subscriptions/$Environment" + LoggingDirectory = (Resolve-Path -Path "$WorkingDirectory/config/logging/$Environment").Path + NetworkingDirectory = (Resolve-Path -Path "$WorkingDirectory/config/networking/$Environment").Path + SubscriptionsDirectory = (Resolve-Path -Path "$WorkingDirectory/config/subscriptions/$Environment").Path + IdentityDirectory = (Resolve-Path -Path "$WorkingDirectory/config/identity/$Environment").Path Variables = $Variables ManagementGroupHierarchy = $ManagementGroupHierarchy diff --git a/scripts/deployments/Functions/HubNetworkWithAzureFirewall.ps1 b/scripts/deployments/Functions/HubNetworkWithAzureFirewall.ps1 index 5cf60bbc..aa1bc444 100644 --- a/scripts/deployments/Functions/HubNetworkWithAzureFirewall.ps1 +++ b/scripts/deployments/Functions/HubNetworkWithAzureFirewall.ps1 @@ -84,7 +84,13 @@ function Set-HubNetwork-With-AzureFirewall { [String]$AzureFirewallPolicyResourceId, [Parameter(Mandatory = $true)] - [String]$LogAnalyticsWorkspaceResourceId + [String]$LogAnalyticsWorkspaceResourceId, + + [Parameter(HelpMessage = "Number of retries to deploy the Hub Network")] + [int]$RetryCount = 5, + + [Parameter(HelpMessage = "Delay, in seconds, between retries to deploy the Hub Network")] + [double]$RetryDelay = 60 ) Set-AzContext -Subscription $SubscriptionId @@ -134,14 +140,36 @@ function Set-HubNetwork-With-AzureFirewall { subscriptionId = $SubscriptionId } - Write-Output "Deploying $PopulatedParametersFilePath to $SubscriptionId in $Region" - New-AzSubscriptionDeployment ` - -Name "main-$Region" ` - -Location $Region ` - -TemplateFile "$($Context.WorkingDirectory)/landingzones/lz-platform-connectivity-hub-azfw/main.bicep" ` - -TemplateParameterFile $PopulatedParametersFilePath ` - -Verbose - + <# This 'New-AzSubscriptionDeployment` command to deploy the hub network has been observed to fail with a transient error condition. It is wrapped in a retry loop to solve for transient errors. #> + $deployAttempt = 1 + $deployed = $false + while (($deployAttempt -le $RetryCount) -and (-not $deployed)) { + if ($deployAttempt -gt 1) { + Write-Output "Waiting $RetryDelay seconds before retrying deployment" + Start-Sleep -Seconds $RetryDelay + } + try { + Write-Output "Deploying $PopulatedParametersFilePath to $SubscriptionId in $Region - Attempt $deployAttempt of $RetryCount" + New-AzSubscriptionDeployment ` + -Name "main-$Region" ` + -Location $Region ` + -TemplateFile "$($Context.WorkingDirectory)/landingzones/lz-platform-connectivity-hub-azfw/main.bicep" ` + -TemplateParameterFile $PopulatedParametersFilePath ` + -Verbose + $deployed = $true + } + catch { + if ($deployAttempt -eq $RetryCount) { + throw + } else { + Write-Output "Error deploying $PopulatedParametersFilePath to $SubscriptionId in $Region" + Write-Output $_.Exception.Message + Write-Output $_.Exception.StackTrace + } + } + $deployAttempt++ + } + #region Check if Private DNS Zones are managed in the Hub. If so, enable Private DNS Zones policy assignment if ($Configuration.parameters.privateDnsZones.value.enabled -eq $true) { $PolicyAssignmentFilePath = "$($Context.PolicySetCustomAssignmentsDirectory)/DNSPrivateEndpoints.bicep" diff --git a/scripts/deployments/Functions/HubNetworkWithNVA.ps1 b/scripts/deployments/Functions/HubNetworkWithNVA.ps1 index ebaff2cf..64d8b648 100644 --- a/scripts/deployments/Functions/HubNetworkWithNVA.ps1 +++ b/scripts/deployments/Functions/HubNetworkWithNVA.ps1 @@ -32,7 +32,13 @@ function Set-HubNetwork-With-NVA { [SecureString]$NvaUsername = $null, [Parameter(Mandatory = $false)] - [SecureString]$NvaPassword = $null + [SecureString]$NvaPassword = $null, + + [Parameter(HelpMessage = "Number of retries to deploy the Hub Network")] + [int]$RetryCount = 5, + + [Parameter(HelpMessage = "Delay, in seconds, between retries to deploy the Hub Network")] + [double]$RetryDelay = 60 ) Set-AzContext -Subscription $SubscriptionId @@ -100,13 +106,35 @@ function Set-HubNetwork-With-NVA { subscriptionId = $SubscriptionId } - Write-Output "Deploying $PopulatedParametersFilePath to $SubscriptionId in $Region" - New-AzSubscriptionDeployment ` - -Name "main-$Region" ` - -Location $Region ` - -TemplateFile "$($Context.WorkingDirectory)/landingzones/lz-platform-connectivity-hub-nva/main.bicep" ` - -TemplateParameterFile $PopulatedParametersFilePath ` - -Verbose + <# This 'New-AzSubscriptionDeployment` command to deploy the hub network has been observed to fail with a transient error condition. It is wrapped in a retry loop to solve for transient errors. #> + $deployAttempt = 1 + $deployed = $false + while (($deployAttempt -le $RetryCount) -and (-not $deployed)) { + if ($deployAttempt -gt 1) { + Write-Output "Waiting $RetryDelay seconds before retrying deployment" + Start-Sleep -Seconds $RetryDelay + } + try { + Write-Output "Deploying $PopulatedParametersFilePath to $SubscriptionId in $Region - Attempt $deployAttempt of $RetryCount" + New-AzSubscriptionDeployment ` + -Name "main-$Region" ` + -Location $Region ` + -TemplateFile "$($Context.WorkingDirectory)/landingzones/lz-platform-connectivity-hub-nva/main.bicep" ` + -TemplateParameterFile $PopulatedParametersFilePath ` + -Verbose + $deployed = $true + } + catch { + if ($deployAttempt -eq $RetryCount) { + throw + } else { + Write-Output "Error deploying $PopulatedParametersFilePath to $SubscriptionId in $Region" + Write-Output $_.Exception.Message + Write-Output $_.Exception.StackTrace + } + } + $deployAttempt++ + } #region Check if Private DNS Zones are managed in the Hub. If so, enable Private DNS Zones policy assignment if ($Configuration.parameters.privateDnsZones.value.enabled -eq $true) { diff --git a/scripts/deployments/Functions/Identity.ps1 b/scripts/deployments/Functions/Identity.ps1 new file mode 100644 index 00000000..cbb57012 --- /dev/null +++ b/scripts/deployments/Functions/Identity.ps1 @@ -0,0 +1,81 @@ +<# +---------------------------------------------------------------------------------- +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. + +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +---------------------------------------------------------------------------------- +#> + +function Set-Identity { + param ( + [Parameter(Mandatory = $true)] + $Context, + + [Parameter(Mandatory = $true)] + [String]$Region, + + [Parameter(Mandatory = $true)] + [String]$ManagementGroupId, + + [Parameter(Mandatory = $true)] + [String]$SubscriptionId, + + [Parameter(Mandatory = $true)] + [String]$ConfigurationFilePath, + + [Parameter(Mandatory = $true)] + [String]$LogAnalyticsWorkspaceResourceId + ) + + Set-AzContext -Subscription $SubscriptionId + + $SchemaFilePath = "$($Context.SchemaDirectory)/landingzones/lz-platform-identity.json" + + Write-Output "Validation JSON parameter configuration using $SchemaFilePath" + Get-Content -Raw $ConfigurationFilePath | Test-Json -SchemaFile $SchemaFilePath + + # Load networking configuration + $Configuration = Get-Content $ConfigurationFilePath | ConvertFrom-Json -Depth 100 + + #region Check if Log Analytics Workspace Id is provided. Otherwise set it. + $LogAnalyticsWorkspaceResourceIdInFile = $Configuration.parameters | Get-Member -Name logAnalyticsWorkspaceResourceId + + if ($null -eq $LogAnalyticsWorkspaceResourceIdInFile -or $Configuration.parameters.logAnalyticsWorkspaceResourceId.value -eq "") { + $LogAnalyticsWorkspaceIdElement = @{ + logAnalyticsWorkspaceResourceId = @{ + value = $LogAnalyticsWorkspaceResourceId + } + } + + $Configuration.parameters | Add-Member $LogAnalyticsWorkspaceIdElement -Force + } + #endregion + + $PopulatedParametersFilePath = $ConfigurationFilePath.Split('.')[0] + '-populated.json' + + Write-Output "Creating new file with runtime populated parameters: $PopulatedParametersFilePath" + $Configuration | ConvertTo-Json -Depth 100 | Set-Content $PopulatedParametersFilePath + + Write-Output "Moving Subscription ($SubscriptionId) to Management Group ($ManagementGroupId)" + New-AzManagementGroupDeployment ` + -ManagementGroupId $ManagementGroupId ` + -Location $Context.DeploymentRegion ` + -TemplateFile "$($Context.WorkingDirectory)/landingzones/utils/mg-move/move-subscription.bicep" ` + -TemplateParameterObject @{ + managementGroupId = $ManagementGroupId + subscriptionId = $SubscriptionId + } ` + -Verbose + + Write-Output "Deploying Identity to $SubscriptionId in $Region with $ConfigurationFilePath" + New-AzSubscriptionDeployment ` + -Name "main-$Region" ` + -Location $Region ` + -TemplateFile "$($Context.WorkingDirectory)/landingzones/lz-platform-identity/main.bicep" ` + -TemplateParameterFile $PopulatedParametersFilePath ` + -Verbose + +} \ No newline at end of file diff --git a/scripts/deployments/Functions/Subscriptions.ps1 b/scripts/deployments/Functions/Subscriptions.ps1 index f257325c..ad33b3a9 100644 --- a/scripts/deployments/Functions/Subscriptions.ps1 +++ b/scripts/deployments/Functions/Subscriptions.ps1 @@ -26,19 +26,21 @@ function Set-Subscriptions { foreach ($subscriptionId in $SubscriptionIds) { # Find the ARM JSON parameters, ensure there's only 1 parameters file for each subscription - $SubscriptonConfigurations = Get-ChildItem -Path $Context.SubscriptionsDirectory -Filter "*$subscriptionId*.json" -Recurse + # $SubscriptionConfigurations = Get-ChildItem -Path $Context.SubscriptionsDirectory -Filter "*$subscriptionId*.json" -Recurse + $pattern = "^$subscriptionId.*(_.*)(_.*)?\.json" + $SubscriptionConfigurations = @(Get-ChildItem -Path $Context.SubscriptionsDirectory -Filter "*$subscriptionId*.json" -File -Recurse | ? { $_.Name -match $pattern }) - if ($SubscriptonConfigurations.Count -eq 0) { - Write-Output "No Subscription JSON paramters files found in $($Context.SubscriptionsDirectory) for $subscriptionId" + if ($SubscriptionConfigurations.Count -eq 0) { + Write-Output "No Subscription JSON parameters files found in $($Context.SubscriptionsDirectory) for $subscriptionId" continue - } elseif ($SubscriptonConfigurations.Count -gt 1) { + } elseif ($SubscriptionConfigurations.Count -gt 1) { Write-Output "Multiple Subscription JSON paramters files found in $($Context.SubscriptionsDirectory) for $subscriptionId. There must only be one." continue } - $DirectoryName = $SubscriptonConfigurations[0].DirectoryName - $FilePath = $SubscriptonConfigurations[0].FullName - $FileName = $SubscriptonConfigurations[0].Name + $DirectoryName = $SubscriptionConfigurations[0].DirectoryName + $FilePath = $SubscriptionConfigurations[0].FullName + $FileName = $SubscriptionConfigurations[0].Name # Parse the file name to get subscription id, archetype and region (optional). # If region is not available in the file name, the use the default region provided @@ -48,8 +50,8 @@ function Set-Subscriptions { $DeploymentRegion = $FileNameParts.Count -eq 3 ? $FileNameParts[2] : $Region # Compute the management group id from the folder structure - $FilePathWithoutBaseDirectory = $DirectoryName -Replace $($Context.SubscriptionsDirectory), "" - $ManagementGroupId = $FilePathWithoutBaseDirectory -Replace [IO.Path]::DirectorySeparatorChar, "" + $FilePathWithoutBaseDirectory = $DirectoryName -Replace [regex]::Escape($Context.SubscriptionsDirectory), "" + $ManagementGroupId = $FilePathWithoutBaseDirectory -Replace [regex]::Escape([IO.Path]::DirectorySeparatorChar.ToString()), "" Write-Output "Deploying Subscription: $SubscriptionId" Write-Output " - Management Group: $ManagementGroupId" @@ -58,10 +60,10 @@ function Set-Subscriptions { Set-AzContext -Subscription $SubscriptionId - $SchemaFilePath = "$($Context.SchemaDirectory)/landingzones/lz-$ArchetypeName.json" + $SchemaFile = (Resolve-Path -Path "$($Context.SchemaDirectory)/landingzones/lz-$ArchetypeName.json").Path Write-Output "Validation JSON parameter configuration using $SchemaFilePath" - Get-Content -Raw $FilePath | Test-Json -SchemaFile $SchemaFilePath + Get-Content -Raw $FilePath | Test-Json -SchemaFile $SchemaFile $Configuration = Get-Content $FilePath | ConvertFrom-Json -Depth 100 @@ -88,11 +90,12 @@ function Set-Subscriptions { $MoveDeploymentName=-join $MoveDeploymentName[0..63] Write-Output "Moving Subscription ($SubscriptionId) to Management Group ($ManagementGroupId)" + $TemplateFile = (Resolve-Path -Path "$($Context.WorkingDirectory)/landingzones/utils/mg-move/move-subscription.bicep").Path New-AzManagementGroupDeployment ` -Name $MoveDeploymentName ` -ManagementGroupId $ManagementGroupId ` -Location $Context.DeploymentRegion ` - -TemplateFile "$($Context.WorkingDirectory)/landingzones/utils/mg-move/move-subscription.bicep" ` + -TemplateFile $TemplateFile ` -TemplateParameterObject @{ managementGroupId = $ManagementGroupId subscriptionId = $SubscriptionId @@ -102,10 +105,11 @@ function Set-Subscriptions { Write-Output "Deploying $PopulatedParametersFilePath to $SubscriptionId in $Region" Set-AzContext -Subscription $SubscriptionId + $TemplateFile = (Resolve-Path -Path "$($Context.WorkingDirectory)/landingzones/lz-$ArchetypeName/main.bicep").Path New-AzSubscriptionDeployment ` -Name "main-$DeploymentRegion" ` -Location $DeploymentRegion ` - -TemplateFile "$($Context.WorkingDirectory)/landingzones/lz-$ArchetypeName/main.bicep" ` + -TemplateFile $TemplateFile ` -TemplateParameterFile $PopulatedParametersFilePath ` -Verbose diff --git a/scripts/deployments/RunWorkflows.ps1 b/scripts/deployments/RunWorkflows.ps1 index ce9a35e3..0cf33bb8 100644 --- a/scripts/deployments/RunWorkflows.ps1 +++ b/scripts/deployments/RunWorkflows.ps1 @@ -61,6 +61,9 @@ OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. .PARAMETER DeployHubNetworkWithNVA If true, run the NVA hub network workflow. + + .PARAMETER DeployIdentity + If true, run the Identity workflow. .PARAMETER DeploySubscriptionIds Comma separated list of quoted subscription ids to run the subscription workflow against. @@ -91,42 +94,42 @@ OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. The firewall password to use for the Hub Network with NVA workflow. .EXAMPLE - PS> .\RunWorkflows.ps1 -EnvironmentName CanadaESLZ-main -LoginInteractiveTenantId '8188040d-6c67-4c5c-b112-36a304b66dad' -DeployManagementGroups + PS> .\RunWorkflows.ps1 -EnvironmentName CanadaPubSecALZ-main -LoginInteractiveTenantId '8188040d-6c67-4c5c-b112-36a304b66dad' -DeployManagementGroups Deploy management groups interactively. .EXAMPLE - PS> .\RunWorkflows.ps1 -EnvironmentName CanadaESLZ-main -LoginInteractiveTenantId '8188040d-6c67-4c5c-b112-36a304b66dad' -DeployManagementGroups -DeployRoles -DeployLogging -DeployCustomPolicyDefinitions -DeployCustomPolicySetDefinitions -DeployCustomPolicySetAssignments -DeployBuiltinPolicySetAssignments -DeployAzureFirewallPolicy -DeployHubNetworkWithAzureFirewall + PS> .\RunWorkflows.ps1 -EnvironmentName CanadaPubSecALZ-main -LoginInteractiveTenantId '8188040d-6c67-4c5c-b112-36a304b66dad' -DeployManagementGroups -DeployRoles -DeployLogging -DeployCustomPolicyDefinitions -DeployCustomPolicySetDefinitions -DeployCustomPolicySetAssignments -DeployBuiltinPolicySetAssignments -DeployAzureFirewallPolicy -DeployHubNetworkWithAzureFirewall Deploy all platform components interactively, with Azure Firewall. .EXAMPLE - PS> .\RunWorkflows.ps1 -EnvironmentName CanadaESLZ-main -LoginInteractiveTenantId '8188040d-6c67-4c5c-b112-36a304b66dad' -DeploySubscriptionIds 'a188040e-6c67-4c5c-b112-36a304b66dad,7188030d-6c67-4c5c-b112-36a304b66dac' + PS> .\RunWorkflows.ps1 -EnvironmentName CanadaPubSecALZ-main -LoginInteractiveTenantId '8188040d-6c67-4c5c-b112-36a304b66dad' -DeploySubscriptionIds 'a188040e-6c67-4c5c-b112-36a304b66dad,7188030d-6c67-4c5c-b112-36a304b66dac' Deploy 2 subscriptions interactively. .EXAMPLE - PS> .\RunWorkflows.ps1 -EnvironmentName CanadaESLZ-main -DeployCustomPolicyDefinitions -DeployCustomPolicySetDefinitions -DeployCustomPolicySetAssignments -DeployBuiltinPolicySetAssignments + PS> .\RunWorkflows.ps1 -EnvironmentName CanadaPubSecALZ-main -DeployCustomPolicyDefinitions -DeployCustomPolicySetDefinitions -DeployCustomPolicySetAssignments -DeployBuiltinPolicySetAssignments Deploy Built-In & Custom Policy Sets, including all default custom policy/policy set definitions. .EXAMPLE - PS> .\RunWorkflows.ps1 -EnvironmentName CanadaESLZ-main -DeployCustomPolicyDefinitions + PS> .\RunWorkflows.ps1 -EnvironmentName CanadaPubSecALZ-main -DeployCustomPolicyDefinitions Deploy Custom Policy Definitions only. .EXAMPLE - PS> .\RunWorkflows.ps1 -EnvironmentName CanadaESLZ-main -DeployCustomPolicySetAssignments -CustomPolicySetAssignmentManagementGroupId pubsec -CustomPolicySetAssignmentNames DefenderForCloud + PS> .\RunWorkflows.ps1 -EnvironmentName CanadaPubSecALZ-main -DeployCustomPolicySetAssignments -CustomPolicySetAssignmentManagementGroupId pubsec -CustomPolicySetAssignmentNames DefenderForCloud Deploy one Custom Policy Set Assignment at management group .EXAMPLE - PS> .\RunWorkflows.ps1 -EnvironmentName CanadaESLZ-main -DeployBuiltinPolicySetAssignments + PS> .\RunWorkflows.ps1 -EnvironmentName CanadaPubSecALZ-main -DeployBuiltinPolicySetAssignments Deploy Built In Policy Assignments .EXAMPLE - PS> .\RunWorkflows.ps1 -EnvironmentName CanadaESLZ-main -DeployBuiltinPolicySetAssignments -BuiltinPolicySetAssignmentManagementGroupId pubsec -BuiltinPolicySetAssignmentNames asb + PS> .\RunWorkflows.ps1 -EnvironmentName CanadaPubSecALZ-main -DeployBuiltinPolicySetAssignments -BuiltinPolicySetAssignmentManagementGroupId pubsec -BuiltinPolicySetAssignmentNames asb Deploy one Built In Policy Assignment at management group @@ -173,6 +176,8 @@ Param( [switch]$DeployHubNetworkWithNVA, [switch]$DeployHubNetworkWithAzureFirewall, + [switch]$DeployIdentity, + [string[]]$DeploySubscriptionIds=@(), # How to deploy @@ -186,8 +191,6 @@ Param( [SecureString]$NvaPassword=$null ) -#Requires -Modules Az, powershell-yaml - $ErrorActionPreference = "Stop" # In order to use this End to End script, you must configure ARM template configurations for Logging, Networking and Subscriptions. @@ -220,6 +223,7 @@ Write-Host "Loading functions..." . ".\Functions\Policy.ps1" . ".\Functions\HubNetworkWithNVA.ps1" . ".\Functions\HubNetworkWithAzureFirewall.ps1" +. ".\Functions\Identity.ps1" . ".\Functions\Subscriptions.ps1" # Az Login interactively @@ -393,6 +397,24 @@ if ($DeployHubNetworkWithAzureFirewall) { -LogAnalyticsWorkspaceResourceId $LoggingConfiguration.LogAnalyticsWorkspaceResourceId } +# Deploy Identity Subscription +if ($DeployIdentity) { + Write-Host "Deploying Identity Subscription..." + # Get Logging information using logging config file + $LoggingConfiguration = Get-LoggingConfiguration ` + -ConfigurationFilePath "$($Context.LoggingDirectory)/$($Context.Variables['var-logging-configurationFileName'])" ` + -SubscriptionId $Context.Variables['var-logging-subscriptionId'] + + #Create Identity Subscription + Set-Identity ` + -Context $Context ` + -Region $Context.Variables['var-identity-region'] ` + -ManagementGroupId $Context.Variables['var-identity-managementGroupId'] ` + -SubscriptionId $Context.Variables['var-identity-subscriptionId'] ` + -ConfigurationFilePath "$($Context.IdentityDirectory)/$($Context.Variables['var-identity-configurationFileName'])" ` + -LogAnalyticsWorkspaceResourceId $LoggingConfiguration.LogAnalyticsWorkspaceResourceId +} + # Deploy Subscription archetypes if (($null -ne $DeploySubscriptionIds) -and ($DeploySubscriptionIds.Count -gt 0)) { Write-Host "Deploying Subscriptions..." diff --git a/scripts/onboarding/create-pipelines.bat b/scripts/onboarding/create-pipelines.bat index b23a564b..3b9546d5 100644 --- a/scripts/onboarding/create-pipelines.bat +++ b/scripts/onboarding/create-pipelines.bat @@ -22,7 +22,7 @@ choice /C YN /M "Do you want to proceed?" if errorlevel 2 exit /b 0 REM Process all pipeline definitions -for %%N in (management-groups roles platform-logging policy platform-connectivity-hub-nva platform-connectivity-hub-azfw platform-connectivity-hub-azfw-policy subscriptions) do ( +for %%N in (management-groups roles platform-logging platform-identity policy platform-connectivity-hub-nva platform-connectivity-hub-azfw platform-connectivity-hub-azfw-policy subscriptions) do ( REM Check for pipeline existence set FOUND= @@ -60,5 +60,5 @@ echo. echo Now that an environment exists for the repository branch [%DEVOPS_REPO_BRANCH%], echo learn more about configuring approvals and checks for deployments associated with this echo environment by reviewing the following documentation: -echo * https://docs.microsoft.com/azure/devops/pipelines/process/approvals +echo * https://learn.microsoft.com/azure/devops/pipelines/process/approvals echo. diff --git a/scripts/onboarding/set-variables.CanadaPubSecALZ.bat b/scripts/onboarding/set-variables.CanadaPubSecALZ.bat new file mode 100644 index 00000000..d6138892 --- /dev/null +++ b/scripts/onboarding/set-variables.CanadaPubSecALZ.bat @@ -0,0 +1,54 @@ +@echo off +REM // ---------------------------------------------------------------------------------- +REM // Copyright (c) Microsoft Corporation. +REM // Licensed under the MIT license. +REM // +REM // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +REM // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +REM // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +REM // ---------------------------------------------------------------------------------- + +REM Azure AD tenant GUID +set DEVOPS_TENANT_ID=df968f64-5f44-4e42-937b-47fb792ee373 + +REM Azure AD tenant root management group name +set DEVOPS_MGMT_GROUP_NAME=Tenant Root Group + +REM Azure service principal name for 'Owner' RBAC at tenant root scope +set DEVOPS_SP_NAME=spn-azure-platform-ops + +REM Azure security group name for 'Owner` RBAC subscription, network, and logging +set DEVOPS_SG_NAME=alz-owners + +REM Azure DevOps organization URL +set DEVOPS_ORG=https://dev.azure.com/CanadaPubSecALZ + +REM Azure DevOps project name (prefer no spaces) +set DEVOPS_PROJECT_NAME=CanadaPubSecALZ + +REM Repository name or URL +set DEVOPS_REPO_NAME_OR_URL=CanadaPubSecALZ + +REM Repository type: 'tfsgit' or 'github' +set DEVOPS_REPO_TYPE=tfsgit + +REM Repository branch name (default) +set DEVOPS_REPO_BRANCH=skeeler-quicksetup + +REM Azure DevOps pipeline name suffix (default) +set DEVOPS_PIPELINE_NAME_SUFFIX=-ci + +REM Azure DevOps service endpoint name (service connection in project settings) +set DEVOPS_SE_NAME=spn-azure-platform-ops + +REM Azure DevOps service endpoint template file (generated) +set DEVOPS_SE_TEMPLATE=service-endpoint.CanadaPubSecALZ.json + +REM Do not change this value (hard-coded in YAML pipeline definition) +set DEVOPS_VARIABLES_GROUP_NAME=firewall-secrets + +REM Are variables in the firewall-secrets group marked as secret? 'true' or 'false'. +set DEVOPS_VARIABLES_ARE_SECRET=true + +REM Folder path for generated output files +set DEVOPS_OUTPUT_DIR=.\output diff --git a/tests/schemas/lz-generic-subscription/BackupRecoveryVaultIsFalse.json b/tests/schemas/lz-generic-subscription/BackupRecoveryVaultIsFalse.json index e052092f..f8ee4222 100644 --- a/tests/schemas/lz-generic-subscription/BackupRecoveryVaultIsFalse.json +++ b/tests/schemas/lz-generic-subscription/BackupRecoveryVaultIsFalse.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-generic-subscription/BackupRecoveryVaultIsTrue.json b/tests/schemas/lz-generic-subscription/BackupRecoveryVaultIsTrue.json index 318a9d6d..08cbae3b 100644 --- a/tests/schemas/lz-generic-subscription/BackupRecoveryVaultIsTrue.json +++ b/tests/schemas/lz-generic-subscription/BackupRecoveryVaultIsTrue.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-generic-subscription/BudgetIsFalse.json b/tests/schemas/lz-generic-subscription/BudgetIsFalse.json index 318a9d6d..08cbae3b 100644 --- a/tests/schemas/lz-generic-subscription/BudgetIsFalse.json +++ b/tests/schemas/lz-generic-subscription/BudgetIsFalse.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-generic-subscription/BudgetIsTrue.json b/tests/schemas/lz-generic-subscription/BudgetIsTrue.json index 93bba825..6d6f27fc 100644 --- a/tests/schemas/lz-generic-subscription/BudgetIsTrue.json +++ b/tests/schemas/lz-generic-subscription/BudgetIsTrue.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-generic-subscription/EmptyResourceTags.json b/tests/schemas/lz-generic-subscription/EmptyResourceTags.json index a34b1a56..5250dd37 100644 --- a/tests/schemas/lz-generic-subscription/EmptyResourceTags.json +++ b/tests/schemas/lz-generic-subscription/EmptyResourceTags.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-generic-subscription/EmptySubscriptionTags.json b/tests/schemas/lz-generic-subscription/EmptySubscriptionTags.json index 0b85bbfa..5369ff95 100644 --- a/tests/schemas/lz-generic-subscription/EmptySubscriptionTags.json +++ b/tests/schemas/lz-generic-subscription/EmptySubscriptionTags.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-generic-subscription/FullDeployment-With-Hub.json b/tests/schemas/lz-generic-subscription/FullDeployment-With-Hub.json index 97dc1822..281f6254 100644 --- a/tests/schemas/lz-generic-subscription/FullDeployment-With-Hub.json +++ b/tests/schemas/lz-generic-subscription/FullDeployment-With-Hub.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-generic-subscription/FullDeployment-With-Location.json b/tests/schemas/lz-generic-subscription/FullDeployment-With-Location.json index 0f9cab09..390aa0cb 100644 --- a/tests/schemas/lz-generic-subscription/FullDeployment-With-Location.json +++ b/tests/schemas/lz-generic-subscription/FullDeployment-With-Location.json @@ -3,7 +3,7 @@ "contentVersion": "1.0.0.0", "parameters": { "location": { - "value": "canadacentral" + "value": "canada" }, "serviceHealthAlerts": { "value": { @@ -13,8 +13,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -25,7 +25,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-generic-subscription/FullDeployment-Without-Hub.json b/tests/schemas/lz-generic-subscription/FullDeployment-Without-Hub.json index fe8c145f..69cf8430 100644 --- a/tests/schemas/lz-generic-subscription/FullDeployment-Without-Hub.json +++ b/tests/schemas/lz-generic-subscription/FullDeployment-Without-Hub.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-generic-subscription/WithoutCustomDNS.json b/tests/schemas/lz-generic-subscription/WithoutCustomDNS.json index 5a0dc008..7b27241c 100644 --- a/tests/schemas/lz-generic-subscription/WithoutCustomDNS.json +++ b/tests/schemas/lz-generic-subscription/WithoutCustomDNS.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-generic-subscription/WithoutSubnets.json b/tests/schemas/lz-generic-subscription/WithoutSubnets.json index 7d7ca67f..85e31378 100644 --- a/tests/schemas/lz-generic-subscription/WithoutSubnets.json +++ b/tests/schemas/lz-generic-subscription/WithoutSubnets.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/BudgetIsFalse.json b/tests/schemas/lz-healthcare/BudgetIsFalse.json index 87755136..1c87c2dc 100644 --- a/tests/schemas/lz-healthcare/BudgetIsFalse.json +++ b/tests/schemas/lz-healthcare/BudgetIsFalse.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/BudgetIsTrue.json b/tests/schemas/lz-healthcare/BudgetIsTrue.json index 58fe56ca..cb8627cb 100644 --- a/tests/schemas/lz-healthcare/BudgetIsTrue.json +++ b/tests/schemas/lz-healthcare/BudgetIsTrue.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/EmptyResourceTags.json b/tests/schemas/lz-healthcare/EmptyResourceTags.json index d380b507..cccb7c5e 100644 --- a/tests/schemas/lz-healthcare/EmptyResourceTags.json +++ b/tests/schemas/lz-healthcare/EmptyResourceTags.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/EmptySubscriptionTags.json b/tests/schemas/lz-healthcare/EmptySubscriptionTags.json index 803311a4..b63e963c 100644 --- a/tests/schemas/lz-healthcare/EmptySubscriptionTags.json +++ b/tests/schemas/lz-healthcare/EmptySubscriptionTags.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/FullDeployment-With-Hub.json b/tests/schemas/lz-healthcare/FullDeployment-With-Hub.json index ce2a3613..03dd5dee 100644 --- a/tests/schemas/lz-healthcare/FullDeployment-With-Hub.json +++ b/tests/schemas/lz-healthcare/FullDeployment-With-Hub.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/FullDeployment-With-Location.json b/tests/schemas/lz-healthcare/FullDeployment-With-Location.json index 6257136b..143d3dbb 100644 --- a/tests/schemas/lz-healthcare/FullDeployment-With-Location.json +++ b/tests/schemas/lz-healthcare/FullDeployment-With-Location.json @@ -13,8 +13,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -25,7 +25,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/FullDeployment-With-OptionalSubnets.json b/tests/schemas/lz-healthcare/FullDeployment-With-OptionalSubnets.json index 435c7fa3..d382ecaf 100644 --- a/tests/schemas/lz-healthcare/FullDeployment-With-OptionalSubnets.json +++ b/tests/schemas/lz-healthcare/FullDeployment-With-OptionalSubnets.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/FullDeployment-Without-Hub.json b/tests/schemas/lz-healthcare/FullDeployment-Without-Hub.json index 7f6d24b0..318691e9 100644 --- a/tests/schemas/lz-healthcare/FullDeployment-Without-Hub.json +++ b/tests/schemas/lz-healthcare/FullDeployment-Without-Hub.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/SQLDB-aadAuthOnly.json b/tests/schemas/lz-healthcare/SQLDB-aadAuthOnly.json index 6c8a5d24..df3f119f 100644 --- a/tests/schemas/lz-healthcare/SQLDB-aadAuthOnly.json +++ b/tests/schemas/lz-healthcare/SQLDB-aadAuthOnly.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/SQLDB-mixedAuth.json b/tests/schemas/lz-healthcare/SQLDB-mixedAuth.json index b764a41d..67990eb4 100644 --- a/tests/schemas/lz-healthcare/SQLDB-mixedAuth.json +++ b/tests/schemas/lz-healthcare/SQLDB-mixedAuth.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/SQLDB-sqlAuth.json b/tests/schemas/lz-healthcare/SQLDB-sqlAuth.json index d0b31d47..76a33f6b 100644 --- a/tests/schemas/lz-healthcare/SQLDB-sqlAuth.json +++ b/tests/schemas/lz-healthcare/SQLDB-sqlAuth.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/SQLDBIsFalse.json b/tests/schemas/lz-healthcare/SQLDBIsFalse.json index a614a2af..cc556997 100644 --- a/tests/schemas/lz-healthcare/SQLDBIsFalse.json +++ b/tests/schemas/lz-healthcare/SQLDBIsFalse.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/Synapse-aadAuthOnly.json b/tests/schemas/lz-healthcare/Synapse-aadAuthOnly.json index 0565d3a7..2d0c3ad8 100644 --- a/tests/schemas/lz-healthcare/Synapse-aadAuthOnly.json +++ b/tests/schemas/lz-healthcare/Synapse-aadAuthOnly.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/Synapse-mixedAuth.json b/tests/schemas/lz-healthcare/Synapse-mixedAuth.json index 55f8bb9f..21c5bdf5 100644 --- a/tests/schemas/lz-healthcare/Synapse-mixedAuth.json +++ b/tests/schemas/lz-healthcare/Synapse-mixedAuth.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/Synapse-sqlAuth.json b/tests/schemas/lz-healthcare/Synapse-sqlAuth.json index cbe13567..169a3e08 100644 --- a/tests/schemas/lz-healthcare/Synapse-sqlAuth.json +++ b/tests/schemas/lz-healthcare/Synapse-sqlAuth.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-healthcare/WithoutCMK.json b/tests/schemas/lz-healthcare/WithoutCMK.json index 6e937885..3933da07 100644 --- a/tests/schemas/lz-healthcare/WithoutCMK.json +++ b/tests/schemas/lz-healthcare/WithoutCMK.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/AKS-AzureCNI-AzureNP.json b/tests/schemas/lz-machinelearning/AKS-AzureCNI-AzureNP.json index 350b28ea..63478c44 100644 --- a/tests/schemas/lz-machinelearning/AKS-AzureCNI-AzureNP.json +++ b/tests/schemas/lz-machinelearning/AKS-AzureCNI-AzureNP.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/AKS-AzureCNI-Calico.json b/tests/schemas/lz-machinelearning/AKS-AzureCNI-Calico.json index 300bcc21..02c9258d 100644 --- a/tests/schemas/lz-machinelearning/AKS-AzureCNI-Calico.json +++ b/tests/schemas/lz-machinelearning/AKS-AzureCNI-Calico.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/AKS-Kubenet-Calico.json b/tests/schemas/lz-machinelearning/AKS-Kubenet-Calico.json index 2477fdb5..2e5be586 100644 --- a/tests/schemas/lz-machinelearning/AKS-Kubenet-Calico.json +++ b/tests/schemas/lz-machinelearning/AKS-Kubenet-Calico.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/AKSIsFalse.json b/tests/schemas/lz-machinelearning/AKSIsFalse.json index b12d62a2..440c127e 100644 --- a/tests/schemas/lz-machinelearning/AKSIsFalse.json +++ b/tests/schemas/lz-machinelearning/AKSIsFalse.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/AppServiceLinuxContainerIsFalse.json b/tests/schemas/lz-machinelearning/AppServiceLinuxContainerIsFalse.json index fd62aad1..210a0005 100644 --- a/tests/schemas/lz-machinelearning/AppServiceLinuxContainerIsFalse.json +++ b/tests/schemas/lz-machinelearning/AppServiceLinuxContainerIsFalse.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/AppServiceLinuxContainerPrivateEndpointIsFalse.json b/tests/schemas/lz-machinelearning/AppServiceLinuxContainerPrivateEndpointIsFalse.json index 7d973272..471279ab 100644 --- a/tests/schemas/lz-machinelearning/AppServiceLinuxContainerPrivateEndpointIsFalse.json +++ b/tests/schemas/lz-machinelearning/AppServiceLinuxContainerPrivateEndpointIsFalse.json @@ -2,6 +2,9 @@ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { + "location": { + "value": "canadacentral" + }, "serviceHealthAlerts": { "value": { "resourceGroupName": "service-health", @@ -10,8 +13,13 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/FullDeployment-With-Location.json "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] +======== + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] +>>>>>>>> upstream/main:tests/schemas/lz-machinelearning/AppServiceLinuxContainerPrivateEndpointIsFalse.json }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +30,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { @@ -69,6 +77,9 @@ "TechnicalContact": "technical-contact-tag" } }, + "logAnalyticsWorkspaceResourceId": { + "value": "/subscriptions/bc0a4f9f-07fa-4284-b1bd-fbad38578d3a/resourcegroups/pubsec-central-logging/providers/microsoft.operationalinsights/workspaces/log-analytics-workspace" + }, "resourceGroups": { "value": { "automation": "azml-automation", @@ -95,11 +106,17 @@ }, "aks": { "value": { - "version": "1.21.2", + "version": "1.22.6", "enabled": true, +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/FullDeployment-With-Location.json + "networkPlugin": "kubenet", + "networkPolicy": "calico", + "podCidr": "11.0.0.0/16", +======== "networkPlugin": "azure", "networkPolicy": "azure", "podCidr": "", +>>>>>>>> upstream/main:tests/schemas/lz-machinelearning/AppServiceLinuxContainerPrivateEndpointIsFalse.json "serviceCidr": "20.0.0.0/16", "dnsServiceIP": "20.0.0.10", "dockerBridgeCidr": "30.0.0.1/16" @@ -110,14 +127,22 @@ "enabled": true, "skuName": "P1V2", "skuTier": "Premium", +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/FullDeployment-With-Location.json + "enablePrivateEndpoint": true +======== "enablePrivateEndpoint": false +>>>>>>>> upstream/main:tests/schemas/lz-machinelearning/AppServiceLinuxContainerPrivateEndpointIsFalse.json } }, "sqldb": { "value": { "enabled": true, +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/FullDeployment-With-Location.json + "aadAuthenticationOnly": true, +======== "sqlAuthenticationUsername": "azadmin", "aadAuthenticationOnly": false, +>>>>>>>> upstream/main:tests/schemas/lz-machinelearning/AppServiceLinuxContainerPrivateEndpointIsFalse.json "aadLoginName": "DBA Group", "aadLoginObjectID": "4e4ea47c-ee21-4add-ad2f-a75d0d8014e0", "aadLoginType": "Group" diff --git a/tests/schemas/lz-machinelearning/BudgetIsFalse.json b/tests/schemas/lz-machinelearning/BudgetIsFalse.json index a73d2981..243a6bef 100644 --- a/tests/schemas/lz-machinelearning/BudgetIsFalse.json +++ b/tests/schemas/lz-machinelearning/BudgetIsFalse.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/BudgetIsTrue.json b/tests/schemas/lz-machinelearning/BudgetIsTrue.json index a53aaab2..95f8b32b 100644 --- a/tests/schemas/lz-machinelearning/BudgetIsTrue.json +++ b/tests/schemas/lz-machinelearning/BudgetIsTrue.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/EmptyResourceTags.json b/tests/schemas/lz-machinelearning/EmptyResourceTags.json index 92358a1e..c1efecfe 100644 --- a/tests/schemas/lz-machinelearning/EmptyResourceTags.json +++ b/tests/schemas/lz-machinelearning/EmptyResourceTags.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/EmptySubscriptionTags.json b/tests/schemas/lz-machinelearning/EmptySubscriptionTags.json index d0fe2367..14811918 100644 --- a/tests/schemas/lz-machinelearning/EmptySubscriptionTags.json +++ b/tests/schemas/lz-machinelearning/EmptySubscriptionTags.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/FullDeployment-With-Hub.json b/tests/schemas/lz-machinelearning/FullDeployment-With-Hub.json index f0aeb9e8..614aa1d2 100644 --- a/tests/schemas/lz-machinelearning/FullDeployment-With-Hub.json +++ b/tests/schemas/lz-machinelearning/FullDeployment-With-Hub.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/FullDeployment-With-Location.json b/tests/schemas/lz-machinelearning/FullDeployment-With-Location.json index ca3ea89c..e2e0472a 100644 --- a/tests/schemas/lz-machinelearning/FullDeployment-With-Location.json +++ b/tests/schemas/lz-machinelearning/FullDeployment-With-Location.json @@ -13,8 +13,13 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/AppServiceLinuxContainerPrivateEndpointIsFalse.json "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] +======== + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] +>>>>>>>> upstream/main:tests/schemas/lz-machinelearning/FullDeployment-With-Location.json }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -25,7 +30,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { @@ -101,11 +106,17 @@ }, "aks": { "value": { - "version": "1.22.6", + "version": "1.21.2", "enabled": true, +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/AppServiceLinuxContainerPrivateEndpointIsFalse.json + "networkPlugin": "azure", + "networkPolicy": "azure", + "podCidr": "", +======== "networkPlugin": "kubenet", "networkPolicy": "calico", "podCidr": "11.0.0.0/16", +>>>>>>>> upstream/main:tests/schemas/lz-machinelearning/FullDeployment-With-Location.json "serviceCidr": "20.0.0.0/16", "dnsServiceIP": "20.0.0.10", "dockerBridgeCidr": "30.0.0.1/16" @@ -116,13 +127,22 @@ "enabled": true, "skuName": "P1V2", "skuTier": "Premium", +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/AppServiceLinuxContainerPrivateEndpointIsFalse.json + "enablePrivateEndpoint": false +======== "enablePrivateEndpoint": true +>>>>>>>> upstream/main:tests/schemas/lz-machinelearning/FullDeployment-With-Location.json } }, "sqldb": { "value": { "enabled": true, +<<<<<<<< HEAD:tests/schemas/lz-machinelearning/AppServiceLinuxContainerPrivateEndpointIsFalse.json + "sqlAuthenticationUsername": "azadmin", + "aadAuthenticationOnly": false, +======== "aadAuthenticationOnly": true, +>>>>>>>> upstream/main:tests/schemas/lz-machinelearning/FullDeployment-With-Location.json "aadLoginName": "DBA Group", "aadLoginObjectID": "4e4ea47c-ee21-4add-ad2f-a75d0d8014e0", "aadLoginType": "Group" diff --git a/tests/schemas/lz-machinelearning/FullDeployment-With-OptionalSubnets.json b/tests/schemas/lz-machinelearning/FullDeployment-With-OptionalSubnets.json index 5e1efdca..a550f31a 100644 --- a/tests/schemas/lz-machinelearning/FullDeployment-With-OptionalSubnets.json +++ b/tests/schemas/lz-machinelearning/FullDeployment-With-OptionalSubnets.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { @@ -98,7 +98,7 @@ }, "aks": { "value": { - "version": "1.22.6", + "version": "1.21.2", "enabled": true, "networkPlugin": "kubenet", "networkPolicy": "calico", diff --git a/tests/schemas/lz-machinelearning/FullDeployment-Without-Hub.json b/tests/schemas/lz-machinelearning/FullDeployment-Without-Hub.json index 93ce56d2..716edbe0 100644 --- a/tests/schemas/lz-machinelearning/FullDeployment-Without-Hub.json +++ b/tests/schemas/lz-machinelearning/FullDeployment-Without-Hub.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/SQLDB-aadAuthOnly.json b/tests/schemas/lz-machinelearning/SQLDB-aadAuthOnly.json index d4576ec1..28ca865e 100644 --- a/tests/schemas/lz-machinelearning/SQLDB-aadAuthOnly.json +++ b/tests/schemas/lz-machinelearning/SQLDB-aadAuthOnly.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/SQLDB-mixedAuth.json b/tests/schemas/lz-machinelearning/SQLDB-mixedAuth.json index 2477fdb5..2e5be586 100644 --- a/tests/schemas/lz-machinelearning/SQLDB-mixedAuth.json +++ b/tests/schemas/lz-machinelearning/SQLDB-mixedAuth.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/SQLDB-sqlAuth.json b/tests/schemas/lz-machinelearning/SQLDB-sqlAuth.json index a53aaab2..95f8b32b 100644 --- a/tests/schemas/lz-machinelearning/SQLDB-sqlAuth.json +++ b/tests/schemas/lz-machinelearning/SQLDB-sqlAuth.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/SQLDBIsFalse.json b/tests/schemas/lz-machinelearning/SQLDBIsFalse.json index a0959ea4..fd17f887 100644 --- a/tests/schemas/lz-machinelearning/SQLDBIsFalse.json +++ b/tests/schemas/lz-machinelearning/SQLDBIsFalse.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/SQLMIIsFalse.json b/tests/schemas/lz-machinelearning/SQLMIIsFalse.json index 5566dc1e..374e6992 100644 --- a/tests/schemas/lz-machinelearning/SQLMIIsFalse.json +++ b/tests/schemas/lz-machinelearning/SQLMIIsFalse.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-machinelearning/WithoutCMK.json b/tests/schemas/lz-machinelearning/WithoutCMK.json index 80baa0f0..4b3a7fa8 100644 --- a/tests/schemas/lz-machinelearning/WithoutCMK.json +++ b/tests/schemas/lz-machinelearning/WithoutCMK.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-connectivity-hub-azfw/BudgetIsFalse.json b/tests/schemas/lz-platform-connectivity-hub-azfw/BudgetIsFalse.json index 35564622..e5770f64 100644 --- a/tests/schemas/lz-platform-connectivity-hub-azfw/BudgetIsFalse.json +++ b/tests/schemas/lz-platform-connectivity-hub-azfw/BudgetIsFalse.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-With-OptionalHubSubnets.json b/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-With-OptionalHubSubnets.json index add1c27c..64a42969 100644 --- a/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-With-OptionalHubSubnets.json +++ b/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-With-OptionalHubSubnets.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-WithAzureFirewallPolicy.json b/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-WithAzureFirewallPolicy.json index 9119fc03..5a129b89 100644 --- a/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-WithAzureFirewallPolicy.json +++ b/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-WithAzureFirewallPolicy.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-WithLogAnalyticsWorkspace.json b/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-WithLogAnalyticsWorkspace.json index 18b0e388..a2e04667 100644 --- a/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-WithLogAnalyticsWorkspace.json +++ b/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-WithLogAnalyticsWorkspace.json @@ -13,8 +13,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -25,7 +25,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-Without-ManagementRestrictedZone.json b/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-Without-ManagementRestrictedZone.json index 39aac970..b528d8c6 100644 --- a/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-Without-ManagementRestrictedZone.json +++ b/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment-Without-ManagementRestrictedZone.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment.json b/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment.json index c925a539..19374297 100644 --- a/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment.json +++ b/tests/schemas/lz-platform-connectivity-hub-azfw/FullDeployment.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-connectivity-hub-nva/BudgetIsFalse.json b/tests/schemas/lz-platform-connectivity-hub-nva/BudgetIsFalse.json index 2c582b88..b359843f 100644 --- a/tests/schemas/lz-platform-connectivity-hub-nva/BudgetIsFalse.json +++ b/tests/schemas/lz-platform-connectivity-hub-nva/BudgetIsFalse.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-With-FortigateBYOL.json b/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-With-FortigateBYOL.json index 8539a30c..e1fdd4a5 100644 --- a/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-With-FortigateBYOL.json +++ b/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-With-FortigateBYOL.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-With-OptionalHubSubnets.json b/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-With-OptionalHubSubnets.json index 8152f034..7dc68bfa 100644 --- a/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-With-OptionalHubSubnets.json +++ b/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-With-OptionalHubSubnets.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-With-Ubuntu.json b/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-With-Ubuntu.json index cf12b48b..5e2aacca 100644 --- a/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-With-Ubuntu.json +++ b/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-With-Ubuntu.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-WithLogAnalyticsWorkspace.json b/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-WithLogAnalyticsWorkspace.json index f3a61229..43d767f4 100644 --- a/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-WithLogAnalyticsWorkspace.json +++ b/tests/schemas/lz-platform-connectivity-hub-nva/FullDeployment-WithLogAnalyticsWorkspace.json @@ -13,8 +13,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -25,7 +25,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-identity/lz-Identity-With-DNS-Resolver.json b/tests/schemas/lz-platform-identity/lz-Identity-With-DNS-Resolver.json new file mode 100644 index 00000000..8c759443 --- /dev/null +++ b/tests/schemas/lz-platform-identity/lz-Identity-With-DNS-Resolver.json @@ -0,0 +1,170 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] + }, + "actionGroupName": "Service health action group", + "actionGroupShortName": "health-alert", + "alertRuleName": "Incidents and Security", + "alertRuleDescription": "Service Health: Incidents and Security" + } + }, + "securityCenter": { + "value": { + "email": "alzcanadapubsec@microsoft.com", + "phone": "6045555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Owner Role", + "roleDefinitionId": "8e3af657-a8ff-443c-a75c-2fe8c4bcb635", + "securityGroupObjectIds": [ + "3a4fa072-cc14-471d-aeac-49afdbde9f7a" + ] + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tbd", + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "automation", + "networking": "networking", + "networkWatcher": "NetworkWatcherRG", + "backupRecoveryVault": "backup", + "domainControllers": "DomainControllersRG", + "dnsResolver": "dns-resolverRG", + "dnsCondionalForwarders": "dns-CondionalForwardersRG", + "privateDnsZones": "pubsec-dns" + } + }, + "automation": { + "value": { + "name": "automation" + } + }, + "backupRecoveryVault": { + "value": { + "enabled": true, + "name": "backup-vault" + } + }, + "privateDnsZones": { + "value": { + "enabled": false, + "resourceGroupName": "pubsec-dns" + } + }, + + "privateDnsResolver": { + "value": { + "enabled": true, + "name": "dns-resolver", + "inboundEndpointName": "dns-resolver-Inbound", + "outboundEndpointName": "dns-resolver-Outbound" + } + }, + + "privateDnsResolverRuleset": { + "value": { + "enabled": true, + "name": "dns-resolver-ruleset", + "linkRuleSetToVnet": true, + "linkRuleSetToVnetName": "dns-resolver-vnet-link", + "forwardingRules": [ + { + "name": "default", + "domain": "dontMakeMeThink.local", + "state": "Enabled", + "targetDnsServers": [ + { + "ipAddress": "10.99.99.100" + }, + { + "ipAddress": "10.99.99.99" + } + ] + } + ] + } + }, + + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4" + } + }, + + "network": { + "value": { + "deployVnet": true, + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "id-vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.15.0.0/24" + ], + "subnets": { + "domainControllers": { + "comments": "Identity Subnet for Domain Controllers and VM-Based DNS Servers", + "name": "DomainControllers", + "addressPrefix": "10.15.0.0/27" + }, + "dnsResolverInbound": { + "comments": "Azure DNS Resolver Inbound Requests subnet", + "name": "AzureDNSResolver-Inbound", + "addressPrefix": "10.15.0.32/27" + }, + "dnsResolverOutbound": { + "comments": "Azure DNS Resolver Outbound Requests subnet", + "name": "AzureDNSResolver-Outbound", + "addressPrefix": "10.15.0.64/27" + }, + "optional": [] + } + } + } + } + } \ No newline at end of file diff --git a/tests/schemas/lz-platform-identity/lz-Identity-With-Private-DNS-Zones-And-DNS-Resolver.json b/tests/schemas/lz-platform-identity/lz-Identity-With-Private-DNS-Zones-And-DNS-Resolver.json new file mode 100644 index 00000000..bb65f4a8 --- /dev/null +++ b/tests/schemas/lz-platform-identity/lz-Identity-With-Private-DNS-Zones-And-DNS-Resolver.json @@ -0,0 +1,170 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] + }, + "actionGroupName": "Service health action group", + "actionGroupShortName": "health-alert", + "alertRuleName": "Incidents and Security", + "alertRuleDescription": "Service Health: Incidents and Security" + } + }, + "securityCenter": { + "value": { + "email": "alzcanadapubsec@microsoft.com", + "phone": "6045555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Owner Role", + "roleDefinitionId": "8e3af657-a8ff-443c-a75c-2fe8c4bcb635", + "securityGroupObjectIds": [ + "3a4fa072-cc14-471d-aeac-49afdbde9f7a" + ] + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tbd", + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "automation", + "networking": "networking", + "networkWatcher": "NetworkWatcherRG", + "backupRecoveryVault": "backup", + "domainControllers": "DomainControllersRG", + "dnsResolver": "dns-resolverRG", + "dnsCondionalForwarders": "dns-CondionalForwardersRG", + "privateDnsZones": "pubsec-dns" + } + }, + "automation": { + "value": { + "name": "automation" + } + }, + "backupRecoveryVault": { + "value": { + "enabled": true, + "name": "backup-vault" + } + }, + "privateDnsZones": { + "value": { + "enabled": true, + "resourceGroupName": "pubsec-dns" + } + }, + + "privateDnsResolver": { + "value": { + "enabled": true, + "name": "dns-resolver", + "inboundEndpointName": "dns-resolver-Inbound", + "outboundEndpointName": "dns-resolver-Outbound" + } + }, + + "privateDnsResolverRuleset": { + "value": { + "enabled": true, + "name": "dns-resolver-ruleset", + "linkRuleSetToVnet": true, + "linkRuleSetToVnetName": "dns-resolver-vnet-link", + "forwardingRules": [ + { + "name": "default", + "domain": "dontMakeMeThink.local", + "state": "Enabled", + "targetDnsServers": [ + { + "ipAddress": "10.99.99.100" + }, + { + "ipAddress": "10.99.99.99" + } + ] + } + ] + } + }, + + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4" + } + }, + + "network": { + "value": { + "deployVnet": true, + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "id-vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.15.0.0/24" + ], + "subnets": { + "domainControllers": { + "comments": "Identity Subnet for Domain Controllers and VM-Based DNS Servers", + "name": "DomainControllers", + "addressPrefix": "10.15.0.0/27" + }, + "dnsResolverInbound": { + "comments": "Azure DNS Resolver Inbound Requests subnet", + "name": "AzureDNSResolver-Inbound", + "addressPrefix": "10.15.0.32/27" + }, + "dnsResolverOutbound": { + "comments": "Azure DNS Resolver Outbound Requests subnet", + "name": "AzureDNSResolver-Outbound", + "addressPrefix": "10.15.0.64/27" + }, + "optional": [] + } + } + } + } + } \ No newline at end of file diff --git a/tests/schemas/lz-platform-identity/lz-Identity-With-Private-DNS-Zones.json b/tests/schemas/lz-platform-identity/lz-Identity-With-Private-DNS-Zones.json new file mode 100644 index 00000000..806fd142 --- /dev/null +++ b/tests/schemas/lz-platform-identity/lz-Identity-With-Private-DNS-Zones.json @@ -0,0 +1,170 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] + }, + "actionGroupName": "Service health action group", + "actionGroupShortName": "health-alert", + "alertRuleName": "Incidents and Security", + "alertRuleDescription": "Service Health: Incidents and Security" + } + }, + "securityCenter": { + "value": { + "email": "alzcanadapubsec@microsoft.com", + "phone": "6045555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Owner Role", + "roleDefinitionId": "8e3af657-a8ff-443c-a75c-2fe8c4bcb635", + "securityGroupObjectIds": [ + "3a4fa072-cc14-471d-aeac-49afdbde9f7a" + ] + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tbd", + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "automation", + "networking": "networking", + "networkWatcher": "NetworkWatcherRG", + "backupRecoveryVault": "backup", + "domainControllers": "DomainControllersRG", + "dnsResolver": "dns-resolverRG", + "dnsCondionalForwarders": "dns-CondionalForwardersRG", + "privateDnsZones": "pubsec-dns" + } + }, + "automation": { + "value": { + "name": "automation" + } + }, + "backupRecoveryVault": { + "value": { + "enabled": true, + "name": "backup-vault" + } + }, + "privateDnsZones": { + "value": { + "enabled": true, + "resourceGroupName": "pubsec-dns" + } + }, + + "privateDnsResolver": { + "value": { + "enabled": false, + "name": "dns-resolver", + "inboundEndpointName": "dns-resolver-Inbound", + "outboundEndpointName": "dns-resolver-Outbound" + } + }, + + "privateDnsResolverRuleset": { + "value": { + "enabled": true, + "name": "dns-resolver-ruleset", + "linkRuleSetToVnet": true, + "linkRuleSetToVnetName": "dns-resolver-vnet-link", + "forwardingRules": [ + { + "name": "default", + "domain": "dontMakeMeThink.local", + "state": "Enabled", + "targetDnsServers": [ + { + "ipAddress": "10.99.99.100" + }, + { + "ipAddress": "10.99.99.99" + } + ] + } + ] + } + }, + + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4" + } + }, + + "network": { + "value": { + "deployVnet": true, + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "id-vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.15.0.0/24" + ], + "subnets": { + "domainControllers": { + "comments": "Identity Subnet for Domain Controllers and VM-Based DNS Servers", + "name": "DomainControllers", + "addressPrefix": "10.15.0.0/27" + }, + "dnsResolverInbound": { + "comments": "Azure DNS Resolver Inbound Requests subnet", + "name": "AzureDNSResolver-Inbound", + "addressPrefix": "10.15.0.32/27" + }, + "dnsResolverOutbound": { + "comments": "Azure DNS Resolver Outbound Requests subnet", + "name": "AzureDNSResolver-Outbound", + "addressPrefix": "10.15.0.64/27" + }, + "optional": [] + } + } + } + } + } \ No newline at end of file diff --git a/tests/schemas/lz-platform-identity/lz-Identity-Without-DNS-Resolver.json b/tests/schemas/lz-platform-identity/lz-Identity-Without-DNS-Resolver.json new file mode 100644 index 00000000..59508d0f --- /dev/null +++ b/tests/schemas/lz-platform-identity/lz-Identity-Without-DNS-Resolver.json @@ -0,0 +1,170 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] + }, + "actionGroupName": "Service health action group", + "actionGroupShortName": "health-alert", + "alertRuleName": "Incidents and Security", + "alertRuleDescription": "Service Health: Incidents and Security" + } + }, + "securityCenter": { + "value": { + "email": "alzcanadapubsec@microsoft.com", + "phone": "6045555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Owner Role", + "roleDefinitionId": "8e3af657-a8ff-443c-a75c-2fe8c4bcb635", + "securityGroupObjectIds": [ + "3a4fa072-cc14-471d-aeac-49afdbde9f7a" + ] + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tbd", + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "automation", + "networking": "networking", + "networkWatcher": "NetworkWatcherRG", + "backupRecoveryVault": "backup", + "domainControllers": "DomainControllersRG", + "dnsResolver": "dns-resolverRG", + "dnsCondionalForwarders": "dns-CondionalForwardersRG", + "privateDnsZones": "pubsec-dns" + } + }, + "automation": { + "value": { + "name": "automation" + } + }, + "backupRecoveryVault": { + "value": { + "enabled": true, + "name": "backup-vault" + } + }, + "privateDnsZones": { + "value": { + "enabled": false, + "resourceGroupName": "pubsec-dns" + } + }, + + "privateDnsResolver": { + "value": { + "enabled": false, + "name": "dns-resolver", + "inboundEndpointName": "dns-resolver-Inbound", + "outboundEndpointName": "dns-resolver-Outbound" + } + }, + + "privateDnsResolverRuleset": { + "value": { + "enabled": true, + "name": "dns-resolver-ruleset", + "linkRuleSetToVnet": true, + "linkRuleSetToVnetName": "dns-resolver-vnet-link", + "forwardingRules": [ + { + "name": "default", + "domain": "dontMakeMeThink.local", + "state": "Enabled", + "targetDnsServers": [ + { + "ipAddress": "10.99.99.100" + }, + { + "ipAddress": "10.99.99.99" + } + ] + } + ] + } + }, + + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4" + } + }, + + "network": { + "value": { + "deployVnet": true, + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "id-vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.15.0.0/24" + ], + "subnets": { + "domainControllers": { + "comments": "Identity Subnet for Domain Controllers and VM-Based DNS Servers", + "name": "DomainControllers", + "addressPrefix": "10.15.0.0/27" + }, + "dnsResolverInbound": { + "comments": "Azure DNS Resolver Inbound Requests subnet", + "name": "AzureDNSResolver-Inbound", + "addressPrefix": "10.15.0.32/27" + }, + "dnsResolverOutbound": { + "comments": "Azure DNS Resolver Outbound Requests subnet", + "name": "AzureDNSResolver-Outbound", + "addressPrefix": "10.15.0.64/27" + }, + "optional": [] + } + } + } + } + } \ No newline at end of file diff --git a/tests/schemas/lz-platform-logging/BudgetIsFalse.json b/tests/schemas/lz-platform-logging/BudgetIsFalse.json index b262eb62..79e941ad 100644 --- a/tests/schemas/lz-platform-logging/BudgetIsFalse.json +++ b/tests/schemas/lz-platform-logging/BudgetIsFalse.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-logging/EmptyResourceTags.json b/tests/schemas/lz-platform-logging/EmptyResourceTags.json index 14d84d2e..3ad883ca 100644 --- a/tests/schemas/lz-platform-logging/EmptyResourceTags.json +++ b/tests/schemas/lz-platform-logging/EmptyResourceTags.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-logging/EmptySubscriptionTags.json b/tests/schemas/lz-platform-logging/EmptySubscriptionTags.json index 79f3e512..45ad91e6 100644 --- a/tests/schemas/lz-platform-logging/EmptySubscriptionTags.json +++ b/tests/schemas/lz-platform-logging/EmptySubscriptionTags.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-logging/FullDeployment-With-Location.json b/tests/schemas/lz-platform-logging/FullDeployment-With-Location.json index 0b2e21e1..f647f207 100644 --- a/tests/schemas/lz-platform-logging/FullDeployment-With-Location.json +++ b/tests/schemas/lz-platform-logging/FullDeployment-With-Location.json @@ -13,8 +13,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -25,7 +25,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-logging/FullDeployment.json b/tests/schemas/lz-platform-logging/FullDeployment.json index 03d8690c..c593d00e 100644 --- a/tests/schemas/lz-platform-logging/FullDeployment.json +++ b/tests/schemas/lz-platform-logging/FullDeployment.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/lz-platform-logging/WithoutSubscriptionRoleAssignments.json b/tests/schemas/lz-platform-logging/WithoutSubscriptionRoleAssignments.json index f134ded8..aa86fc91 100644 --- a/tests/schemas/lz-platform-logging/WithoutSubscriptionRoleAssignments.json +++ b/tests/schemas/lz-platform-logging/WithoutSubscriptionRoleAssignments.json @@ -10,8 +10,8 @@ "receivers": { "app": [ "alzcanadapubsec@microsoft.com" ], "email": [ "alzcanadapubsec@microsoft.com" ], - "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], - "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + "sms": [ { "countryCode": "1", "phoneNumber": "6045555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "6045555555" } ] }, "actionGroupName": "Service health action group", "actionGroupShortName": "health-alert", @@ -22,7 +22,7 @@ "securityCenter": { "value": { "email": "alzcanadapubsec@microsoft.com", - "phone": "5555555555" + "phone": "6045555555" } }, "subscriptionRoleAssignments": { diff --git a/tests/schemas/run-tests.sh b/tests/schemas/run-tests.sh index 2dfab965..f18ae56b 100644 --- a/tests/schemas/run-tests.sh +++ b/tests/schemas/run-tests.sh @@ -12,6 +12,8 @@ pwsh -File ./validate-deployment-config.ps1 -SchemaFile '../../schemas/latest/l pwsh -File ./validate-deployment-config.ps1 -SchemaFile '../../schemas/latest/landingzones/lz-platform-connectivity-hub-nva.json' -TestFolder '../../config/networking/*/hub-nva/' -FileFilter '*.json' +pwsh -File ./validate-deployment-config.ps1 -SchemaFile '../../schemas/latest/landingzones/lz-platform-identity.json' -TestFolder '../../config/identity/' -FileFilter '*.json' + pwsh -File ./validate-deployment-config.ps1 -SchemaFile '../../schemas/latest/landingzones/lz-generic-subscription.json' -TestFolder '../../config/subscriptions/' -FileFilter '*generic-subscription*.json' pwsh -File ./validate-deployment-config.ps1 -SchemaFile '../../schemas/latest/landingzones/lz-machinelearning.json' -TestFolder '../../config/subscriptions/' -FileFilter '*machinelearning*.json'