Blog Post

FastTrack for Azure
30 MIN READ

How to expose NGINX Ingress Controller via Azure Front Door and Azure Private Link Service

paolosalvatori's avatar
Mar 14, 2023
This article shows how to use Azure Front Door Premium, Azure Web Application Firewall, and Azure Private Link Service (PLS) to securely expose and protect a workload running in Azure Kubernetes Serv...
Updated Mar 04, 2024
Version 11.0
"}},"componentScriptGroups({\"componentId\":\"custom.widget.MicrosoftFooter\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"components/community/NavbarDropdownToggle\"]})":[{"__ref":"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1745505307000"}],"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/users/UserAvatar\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1745505307000"}],"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/ranks/UserRankLabel\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1745505307000"}],"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"components/tags/TagView/TagViewChip\"]})":[{"__ref":"CachedAsset:text:en_US-components/tags/TagView/TagViewChip-1745505307000"}],"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"components/users/UserRegistrationDate\"]})":[{"__ref":"CachedAsset:text:en_US-components/users/UserRegistrationDate-1745505307000"}],"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeAvatar\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeAvatar-1745505307000"}],"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeDescription\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeDescription-1745505307000"}],"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/ThreadedReplyList\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/ThreadedReplyList-1745505307000"}],"cachedText({\"lastModified\":\"1745505307000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeIcon\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeIcon-1745505307000"}]},"Theme:customTheme1":{"__typename":"Theme","id":"customTheme1"},"User:user:-1":{"__typename":"User","id":"user:-1","uid":-1,"login":"Deleted","email":"","avatar":null,"rank":null,"kudosWeight":1,"registrationData":{"__typename":"RegistrationData","status":"ANONYMOUS","registrationTime":null,"confirmEmailStatus":false,"registrationAccessLevel":"VIEW","ssoRegistrationFields":[]},"ssoId":null,"profileSettings":{"__typename":"ProfileSettings","dateDisplayStyle":{"__typename":"InheritableStringSettingWithPossibleValues","key":"layout.friendly_dates_enabled","value":"false","localValue":"true","possibleValues":["true","false"]},"dateDisplayFormat":{"__typename":"InheritableStringSetting","key":"layout.format_pattern_date","value":"MMM dd yyyy","localValue":"MM-dd-yyyy"},"language":{"__typename":"InheritableStringSettingWithPossibleValues","key":"profile.language","value":"en-US","localValue":null,"possibleValues":["en-US","es-ES"]},"repliesSortOrder":{"__typename":"InheritableStringSettingWithPossibleValues","key":"config.user_replies_sort_order","value":"DEFAULT","localValue":"DEFAULT","possibleValues":["DEFAULT","LIKES","PUBLISH_TIME","REVERSE_PUBLISH_TIME"]}},"deleted":false},"CachedAsset:pages-1746564016129":{"__typename":"CachedAsset","id":"pages-1746564016129","value":[{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"BlogViewAllPostsPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId/all-posts/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"CasePortalPage","type":"CASE_PORTAL","urlPath":"/caseportal","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"CreateGroupHubPage","type":"GROUP_HUB","urlPath":"/groups/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"CaseViewPage","type":"CASE_DETAILS","urlPath":"/case/:caseId/:caseNumber","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"InboxPage","type":"COMMUNITY","urlPath":"/inbox","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"HelpFAQPage","type":"COMMUNITY","urlPath":"/help","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"IdeaMessagePage","type":"IDEA_POST","urlPath":"/idea/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"IdeaViewAllIdeasPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/all-ideas/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"LoginPage","type":"USER","urlPath":"/signin","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"BlogPostPage","type":"BLOG","urlPath":"/category/:categoryId/blogs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"UserBlogPermissions.Page","type":"COMMUNITY","urlPath":"/c/user-blog-permissions/page","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"ThemeEditorPage","type":"COMMUNITY","urlPath":"/designer/themes","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"TkbViewAllArticlesPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId/all-articles/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1730819800000,"localOverride":null,"page":{"id":"AllEvents","type":"CUSTOM","urlPath":"/Events","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"OccasionEditPage","type":"EVENT","urlPath":"/event/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"OAuthAuthorizationAllowPage","type":"USER","urlPath":"/auth/authorize/allow","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"PageEditorPage","type":"COMMUNITY","urlPath":"/designer/pages","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"PostPage","type":"COMMUNITY","urlPath":"/category/:categoryId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"ForumBoardPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"TkbBoardPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"EventPostPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"UserBadgesPage","type":"COMMUNITY","urlPath":"/users/:login/:userId/badges","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"GroupHubMembershipAction","type":"GROUP_HUB","urlPath":"/membership/join/:nodeId/:membershipType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"MaintenancePage","type":"COMMUNITY","urlPath":"/maintenance","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"IdeaReplyPage","type":"IDEA_REPLY","urlPath":"/idea/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"UserSettingsPage","type":"USER","urlPath":"/mysettings/:userSettingsTab","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"GroupHubsPage","type":"GROUP_HUB","urlPath":"/groups","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"ForumPostPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"OccasionRsvpActionPage","type":"OCCASION","urlPath":"/event/:boardId/:messageSubject/:messageId/rsvp/:responseType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"VerifyUserEmailPage","type":"USER","urlPath":"/verifyemail/:userId/:verifyEmailToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"AllOccasionsPage","type":"OCCASION","urlPath":"/category/:categoryId/events/:boardId/all-events/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"EventBoardPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"TkbReplyPage","type":"TKB_REPLY","urlPath":"/kb/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"IdeaBoardPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"CommunityGuideLinesPage","type":"COMMUNITY","urlPath":"/communityguidelines","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"CaseCreatePage","type":"SALESFORCE_CASE_CREATION","urlPath":"/caseportal/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"TkbEditPage","type":"TKB","urlPath":"/kb/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"ForgotPasswordPage","type":"USER","urlPath":"/forgotpassword","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"IdeaEditPage","type":"IDEA","urlPath":"/idea/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"TagPage","type":"COMMUNITY","urlPath":"/tag/:tagName","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"BlogBoardPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"OccasionMessagePage","type":"OCCASION_TOPIC","urlPath":"/event/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"ManageContentPage","type":"COMMUNITY","urlPath":"/managecontent","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"ClosedMembershipNodeNonMembersPage","type":"GROUP_HUB","urlPath":"/closedgroup/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"CommunityPage","type":"COMMUNITY","urlPath":"/","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"ForumMessagePage","type":"FORUM_TOPIC","urlPath":"/discussions/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"IdeaPostPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1730819800000,"localOverride":null,"page":{"id":"CommunityHub.Page","type":"CUSTOM","urlPath":"/Directory","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"BlogMessagePage","type":"BLOG_ARTICLE","urlPath":"/blog/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"RegistrationPage","type":"USER","urlPath":"/register","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"EditGroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"ForumEditPage","type":"FORUM","urlPath":"/discussions/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"ResetPasswordPage","type":"USER","urlPath":"/resetpassword/:userId/:resetPasswordToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1730819800000,"localOverride":null,"page":{"id":"AllBlogs.Page","type":"CUSTOM","urlPath":"/blogs","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"TkbMessagePage","type":"TKB_ARTICLE","urlPath":"/kb/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"BlogEditPage","type":"BLOG","urlPath":"/blog/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"ManageUsersPage","type":"USER","urlPath":"/users/manage/:tab?/:manageUsersTab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"ForumReplyPage","type":"FORUM_REPLY","urlPath":"/discussions/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"PrivacyPolicyPage","type":"COMMUNITY","urlPath":"/privacypolicy","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"NotificationPage","type":"COMMUNITY","urlPath":"/notifications","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"UserPage","type":"USER","urlPath":"/users/:login/:userId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"HealthCheckPage","type":"COMMUNITY","urlPath":"/health","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"OccasionReplyPage","type":"OCCASION_REPLY","urlPath":"/event/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"ManageMembersPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/manage/:tab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"SearchResultsPage","type":"COMMUNITY","urlPath":"/search","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"BlogReplyPage","type":"BLOG_REPLY","urlPath":"/blog/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"GroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"TermsOfServicePage","type":"COMMUNITY","urlPath":"/termsofservice","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"CategoryPage","type":"CATEGORY","urlPath":"/category/:categoryId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"ForumViewAllTopicsPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/all-topics/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"TkbPostPage","type":"TKB","urlPath":"/category/:categoryId/kbs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1746564016129,"localOverride":null,"page":{"id":"GroupHubPostPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"}],"localOverride":false},"CachedAsset:text:en_US-components/context/AppContext/AppContextProvider-0":{"__typename":"CachedAsset","id":"text:en_US-components/context/AppContext/AppContextProvider-0","value":{"noCommunity":"Cannot find community","noUser":"Cannot find current user","noNode":"Cannot find node with id {nodeId}","noMessage":"Cannot find message with id {messageId}","userBanned":"We're sorry, but you have been banned from using this site.","userBannedReason":"You have been banned for the following reason: {reason}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Loading/LoadingDot-0":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-0","value":{"title":"Loading..."},"localOverride":false},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/cmstNC05WEo0blc\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/cmstNC05WEo0blc","height":512,"width":512,"mimeType":"image/png"},"Rank:rank:4":{"__typename":"Rank","id":"rank:4","position":6,"name":"Microsoft","color":"333333","icon":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/cmstNC05WEo0blc\"}"},"rankStyle":"OUTLINE"},"User:user:988334":{"__typename":"User","id":"user:988334","uid":988334,"login":"paolosalvatori","deleted":false,"avatar":{"__typename":"UserAvatar","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/dS05ODgzMzQtMzg1MjYyaTE4QTU5MkIyQUVCMkM0MDE"},"rank":{"__ref":"Rank:rank:4"},"email":"","messagesCount":67,"biography":null,"topicsCount":30,"kudosReceivedCount":160,"kudosGivenCount":29,"kudosWeight":1,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2021-03-05T07:56:49.951-08:00","confirmEmailStatus":null},"followersCount":null,"solutionsCount":0},"Category:category:FastTrack":{"__typename":"Category","id":"category:FastTrack","entityType":"CATEGORY","displayId":"FastTrack","nodeType":"category","depth":3,"title":"Microsoft FastTrack","shortTitle":"Microsoft FastTrack","parent":{"__ref":"Category:category:products-services"}},"Category:category:top":{"__typename":"Category","id":"category:top","entityType":"CATEGORY","displayId":"top","nodeType":"category","depth":0,"title":"Top","shortTitle":"Top"},"Category:category:communities":{"__typename":"Category","id":"category:communities","entityType":"CATEGORY","displayId":"communities","nodeType":"category","depth":1,"parent":{"__ref":"Category:category:top"},"title":"Communities","shortTitle":"Communities"},"Category:category:products-services":{"__typename":"Category","id":"category:products-services","entityType":"CATEGORY","displayId":"products-services","nodeType":"category","depth":2,"parent":{"__ref":"Category:category:communities"},"title":"Products","shortTitle":"Products"},"Blog:board:FastTrackforAzureBlog":{"__typename":"Blog","id":"board:FastTrackforAzureBlog","entityType":"BLOG","displayId":"FastTrackforAzureBlog","nodeType":"board","depth":4,"conversationStyle":"BLOG","repliesProperties":{"__typename":"RepliesProperties","sortOrder":"REVERSE_PUBLISH_TIME","repliesFormat":"threaded"},"tagProperties":{"__typename":"TagNodeProperties","tagsEnabled":{"__typename":"PolicyResult","failureReason":null}},"requireTags":true,"tagType":"PRESET_ONLY","description":"","title":"FastTrack for Azure","shortTitle":"FastTrack for Azure","parent":{"__ref":"Category:category:FastTrack"},"ancestors":{"__typename":"CoreNodeConnection","edges":[{"__typename":"CoreNodeEdge","node":{"__ref":"Community:community:gxcuf89792"}},{"__typename":"CoreNodeEdge","node":{"__ref":"Category:category:communities"}},{"__typename":"CoreNodeEdge","node":{"__ref":"Category:category:products-services"}},{"__typename":"CoreNodeEdge","node":{"__ref":"Category:category:FastTrack"}}]},"userContext":{"__typename":"NodeUserContext","canAddAttachments":false,"canUpdateNode":false,"canPostMessages":false,"isSubscribed":false},"theme":{"__ref":"Theme:customTheme1"},"boardPolicies":{"__typename":"BoardPolicies","canViewSpamDashBoard":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.feature.moderation_spam.action.access_spam_quarantine.allowed.accessDenied","key":"error.lithium.policies.feature.moderation_spam.action.access_spam_quarantine.allowed.accessDenied","args":[]}},"canArchiveMessage":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.content_archivals.enable_content_archival_settings.accessDenied","key":"error.lithium.policies.content_archivals.enable_content_archival_settings.accessDenied","args":[]}},"canPublishArticleOnCreate":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.forums.policy_can_publish_on_create_workflow_action.accessDenied","key":"error.lithium.policies.forums.policy_can_publish_on_create_workflow_action.accessDenied","args":[]}}}},"BlogTopicMessage:message:3767535":{"__typename":"BlogTopicMessage","uid":3767535,"subject":"How to expose NGINX Ingress Controller via Azure Front Door and Azure Private Link Service","id":"message:3767535","revisionNum":11,"repliesCount":7,"author":{"__ref":"User:user:988334"},"depth":0,"hasGivenKudo":false,"board":{"__ref":"Blog:board:FastTrackforAzureBlog"},"conversation":{"__ref":"Conversation:conversation:3767535"},"messagePolicies":{"__typename":"MessagePolicies","canPublishArticleOnEdit":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.forums.policy_can_publish_on_edit_workflow_action.accessDenied","key":"error.lithium.policies.forums.policy_can_publish_on_edit_workflow_action.accessDenied","args":[]}},"canModerateSpamMessage":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","key":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","args":[]}}},"contentWorkflow":{"__typename":"ContentWorkflow","state":"PUBLISH","scheduledPublishTime":null,"scheduledTimezone":null,"userContext":{"__typename":"MessageWorkflowContext","canSubmitForReview":null,"canEdit":false,"canRecall":null,"canSubmitForPublication":null,"canReturnToAuthor":null,"canPublish":null,"canReturnToReview":null,"canSchedule":false},"shortScheduledTimezone":null},"readOnly":false,"editFrozen":false,"moderationData":{"__ref":"ModerationData:moderation_data:3767535"},"teaser@stripHtml({\"truncateLength\":200})":" This article shows how to use Azure Front Door Premium, Azure Web Application Firewall, and Azure Private Link Service (PLS) to securely expose and protect a workload running in Azure Kubernetes Serv...","body@stripHtml({\"truncateLength\":200})":" This article shows how to use Azure Front Door Premium, Azure Web Application Firewall, and Azure Private Link Service (PLS) to securely expose and protect a workload running in Azure Kubernetes Serv...","body@stringLength":"94918","rawBody":"

This article shows how to use Azure Front Door Premium, Azure Web Application Firewall, and Azure Private Link Service (PLS) to securely expose and protect a workload running in Azure Kubernetes Service(AKS). The sample application is exposed via the NGINX Ingress Controller configured to use a private IP address as a frontend IP configuration of the kubernetes-internal internal load balancer. For more information, see Create an ingress controller using an internal IP address.

\n

 

\n

In addition, this sample shows how to deploy an Azure Kubernetes Service cluster with the API Server VNET Integration and how to use an Azure NAT Gateway to manage outbound connections initiated by AKS-hosted workloads. AKS clusters with API Server VNET integration provide advantages: for example, they can have public network access or private cluster mode enabled or disabled without redeploying the cluster. For more information, see Create an Azure Kubernetes Service cluster with API Server VNet Integration.

\n

 

\n

You can find the companion code and Bicep modules in this GitHub repository.

\n

 

\n

Prerequisites

\n\n

 

\n

Architecture

\n

This sample provides a set of Bicep modules to deploy and configure an Azure Front Door Premium with an WAF Policy as global load balancer in front of a public or a private AKS cluster with API Server VNET Integration, Azure CNI as a network plugin and Dynamic IP Allocation. The following diagram shows the architecture and network topology deployed by the sample:

\n
 
\n

\n

 

\n

 

\n

A Deployment Script is used to create the NGINX Ingress Controller, configured to use a private IP address as frontend IP configuration of the kubernetes-internal internal load balancer via Helm and a sample httpbin web application via YAML manifests. The Origin child resource of the Azure Front Door Premium global load balancer is configured to call the sample application via Azure Private Link Service, the AKS the kubernetes-internal internal load balancer, and the NGINX Ingress Controller, as shown in the following figure:

\n

 

\n

\n

 

\n

Bicep modules are parametric, so you can choose any network plugin:

\n\n
\n

NOTE
The sample was tested only with Azure CNI and Azure CNI Overlay

\n
\n

The Bicep modules in the companion sample also allow to install the following extensions and add-ons for Azure Kubernetes Service(AKS):

\n\n

In a production environment, we strongly recommend deploying a private AKS cluster with Uptime SLA. For more information, see private AKS cluster with a Public DNS address. Alternatively, you can deploy a public AKS cluster and secure access to the API server using authorized IP address ranges.

\n

The Bicep modules deploy the following Azure resources:

\n\n
\n

NOTE
You can find the architecture.vsdx file used for the diagram under the visio folder.

\n
\n

 

\n

What is Bicep?

\n

Bicep is a domain-specific language (DSL) that uses a declarative syntax to deploy Azure resources. It provides concise syntax, reliable type safety, and support for code reuse. Bicep offers the best authoring experience for your infrastructure-as-code solutions in Azure.

\n

 

\n

Deploy the Bicep modules

\n

You can deploy the Bicep modules in the bicep folder using the deploy.sh Bash script in the same folder. Specify a value for the following parameters in the deploy.sh script and main.parameters.json parameters file before deploying the Bicep modules.

\n\n

We suggest reading sensitive configuration data such as passwords or SSH keys from a pre-existing Azure Key Vault resource. For more information, see Use Azure Key Vault to pass secure parameter value during Bicep deployment.

\n

 

\n

 

\n#!/bin/bash\n\n# Template\ntemplate=\"main.bicep\"\nparameters=\"main.parameters.json\"\n\n# AKS cluster name\nprefix=\"<Azure-Resource-Name-Prefix>\"\naksName=\"${prefix}Aks\"\nvalidateTemplate=1\nuseWhatIf=0\nupdate=1\ninstallExtensions=0\n\n# Name and location of the resource group for the Azure Kubernetes Service (AKS) cluster\nresourceGroupName=\"${prefix}RG\"\nlocation=\"westeurope\"\ndeploymentName=\"main\"\n\n# Subscription id, subscription name, and tenant id of the current subscription\nsubscriptionId=$(az account show --query id --output tsv)\nsubscriptionName=$(az account show --query name --output tsv)\ntenantId=$(az account show --query tenantId --output tsv)\n\n# Install aks-preview Azure extension\nif [[ $installExtensions == 1 ]]; then\n echo \"Checking if [aks-preview] extension is already installed...\"\n az extension show --name aks-preview &>/dev/null\n\n if [[ $? == 0 ]]; then\n echo \"[aks-preview] extension is already installed\"\n\n # Update the extension to make sure you have the latest version installed\n echo \"Updating [aks-preview] extension...\"\n az extension update --name aks-preview &>/dev/null\n else\n echo \"[aks-preview] extension is not installed. Installing...\"\n\n # Install aks-preview extension\n az extension add --name aks-preview 1>/dev/null\n\n if [[ $? == 0 ]]; then\n echo \"[aks-preview] extension successfully installed\"\n else\n echo \"Failed to install [aks-preview] extension\"\n exit\n fi\n fi\n\n # Registering AKS feature extensions\n aksExtensions=(\n \"PodSecurityPolicyPreview\"\n \"KubeletDisk\"\n \"AKS-KedaPreview\"\n \"RunCommandPreview\"\n \"EnablePodIdentityPreview \"\n \"UserAssignedIdentityPreview\"\n \"EnablePrivateClusterPublicFQDN\"\n \"PodSubnetPreview\"\n \"EnableOIDCIssuerPreview\"\n \"EnableWorkloadIdentityPreview\"\n \"EnableImageCleanerPreview\"\n \"AKS-VPAPreview\"\n \"AzureOverlayPreview\"\n \"KubeProxyConfigurationPreview\"\n )\n ok=0\n registeringExtensions=()\n for aksExtension in ${aksExtensions[@]}; do\n echo \"Checking if [$aksExtension] extension is already registered...\"\n extension=$(az feature list -o table --query \"[?contains(name, 'Microsoft.ContainerService/$aksExtension') && @.properties.state == 'Registered'].{Name:name}\" --output tsv)\n if [[ -z $extension ]]; then\n echo \"[$aksExtension] extension is not registered.\"\n echo \"Registering [$aksExtension] extension...\"\n az feature register --name $aksExtension --namespace Microsoft.ContainerService\n registeringExtensions+=(\"$aksExtension\")\n ok=1\n else\n echo \"[$aksExtension] extension is already registered.\"\n fi\n done\n echo $registeringExtensions\n delay=1\n for aksExtension in ${registeringExtensions[@]}; do\n echo -n \"Checking if [$aksExtension] extension is already registered...\"\n while true; do\n extension=$(az feature list -o table --query \"[?contains(name, 'Microsoft.ContainerService/$aksExtension') && @.properties.state == 'Registered'].{Name:name}\" --output tsv)\n if [[ -z $extension ]]; then\n echo -n \".\"\n sleep $delay\n else\n echo \".\"\n break\n fi\n done\n done\n\n if [[ $ok == 1 ]]; then\n echo \"Refreshing the registration of the Microsoft.ContainerService resource provider...\"\n az provider register --namespace Microsoft.ContainerService\n echo \"Microsoft.ContainerService resource provider registration successfully refreshed\"\n fi\nfi\n\n# Get the last Kubernetes version available in the region\nkubernetesVersion=$(az aks get-versions --location $location --query \"orchestrators[?isPreview==false].orchestratorVersion | sort(@) | [-1]\" --output tsv)\n\nif [[ -n $kubernetesVersion ]]; then\n echo \"Successfully retrieved the last Kubernetes version [$kubernetesVersion] supported by AKS in [$location] Azure region\"\nelse\n echo \"Failed to retrieve the last Kubernetes version supported by AKS in [$location] Azure region\"\n exit\nfi\n\n# Check if the resource group already exists\necho \"Checking if [$resourceGroupName] resource group actually exists in the [$subscriptionName] subscription...\"\n\naz group show --name $resourceGroupName &>/dev/null\n\nif [[ $? != 0 ]]; then\n echo \"No [$resourceGroupName] resource group actually exists in the [$subscriptionName] subscription\"\n echo \"Creating [$resourceGroupName] resource group in the [$subscriptionName] subscription...\"\n\n # Create the resource group\n az group create --name $resourceGroupName --location $location 1>/dev/null\n\n if [[ $? == 0 ]]; then\n echo \"[$resourceGroupName] resource group successfully created in the [$subscriptionName] subscription\"\n else\n echo \"Failed to create [$resourceGroupName] resource group in the [$subscriptionName] subscription\"\n exit\n fi\nelse\n echo \"[$resourceGroupName] resource group already exists in the [$subscriptionName] subscription\"\nfi\n\n# Create AKS cluster if does not exist\necho \"Checking if [$aksName] aks cluster actually exists in the [$resourceGroupName] resource group...\"\n\naz aks show --name $aksName --resource-group $resourceGroupName &>/dev/null\nnotExists=$?\n\nif [[ $notExists != 0 || $update == 1 ]]; then\n\n if [[ $notExists != 0 ]]; then\n echo \"No [$aksName] aks cluster actually exists in the [$resourceGroupName] resource group\"\n else\n echo \"[$aksName] aks cluster already exists in the [$resourceGroupName] resource group. Updating the cluster...\"\n fi\n\n # Delete any existing role assignments for the user-defined managed identity of the AKS cluster\n # in case you are re-deploying the solution in an existing resource group\n echo \"Retrieving the list of role assignments on [$resourceGroupName] resource group...\"\n assignmentIds=$(az role assignment list \\\n --scope \"/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}\" \\\n --query [].id \\\n --output tsv \\\n --only-show-errors)\n\n if [[ -n $assignmentIds ]]; then\n echo \"[${#assignmentIds[@]}] role assignments have been found on [$resourceGroupName] resource group\"\n for assignmentId in ${assignmentIds[@]}; do\n if [[ -n $assignmentId ]]; then\n az role assignment delete --ids $assignmentId\n\n if [[ $? == 0 ]]; then\n assignmentName=$(echo $assignmentId | awk -F '/' '{print $NF}')\n echo \"[$assignmentName] role assignment on [$resourceGroupName] resource group successfully deleted\"\n fi\n fi\n done\n else\n echo \"No role assignment actually exists on [$resourceGroupName] resource group\"\n fi\n\n # Get the kubelet managed identity used by the AKS cluster\n echo \"Retrieving the kubelet identity from the [$aksName] AKS cluster...\"\n clientId=$(az aks show \\\n --name $aksName \\\n --resource-group $resourceGroupName \\\n --query identityProfile.kubeletidentity.clientId \\\n --output tsv 2>/dev/null)\n\n if [[ -n $clientId ]]; then\n # Delete any role assignment to kubelet managed identity on any ACR in the resource group\n echo \"kubelet identity of the [$aksName] AKS cluster successfully retrieved\"\n echo \"Retrieving the list of ACR resources in the [$resourceGroupName] resource group...\"\n acrIds=$(az acr list \\\n --resource-group $resourceGroupName \\\n --query [].id \\\n --output tsv)\n\n if [[ -n $acrIds ]]; then\n echo \"[${#acrIds[@]}] ACR resources have been found in [$resourceGroupName] resource group\"\n for acrId in ${acrIds[@]}; do\n if [[ -n $acrId ]]; then\n acrName=$(echo $acrId | awk -F '/' '{print $NF}')\n echo \"Retrieving the list of role assignments on [$acrName] ACR...\"\n assignmentIds=$(az role assignment list \\\n --scope \"$acrId\" \\\n --query [].id \\\n --output tsv \\\n --only-show-errors)\n\n if [[ -n $assignmentIds ]]; then\n echo \"[${#assignmentIds[@]}] role assignments have been found on [$acrName] ACR\"\n for assignmentId in ${assignmentIds[@]}; do\n if [[ -n $assignmentId ]]; then\n az role assignment delete --ids $assignmentId\n\n if [[ $? == 0 ]]; then\n assignmentName=$(echo $assignmentId | awk -F '/' '{print $NF}')\n echo \"[$assignmentName] role assignment on [$acrName] ACR successfully deleted\"\n fi\n fi\n done\n else\n echo \"No role assignment actually exists on [$acrName] ACR\"\n fi\n fi\n done\n else\n echo \"No ACR actually exists in [$resourceGroupName] resource group\"\n fi\n else\n echo \"No kubelet identity exists for the [$aksName] AKS cluster\"\n fi\n\n # Validate the Bicep template\n if [[ $validateTemplate == 1 ]]; then\n if [[ $useWhatIf == 1 ]]; then\n # Execute a deployment What-If operation at resource group scope.\n echo \"Previewing changes deployed by [$template] Bicep template...\"\n az deployment group what-if \\\n --resource-group $resourceGroupName \\\n --template-file $template \\\n --parameters $parameters \\\n --parameters prefix=$prefix \\\n location=$location \\\n aksClusterKubernetesVersion=$kubernetesVersion\n\n if [[ $? == 0 ]]; then\n echo \"[$template] Bicep template validation succeeded\"\n else\n echo \"Failed to validate [$template] Bicep template\"\n exit\n fi\n else\n # Validate the Bicep template\n echo \"Validating [$template] Bicep template...\"\n output=$(az deployment group validate \\\n --resource-group $resourceGroupName \\\n --template-file $template \\\n --parameters $parameters \\\n --parameters prefix=$prefix \\\n location=$location \\\n aksClusterKubernetesVersion=$kubernetesVersion)\n\n if [[ $? == 0 ]]; then\n echo \"[$template] Bicep template validation succeeded\"\n else\n echo \"Failed to validate [$template] Bicep template\"\n echo $output\n exit\n fi\n fi\n fi\n\n # Deploy the Bicep template\n echo \"Deploying [$template] Bicep template...\"\n az deployment group create \\\n --name $deploymentName \\\n --resource-group $resourceGroupName \\\n --only-show-errors \\\n --template-file $template \\\n --parameters $parameters \\\n --parameters prefix=$prefix \\\n location=$location \\\n aksClusterKubernetesVersion=$kubernetesVersion 1>/dev/null\n\n if [[ $? == 0 ]]; then\n echo \"[$template] Bicep template successfully provisioned\"\n else\n echo \"Failed to provision the [$template] Bicep template\"\n exit\n fi\nelse\n echo \"[$aksName] aks cluster already exists in the [$resourceGroupName] resource group\"\nfi\n\n# Create AKS cluster if does not exist\necho \"Checking if [$aksName] aks cluster actually exists in the [$resourceGroupName] resource group...\"\n\naz aks show --name $aksName --resource-group $resourceGroupName &>/dev/null\n\nif [[ $? != 0 ]]; then\n echo \"No [$aksName] aks cluster actually exists in the [$resourceGroupName] resource group\"\n exit\nfi\n\n# Get the user principal name of the current user\necho \"Retrieving the user principal name of the current user from the [$tenantId] Azure AD tenant...\"\nuserPrincipalName=$(az account show --query user.name --output tsv)\nif [[ -n $userPrincipalName ]]; then\n echo \"[$userPrincipalName] user principal name successfully retrieved from the [$tenantId] Azure AD tenant\"\nelse\n echo \"Failed to retrieve the user principal name of the current user from the [$tenantId] Azure AD tenant\"\n exit\nfi\n\n# Retrieve the objectId of the user in the Azure AD tenant used by AKS for user authentication\necho \"Retrieving the objectId of the [$userPrincipalName] user principal name from the [$tenantId] Azure AD tenant...\"\nuserObjectId=$(az ad user show --id $userPrincipalName --query id --output tsv 2>/dev/null)\n\nif [[ -n $userObjectId ]]; then\n echo \"[$userObjectId] objectId successfully retrieved for the [$userPrincipalName] user principal name\"\nelse\n echo \"Failed to retrieve the objectId of the [$userPrincipalName] user principal name\"\n exit\nfi\n\n# Retrieve the resource id of the AKS cluster\necho \"Retrieving the resource id of the [$aksName] AKS cluster...\"\naksClusterId=$(az aks show \\\n --name \"$aksName\" \\\n --resource-group \"$resourceGroupName\" \\\n --query id \\\n --output tsv 2>/dev/null)\n\nif [[ -n $aksClusterId ]]; then\n echo \"Resource id of the [$aksName] AKS cluster successfully retrieved\"\nelse\n echo \"Failed to retrieve the resource id of the [$aksName] AKS cluster\"\n exit\nfi\n\n# Assign Azure Kubernetes Service RBAC Cluster Admin role to the current user\nrole=\"Azure Kubernetes Service RBAC Cluster Admin\"\necho \"Checking if [$userPrincipalName] user has been assigned to [$role] role on the [$aksName] AKS cluster...\"\ncurrent=$(az role assignment list \\\n --assignee $userObjectId \\\n --scope $aksClusterId \\\n --query \"[?roleDefinitionName=='$role'].roleDefinitionName\" \\\n --output tsv 2>/dev/null)\n\nif [[ $current == \"Owner\" ]] || [[ $current == \"Contributor\" ]] || [[ $current == \"$role\" ]]; then\n echo \"[$userPrincipalName] user is already assigned to the [$current] role on the [$aksName] AKS cluster\"\nelse\n echo \"[$userPrincipalName] user is not assigned to the [$role] role on the [$aksName] AKS cluster\"\n echo \"Assigning the [$userPrincipalName] user to the [$role] role on the [$aksName] AKS cluster...\"\n\n az role assignment create \\\n --role \"$role\" \\\n --assignee $userObjectId \\\n --scope $aksClusterId \\\n --only-show-errors 1>/dev/null\n\n if [[ $? == 0 ]]; then\n echo \"[$userPrincipalName] user successfully assigned to the [$role] role on the [$aksName] AKS cluster\"\n else\n echo \"Failed to assign the [$userPrincipalName] user to the [$role] role on the [$aksName] AKS cluster\"\n exit\n fi\nfi\n\n# Assign Azure Kubernetes Service Cluster Admin Role role to the current user\nrole=\"Azure Kubernetes Service Cluster Admin Role\"\necho \"Checking if [$userPrincipalName] user has been assigned to [$role] role on the [$aksName] AKS cluster...\"\ncurrent=$(az role assignment list \\\n --assignee $userObjectId \\\n --scope $aksClusterId \\\n --query \"[?roleDefinitionName=='$role'].roleDefinitionName\" \\\n --output tsv 2>/dev/null)\n\nif [[ $current == \"Owner\" ]] || [[ $current == \"Contributor\" ]] || [[ $current == \"$role\" ]]; then\n echo \"[$userPrincipalName] user is already assigned to the [$current] role on the [$aksName] AKS cluster\"\nelse\n echo \"[$userPrincipalName] user is not assigned to the [$role] role on the [$aksName] AKS cluster\"\n echo \"Assigning the [$userPrincipalName] user to the [$role] role on the [$aksName] AKS cluster...\"\n\n az role assignment create \\\n --role \"$role\" \\\n --assignee $userObjectId \\\n --scope $aksClusterId \\\n --only-show-errors 1>/dev/null\n\n if [[ $? == 0 ]]; then\n echo \"[$userPrincipalName] user successfully assigned to the [$role] role on the [$aksName] AKS cluster\"\n else\n echo \"Failed to assign the [$userPrincipalName] user to the [$role] role on the [$aksName] AKS cluster\"\n exit\n fi\nfi\n\n# Get the FQDN of the Azure Front Door endpoint\nazureFrontDoorEndpointFqdn=$(az deployment group show \\\n --name $deploymentName \\\n --resource-group $resourceGroupName \\\n --query properties.outputs.frontDoorEndpointFqdn.value \\\n --output tsv)\n\nif [[ -n $azureFrontDoorEndpointFqdn ]]; then\n echo \"FQDN of the Azure Front Door endpoint: $azureFrontDoorEndpointFqdn\"\nelse\n echo \"Failed to get the FQDN of the Azure Front Door endpoint\"\n exit -1\nfi\n\n# Get the private link service name\nprivateLinkServiceName=$(az deployment group show \\\n --name $deploymentName \\\n --resource-group $resourceGroupName \\\n --query properties.outputs.privateLinkServiceName.value \\\n --output tsv)\n\nif [[ -z $privateLinkServiceName ]]; then\n echo \"Failed to get the private link service name\"\n exit -1\nfi\n\n# Get the resource id of the Private Endpoint Connection\nprivateEndpointConnectionId=$(az network private-endpoint-connection list \\\n --name $privateLinkServiceName \\\n --resource-group $resourceGroupName \\\n --type Microsoft.Network/privateLinkServices \\\n --query [0].id \\\n --output tsv)\n\nif [[ -n $privateEndpointConnectionId ]]; then\n echo \"Resource id of the Private Endpoint Connection: $privateEndpointConnectionId\"\nelse\n echo \"Failed to get the resource id of the Private Endpoint Connection\"\n exit -1\nfi\n\n# Approve the private endpoint connection\necho \"Approving [$privateEndpointConnectionId] private endpoint connection ID...\"\naz network private-endpoint-connection approve \\\n --name $privateLinkServiceName \\\n --resource-group $resourceGroupName \\\n --id $privateEndpointConnectionId \\\n --description \"Approved\" 1>/dev/null\n\nif [[ $? == 0 ]]; then\n echo \"[$privateEndpointConnectionId] private endpoint connection ID successfully approved\"\nelse\n echo \"Failed to approve [$privateEndpointConnectionId] private endpoint connection ID\"\n exit -1\nfi\n

 

\n

 

\n

The last steps of the Bash script perform the following actions:

\n\n

If you miss running these steps, Azure Front Door cannot invoke the httpbin web application via the Azure Private Link Service, and the kubernetes-internal internal load balancer of the AKS cluster.

\n

 

\n

Bicep Modules

\n

The companion sample contains several modules. The following table shows the module used to create and configure Azure Front Door and its child resources.

\n

 

\n

 

\n// Parameters\n@description('Specifies the name of the Azure Front Door.')\nparam frontDoorName string\n\n@description('The name of the SKU to use when creating the Front Door profile.')\n@allowed([\n 'Standard_AzureFrontDoor'\n 'Premium_AzureFrontDoor'\n])\nparam frontDoorSkuName string = 'Premium_AzureFrontDoor'\n\n@description('Specifies the send and receive timeout on forwarding request to the origin. When timeout is reached, the request fails and returns.')\nparam originResponseTimeoutSeconds int = 30\n\n@description('Specifies the name of the Azure Front Door Origin Group for the web application.')\nparam originGroupName string\n\n@description('Specifies the name of the Azure Front Door Origin for the web application.')\nparam originName string\n\n@description('Specifies the address of the origin. Domain names, IPv4 addresses, and IPv6 addresses are supported.This should be unique across all origins in an endpoint.')\nparam hostName string\n\n@description('Specifies the value of the HTTP port. Must be between 1 and 65535.')\nparam httpPort int = 80\n\n@description('Specifies the value of the HTTPS port. Must be between 1 and 65535.')\nparam httpsPort int = 443\n\n@description('Specifies the host header value sent to the origin with each request. If you leave this blank, the request hostname determines this value. Azure Front Door origins, such as Web Apps, Blob Storage, and Cloud Services require this host header value to match the origin hostname by default. This overrides the host header defined at Endpoint.')\nparam originHostHeader string\n\n@description('Specifies the priority of origin in given origin group for load balancing. Higher priorities will not be used for load balancing if any lower priority origin is healthy.Must be between 1 and 5.')\n@minValue(1)\n@maxValue(5)\nparam priority int = 1\n\n@description('Specifies the weight of the origin in a given origin group for load balancing. Must be between 1 and 1000.')\n@minValue(1)\n@maxValue(1000)\nparam weight int = 1000\n\n@description('Specifies whether to enable health probes to be made against backends defined under backendPools. Health probes can only be disabled if there is a single enabled backend in single enabled backend pool.')\n@allowed([\n 'Enabled'\n 'Disabled'\n])\nparam originEnabledState string = 'Enabled'\n\n@description('Specifies the resource id of a private link service.')\nparam privateLinkResourceId string\n\n@description('Specifies the number of samples to consider for load balancing decisions.')\nparam sampleSize int = 4\n\n@description('Specifies the number of samples within the sample period that must succeed.')\nparam successfulSamplesRequired int = 3\n\n@description('Specifies the additional latency in milliseconds for probes to fall into the lowest latency bucket.')\nparam additionalLatencyInMilliseconds int = 50\n\n@description('Specifies path relative to the origin that is used to determine the health of the origin.')\nparam probePath string = '/'\n\n@description('Specifies the health probe request type.')\n@allowed([\n 'GET'\n 'HEAD'\n 'NotSet'\n])\nparam probeRequestType string = 'GET'\n\n@description('Specifies the health probe protocol.')\n@allowed([\n 'Http'\n 'Https'\n 'NotSet'\n])\nparam probeProtocol string = 'Http'\n\n@description('Specifies the number of seconds between health probes.Default is 240 seconds.')\nparam probeIntervalInSeconds int = 60\n\n@description('Specifies whether to allow session affinity on this host. Valid options are Enabled or Disabled.')\n@allowed([\n 'Enabled'\n 'Disabled'\n])\nparam sessionAffinityState string = 'Disabled'\n\n@description('Specifies the endpoint name reuse scope. The default value is TenantReuse.')\n@allowed([\n 'NoReuse'\n 'ResourceGroupReuse'\n 'SubscriptionReuse'\n 'TenantReuse'\n])\nparam autoGeneratedDomainNameLabelScope string = 'TenantReuse'\n\n@description('Specifies the name of the Azure Front Door Route for the web application.')\nparam routeName string\n\n@description('Specifies the domains referenced by the endpoint.')\nparam customDomains array = []\n\n@description('Specifies a directory path on the origin that Azure Front Door can use to retrieve content from, e.g. contoso.cloudapp.net/originpath.')\nparam originPath string = '/'\n\n@description('Specifies the rule sets referenced by this endpoint.')\nparam ruleSets array = []\n\n@description('Specifies the list of supported protocols for this route')\nparam supportedProtocols array = [\n 'Http'\n 'Https'\n]\n\n@description('Specifies the route patterns of the rule.')\nparam routePatternsToMatch array = [ '/*' ]\n\n@description('Specifies the protocol this rule will use when forwarding traffic to backends.')\n@allowed([\n 'HttpOnly'\n 'HttpsOnly'\n 'MatchRequest'\n])\nparam forwardingProtocol string = 'HttpOnly'\n\n@description('Specifies whether this route will be linked to the default endpoint domain.')\n@allowed([\n 'Enabled'\n 'Disabled'\n])\nparam linkToDefaultDomain string = 'Enabled'\n\n@description('Specifies whether to automatically redirect HTTP traffic to HTTPS traffic. Note that this is a easy way to set up this rule and it will be the first rule that gets executed.')\n@allowed([\n 'Enabled'\n 'Disabled'\n])\nparam httpsRedirect string = 'Enabled'\n\n@description('Specifies the name of the Azure Front Door Endpoint for the web application.')\nparam endpointName string\n\n@description('Specifies whether to enable use of this rule. Permitted values are Enabled or Disabled')\n@allowed([\n 'Enabled'\n 'Disabled'\n])\nparam endpointEnabledState string = 'Enabled'\n\n@description('Specifies the name of the Azure Front Door WAF policy.')\nparam wafPolicyName string\n\n@description('Specifies the WAF policy is in detection mode or prevention mode.')\n@allowed([\n 'Detection'\n 'Prevention'\n])\nparam wafPolicyMode string = 'Prevention'\n\n@description('Specifies if the policy is in enabled or disabled state. Defaults to Enabled if not specified.')\nparam wafPolicyEnabledState string = 'Enabled'\n\n@description('Specifies the list of managed rule sets to configure on the WAF.')\nparam wafManagedRuleSets array = []\n\n@description('Specifies the list of custom rulesto configure on the WAF.')\nparam wafCustomRules array = []\n\n@description('Specifies if the WAF policy managed rules will inspect the request body content.')\n@allowed([\n 'Enabled'\n 'Disabled'\n])\nparam wafPolicyRequestBodyCheck string = 'Enabled'\n\n@description('Specifies name of the security policy.')\nparam securityPolicyName string\n\n@description('Specifies the list of patterns to match by the security policy.')\nparam securityPolicyPatternsToMatch array = [ '/*' ]\n\n@description('Specifies the resource id of the Log Analytics workspace.')\nparam workspaceId string\n\n@description('Specifies the workspace data retention in days.')\nparam retentionInDays int = 60\n\n@description('Specifies the location.')\nparam location string = resourceGroup().location\n\n@description('Specifies the resource tags.')\nparam tags object\n\n\n// Variables\n\nvar diagnosticSettingsName = 'diagnosticSettings'\nvar logCategories = [\n 'FrontDoorAccessLog'\n 'FrontDoorHealthProbeLog'\n 'FrontDoorWebApplicationFirewallLog'\n]\nvar metricCategories = [\n 'AllMetrics'\n]\nvar logs = [for category in logCategories: {\n category: category\n enabled: true\n retentionPolicy: {\n enabled: true\n days: retentionInDays\n }\n}]\nvar metrics = [for category in metricCategories: {\n category: category\n enabled: true\n retentionPolicy: {\n enabled: true\n days: retentionInDays\n }\n}]\n\n// Resources\nresource frontDoor 'Microsoft.Cdn/profiles@2022-11-01-preview' = {\n name: frontDoorName\n location: 'Global'\n tags: tags\n sku: {\n name: frontDoorSkuName\n }\n properties: {\n originResponseTimeoutSeconds: originResponseTimeoutSeconds\n extendedProperties: {\n }\n }\n}\n\nresource originGroup 'Microsoft.Cdn/profiles/origingroups@2022-11-01-preview' = {\n parent: frontDoor\n name: originGroupName\n properties: {\n loadBalancingSettings: {\n sampleSize: sampleSize\n successfulSamplesRequired: successfulSamplesRequired\n additionalLatencyInMilliseconds: additionalLatencyInMilliseconds\n }\n healthProbeSettings: {\n probePath: probePath\n probeRequestType: probeRequestType\n probeProtocol: probeProtocol\n probeIntervalInSeconds: probeIntervalInSeconds\n }\n sessionAffinityState: sessionAffinityState\n }\n}\n\nresource origin 'Microsoft.Cdn/profiles/origingroups/origins@2022-11-01-preview' = {\n parent: originGroup\n name: originName\n properties: {\n hostName: hostName\n httpPort: httpPort\n httpsPort: httpsPort\n originHostHeader: originHostHeader\n priority: priority\n weight: weight\n enabledState: originEnabledState\n sharedPrivateLinkResource: empty(privateLinkResourceId) ? {} : {\n privateLink: {\n id: privateLinkResourceId\n }\n privateLinkLocation: location\n status: 'Approved'\n requestMessage: 'Please approve this request to allow Front Door to access the container app'\n }\n enforceCertificateNameCheck: true\n }\n}\n\nresource endpoint 'Microsoft.Cdn/profiles/afdEndpoints@2022-11-01-preview' = {\n parent: frontDoor\n name: endpointName\n location: 'Global'\n properties: {\n autoGeneratedDomainNameLabelScope: toUpper(autoGeneratedDomainNameLabelScope)\n enabledState: endpointEnabledState\n }\n}\n\nresource route 'Microsoft.Cdn/profiles/afdEndpoints/routes@2022-11-01-preview' = {\n parent: endpoint\n name: routeName\n properties: {\n customDomains: customDomains\n originGroup: {\n id: originGroup.id\n }\n originPath: originPath\n ruleSets: ruleSets\n supportedProtocols: supportedProtocols\n patternsToMatch: routePatternsToMatch\n forwardingProtocol: forwardingProtocol\n linkToDefaultDomain: linkToDefaultDomain\n httpsRedirect: httpsRedirect\n }\n dependsOn: [\n origin\n ]\n}\n\nresource wafPolicy 'Microsoft.Network/FrontDoorWebApplicationFirewallPolicies@2022-05-01' = {\n name: wafPolicyName\n location: 'Global'\n tags: tags\n sku: {\n name: frontDoorSkuName\n }\n properties: {\n policySettings: {\n enabledState: wafPolicyEnabledState\n mode: wafPolicyMode\n requestBodyCheck: wafPolicyRequestBodyCheck\n }\n managedRules: {\n managedRuleSets: wafManagedRuleSets\n }\n customRules: {\n rules: wafCustomRules\n }\n }\n}\n\nresource securityPolicy 'Microsoft.Cdn/profiles/securitypolicies@2022-11-01-preview' = {\n parent: frontDoor\n name: securityPolicyName\n properties: {\n parameters: {\n type: 'WebApplicationFirewall'\n wafPolicy: {\n id: wafPolicy.id\n }\n associations: [\n {\n domains: [\n {\n id: endpoint.id\n }\n ]\n patternsToMatch: securityPolicyPatternsToMatch\n }\n ]\n\n }\n }\n}\n\n// Diagnostics Settings\nresource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {\n name: diagnosticSettingsName\n scope: frontDoor\n properties: {\n workspaceId: workspaceId\n logs: logs\n metrics: metrics\n }\n}\n\n// Outputs\noutput id string = frontDoor.id\noutput name string = frontDoor.name\noutput endpointFqdn string = endpoint.properties.hostName\n

 

\n

 

\n

Deployment Script

\n

The sample makes use of a Deployment Script to run the install-helm-charts-and-app.sh Bash script which installs the httpbin web application via YAML templates and the following packages to the AKS cluster via Helm. For more information on deployment scripts, see Use deployment scripts in Bicep

\n\n

 

\n

 

\n# Install kubectl\naz aks install-cli --only-show-errors\n\n# Get AKS credentials\naz aks get-credentials \\\n --admin \\\n --name $clusterName \\\n --resource-group $resourceGroupName \\\n --subscription $subscriptionId \\\n --only-show-errors\n\n# Check if the cluster is private or not\nprivate=$(az aks show --name $clusterName \\\n --resource-group $resourceGroupName \\\n --subscription $subscriptionId \\\n --query apiServerAccessProfile.enablePrivateCluster \\\n --output tsv)\n\n# Install Helm\ncurl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 -o get_helm.sh -s\nchmod 700 get_helm.sh\n./get_helm.sh &>/dev/null\n\n# Add Helm repos\nhelm repo add prometheus-community https://prometheus-community.github.io/helm-charts\nhelm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx\nhelm repo add jetstack https://charts.jetstack.io\n\n# Update Helm repos\nhelm repo update\n\nif [[ $private == 'true' ]]; then\n # Log whether the cluster is public or private\n echo \"$clusterName AKS cluster is public\"\n\n # Install Prometheus\n command=\"helm install prometheus prometheus-community/kube-prometheus-stack \\\n --create-namespace \\\n --namespace prometheus \\\n --set prometheus.prometheusSpec.podMonitorSelectorNilUsesHelmValues=false \\\n --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false\"\n\n az aks command invoke \\\n --name $clusterName \\\n --resource-group $resourceGroupName \\\n --subscription $subscriptionId \\\n --command \"$command\"\n\n # Install NGINX ingress controller using the internal load balancer\n command=\"helm install nginx-ingress ingress-nginx/ingress-nginx \\\n --create-namespace \\\n --namespace ingress-basic \\\n --set controller.config.enable-modsecurity=true \\\n --set controller.config.enable-owasp-modsecurity-crs=true \\\n --set controller.config.modsecurity-snippet=\\\n'SecRuleEngine On\nSecRequestBodyAccess On\nSecAuditLog /dev/stdout\nSecAuditLogFormat JSON\nSecAuditEngine RelevantOnly' \\\n --set controller.replicaCount=3 \\\n --set controller.nodeSelector.\\\"kubernetes\\.io/os\\\"=linux \\\n --set defaultBackend.nodeSelector.\\\"kubernetes\\.io/os\\\"=linux \\\n --set controller.metrics.enabled=true \\\n --set controller.metrics.serviceMonitor.enabled=true \\\n --set controller.metrics.serviceMonitor.additionalLabels.release=\\\"prometheus\\\" \\\n --set controller.service.annotations.\\\"service\\.beta\\.kubernetes\\.io/azure-load-balancer-health-probe-request-path\\\"=/healthz \\\n --set controller.service.annotations.\\\"service\\.beta\\.kubernetes\\.io/azure-load-balancer-internal\\\"=true\"\n\n az aks command invoke \\\n --name $clusterName \\\n --resource-group $resourceGroupName \\\n --subscription $subscriptionId \\\n --command \"$command\"\n\n # Install certificate manager\n command=\"helm install cert-manager jetstack/cert-manager \\\n --create-namespace \\\n --namespace cert-manager \\\n --set installCRDs=true \\\n --set nodeSelector.\\\"kubernetes\\.io/os\\\"=linux\"\n\n az aks command invoke \\\n --name $clusterName \\\n --resource-group $resourceGroupName \\\n --subscription $subscriptionId \\\n --command \"$command\"\n\n # Create cluster issuer\n command=\"cat <<EOF | kubectl apply -f -\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n name: letsencrypt-nginx\nspec:\n acme:\n server: https://acme-v02.api.letsencrypt.org/directory\n email: $email\n privateKeySecretRef:\n name: letsencrypt\n solvers:\n - http01:\n ingress:\n class: nginx\n podTemplate:\n spec:\n nodeSelector:\n \"kubernetes.io/os\": linux\nEOF\"\n\n az aks command invoke \\\n --name $clusterName \\\n --resource-group $resourceGroupName \\\n --subscription $subscriptionId \\\n --command \"$command\"\n\n # Create a namespace for the application\n command=\"kubectl create namespace $namespace\"\n\n az aks command invoke \\\n --name $clusterName \\\n --resource-group $resourceGroupName \\\n --subscription $subscriptionId \\\n --command \"$command\"\n\n # Create a deployment and service for the application\n command=\"cat <<EOF | kubectl apply -n $namespace -f -\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: httpbin\nspec:\n replicas: 3\n selector:\n matchLabels:\n app: httpbin\n template:\n metadata:\n labels:\n app: httpbin\n spec:\n topologySpreadConstraints:\n - maxSkew: 1\n topologyKey: topology.kubernetes.io/zone\n whenUnsatisfiable: DoNotSchedule\n labelSelector:\n matchLabels:\n app: httpbin\n - maxSkew: 1\n topologyKey: kubernetes.io/hostname\n whenUnsatisfiable: DoNotSchedule\n labelSelector:\n matchLabels:\n app: httpbin\n nodeSelector:\n \"kubernetes.io/os\": linux\n containers:\n - image: docker.io/kennethreitz/httpbin\n imagePullPolicy: IfNotPresent\n name: httpbin\n resources:\n requests:\n memory: \"64Mi\"\n cpu: \"125m\"\n limits:\n memory: \"128Mi\"\n cpu: \"250m\"\n ports:\n - containerPort: 80\n env:\n - name: PORT\n value: \"80\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n name: httpbin\nspec:\n ports:\n - port: 80\n targetPort: 80\n protocol: TCP\n type: ClusterIP\n selector:\n app: httpbin\nEOF\"\n\n az aks command invoke \\\n --name $clusterName \\\n --resource-group $resourceGroupName \\\n --subscription $subscriptionId \\\n --command \"$command\"\n\n # Create an ingress resource for the application\n command=\"cat <<EOF | kubectl apply -n $namespace -f -\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n name: httpbin\nspec:\n ingressClassName: nginx\n rules:\n - host: $hostName\n http:\n paths:\n - path: /\n pathType: Prefix\n backend:\n service:\n name: httpbin\n port:\n number: 80\nEOF\"\n\n az aks command invoke \\\n --name $clusterName \\\n --resource-group $resourceGroupName \\\n --subscription $subscriptionId \\\n --command \"$command\"\n\nelse\n # Log whether the cluster is public or private\n echo \"$clusterName AKS cluster is private\"\n\n # Install Prometheus\n helm install prometheus prometheus-community/kube-prometheus-stack \\\n --create-namespace \\\n --namespace prometheus \\\n --set prometheus.prometheusSpec.podMonitorSelectorNilUsesHelmValues=false \\\n --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false\n\n # Install NGINX ingress controller using the internal load balancer\n helm install nginx-ingress ingress-nginx/ingress-nginx \\\n --create-namespace \\\n --namespace ingress-basic \\\n --set controller.config.enable-modsecurity=true \\\n --set controller.config.enable-owasp-modsecurity-crs=true \\\n --set controller.config.modsecurity-snippet='SecRuleEngine On\nSecRequestBodyAccess On\nSecAuditLog /dev/stdout\nSecAuditLogFormat JSON\nSecAuditEngine RelevantOnly' \\\n --set controller.replicaCount=3 \\\n --set controller.nodeSelector.\"kubernetes\\.io/os\"=linux \\\n --set defaultBackend.nodeSelector.\"kubernetes\\.io/os\"=linux \\\n --set controller.metrics.enabled=true \\\n --set controller.metrics.serviceMonitor.enabled=true \\\n --set controller.metrics.serviceMonitor.additionalLabels.release=\"prometheus\" \\\n --set controller.service.annotations.\"service\\.beta\\.kubernetes\\.io/azure-load-balancer-health-probe-request-path\"=/healthz \\\n --set controller.service.annotations.\"service\\.beta\\.kubernetes\\.io/azure-load-balancer-internal\"=true\n\n helm install $nginxReleaseName $nginxRepoName/$nginxChartName \\\n --create-namespace \\\n --namespace $nginxNamespace\n\n # Install certificate manager\n helm install cert-manager jetstack/cert-manager \\\n --create-namespace \\\n --namespace cert-manager \\\n --set installCRDs=true \\\n --set nodeSelector.\"kubernetes\\.io/os\"=linux\n\n # Create cluster issuer\n cat <<EOF | kubectl apply -f -\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n name: letsencrypt-nginx\nspec:\n acme:\n server: https://acme-v02.api.letsencrypt.org/directory\n email: $email\n privateKeySecretRef:\n name: letsencrypt\n solvers:\n - http01:\n ingress:\n class: nginx\n podTemplate:\n spec:\n nodeSelector:\n \"kubernetes.io/os\": linux\nEOF\n\n # Create a namespace for the application\n kubectl create namespace $namespace\n\n # Create a deployment and service for the application\n cat <<EOF | kubectl apply -n $namespace -f -\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: httpbin\nspec:\n replicas: 3\n selector:\n matchLabels:\n app: httpbin\n template:\n metadata:\n labels:\n app: httpbin\n spec:\n topologySpreadConstraints:\n - maxSkew: 1\n topologyKey: topology.kubernetes.io/zone\n whenUnsatisfiable: DoNotSchedule\n labelSelector:\n matchLabels:\n app: httpbin\n - maxSkew: 1\n topologyKey: kubernetes.io/hostname\n whenUnsatisfiable: DoNotSchedule\n labelSelector:\n matchLabels:\n app: httpbin\n nodeSelector:\n \"kubernetes.io/os\": linux\n containers:\n - image: docker.io/kennethreitz/httpbin\n imagePullPolicy: IfNotPresent\n name: httpbin\n resources:\n requests:\n memory: \"64Mi\"\n cpu: \"125m\"\n limits:\n memory: \"128Mi\"\n cpu: \"250m\"\n ports:\n - containerPort: 80\n env:\n - name: PORT\n value: \"80\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n name: httpbin\nspec:\n ports:\n - port: 80\n targetPort: 80\n protocol: TCP\n type: ClusterIP\n selector:\n app: httpbin\nEOF\n\n # Create an ingress resource for the application\n cat <<EOF | kubectl apply -n $namespace -f -\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n name: httpbin\nspec:\n ingressClassName: nginx\n rules:\n - host: $hostName\n http:\n paths:\n - path: /\n pathType: Prefix\n backend:\n service:\n name: httpbin\n port:\n number: 80\nEOF\n\nfi\n\n# Create output as JSON file\necho '{}' |\n jq --arg x 'prometheus' '.prometheus=$x' |\n jq --arg x 'cert-manager' '.certManager=$x' |\n jq --arg x 'ingress-basic' '.nginxIngressController=$x' >$AZ_SCRIPTS_OUTPUT_PATH\n

 

\n

 

\n

As you can note, when deploying the NGINX Ingress Controller via Helm, the service.beta.kubernetes.io/azure-load-balancer-internal to create the kubernetes-internal internal load balancer in the node resource group of the AKS cluster and expose the ingress controller service via a private IP address.

\n

In this sample, the httpbin web application via YAML templates. In particular, an ingress is used to expose the application via the NGINX Ingress Controller via the HTTP protocol and using the httpbin.local hostname. The ingress object can be easily modified to expose the server via HTTPS and provide a certificate for TLS termination. You can use the cert-manager to issue a Let's Encrypt certificate. For more information, see Securing NGINX-ingress. In particular, cert-manager can create and then delete DNS-01 records in Azure DNS but it needs to authenticate to Azure first. The suggested authentication method is Managed Identity Using AAD Workload Identity.

\n

 

\n

Alternative Solution

\n

Azure Private Link Service (PLS) is an infrastructure component that allows users to privately connect via an Azure Private Endpoint (PE) in a virtual network in Azure and a Frontend IP Configuration associated with an internal or public Azure Load Balancer (ALB). With Private Link, users as service providers can securely provide their services to consumers who can connect from within Azure or on-premises without data exfiltration risks.

\n

Before Private Link Service integration, users who wanted private connectivity from on-premises or other virtual networks to their services in an Azure Kubernetes Service(AKS) cluster were required to create a Private Link Service (PLS) to reference the cluster Azure Load Balancer, like in this sample. The user would then create an Azure Private Endpoint (PE) to connect to the PLS to enable private connectivity. With the Azure Private Link Service Integration feature, a managed Azure Private Link Service (PLS) to the AKS cluster load balancer can be created automatically, and the user would only be required to create Private Endpoint connections to it for private connectivity. You can expose a Kubernetes service via a Private Link Service using annotations. For more information, see Azure Private Link Service Integration.

\n

 

\n

CI/CD and GitOps Considerations

\n

Azure Private Link Service Integration simplifies the creation of a Azure Private Link Service (PLS) when deploying Kubernetes services or ingress controllers via a classic CI/CD pipeline using Azure DevOps, GitHub Actions, Jenkins, or GitLab, but even when using a GitOps approach with Argo CD or Flux v2.

\n

For every workload that you expose via Azure Private Link Service (PLS) and Azure Front Door Premium, you need to create - Microsoft.Cdn/profiles/originGroups: an Origin Group, an Origin, endpoint, a route, and a security policy if you want to protect the workload with a WAF policy. You can accomplish this task using [az network front-door](az network front-door) Azure CLI commands in the CD pipeline used to deploy your service.

\n

 

\n

Test the application

\n

If the deployment succeeds, and the private endpoint connection from the Azure Front Door Premium instance to the Azure Private Link Service (PLS) is approved, you should be able to access the AKS-hosted httpbin web application as follows:

\n\n
 
\n

\n

 

\n

 

\n\n
 
\n

\n

 

\n

You can use the bicep/calls.sh Bash script to simulate a few attacks and see the managed rule set and custom rule of the Azure Web Application Firewall in action.

\n

 

\n

 

\n#!/bin/bash\n\n# Variables\nurl=\"<Front Door Endpoint Hostname URL>\"\n\n# Call REST API\necho \"Calling REST API...\"\ncurl -I -s \"$url\"\n\n# Simulate SQL injection\necho \"Simulating SQL injection...\"\ncurl -I -s \"${url}?users=ExampleSQLInjection%27%20--\"\n\n# Simulate XSS\necho \"Simulating XSS...\"\ncurl -I -s \"${url}?users=ExampleXSS%3Cscript%3Ealert%28%27XSS%27%29%3C%2Fscript%3E\"\n\n# A custom rule blocks any request with the word blockme in the querystring.\necho \"Simulating query string manipulation with the 'attack' word in the query string...\"\ncurl -I -s \"${url}?task=blockme\"\n

 

\n

 

\n

The Bash script should produce the following output, where the first call succeeds, while the remaining one are blocked by the WAF Policy configured in prevention mode.

\n

 

\n

 

\nCalling REST API...\nHTTP/2 200\ncontent-length: 9593\ncontent-type: text/html; charset=utf-8\naccept-ranges: bytes\nvary: Accept-Encoding\naccess-control-allow-origin: *\naccess-control-allow-credentials: true\nx-azure-ref: 05mwQZAAAAADma91JbmU0TJqRqS2lyFurTUlMMzBFREdFMDYwOQA3YTk2NzZiMS0xZmRjLTQ0OWYtYmI1My1hNDUxMDVjNGZmYmM=\nx-cache: CONFIG_NOCACHE\ndate: Tue, 14 Mar 2023 12:47:33 GMT\n\nSimulating SQL injection...\nHTTP/2 403\nx-azure-ref: 05mwQZAAAAABaQCSGQToQT4tifYGpmsTmTUlMMzBFREdFMDYxNQA3YTk2NzZiMS0xZmRjLTQ0OWYtYmI1My1hNDUxMDVjNGZmYmM=\ndate: Tue, 14 Mar 2023 12:47:34 GMT\n\nSimulating XSS...\nHTTP/2 403\nx-azure-ref: 05mwQZAAAAAAJZzCrTmN4TLY+bZOxskzOTUlMMzBFREdFMDYxMwA3YTk2NzZiMS0xZmRjLTQ0OWYtYmI1My1hNDUxMDVjNGZmYmM=\ndate: Tue, 14 Mar 2023 12:47:33 GMT\n\nSimulating query string manipulation with the 'attack' word in the query string...\nHTTP/2 403\nx-azure-ref: 05mwQZAAAAADAle0hOg4FTYH6Q1LHIP50TUlMMzBFREdFMDYyMAA3YTk2NzZiMS0xZmRjLTQ0OWYtYmI1My1hNDUxMDVjNGZmYmM=\ndate: Tue, 14 Mar 2023 12:47:33 GMT\n

 

\n

 

\n

Front Door WAF Policies and Application Gateway WAF policies can be configured to run in the following two modes:

\n\n

For more information, see Azure Web Application Firewall on Azure Front Door.

\n

 

\n

Review deployed resources

\n

Use the Azure portal, Azure CLI, or Azure PowerShell to list the deployed resources in the resource group.

\n

Azure CLI

\n

 

\n

 

\naz resource list --resource-group <resource-group-name>\n

 

\n

 

\n

PowerShell

\n

 

\n

 

\nGet-AzResource -ResourceGroupName <resource-group-name>\n

 

\n

 

\n

Clean up resources

\n

When you no longer need the resources you created, delete the resource group. This will remove all the Azure resources.

\n

Azure CLI

\n

 

\n

 

\naz group delete --name <resource-group-name>\n

 

\n

 

\n

PowerShell

\n

 

\n

 

\nRemove-AzResourceGroup -Name <resource-group-name>\n

 

\n

 

\n

Next Steps

\n

You could add a custom domain to your Front Door. If you use Azure DNS to manage your domain, you could extend the Bicep modules to automatically create a custom domain for your Front Door and create a CNAME DNS record in your public DNS zone.

","kudosSumWeight":4,"postTime":"2023-03-14T06:26:28.656-07:00","images":{"__typename":"AssociatedImageConnection","edges":[{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDE","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTQ1MDM5N2k4OUY1N0VCRjg4MUMwREY5?revision=11\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDI","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTQ1MTE3N2k2MUZCOEU5NTQwMzhENUNC?revision=11\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDM","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTU1Nzc2M2kyM0Q1OEY0NUVFOTA2OTA4?revision=11\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDQ","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTQ1MDQwM2k1RDMwNDE5RkFGMTFCRUNC?revision=11\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDU","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTQ1MDQwNGkxQjQzM0VCNEM0NDAxQTAx?revision=11\"}"}}],"totalCount":5,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"attachments":{"__typename":"AttachmentConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"tags":{"__typename":"TagConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[{"__typename":"TagEdge","cursor":"MjUuM3wyLjF8b3wxMHxfTlZffDE","node":{"__typename":"Tag","id":"tag:App","text":"App","time":"2016-07-12T09:46:46.175-07:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuM3wyLjF8b3wxMHxfTlZffDI","node":{"__typename":"Tag","id":"tag:Cloud Native Apps","text":"Cloud Native Apps","time":"2021-01-08T08:35:18.760-08:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuM3wyLjF8b3wxMHxfTlZffDM","node":{"__typename":"Tag","id":"tag:Infra","text":"Infra","time":"2020-03-04T21:21:46.909-08:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}}]},"timeToRead":30,"rawTeaser":"

This article shows how to use Azure Front Door PremiumAzure Web Application Firewall, and Azure Private Link Service (PLS) to securely expose and protect a workload running in Azure Kubernetes Service(AKS). The sample application is exposed via the NGINX Ingress Controller configured to use a private IP address as a frontend IP configuration of the kubernetes-internal internal load balancer. For more information, see Create an ingress controller using an internal IP address.

\n

\n

 

","introduction":"","coverImage":null,"coverImageProperties":{"__typename":"CoverImageProperties","style":"STANDARD","titlePosition":"BOTTOM","altText":""},"currentRevision":{"__ref":"Revision:revision:3767535_11"},"latestVersion":{"__typename":"FriendlyVersion","major":"11","minor":"0"},"metrics":{"__typename":"MessageMetrics","views":21025},"visibilityScope":"PUBLIC","canonicalUrl":null,"seoTitle":null,"seoDescription":null,"placeholder":false,"originalMessageForPlaceholder":null,"contributors":{"__typename":"UserConnection","edges":[]},"nonCoAuthorContributors":{"__typename":"UserConnection","edges":[]},"coAuthors":{"__typename":"UserConnection","edges":[]},"blogMessagePolicies":{"__typename":"BlogMessagePolicies","canDoAuthoringActionsOnBlog":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.blog.action_can_do_authoring_action.accessDenied","key":"error.lithium.policies.blog.action_can_do_authoring_action.accessDenied","args":[]}}},"archivalData":null,"customFields":[],"revisions({\"constraints\":{\"isPublished\":{\"eq\":true}},\"first\":1})":{"__typename":"RevisionConnection","totalCount":11},"body":"

This article shows how to use Azure Front Door Premium, Azure Web Application Firewall, and Azure Private Link Service (PLS) to securely expose and protect a workload running in Azure Kubernetes Service(AKS). The sample application is exposed via the NGINX Ingress Controller configured to use a private IP address as a frontend IP configuration of the kubernetes-internal internal load balancer. For more information, see Create an ingress controller using an internal IP address.

\n

 

\n

In addition, this sample shows how to deploy an Azure Kubernetes Service cluster with the API Server VNET Integration and how to use an Azure NAT Gateway to manage outbound connections initiated by AKS-hosted workloads. AKS clusters with API Server VNET integration provide advantages: for example, they can have public network access or private cluster mode enabled or disabled without redeploying the cluster. For more information, see Create an Azure Kubernetes Service cluster with API Server VNet Integration.

\n

 

\n

You can find the companion code and Bicep modules in this GitHub repository.

\n

 

\n

Prerequisites

\n\n

 

\n

Architecture

\n

This sample provides a set of Bicep modules to deploy and configure an Azure Front Door Premium with an WAF Policy as global load balancer in front of a public or a private AKS cluster with API Server VNET Integration, Azure CNI as a network plugin and Dynamic IP Allocation. The following diagram shows the architecture and network topology deployed by the sample:

\n
 
\n

\n

 

\n

 

\n

A Deployment Script is used to create the NGINX Ingress Controller, configured to use a private IP address as frontend IP configuration of the kubernetes-internal internal load balancer via Helm and a sample httpbin web application via YAML manifests. The Origin child resource of the Azure Front Door Premium global load balancer is configured to call the sample application via Azure Private Link Service, the AKS the kubernetes-internal internal load balancer, and the NGINX Ingress Controller, as shown in the following figure:

\n

 

\n

\n

 

\n

Bicep modules are parametric, so you can choose any network plugin:

\n\n
\n

NOTE
The sample was tested only with Azure CNI and Azure CNI Overlay

\n
\n

The Bicep modules in the companion sample also allow to install the following extensions and add-ons for Azure Kubernetes Service(AKS):

\n\n

In a production environment, we strongly recommend deploying a private AKS cluster with Uptime SLA. For more information, see private AKS cluster with a Public DNS address. Alternatively, you can deploy a public AKS cluster and secure access to the API server using authorized IP address ranges.

\n

The Bicep modules deploy the following Azure resources:

\n\n
\n

NOTE
You can find the architecture.vsdx file used for the diagram under the visio folder.

\n
\n

 

\n

What is Bicep?

\n

Bicep is a domain-specific language (DSL) that uses a declarative syntax to deploy Azure resources. It provides concise syntax, reliable type safety, and support for code reuse. Bicep offers the best authoring experience for your infrastructure-as-code solutions in Azure.

\n

 

\n

Deploy the Bicep modules

\n

You can deploy the Bicep modules in the bicep folder using the deploy.sh Bash script in the same folder. Specify a value for the following parameters in the deploy.sh script and main.parameters.json parameters file before deploying the Bicep modules.

\n\n

We suggest reading sensitive configuration data such as passwords or SSH keys from a pre-existing Azure Key Vault resource. For more information, see Use Azure Key Vault to pass secure parameter value during Bicep deployment.

\n

 

\n

 

\n
#!/bin/bash\n\n# Template\ntemplate=\"main.bicep\"\nparameters=\"main.parameters.json\"\n\n# AKS cluster name\nprefix=\"<Azure-Resource-Name-Prefix>\"\naksName=\"${prefix}Aks\"\nvalidateTemplate=1\nuseWhatIf=0\nupdate=1\ninstallExtensions=0\n\n# Name and location of the resource group for the Azure Kubernetes Service (AKS) cluster\nresourceGroupName=\"${prefix}RG\"\nlocation=\"westeurope\"\ndeploymentName=\"main\"\n\n# Subscription id, subscription name, and tenant id of the current subscription\nsubscriptionId=$(az account show --query id --output tsv)\nsubscriptionName=$(az account show --query name --output tsv)\ntenantId=$(az account show --query tenantId --output tsv)\n\n# Install aks-preview Azure extension\nif [[ $installExtensions == 1 ]]; then\n  echo \"Checking if [aks-preview] extension is already installed...\"\n  az extension show --name aks-preview &>/dev/null\n\n  if [[ $? == 0 ]]; then\n    echo \"[aks-preview] extension is already installed\"\n\n    # Update the extension to make sure you have the latest version installed\n    echo \"Updating [aks-preview] extension...\"\n    az extension update --name aks-preview &>/dev/null\n  else\n    echo \"[aks-preview] extension is not installed. Installing...\"\n\n    # Install aks-preview extension\n    az extension add --name aks-preview 1>/dev/null\n\n    if [[ $? == 0 ]]; then\n      echo \"[aks-preview] extension successfully installed\"\n    else\n      echo \"Failed to install [aks-preview] extension\"\n      exit\n    fi\n  fi\n\n  # Registering AKS feature extensions\n  aksExtensions=(\n    \"PodSecurityPolicyPreview\"\n    \"KubeletDisk\"\n    \"AKS-KedaPreview\"\n    \"RunCommandPreview\"\n    \"EnablePodIdentityPreview \"\n    \"UserAssignedIdentityPreview\"\n    \"EnablePrivateClusterPublicFQDN\"\n    \"PodSubnetPreview\"\n    \"EnableOIDCIssuerPreview\"\n    \"EnableWorkloadIdentityPreview\"\n    \"EnableImageCleanerPreview\"\n    \"AKS-VPAPreview\"\n    \"AzureOverlayPreview\"\n    \"KubeProxyConfigurationPreview\"\n  )\n  ok=0\n  registeringExtensions=()\n  for aksExtension in ${aksExtensions[@]}; do\n    echo \"Checking if [$aksExtension] extension is already registered...\"\n    extension=$(az feature list -o table --query \"[?contains(name, 'Microsoft.ContainerService/$aksExtension') && @.properties.state == 'Registered'].{Name:name}\" --output tsv)\n    if [[ -z $extension ]]; then\n      echo \"[$aksExtension] extension is not registered.\"\n      echo \"Registering [$aksExtension] extension...\"\n      az feature register --name $aksExtension --namespace Microsoft.ContainerService\n      registeringExtensions+=(\"$aksExtension\")\n      ok=1\n    else\n      echo \"[$aksExtension] extension is already registered.\"\n    fi\n  done\n  echo $registeringExtensions\n  delay=1\n  for aksExtension in ${registeringExtensions[@]}; do\n    echo -n \"Checking if [$aksExtension] extension is already registered...\"\n    while true; do\n      extension=$(az feature list -o table --query \"[?contains(name, 'Microsoft.ContainerService/$aksExtension') && @.properties.state == 'Registered'].{Name:name}\" --output tsv)\n      if [[ -z $extension ]]; then\n        echo -n \".\"\n        sleep $delay\n      else\n        echo \".\"\n        break\n      fi\n    done\n  done\n\n  if [[ $ok == 1 ]]; then\n    echo \"Refreshing the registration of the Microsoft.ContainerService resource provider...\"\n    az provider register --namespace Microsoft.ContainerService\n    echo \"Microsoft.ContainerService resource provider registration successfully refreshed\"\n  fi\nfi\n\n# Get the last Kubernetes version available in the region\nkubernetesVersion=$(az aks get-versions --location $location --query \"orchestrators[?isPreview==false].orchestratorVersion | sort(@) | [-1]\" --output tsv)\n\nif [[ -n $kubernetesVersion ]]; then\n  echo \"Successfully retrieved the last Kubernetes version [$kubernetesVersion] supported by AKS in [$location] Azure region\"\nelse\n  echo \"Failed to retrieve the last Kubernetes version supported by AKS in [$location] Azure region\"\n  exit\nfi\n\n# Check if the resource group already exists\necho \"Checking if [$resourceGroupName] resource group actually exists in the [$subscriptionName] subscription...\"\n\naz group show --name $resourceGroupName &>/dev/null\n\nif [[ $? != 0 ]]; then\n  echo \"No [$resourceGroupName] resource group actually exists in the [$subscriptionName] subscription\"\n  echo \"Creating [$resourceGroupName] resource group in the [$subscriptionName] subscription...\"\n\n  # Create the resource group\n  az group create --name $resourceGroupName --location $location 1>/dev/null\n\n  if [[ $? == 0 ]]; then\n    echo \"[$resourceGroupName] resource group successfully created in the [$subscriptionName] subscription\"\n  else\n    echo \"Failed to create [$resourceGroupName] resource group in the [$subscriptionName] subscription\"\n    exit\n  fi\nelse\n  echo \"[$resourceGroupName] resource group already exists in the [$subscriptionName] subscription\"\nfi\n\n# Create AKS cluster if does not exist\necho \"Checking if [$aksName] aks cluster actually exists in the [$resourceGroupName] resource group...\"\n\naz aks show --name $aksName --resource-group $resourceGroupName &>/dev/null\nnotExists=$?\n\nif [[ $notExists != 0 || $update == 1 ]]; then\n\n  if [[ $notExists != 0 ]]; then\n    echo \"No [$aksName] aks cluster actually exists in the [$resourceGroupName] resource group\"\n  else\n    echo \"[$aksName] aks cluster already exists in the [$resourceGroupName] resource group. Updating the cluster...\"\n  fi\n\n  # Delete any existing role assignments for the user-defined managed identity of the AKS cluster\n  # in case you are re-deploying the solution in an existing resource group\n  echo \"Retrieving the list of role assignments on [$resourceGroupName] resource group...\"\n  assignmentIds=$(az role assignment list \\\n    --scope \"/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}\" \\\n    --query [].id \\\n    --output tsv \\\n    --only-show-errors)\n\n  if [[ -n $assignmentIds ]]; then\n    echo \"[${#assignmentIds[@]}] role assignments have been found on [$resourceGroupName] resource group\"\n    for assignmentId in ${assignmentIds[@]}; do\n      if [[ -n $assignmentId ]]; then\n        az role assignment delete --ids $assignmentId\n\n        if [[ $? == 0 ]]; then\n          assignmentName=$(echo $assignmentId | awk -F '/' '{print $NF}')\n          echo \"[$assignmentName] role assignment on [$resourceGroupName] resource group successfully deleted\"\n        fi\n      fi\n    done\n  else\n    echo \"No role assignment actually exists on [$resourceGroupName] resource group\"\n  fi\n\n  # Get the kubelet managed identity used by the AKS cluster\n  echo \"Retrieving the kubelet identity from the [$aksName] AKS cluster...\"\n  clientId=$(az aks show \\\n    --name $aksName \\\n    --resource-group $resourceGroupName \\\n    --query identityProfile.kubeletidentity.clientId \\\n    --output tsv 2>/dev/null)\n\n  if [[ -n $clientId ]]; then\n    # Delete any role assignment to kubelet managed identity on any ACR in the resource group\n    echo \"kubelet identity of the [$aksName] AKS cluster successfully retrieved\"\n    echo \"Retrieving the list of ACR resources in the [$resourceGroupName] resource group...\"\n    acrIds=$(az acr list \\\n      --resource-group $resourceGroupName \\\n      --query [].id \\\n      --output tsv)\n\n    if [[ -n $acrIds ]]; then\n      echo \"[${#acrIds[@]}] ACR resources have been found in [$resourceGroupName] resource group\"\n      for acrId in ${acrIds[@]}; do\n        if [[ -n $acrId ]]; then\n          acrName=$(echo $acrId | awk -F '/' '{print $NF}')\n          echo \"Retrieving the list of role assignments on [$acrName] ACR...\"\n          assignmentIds=$(az role assignment list \\\n            --scope \"$acrId\" \\\n            --query [].id \\\n            --output tsv \\\n            --only-show-errors)\n\n          if [[ -n $assignmentIds ]]; then\n            echo \"[${#assignmentIds[@]}] role assignments have been found on [$acrName] ACR\"\n            for assignmentId in ${assignmentIds[@]}; do\n              if [[ -n $assignmentId ]]; then\n                az role assignment delete --ids $assignmentId\n\n                if [[ $? == 0 ]]; then\n                  assignmentName=$(echo $assignmentId | awk -F '/' '{print $NF}')\n                  echo \"[$assignmentName] role assignment on [$acrName] ACR successfully deleted\"\n                fi\n              fi\n            done\n          else\n            echo \"No role assignment actually exists on [$acrName] ACR\"\n          fi\n        fi\n      done\n    else\n      echo \"No ACR actually exists in [$resourceGroupName] resource group\"\n    fi\n  else\n    echo \"No kubelet identity exists for the [$aksName] AKS cluster\"\n  fi\n\n  # Validate the Bicep template\n  if [[ $validateTemplate == 1 ]]; then\n    if [[ $useWhatIf == 1 ]]; then\n      # Execute a deployment What-If operation at resource group scope.\n      echo \"Previewing changes deployed by [$template] Bicep template...\"\n      az deployment group what-if \\\n        --resource-group $resourceGroupName \\\n        --template-file $template \\\n        --parameters $parameters \\\n        --parameters prefix=$prefix \\\n        location=$location \\\n        aksClusterKubernetesVersion=$kubernetesVersion\n\n      if [[ $? == 0 ]]; then\n        echo \"[$template] Bicep template validation succeeded\"\n      else\n        echo \"Failed to validate [$template] Bicep template\"\n        exit\n      fi\n    else\n      # Validate the Bicep template\n      echo \"Validating [$template] Bicep template...\"\n      output=$(az deployment group validate \\\n        --resource-group $resourceGroupName \\\n        --template-file $template \\\n        --parameters $parameters \\\n        --parameters prefix=$prefix \\\n        location=$location \\\n        aksClusterKubernetesVersion=$kubernetesVersion)\n\n      if [[ $? == 0 ]]; then\n        echo \"[$template] Bicep template validation succeeded\"\n      else\n        echo \"Failed to validate [$template] Bicep template\"\n        echo $output\n        exit\n      fi\n    fi\n  fi\n\n  # Deploy the Bicep template\n  echo \"Deploying [$template] Bicep template...\"\n  az deployment group create \\\n    --name $deploymentName \\\n    --resource-group $resourceGroupName \\\n    --only-show-errors \\\n    --template-file $template \\\n    --parameters $parameters \\\n    --parameters prefix=$prefix \\\n    location=$location \\\n    aksClusterKubernetesVersion=$kubernetesVersion 1>/dev/null\n\n  if [[ $? == 0 ]]; then\n    echo \"[$template] Bicep template successfully provisioned\"\n  else\n    echo \"Failed to provision the [$template] Bicep template\"\n    exit\n  fi\nelse\n  echo \"[$aksName] aks cluster already exists in the [$resourceGroupName] resource group\"\nfi\n\n# Create AKS cluster if does not exist\necho \"Checking if [$aksName] aks cluster actually exists in the [$resourceGroupName] resource group...\"\n\naz aks show --name $aksName --resource-group $resourceGroupName &>/dev/null\n\nif [[ $? != 0 ]]; then\n  echo \"No [$aksName] aks cluster actually exists in the [$resourceGroupName] resource group\"\n  exit\nfi\n\n# Get the user principal name of the current user\necho \"Retrieving the user principal name of the current user from the [$tenantId] Azure AD tenant...\"\nuserPrincipalName=$(az account show --query user.name --output tsv)\nif [[ -n $userPrincipalName ]]; then\n  echo \"[$userPrincipalName] user principal name successfully retrieved from the [$tenantId] Azure AD tenant\"\nelse\n  echo \"Failed to retrieve the user principal name of the current user from the [$tenantId] Azure AD tenant\"\n  exit\nfi\n\n# Retrieve the objectId of the user in the Azure AD tenant used by AKS for user authentication\necho \"Retrieving the objectId of the [$userPrincipalName] user principal name from the [$tenantId] Azure AD tenant...\"\nuserObjectId=$(az ad user show --id $userPrincipalName --query id --output tsv 2>/dev/null)\n\nif [[ -n $userObjectId ]]; then\n  echo \"[$userObjectId] objectId successfully retrieved for the [$userPrincipalName] user principal name\"\nelse\n  echo \"Failed to retrieve the objectId of the [$userPrincipalName] user principal name\"\n  exit\nfi\n\n# Retrieve the resource id of the AKS cluster\necho \"Retrieving the resource id of the [$aksName] AKS cluster...\"\naksClusterId=$(az aks show \\\n  --name \"$aksName\" \\\n  --resource-group \"$resourceGroupName\" \\\n  --query id \\\n  --output tsv 2>/dev/null)\n\nif [[ -n $aksClusterId ]]; then\n  echo \"Resource id of the [$aksName] AKS cluster successfully retrieved\"\nelse\n  echo \"Failed to retrieve the resource id of the [$aksName] AKS cluster\"\n  exit\nfi\n\n# Assign Azure Kubernetes Service RBAC Cluster Admin role to the current user\nrole=\"Azure Kubernetes Service RBAC Cluster Admin\"\necho \"Checking if [$userPrincipalName] user has been assigned to [$role] role on the [$aksName] AKS cluster...\"\ncurrent=$(az role assignment list \\\n  --assignee $userObjectId \\\n  --scope $aksClusterId \\\n  --query \"[?roleDefinitionName=='$role'].roleDefinitionName\" \\\n  --output tsv 2>/dev/null)\n\nif [[ $current == \"Owner\" ]] || [[ $current == \"Contributor\" ]] || [[ $current == \"$role\" ]]; then\n  echo \"[$userPrincipalName] user is already assigned to the [$current] role on the [$aksName] AKS cluster\"\nelse\n  echo \"[$userPrincipalName] user is not assigned to the [$role] role on the [$aksName] AKS cluster\"\n  echo \"Assigning the [$userPrincipalName] user to the [$role] role on the [$aksName] AKS cluster...\"\n\n  az role assignment create \\\n    --role \"$role\" \\\n    --assignee $userObjectId \\\n    --scope $aksClusterId \\\n    --only-show-errors 1>/dev/null\n\n  if [[ $? == 0 ]]; then\n    echo \"[$userPrincipalName] user successfully assigned to the [$role] role on the [$aksName] AKS cluster\"\n  else\n    echo \"Failed to assign the [$userPrincipalName] user to the [$role] role on the [$aksName] AKS cluster\"\n    exit\n  fi\nfi\n\n# Assign Azure Kubernetes Service Cluster Admin Role role to the current user\nrole=\"Azure Kubernetes Service Cluster Admin Role\"\necho \"Checking if [$userPrincipalName] user has been assigned to [$role] role on the [$aksName] AKS cluster...\"\ncurrent=$(az role assignment list \\\n  --assignee $userObjectId \\\n  --scope $aksClusterId \\\n  --query \"[?roleDefinitionName=='$role'].roleDefinitionName\" \\\n  --output tsv 2>/dev/null)\n\nif [[ $current == \"Owner\" ]] || [[ $current == \"Contributor\" ]] || [[ $current == \"$role\" ]]; then\n  echo \"[$userPrincipalName] user is already assigned to the [$current] role on the [$aksName] AKS cluster\"\nelse\n  echo \"[$userPrincipalName] user is not assigned to the [$role] role on the [$aksName] AKS cluster\"\n  echo \"Assigning the [$userPrincipalName] user to the [$role] role on the [$aksName] AKS cluster...\"\n\n  az role assignment create \\\n    --role \"$role\" \\\n    --assignee $userObjectId \\\n    --scope $aksClusterId \\\n    --only-show-errors 1>/dev/null\n\n  if [[ $? == 0 ]]; then\n    echo \"[$userPrincipalName] user successfully assigned to the [$role] role on the [$aksName] AKS cluster\"\n  else\n    echo \"Failed to assign the [$userPrincipalName] user to the [$role] role on the [$aksName] AKS cluster\"\n    exit\n  fi\nfi\n\n# Get the FQDN of the Azure Front Door endpoint\nazureFrontDoorEndpointFqdn=$(az deployment group show \\\n --name $deploymentName \\\n --resource-group $resourceGroupName \\\n --query properties.outputs.frontDoorEndpointFqdn.value \\\n --output tsv)\n\nif [[ -n $azureFrontDoorEndpointFqdn ]]; then\n echo \"FQDN of the Azure Front Door endpoint: $azureFrontDoorEndpointFqdn\"\nelse\n echo \"Failed to get the FQDN of the Azure Front Door endpoint\"\n exit -1\nfi\n\n# Get the private link service name\nprivateLinkServiceName=$(az deployment group show \\\n --name $deploymentName \\\n --resource-group $resourceGroupName \\\n --query properties.outputs.privateLinkServiceName.value \\\n --output tsv)\n\nif [[ -z $privateLinkServiceName ]]; then\n echo \"Failed to get the private link service name\"\n exit -1\nfi\n\n# Get the resource id of the Private Endpoint Connection\nprivateEndpointConnectionId=$(az network private-endpoint-connection list \\\n --name $privateLinkServiceName \\\n --resource-group $resourceGroupName \\\n --type Microsoft.Network/privateLinkServices \\\n --query [0].id \\\n --output tsv)\n\nif [[ -n $privateEndpointConnectionId ]]; then\n echo \"Resource id of the Private Endpoint Connection: $privateEndpointConnectionId\"\nelse\n echo \"Failed to get the resource id of the Private Endpoint Connection\"\n exit -1\nfi\n\n# Approve the private endpoint connection\necho \"Approving [$privateEndpointConnectionId] private endpoint connection ID...\"\naz network private-endpoint-connection approve \\\n --name $privateLinkServiceName \\\n --resource-group $resourceGroupName \\\n --id $privateEndpointConnectionId \\\n --description \"Approved\" 1>/dev/null\n\nif [[ $? == 0 ]]; then\n echo \"[$privateEndpointConnectionId] private endpoint connection ID successfully approved\"\nelse\n echo \"Failed to approve [$privateEndpointConnectionId] private endpoint connection ID\"\n exit -1\nfi
\n

 

\n

 

\n

The last steps of the Bash script perform the following actions:

\n\n

If you miss running these steps, Azure Front Door cannot invoke the httpbin web application via the Azure Private Link Service, and the kubernetes-internal internal load balancer of the AKS cluster.

\n

 

\n

Bicep Modules

\n

The companion sample contains several modules. The following table shows the module used to create and configure Azure Front Door and its child resources.

\n

 

\n

 

\n
// Parameters\n@description('Specifies the name of the Azure Front Door.')\nparam frontDoorName string\n\n@description('The name of the SKU to use when creating the Front Door profile.')\n@allowed([\n  'Standard_AzureFrontDoor'\n  'Premium_AzureFrontDoor'\n])\nparam frontDoorSkuName string = 'Premium_AzureFrontDoor'\n\n@description('Specifies the send and receive timeout on forwarding request to the origin. When timeout is reached, the request fails and returns.')\nparam originResponseTimeoutSeconds int = 30\n\n@description('Specifies the name of the Azure Front Door Origin Group for the web application.')\nparam originGroupName string\n\n@description('Specifies the name of the Azure Front Door Origin for the web application.')\nparam originName string\n\n@description('Specifies the address of the origin. Domain names, IPv4 addresses, and IPv6 addresses are supported.This should be unique across all origins in an endpoint.')\nparam hostName string\n\n@description('Specifies the value of the HTTP port. Must be between 1 and 65535.')\nparam httpPort int = 80\n\n@description('Specifies the value of the HTTPS port. Must be between 1 and 65535.')\nparam httpsPort int = 443\n\n@description('Specifies the host header value sent to the origin with each request. If you leave this blank, the request hostname determines this value. Azure Front Door origins, such as Web Apps, Blob Storage, and Cloud Services require this host header value to match the origin hostname by default. This overrides the host header defined at Endpoint.')\nparam originHostHeader string\n\n@description('Specifies the priority of origin in given origin group for load balancing. Higher priorities will not be used for load balancing if any lower priority origin is healthy.Must be between 1 and 5.')\n@minValue(1)\n@maxValue(5)\nparam priority int = 1\n\n@description('Specifies the weight of the origin in a given origin group for load balancing. Must be between 1 and 1000.')\n@minValue(1)\n@maxValue(1000)\nparam weight int = 1000\n\n@description('Specifies whether to enable health probes to be made against backends defined under backendPools. Health probes can only be disabled if there is a single enabled backend in single enabled backend pool.')\n@allowed([\n  'Enabled'\n  'Disabled'\n])\nparam originEnabledState string = 'Enabled'\n\n@description('Specifies the resource id of a private link service.')\nparam privateLinkResourceId string\n\n@description('Specifies the number of samples to consider for load balancing decisions.')\nparam sampleSize int = 4\n\n@description('Specifies the number of samples within the sample period that must succeed.')\nparam successfulSamplesRequired int = 3\n\n@description('Specifies the additional latency in milliseconds for probes to fall into the lowest latency bucket.')\nparam additionalLatencyInMilliseconds int = 50\n\n@description('Specifies path relative to the origin that is used to determine the health of the origin.')\nparam probePath string = '/'\n\n@description('Specifies the health probe request type.')\n@allowed([\n  'GET'\n  'HEAD'\n  'NotSet'\n])\nparam probeRequestType string = 'GET'\n\n@description('Specifies the health probe protocol.')\n@allowed([\n  'Http'\n  'Https'\n  'NotSet'\n])\nparam probeProtocol string = 'Http'\n\n@description('Specifies the number of seconds between health probes.Default is 240 seconds.')\nparam probeIntervalInSeconds int = 60\n\n@description('Specifies whether to allow session affinity on this host. Valid options are Enabled or Disabled.')\n@allowed([\n  'Enabled'\n  'Disabled'\n])\nparam sessionAffinityState string = 'Disabled'\n\n@description('Specifies the endpoint name reuse scope. The default value is TenantReuse.')\n@allowed([\n  'NoReuse'\n  'ResourceGroupReuse'\n  'SubscriptionReuse'\n  'TenantReuse'\n])\nparam autoGeneratedDomainNameLabelScope string = 'TenantReuse'\n\n@description('Specifies the name of the Azure Front Door Route for the web application.')\nparam routeName string\n\n@description('Specifies the domains referenced by the endpoint.')\nparam customDomains array = []\n\n@description('Specifies a directory path on the origin that Azure Front Door can use to retrieve content from, e.g. contoso.cloudapp.net/originpath.')\nparam originPath string = '/'\n\n@description('Specifies the rule sets referenced by this endpoint.')\nparam ruleSets array = []\n\n@description('Specifies the list of supported protocols for this route')\nparam supportedProtocols array  = [\n  'Http'\n  'Https'\n]\n\n@description('Specifies the route patterns of the rule.')\nparam routePatternsToMatch array = [ '/*' ]\n\n@description('Specifies the protocol this rule will use when forwarding traffic to backends.')\n@allowed([\n  'HttpOnly'\n  'HttpsOnly'\n  'MatchRequest'\n])\nparam forwardingProtocol string = 'HttpOnly'\n\n@description('Specifies whether this route will be linked to the default endpoint domain.')\n@allowed([\n  'Enabled'\n  'Disabled'\n])\nparam linkToDefaultDomain string = 'Enabled'\n\n@description('Specifies whether to automatically redirect HTTP traffic to HTTPS traffic. Note that this is a easy way to set up this rule and it will be the first rule that gets executed.')\n@allowed([\n  'Enabled'\n  'Disabled'\n])\nparam httpsRedirect string = 'Enabled'\n\n@description('Specifies the name of the Azure Front Door Endpoint for the web application.')\nparam endpointName string\n\n@description('Specifies whether to enable use of this rule. Permitted values are Enabled or Disabled')\n@allowed([\n  'Enabled'\n  'Disabled'\n])\nparam endpointEnabledState string = 'Enabled'\n\n@description('Specifies the name of the Azure Front Door WAF policy.')\nparam wafPolicyName string\n\n@description('Specifies the WAF policy is in detection mode or prevention mode.')\n@allowed([\n  'Detection'\n  'Prevention'\n])\nparam wafPolicyMode string = 'Prevention'\n\n@description('Specifies if the policy is in enabled or disabled state. Defaults to Enabled if not specified.')\nparam wafPolicyEnabledState string = 'Enabled'\n\n@description('Specifies the list of managed rule sets to configure on the WAF.')\nparam wafManagedRuleSets array = []\n\n@description('Specifies the list of custom rulesto configure on the WAF.')\nparam wafCustomRules array = []\n\n@description('Specifies if the WAF policy managed rules will inspect the request body content.')\n@allowed([\n  'Enabled'\n  'Disabled'\n])\nparam wafPolicyRequestBodyCheck string = 'Enabled'\n\n@description('Specifies name of the security policy.')\nparam securityPolicyName string\n\n@description('Specifies the list of patterns to match by the security policy.')\nparam securityPolicyPatternsToMatch array = [ '/*' ]\n\n@description('Specifies the resource id of the Log Analytics workspace.')\nparam workspaceId string\n\n@description('Specifies the workspace data retention in days.')\nparam retentionInDays int = 60\n\n@description('Specifies the location.')\nparam location string = resourceGroup().location\n\n@description('Specifies the resource tags.')\nparam tags object\n\n\n// Variables\n\nvar diagnosticSettingsName = 'diagnosticSettings'\nvar logCategories = [\n  'FrontDoorAccessLog'\n  'FrontDoorHealthProbeLog'\n  'FrontDoorWebApplicationFirewallLog'\n]\nvar metricCategories = [\n  'AllMetrics'\n]\nvar logs = [for category in logCategories: {\n  category: category\n  enabled: true\n  retentionPolicy: {\n    enabled: true\n    days: retentionInDays\n  }\n}]\nvar metrics = [for category in metricCategories: {\n  category: category\n  enabled: true\n  retentionPolicy: {\n    enabled: true\n    days: retentionInDays\n  }\n}]\n\n// Resources\nresource frontDoor 'Microsoft.Cdn/profiles@2022-11-01-preview' = {\n  name: frontDoorName\n  location: 'Global'\n  tags: tags\n  sku: {\n    name: frontDoorSkuName\n  }\n  properties: {\n    originResponseTimeoutSeconds: originResponseTimeoutSeconds\n    extendedProperties: {\n    }\n  }\n}\n\nresource originGroup 'Microsoft.Cdn/profiles/origingroups@2022-11-01-preview' = {\n  parent: frontDoor\n  name: originGroupName\n  properties: {\n    loadBalancingSettings: {\n      sampleSize: sampleSize\n      successfulSamplesRequired: successfulSamplesRequired\n      additionalLatencyInMilliseconds: additionalLatencyInMilliseconds\n    }\n    healthProbeSettings: {\n      probePath: probePath\n      probeRequestType: probeRequestType\n      probeProtocol: probeProtocol\n      probeIntervalInSeconds: probeIntervalInSeconds\n    }\n    sessionAffinityState: sessionAffinityState\n  }\n}\n\nresource origin 'Microsoft.Cdn/profiles/origingroups/origins@2022-11-01-preview' = {\n  parent: originGroup\n  name: originName\n  properties: {\n    hostName: hostName\n    httpPort: httpPort\n    httpsPort: httpsPort\n    originHostHeader: originHostHeader\n    priority: priority\n    weight: weight\n    enabledState: originEnabledState\n    sharedPrivateLinkResource: empty(privateLinkResourceId) ? {} : {\n      privateLink: {\n        id: privateLinkResourceId\n      }\n      privateLinkLocation: location\n      status: 'Approved'\n      requestMessage: 'Please approve this request to allow Front Door to access the container app'\n    }\n    enforceCertificateNameCheck: true\n  }\n}\n\nresource endpoint 'Microsoft.Cdn/profiles/afdEndpoints@2022-11-01-preview' = {\n  parent: frontDoor\n  name: endpointName\n  location: 'Global'\n  properties: {\n    autoGeneratedDomainNameLabelScope: toUpper(autoGeneratedDomainNameLabelScope)\n    enabledState: endpointEnabledState\n  }\n}\n\nresource route 'Microsoft.Cdn/profiles/afdEndpoints/routes@2022-11-01-preview' = {\n  parent: endpoint\n  name: routeName\n  properties: {\n    customDomains: customDomains\n    originGroup: {\n      id: originGroup.id\n    }\n    originPath: originPath\n    ruleSets: ruleSets\n    supportedProtocols: supportedProtocols\n    patternsToMatch: routePatternsToMatch\n    forwardingProtocol: forwardingProtocol\n    linkToDefaultDomain: linkToDefaultDomain\n    httpsRedirect: httpsRedirect\n  }\n  dependsOn: [\n    origin\n  ]\n}\n\nresource wafPolicy 'Microsoft.Network/FrontDoorWebApplicationFirewallPolicies@2022-05-01' = {\n  name: wafPolicyName\n  location: 'Global'\n  tags: tags\n  sku: {\n    name: frontDoorSkuName\n  }\n  properties: {\n    policySettings: {\n      enabledState: wafPolicyEnabledState\n      mode: wafPolicyMode\n      requestBodyCheck: wafPolicyRequestBodyCheck\n    }\n    managedRules: {\n      managedRuleSets: wafManagedRuleSets\n    }\n    customRules: {\n      rules: wafCustomRules\n    }\n  }\n}\n\nresource securityPolicy 'Microsoft.Cdn/profiles/securitypolicies@2022-11-01-preview' = {\n  parent: frontDoor\n  name: securityPolicyName\n  properties: {\n    parameters: {\n      type: 'WebApplicationFirewall'\n      wafPolicy: {\n        id: wafPolicy.id\n      }\n      associations: [\n        {\n          domains: [\n            {\n              id: endpoint.id\n            }\n          ]\n          patternsToMatch: securityPolicyPatternsToMatch\n        }\n      ]\n\n    }\n  }\n}\n\n// Diagnostics Settings\nresource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {\n  name: diagnosticSettingsName\n  scope: frontDoor\n  properties: {\n    workspaceId: workspaceId\n    logs: logs\n    metrics: metrics\n  }\n}\n\n// Outputs\noutput id string = frontDoor.id\noutput name string = frontDoor.name\noutput endpointFqdn string = endpoint.properties.hostName
\n

 

\n

 

\n

Deployment Script

\n

The sample makes use of a Deployment Script to run the install-helm-charts-and-app.sh Bash script which installs the httpbin web application via YAML templates and the following packages to the AKS cluster via Helm. For more information on deployment scripts, see Use deployment scripts in Bicep

\n\n

 

\n

 

\n
# Install kubectl\naz aks install-cli --only-show-errors\n\n# Get AKS credentials\naz aks get-credentials \\\n  --admin \\\n  --name $clusterName \\\n  --resource-group $resourceGroupName \\\n  --subscription $subscriptionId \\\n  --only-show-errors\n\n# Check if the cluster is private or not\nprivate=$(az aks show --name $clusterName \\\n  --resource-group $resourceGroupName \\\n  --subscription $subscriptionId \\\n  --query apiServerAccessProfile.enablePrivateCluster \\\n  --output tsv)\n\n# Install Helm\ncurl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 -o get_helm.sh -s\nchmod 700 get_helm.sh\n./get_helm.sh &>/dev/null\n\n# Add Helm repos\nhelm repo add prometheus-community https://prometheus-community.github.io/helm-charts\nhelm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx\nhelm repo add jetstack https://charts.jetstack.io\n\n# Update Helm repos\nhelm repo update\n\nif [[ $private == 'true' ]]; then\n  # Log whether the cluster is public or private\n  echo \"$clusterName AKS cluster is public\"\n\n  # Install Prometheus\n  command=\"helm install prometheus prometheus-community/kube-prometheus-stack \\\n  --create-namespace \\\n  --namespace prometheus \\\n  --set prometheus.prometheusSpec.podMonitorSelectorNilUsesHelmValues=false \\\n  --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false\"\n\n  az aks command invoke \\\n    --name $clusterName \\\n    --resource-group $resourceGroupName \\\n    --subscription $subscriptionId \\\n    --command \"$command\"\n\n  # Install NGINX ingress controller using the internal load balancer\n  command=\"helm install nginx-ingress ingress-nginx/ingress-nginx \\\n    --create-namespace \\\n    --namespace ingress-basic \\\n    --set controller.config.enable-modsecurity=true \\\n    --set controller.config.enable-owasp-modsecurity-crs=true \\\n    --set controller.config.modsecurity-snippet=\\\n'SecRuleEngine On\nSecRequestBodyAccess On\nSecAuditLog /dev/stdout\nSecAuditLogFormat JSON\nSecAuditEngine RelevantOnly' \\\n    --set controller.replicaCount=3 \\\n    --set controller.nodeSelector.\\\"kubernetes\\.io/os\\\"=linux \\\n    --set defaultBackend.nodeSelector.\\\"kubernetes\\.io/os\\\"=linux \\\n    --set controller.metrics.enabled=true \\\n    --set controller.metrics.serviceMonitor.enabled=true \\\n    --set controller.metrics.serviceMonitor.additionalLabels.release=\\\"prometheus\\\" \\\n    --set controller.service.annotations.\\\"service\\.beta\\.kubernetes\\.io/azure-load-balancer-health-probe-request-path\\\"=/healthz \\\n    --set controller.service.annotations.\\\"service\\.beta\\.kubernetes\\.io/azure-load-balancer-internal\\\"=true\"\n\n  az aks command invoke \\\n    --name $clusterName \\\n    --resource-group $resourceGroupName \\\n    --subscription $subscriptionId \\\n    --command \"$command\"\n\n  # Install certificate manager\n  command=\"helm install cert-manager jetstack/cert-manager \\\n    --create-namespace \\\n    --namespace cert-manager \\\n    --set installCRDs=true \\\n    --set nodeSelector.\\\"kubernetes\\.io/os\\\"=linux\"\n\n  az aks command invoke \\\n    --name $clusterName \\\n    --resource-group $resourceGroupName \\\n    --subscription $subscriptionId \\\n    --command \"$command\"\n\n  # Create cluster issuer\n  command=\"cat <<EOF | kubectl apply -f -\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: letsencrypt-nginx\nspec:\n  acme:\n    server: https://acme-v02.api.letsencrypt.org/directory\n    email: $email\n    privateKeySecretRef:\n      name: letsencrypt\n    solvers:\n    - http01:\n        ingress:\n          class: nginx\n          podTemplate:\n            spec:\n              nodeSelector:\n                \"kubernetes.io/os\": linux\nEOF\"\n\n  az aks command invoke \\\n    --name $clusterName \\\n    --resource-group $resourceGroupName \\\n    --subscription $subscriptionId \\\n    --command \"$command\"\n\n  # Create a namespace for the application\n  command=\"kubectl create namespace $namespace\"\n\n  az aks command invoke \\\n    --name $clusterName \\\n    --resource-group $resourceGroupName \\\n    --subscription $subscriptionId \\\n    --command \"$command\"\n\n  # Create a deployment and service for the application\n  command=\"cat <<EOF | kubectl apply -n $namespace -f -\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: httpbin\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: httpbin\n  template:\n    metadata:\n      labels:\n        app: httpbin\n    spec:\n      topologySpreadConstraints:\n      - maxSkew: 1\n        topologyKey: topology.kubernetes.io/zone\n        whenUnsatisfiable: DoNotSchedule\n        labelSelector:\n          matchLabels:\n            app: httpbin\n      - maxSkew: 1\n        topologyKey: kubernetes.io/hostname\n        whenUnsatisfiable: DoNotSchedule\n        labelSelector:\n          matchLabels:\n            app: httpbin\n      nodeSelector:\n        \"kubernetes.io/os\": linux\n      containers:\n      - image: docker.io/kennethreitz/httpbin\n        imagePullPolicy: IfNotPresent\n        name: httpbin\n        resources:\n          requests:\n            memory: \"64Mi\"\n            cpu: \"125m\"\n          limits:\n            memory: \"128Mi\"\n            cpu: \"250m\"\n        ports:\n        - containerPort: 80\n        env:\n        - name: PORT\n          value: \"80\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: httpbin\nspec:\n  ports:\n    - port: 80\n      targetPort: 80\n      protocol: TCP\n  type: ClusterIP\n  selector:\n    app: httpbin\nEOF\"\n\n  az aks command invoke \\\n    --name $clusterName \\\n    --resource-group $resourceGroupName \\\n    --subscription $subscriptionId \\\n    --command \"$command\"\n\n  # Create an ingress resource for the application\n  command=\"cat <<EOF | kubectl apply -n $namespace -f -\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: httpbin\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: $hostName\n    http:\n      paths:\n      - path: /\n        pathType: Prefix\n        backend:\n          service:\n            name: httpbin\n            port:\n              number: 80\nEOF\"\n\n  az aks command invoke \\\n    --name $clusterName \\\n    --resource-group $resourceGroupName \\\n    --subscription $subscriptionId \\\n    --command \"$command\"\n\nelse\n  # Log whether the cluster is public or private\n  echo \"$clusterName AKS cluster is private\"\n\n  # Install Prometheus\n  helm install prometheus prometheus-community/kube-prometheus-stack \\\n    --create-namespace \\\n    --namespace prometheus \\\n    --set prometheus.prometheusSpec.podMonitorSelectorNilUsesHelmValues=false \\\n    --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false\n\n  # Install NGINX ingress controller using the internal load balancer\n  helm install nginx-ingress ingress-nginx/ingress-nginx \\\n    --create-namespace \\\n    --namespace ingress-basic \\\n    --set controller.config.enable-modsecurity=true \\\n    --set controller.config.enable-owasp-modsecurity-crs=true \\\n    --set controller.config.modsecurity-snippet='SecRuleEngine On\nSecRequestBodyAccess On\nSecAuditLog /dev/stdout\nSecAuditLogFormat JSON\nSecAuditEngine RelevantOnly' \\\n    --set controller.replicaCount=3 \\\n    --set controller.nodeSelector.\"kubernetes\\.io/os\"=linux \\\n    --set defaultBackend.nodeSelector.\"kubernetes\\.io/os\"=linux \\\n    --set controller.metrics.enabled=true \\\n    --set controller.metrics.serviceMonitor.enabled=true \\\n    --set controller.metrics.serviceMonitor.additionalLabels.release=\"prometheus\" \\\n    --set controller.service.annotations.\"service\\.beta\\.kubernetes\\.io/azure-load-balancer-health-probe-request-path\"=/healthz \\\n    --set controller.service.annotations.\"service\\.beta\\.kubernetes\\.io/azure-load-balancer-internal\"=true\n\n  helm install $nginxReleaseName $nginxRepoName/$nginxChartName \\\n    --create-namespace \\\n    --namespace $nginxNamespace\n\n  # Install certificate manager\n  helm install cert-manager jetstack/cert-manager \\\n    --create-namespace \\\n    --namespace cert-manager \\\n    --set installCRDs=true \\\n    --set nodeSelector.\"kubernetes\\.io/os\"=linux\n\n  # Create cluster issuer\n  cat <<EOF | kubectl apply -f -\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: letsencrypt-nginx\nspec:\n  acme:\n    server: https://acme-v02.api.letsencrypt.org/directory\n    email: $email\n    privateKeySecretRef:\n      name: letsencrypt\n    solvers:\n    - http01:\n        ingress:\n          class: nginx\n          podTemplate:\n            spec:\n              nodeSelector:\n                \"kubernetes.io/os\": linux\nEOF\n\n  # Create a namespace for the application\n  kubectl create namespace $namespace\n\n  # Create a deployment and service for the application\n  cat <<EOF | kubectl apply -n $namespace -f -\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: httpbin\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: httpbin\n  template:\n    metadata:\n      labels:\n        app: httpbin\n    spec:\n      topologySpreadConstraints:\n      - maxSkew: 1\n        topologyKey: topology.kubernetes.io/zone\n        whenUnsatisfiable: DoNotSchedule\n        labelSelector:\n          matchLabels:\n            app: httpbin\n      - maxSkew: 1\n        topologyKey: kubernetes.io/hostname\n        whenUnsatisfiable: DoNotSchedule\n        labelSelector:\n          matchLabels:\n            app: httpbin\n      nodeSelector:\n        \"kubernetes.io/os\": linux\n      containers:\n      - image: docker.io/kennethreitz/httpbin\n        imagePullPolicy: IfNotPresent\n        name: httpbin\n        resources:\n          requests:\n            memory: \"64Mi\"\n            cpu: \"125m\"\n          limits:\n            memory: \"128Mi\"\n            cpu: \"250m\"\n        ports:\n        - containerPort: 80\n        env:\n        - name: PORT\n          value: \"80\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: httpbin\nspec:\n  ports:\n    - port: 80\n      targetPort: 80\n      protocol: TCP\n  type: ClusterIP\n  selector:\n    app: httpbin\nEOF\n\n  # Create an ingress resource for the application\n  cat <<EOF | kubectl apply -n $namespace -f -\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: httpbin\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: $hostName\n    http:\n      paths:\n      - path: /\n        pathType: Prefix\n        backend:\n          service:\n            name: httpbin\n            port:\n              number: 80\nEOF\n\nfi\n\n# Create output as JSON file\necho '{}' |\n  jq --arg x 'prometheus' '.prometheus=$x' |\n  jq --arg x 'cert-manager' '.certManager=$x' |\n  jq --arg x 'ingress-basic' '.nginxIngressController=$x' >$AZ_SCRIPTS_OUTPUT_PATH
\n

 

\n

 

\n

As you can note, when deploying the NGINX Ingress Controller via Helm, the service.beta.kubernetes.io/azure-load-balancer-internal to create the kubernetes-internal internal load balancer in the node resource group of the AKS cluster and expose the ingress controller service via a private IP address.

\n

In this sample, the httpbin web application via YAML templates. In particular, an ingress is used to expose the application via the NGINX Ingress Controller via the HTTP protocol and using the httpbin.local hostname. The ingress object can be easily modified to expose the server via HTTPS and provide a certificate for TLS termination. You can use the cert-manager to issue a Let's Encrypt certificate. For more information, see Securing NGINX-ingress. In particular, cert-manager can create and then delete DNS-01 records in Azure DNS but it needs to authenticate to Azure first. The suggested authentication method is Managed Identity Using AAD Workload Identity.

\n

 

\n

Alternative Solution

\n

Azure Private Link Service (PLS) is an infrastructure component that allows users to privately connect via an Azure Private Endpoint (PE) in a virtual network in Azure and a Frontend IP Configuration associated with an internal or public Azure Load Balancer (ALB). With Private Link, users as service providers can securely provide their services to consumers who can connect from within Azure or on-premises without data exfiltration risks.

\n

Before Private Link Service integration, users who wanted private connectivity from on-premises or other virtual networks to their services in an Azure Kubernetes Service(AKS) cluster were required to create a Private Link Service (PLS) to reference the cluster Azure Load Balancer, like in this sample. The user would then create an Azure Private Endpoint (PE) to connect to the PLS to enable private connectivity. With the Azure Private Link Service Integration feature, a managed Azure Private Link Service (PLS) to the AKS cluster load balancer can be created automatically, and the user would only be required to create Private Endpoint connections to it for private connectivity. You can expose a Kubernetes service via a Private Link Service using annotations. For more information, see Azure Private Link Service Integration.

\n

 

\n

CI/CD and GitOps Considerations

\n

Azure Private Link Service Integration simplifies the creation of a Azure Private Link Service (PLS) when deploying Kubernetes services or ingress controllers via a classic CI/CD pipeline using Azure DevOps, GitHub Actions, Jenkins, or GitLab, but even when using a GitOps approach with Argo CD or Flux v2.

\n

For every workload that you expose via Azure Private Link Service (PLS) and Azure Front Door Premium, you need to create - Microsoft.Cdn/profiles/originGroups: an Origin Group, an Origin, endpoint, a route, and a security policy if you want to protect the workload with a WAF policy. You can accomplish this task using [az network front-door](az network front-door) Azure CLI commands in the CD pipeline used to deploy your service.

\n

 

\n

Test the application

\n

If the deployment succeeds, and the private endpoint connection from the Azure Front Door Premium instance to the Azure Private Link Service (PLS) is approved, you should be able to access the AKS-hosted httpbin web application as follows:

\n\n
 
\n

\n

 

\n

 

\n\n
 
\n

\n

 

\n

You can use the bicep/calls.sh Bash script to simulate a few attacks and see the managed rule set and custom rule of the Azure Web Application Firewall in action.

\n

 

\n

 

\n
#!/bin/bash\n\n# Variables\nurl=\"<Front Door Endpoint Hostname URL>\"\n\n# Call REST API\necho \"Calling REST API...\"\ncurl -I -s \"$url\"\n\n# Simulate SQL injection\necho \"Simulating SQL injection...\"\ncurl -I -s \"${url}?users=ExampleSQLInjection%27%20--\"\n\n# Simulate XSS\necho \"Simulating XSS...\"\ncurl -I -s \"${url}?users=ExampleXSS%3Cscript%3Ealert%28%27XSS%27%29%3C%2Fscript%3E\"\n\n# A custom rule blocks any request with the word blockme in the querystring.\necho \"Simulating query string manipulation with the 'attack' word in the query string...\"\ncurl -I -s \"${url}?task=blockme\"
\n

 

\n

 

\n

The Bash script should produce the following output, where the first call succeeds, while the remaining one are blocked by the WAF Policy configured in prevention mode.

\n

 

\n

 

\n
Calling REST API...\nHTTP/2 200\ncontent-length: 9593\ncontent-type: text/html; charset=utf-8\naccept-ranges: bytes\nvary: Accept-Encoding\naccess-control-allow-origin: *\naccess-control-allow-credentials: true\nx-azure-ref: 05mwQZAAAAADma91JbmU0TJqRqS2lyFurTUlMMzBFREdFMDYwOQA3YTk2NzZiMS0xZmRjLTQ0OWYtYmI1My1hNDUxMDVjNGZmYmM=\nx-cache: CONFIG_NOCACHE\ndate: Tue, 14 Mar 2023 12:47:33 GMT\n\nSimulating SQL injection...\nHTTP/2 403\nx-azure-ref: 05mwQZAAAAABaQCSGQToQT4tifYGpmsTmTUlMMzBFREdFMDYxNQA3YTk2NzZiMS0xZmRjLTQ0OWYtYmI1My1hNDUxMDVjNGZmYmM=\ndate: Tue, 14 Mar 2023 12:47:34 GMT\n\nSimulating XSS...\nHTTP/2 403\nx-azure-ref: 05mwQZAAAAAAJZzCrTmN4TLY+bZOxskzOTUlMMzBFREdFMDYxMwA3YTk2NzZiMS0xZmRjLTQ0OWYtYmI1My1hNDUxMDVjNGZmYmM=\ndate: Tue, 14 Mar 2023 12:47:33 GMT\n\nSimulating query string manipulation with the 'attack' word in the query string...\nHTTP/2 403\nx-azure-ref: 05mwQZAAAAADAle0hOg4FTYH6Q1LHIP50TUlMMzBFREdFMDYyMAA3YTk2NzZiMS0xZmRjLTQ0OWYtYmI1My1hNDUxMDVjNGZmYmM=\ndate: Tue, 14 Mar 2023 12:47:33 GMT
\n

 

\n

 

\n

Front Door WAF Policies and Application Gateway WAF policies can be configured to run in the following two modes:

\n\n

For more information, see Azure Web Application Firewall on Azure Front Door.

\n

 

\n

Review deployed resources

\n

Use the Azure portal, Azure CLI, or Azure PowerShell to list the deployed resources in the resource group.

\n

Azure CLI

\n

 

\n

 

\n
az resource list --resource-group <resource-group-name>
\n

 

\n

 

\n

PowerShell

\n

 

\n

 

\n
Get-AzResource -ResourceGroupName <resource-group-name>
\n

 

\n

 

\n

Clean up resources

\n

When you no longer need the resources you created, delete the resource group. This will remove all the Azure resources.

\n

Azure CLI

\n

 

\n

 

\n
az group delete --name <resource-group-name>
\n

 

\n

 

\n

PowerShell

\n

 

\n

 

\n
Remove-AzResourceGroup -Name <resource-group-name>
\n

 

\n

 

\n

Next Steps

\n

You could add a custom domain to your Front Door. If you use Azure DNS to manage your domain, you could extend the Bicep modules to automatically create a custom domain for your Front Door and create a CNAME DNS record in your public DNS zone.

"},"Conversation:conversation:3767535":{"__typename":"Conversation","id":"conversation:3767535","solved":false,"topic":{"__ref":"BlogTopicMessage:message:3767535"},"lastPostingActivityTime":"2024-03-04T07:30:30.659-08:00","lastPostTime":"2023-06-26T18:38:57.123-07:00","unreadReplyCount":7,"isSubscribed":false},"ModerationData:moderation_data:3767535":{"__typename":"ModerationData","id":"moderation_data:3767535","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTQ1MDM5N2k4OUY1N0VCRjg4MUMwREY5?revision=11\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTQ1MDM5N2k4OUY1N0VCRjg4MUMwREY5?revision=11","title":"architecture.png","associationType":"TEASER","width":980,"height":630,"altText":null},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTQ1MTE3N2k2MUZCOEU5NTQwMzhENUNC?revision=11\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTQ1MTE3N2k2MUZCOEU5NTQwMzhENUNC?revision=11","title":"architecture.png","associationType":"BODY","width":980,"height":620,"altText":null},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTU1Nzc2M2kyM0Q1OEY0NUVFOTA2OTA4?revision=11\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTU1Nzc2M2kyM0Q1OEY0NUVFOTA2OTA4?revision=11","title":"flow.png","associationType":"BODY","width":980,"height":720,"altText":null},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTQ1MDQwM2k1RDMwNDE5RkFGMTFCRUNC?revision=11\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTQ1MDQwM2k1RDMwNDE5RkFGMTFCRUNC?revision=11","title":"azure-portal.png","associationType":"BODY","width":1801,"height":1200,"altText":null},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTQ1MDQwNGkxQjQzM0VCNEM0NDAxQTAx?revision=11\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0zNzY3NTM1LTQ1MDQwNGkxQjQzM0VCNEM0NDAxQTAx?revision=11","title":"httpbin.png","associationType":"BODY","width":1618,"height":1271,"altText":null},"Revision:revision:3767535_11":{"__typename":"Revision","id":"revision:3767535_11","lastEditTime":"2024-03-04T07:30:21.259-08:00"},"BlogReplyMessage:message:3787135":{"__typename":"BlogReplyMessage","id":"message:3787135","conversation":{"__ref":"Conversation:conversation:3767535"},"author":{"__ref":"User:user:988334"},"revisionNum":1,"uid":3787135,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Blog:board:FastTrackforAzureBlog"},"subject":"Re: How to expose NGINX Ingress Controller via Azure Front Door and Azure Private Link Service","readOnly":false,"editFrozen":false,"moderationData":{"__ref":"ModerationData:moderation_data:3787135"},"parent":{"__ref":"BlogTopicMessage:message:3767535"},"body":"

Hi Richard_Hooper the ModSecurity is not necessary as the Bicep modules deploys a WAF policy for Azure Front Door. When directly exposing workloads to the public internet without a regional (Application Gateway) or global (Front Door) layer-7 load balancer in front, is a good practice to protect workloads exposes via the NGINX ingress controller via ModSecurity. So I usually configure it in my demos. In this case is not necessary, so I'll remove it to avoid any confusion.

Thanks
Paolo

","body@stringLength":"716","rawBody":"

Hi  the ModSecurity is not necessary as the Bicep modules deploys a WAF policy for Azure Front Door. When directly exposing workloads to the public internet without a regional (Application Gateway) or global (Front Door) layer-7 load balancer in front, is a good practice to protect workloads exposes via the NGINX ingress controller via ModSecurity. So I usually configure it in my demos. In this case is not necessary, so I'll remove it to avoid any confusion.

Thanks
Paolo

","kudosSumWeight":1,"repliesCount":0,"postTime":"2023-04-04T06:57:57.602-07:00","images":{"__typename":"AssociatedImageConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"attachments":{"__typename":"AttachmentConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"timeToRead":1,"currentRevision":{"__ref":"Revision:revision:3787135_1"},"latestVersion":{"__typename":"FriendlyVersion","major":"1","minor":"0"},"metrics":{"__typename":"MessageMetrics","views":10595},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"messagePolicies":{"__typename":"MessagePolicies","canModerateSpamMessage":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","key":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","args":[]}}},"customFields":[],"replies":{"__typename":"MessageConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"CachedAsset:theme:customTheme1-1746564015560":{"__typename":"CachedAsset","id":"theme:customTheme1-1746564015560","value":{"id":"customTheme1","animation":{"fast":"150ms","normal":"250ms","slow":"500ms","slowest":"750ms","function":"cubic-bezier(0.07, 0.91, 0.51, 1)","__typename":"AnimationThemeSettings"},"avatar":{"borderRadius":"50%","collections":["default"],"__typename":"AvatarThemeSettings"},"basics":{"browserIcon":{"imageAssetName":"favicon-1730836283320.png","imageLastModified":"1730836286415","__typename":"ThemeAsset"},"customerLogo":{"imageAssetName":"favicon-1730836271365.png","imageLastModified":"1730836274203","__typename":"ThemeAsset"},"maximumWidthOfPageContent":"1300px","oneColumnNarrowWidth":"800px","gridGutterWidthMd":"30px","gridGutterWidthXs":"10px","pageWidthStyle":"WIDTH_OF_BROWSER","__typename":"BasicsThemeSettings"},"buttons":{"borderRadiusSm":"3px","borderRadius":"3px","borderRadiusLg":"5px","paddingY":"5px","paddingYLg":"7px","paddingYHero":"var(--lia-bs-btn-padding-y-lg)","paddingX":"12px","paddingXLg":"16px","paddingXHero":"60px","fontStyle":"NORMAL","fontWeight":"700","textTransform":"NONE","disabledOpacity":0.5,"primaryTextColor":"var(--lia-bs-white)","primaryTextHoverColor":"var(--lia-bs-white)","primaryTextActiveColor":"var(--lia-bs-white)","primaryBgColor":"var(--lia-bs-primary)","primaryBgHoverColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) * 0.85))","primaryBgActiveColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) * 0.7))","primaryBorder":"1px solid transparent","primaryBorderHover":"1px solid transparent","primaryBorderActive":"1px solid transparent","primaryBorderFocus":"1px solid var(--lia-bs-white)","primaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","secondaryTextColor":"var(--lia-bs-gray-900)","secondaryTextHoverColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.95))","secondaryTextActiveColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.9))","secondaryBgColor":"var(--lia-bs-gray-200)","secondaryBgHoverColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.96))","secondaryBgActiveColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.92))","secondaryBorder":"1px solid transparent","secondaryBorderHover":"1px solid transparent","secondaryBorderActive":"1px solid transparent","secondaryBorderFocus":"1px solid transparent","secondaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","tertiaryTextColor":"var(--lia-bs-gray-900)","tertiaryTextHoverColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.95))","tertiaryTextActiveColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.9))","tertiaryBgColor":"transparent","tertiaryBgHoverColor":"transparent","tertiaryBgActiveColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.04)","tertiaryBorder":"1px solid transparent","tertiaryBorderHover":"1px solid hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","tertiaryBorderActive":"1px solid transparent","tertiaryBorderFocus":"1px solid transparent","tertiaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","destructiveTextColor":"var(--lia-bs-danger)","destructiveTextHoverColor":"hsl(var(--lia-bs-danger-h), var(--lia-bs-danger-s), calc(var(--lia-bs-danger-l) * 0.95))","destructiveTextActiveColor":"hsl(var(--lia-bs-danger-h), var(--lia-bs-danger-s), calc(var(--lia-bs-danger-l) * 0.9))","destructiveBgColor":"var(--lia-bs-gray-200)","destructiveBgHoverColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.96))","destructiveBgActiveColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.92))","destructiveBorder":"1px solid transparent","destructiveBorderHover":"1px solid transparent","destructiveBorderActive":"1px solid transparent","destructiveBorderFocus":"1px solid transparent","destructiveBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","__typename":"ButtonsThemeSettings"},"border":{"color":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","mainContent":"NONE","sideContent":"LIGHT","radiusSm":"3px","radius":"5px","radiusLg":"9px","radius50":"100vw","__typename":"BorderThemeSettings"},"boxShadow":{"xs":"0 0 0 1px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.08), 0 3px 0 -1px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.16)","sm":"0 2px 4px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.12)","md":"0 5px 15px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.3)","lg":"0 10px 30px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.3)","__typename":"BoxShadowThemeSettings"},"cards":{"bgColor":"var(--lia-panel-bg-color)","borderRadius":"var(--lia-panel-border-radius)","boxShadow":"var(--lia-box-shadow-xs)","__typename":"CardsThemeSettings"},"chip":{"maxWidth":"300px","height":"30px","__typename":"ChipThemeSettings"},"coreTypes":{"defaultMessageLinkColor":"var(--lia-bs-link-color)","defaultMessageLinkDecoration":"none","defaultMessageLinkFontStyle":"NORMAL","defaultMessageLinkFontWeight":"400","defaultMessageFontStyle":"NORMAL","defaultMessageFontWeight":"400","defaultMessageFontFamily":"var(--lia-bs-font-family-base)","forumColor":"#4099E2","forumFontFamily":"var(--lia-bs-font-family-base)","forumFontWeight":"var(--lia-default-message-font-weight)","forumLineHeight":"var(--lia-bs-line-height-base)","forumFontStyle":"var(--lia-default-message-font-style)","forumMessageLinkColor":"var(--lia-default-message-link-color)","forumMessageLinkDecoration":"var(--lia-default-message-link-decoration)","forumMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","forumMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","forumSolvedColor":"#148563","blogColor":"#1CBAA0","blogFontFamily":"var(--lia-bs-font-family-base)","blogFontWeight":"var(--lia-default-message-font-weight)","blogLineHeight":"1.75","blogFontStyle":"var(--lia-default-message-font-style)","blogMessageLinkColor":"var(--lia-default-message-link-color)","blogMessageLinkDecoration":"var(--lia-default-message-link-decoration)","blogMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","blogMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","tkbColor":"#4C6B90","tkbFontFamily":"var(--lia-bs-font-family-base)","tkbFontWeight":"var(--lia-default-message-font-weight)","tkbLineHeight":"1.75","tkbFontStyle":"var(--lia-default-message-font-style)","tkbMessageLinkColor":"var(--lia-default-message-link-color)","tkbMessageLinkDecoration":"var(--lia-default-message-link-decoration)","tkbMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","tkbMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","qandaColor":"#4099E2","qandaFontFamily":"var(--lia-bs-font-family-base)","qandaFontWeight":"var(--lia-default-message-font-weight)","qandaLineHeight":"var(--lia-bs-line-height-base)","qandaFontStyle":"var(--lia-default-message-link-font-style)","qandaMessageLinkColor":"var(--lia-default-message-link-color)","qandaMessageLinkDecoration":"var(--lia-default-message-link-decoration)","qandaMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","qandaMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","qandaSolvedColor":"#3FA023","ideaColor":"#FF8000","ideaFontFamily":"var(--lia-bs-font-family-base)","ideaFontWeight":"var(--lia-default-message-font-weight)","ideaLineHeight":"var(--lia-bs-line-height-base)","ideaFontStyle":"var(--lia-default-message-font-style)","ideaMessageLinkColor":"var(--lia-default-message-link-color)","ideaMessageLinkDecoration":"var(--lia-default-message-link-decoration)","ideaMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","ideaMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","contestColor":"#FCC845","contestFontFamily":"var(--lia-bs-font-family-base)","contestFontWeight":"var(--lia-default-message-font-weight)","contestLineHeight":"var(--lia-bs-line-height-base)","contestFontStyle":"var(--lia-default-message-link-font-style)","contestMessageLinkColor":"var(--lia-default-message-link-color)","contestMessageLinkDecoration":"var(--lia-default-message-link-decoration)","contestMessageLinkFontStyle":"ITALIC","contestMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","occasionColor":"#D13A1F","occasionFontFamily":"var(--lia-bs-font-family-base)","occasionFontWeight":"var(--lia-default-message-font-weight)","occasionLineHeight":"var(--lia-bs-line-height-base)","occasionFontStyle":"var(--lia-default-message-font-style)","occasionMessageLinkColor":"var(--lia-default-message-link-color)","occasionMessageLinkDecoration":"var(--lia-default-message-link-decoration)","occasionMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","occasionMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","grouphubColor":"#333333","categoryColor":"#949494","communityColor":"#FFFFFF","productColor":"#949494","__typename":"CoreTypesThemeSettings"},"colors":{"black":"#000000","white":"#FFFFFF","gray100":"#F7F7F7","gray200":"#F7F7F7","gray300":"#E8E8E8","gray400":"#D9D9D9","gray500":"#CCCCCC","gray600":"#717171","gray700":"#707070","gray800":"#545454","gray900":"#333333","dark":"#545454","light":"#F7F7F7","primary":"#0069D4","secondary":"#333333","bodyText":"#1E1E1E","bodyBg":"#FFFFFF","info":"#409AE2","success":"#41C5AE","warning":"#FCC844","danger":"#BC341B","alertSystem":"#FF6600","textMuted":"#707070","highlight":"#FFFCAD","outline":"var(--lia-bs-primary)","custom":["#D3F5A4","#243A5E"],"__typename":"ColorsThemeSettings"},"divider":{"size":"3px","marginLeft":"4px","marginRight":"4px","borderRadius":"50%","bgColor":"var(--lia-bs-gray-600)","bgColorActive":"var(--lia-bs-gray-600)","__typename":"DividerThemeSettings"},"dropdown":{"fontSize":"var(--lia-bs-font-size-sm)","borderColor":"var(--lia-bs-border-color)","borderRadius":"var(--lia-bs-border-radius-sm)","dividerBg":"var(--lia-bs-gray-300)","itemPaddingY":"5px","itemPaddingX":"20px","headerColor":"var(--lia-bs-gray-700)","__typename":"DropdownThemeSettings"},"email":{"link":{"color":"#0069D4","hoverColor":"#0061c2","decoration":"none","hoverDecoration":"underline","__typename":"EmailLinkSettings"},"border":{"color":"#e4e4e4","__typename":"EmailBorderSettings"},"buttons":{"borderRadiusLg":"5px","paddingXLg":"16px","paddingYLg":"7px","fontWeight":"700","primaryTextColor":"#ffffff","primaryTextHoverColor":"#ffffff","primaryBgColor":"#0069D4","primaryBgHoverColor":"#005cb8","primaryBorder":"1px solid transparent","primaryBorderHover":"1px solid transparent","__typename":"EmailButtonsSettings"},"panel":{"borderRadius":"5px","borderColor":"#e4e4e4","__typename":"EmailPanelSettings"},"__typename":"EmailThemeSettings"},"emoji":{"skinToneDefault":"#ffcd43","skinToneLight":"#fae3c5","skinToneMediumLight":"#e2cfa5","skinToneMedium":"#daa478","skinToneMediumDark":"#a78058","skinToneDark":"#5e4d43","__typename":"EmojiThemeSettings"},"heading":{"color":"var(--lia-bs-body-color)","fontFamily":"Segoe UI","fontStyle":"NORMAL","fontWeight":"400","h1FontSize":"34px","h2FontSize":"32px","h3FontSize":"28px","h4FontSize":"24px","h5FontSize":"20px","h6FontSize":"16px","lineHeight":"1.3","subHeaderFontSize":"11px","subHeaderFontWeight":"500","h1LetterSpacing":"normal","h2LetterSpacing":"normal","h3LetterSpacing":"normal","h4LetterSpacing":"normal","h5LetterSpacing":"normal","h6LetterSpacing":"normal","subHeaderLetterSpacing":"2px","h1FontWeight":"var(--lia-bs-headings-font-weight)","h2FontWeight":"var(--lia-bs-headings-font-weight)","h3FontWeight":"var(--lia-bs-headings-font-weight)","h4FontWeight":"var(--lia-bs-headings-font-weight)","h5FontWeight":"var(--lia-bs-headings-font-weight)","h6FontWeight":"var(--lia-bs-headings-font-weight)","__typename":"HeadingThemeSettings"},"icons":{"size10":"10px","size12":"12px","size14":"14px","size16":"16px","size20":"20px","size24":"24px","size30":"30px","size40":"40px","size50":"50px","size60":"60px","size80":"80px","size120":"120px","size160":"160px","__typename":"IconsThemeSettings"},"imagePreview":{"bgColor":"var(--lia-bs-gray-900)","titleColor":"var(--lia-bs-white)","controlColor":"var(--lia-bs-white)","controlBgColor":"var(--lia-bs-gray-800)","__typename":"ImagePreviewThemeSettings"},"input":{"borderColor":"var(--lia-bs-gray-600)","disabledColor":"var(--lia-bs-gray-600)","focusBorderColor":"var(--lia-bs-primary)","labelMarginBottom":"10px","btnFontSize":"var(--lia-bs-font-size-sm)","focusBoxShadow":"0 0 0 3px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","checkLabelMarginBottom":"2px","checkboxBorderRadius":"3px","borderRadiusSm":"var(--lia-bs-border-radius-sm)","borderRadius":"var(--lia-bs-border-radius)","borderRadiusLg":"var(--lia-bs-border-radius-lg)","formTextMarginTop":"4px","textAreaBorderRadius":"var(--lia-bs-border-radius)","activeFillColor":"var(--lia-bs-primary)","__typename":"InputThemeSettings"},"loading":{"dotDarkColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.2)","dotLightColor":"hsla(var(--lia-bs-white-h), var(--lia-bs-white-s), var(--lia-bs-white-l), 0.5)","barDarkColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.06)","barLightColor":"hsla(var(--lia-bs-white-h), var(--lia-bs-white-s), var(--lia-bs-white-l), 0.4)","__typename":"LoadingThemeSettings"},"link":{"color":"var(--lia-bs-primary)","hoverColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) - 10%))","decoration":"none","hoverDecoration":"underline","__typename":"LinkThemeSettings"},"listGroup":{"itemPaddingY":"15px","itemPaddingX":"15px","borderColor":"var(--lia-bs-gray-300)","__typename":"ListGroupThemeSettings"},"modal":{"contentTextColor":"var(--lia-bs-body-color)","contentBg":"var(--lia-bs-white)","backgroundBg":"var(--lia-bs-black)","smSize":"440px","mdSize":"760px","lgSize":"1080px","backdropOpacity":0.3,"contentBoxShadowXs":"var(--lia-bs-box-shadow-sm)","contentBoxShadow":"var(--lia-bs-box-shadow)","headerFontWeight":"700","__typename":"ModalThemeSettings"},"navbar":{"position":"FIXED","background":{"attachment":null,"clip":null,"color":"var(--lia-bs-white)","imageAssetName":"","imageLastModified":"0","origin":null,"position":"CENTER_CENTER","repeat":"NO_REPEAT","size":"COVER","__typename":"BackgroundProps"},"backgroundOpacity":0.8,"paddingTop":"15px","paddingBottom":"15px","borderBottom":"1px solid var(--lia-bs-border-color)","boxShadow":"var(--lia-bs-box-shadow-sm)","brandMarginRight":"30px","brandMarginRightSm":"10px","brandLogoHeight":"30px","linkGap":"10px","linkJustifyContent":"flex-start","linkPaddingY":"5px","linkPaddingX":"10px","linkDropdownPaddingY":"9px","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkColor":"var(--lia-bs-body-color)","linkHoverColor":"var(--lia-bs-primary)","linkFontSize":"var(--lia-bs-font-size-sm)","linkFontStyle":"NORMAL","linkFontWeight":"400","linkTextTransform":"NONE","linkLetterSpacing":"normal","linkBorderRadius":"var(--lia-bs-border-radius-sm)","linkBgColor":"transparent","linkBgHoverColor":"transparent","linkBorder":"none","linkBorderHover":"none","linkBoxShadow":"none","linkBoxShadowHover":"none","linkTextBorderBottom":"none","linkTextBorderBottomHover":"none","dropdownPaddingTop":"10px","dropdownPaddingBottom":"15px","dropdownPaddingX":"10px","dropdownMenuOffset":"2px","dropdownDividerMarginTop":"10px","dropdownDividerMarginBottom":"10px","dropdownBorderColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","controllerBgHoverColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.1)","controllerIconColor":"var(--lia-bs-body-color)","controllerIconHoverColor":"var(--lia-bs-body-color)","controllerTextColor":"var(--lia-nav-controller-icon-color)","controllerTextHoverColor":"var(--lia-nav-controller-icon-hover-color)","controllerHighlightColor":"hsla(30, 100%, 50%)","controllerHighlightTextColor":"var(--lia-yiq-light)","controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerColor":"var(--lia-nav-controller-icon-color)","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","hamburgerBgColor":"transparent","hamburgerBgHoverColor":"transparent","hamburgerBorder":"none","hamburgerBorderHover":"none","collapseMenuMarginLeft":"20px","collapseMenuDividerBg":"var(--lia-nav-link-color)","collapseMenuDividerOpacity":0.16,"__typename":"NavbarThemeSettings"},"pager":{"textColor":"var(--lia-bs-link-color)","textFontWeight":"var(--lia-font-weight-md)","textFontSize":"var(--lia-bs-font-size-sm)","__typename":"PagerThemeSettings"},"panel":{"bgColor":"var(--lia-bs-white)","borderRadius":"var(--lia-bs-border-radius)","borderColor":"var(--lia-bs-border-color)","boxShadow":"none","__typename":"PanelThemeSettings"},"popover":{"arrowHeight":"8px","arrowWidth":"16px","maxWidth":"300px","minWidth":"100px","headerBg":"var(--lia-bs-white)","borderColor":"var(--lia-bs-border-color)","borderRadius":"var(--lia-bs-border-radius)","boxShadow":"0 0.5rem 1rem hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.15)","__typename":"PopoverThemeSettings"},"prism":{"color":"#000000","bgColor":"#f5f2f0","fontFamily":"var(--font-family-monospace)","fontSize":"var(--lia-bs-font-size-base)","fontWeightBold":"var(--lia-bs-font-weight-bold)","fontStyleItalic":"italic","tabSize":2,"highlightColor":"#b3d4fc","commentColor":"#62707e","punctuationColor":"#6f6f6f","namespaceOpacity":"0.7","propColor":"#990055","selectorColor":"#517a00","operatorColor":"#906736","operatorBgColor":"hsla(0, 0%, 100%, 0.5)","keywordColor":"#0076a9","functionColor":"#d3284b","variableColor":"#c14700","__typename":"PrismThemeSettings"},"rte":{"bgColor":"var(--lia-bs-white)","borderRadius":"var(--lia-panel-border-radius)","boxShadow":" var(--lia-panel-box-shadow)","customColor1":"#bfedd2","customColor2":"#fbeeb8","customColor3":"#f8cac6","customColor4":"#eccafa","customColor5":"#c2e0f4","customColor6":"#2dc26b","customColor7":"#f1c40f","customColor8":"#e03e2d","customColor9":"#b96ad9","customColor10":"#3598db","customColor11":"#169179","customColor12":"#e67e23","customColor13":"#ba372a","customColor14":"#843fa1","customColor15":"#236fa1","customColor16":"#ecf0f1","customColor17":"#ced4d9","customColor18":"#95a5a6","customColor19":"#7e8c8d","customColor20":"#34495e","customColor21":"#000000","customColor22":"#ffffff","defaultMessageHeaderMarginTop":"40px","defaultMessageHeaderMarginBottom":"20px","defaultMessageItemMarginTop":"0","defaultMessageItemMarginBottom":"10px","diffAddedColor":"hsla(170, 53%, 51%, 0.4)","diffChangedColor":"hsla(43, 97%, 63%, 0.4)","diffNoneColor":"hsla(0, 0%, 80%, 0.4)","diffRemovedColor":"hsla(9, 74%, 47%, 0.4)","specialMessageHeaderMarginTop":"40px","specialMessageHeaderMarginBottom":"20px","specialMessageItemMarginTop":"0","specialMessageItemMarginBottom":"10px","__typename":"RteThemeSettings"},"tags":{"bgColor":"var(--lia-bs-gray-200)","bgHoverColor":"var(--lia-bs-gray-400)","borderRadius":"var(--lia-bs-border-radius-sm)","color":"var(--lia-bs-body-color)","hoverColor":"var(--lia-bs-body-color)","fontWeight":"var(--lia-font-weight-md)","fontSize":"var(--lia-font-size-xxs)","textTransform":"UPPERCASE","letterSpacing":"0.5px","__typename":"TagsThemeSettings"},"toasts":{"borderRadius":"var(--lia-bs-border-radius)","paddingX":"12px","__typename":"ToastsThemeSettings"},"typography":{"fontFamilyBase":"Segoe UI","fontStyleBase":"NORMAL","fontWeightBase":"400","fontWeightLight":"300","fontWeightNormal":"400","fontWeightMd":"500","fontWeightBold":"700","letterSpacingSm":"normal","letterSpacingXs":"normal","lineHeightBase":"1.5","fontSizeBase":"16px","fontSizeXxs":"11px","fontSizeXs":"12px","fontSizeSm":"14px","fontSizeLg":"20px","fontSizeXl":"24px","smallFontSize":"14px","customFonts":[{"source":"SERVER","name":"Segoe UI","styles":[{"style":"NORMAL","weight":"400","__typename":"FontStyleData"},{"style":"NORMAL","weight":"300","__typename":"FontStyleData"},{"style":"NORMAL","weight":"600","__typename":"FontStyleData"},{"style":"NORMAL","weight":"700","__typename":"FontStyleData"},{"style":"ITALIC","weight":"400","__typename":"FontStyleData"}],"assetNames":["SegoeUI-normal-400.woff2","SegoeUI-normal-300.woff2","SegoeUI-normal-600.woff2","SegoeUI-normal-700.woff2","SegoeUI-italic-400.woff2"],"__typename":"CustomFont"},{"source":"SERVER","name":"MWF Fluent Icons","styles":[{"style":"NORMAL","weight":"400","__typename":"FontStyleData"}],"assetNames":["MWFFluentIcons-normal-400.woff2"],"__typename":"CustomFont"}],"__typename":"TypographyThemeSettings"},"unstyledListItem":{"marginBottomSm":"5px","marginBottomMd":"10px","marginBottomLg":"15px","marginBottomXl":"20px","marginBottomXxl":"25px","__typename":"UnstyledListItemThemeSettings"},"yiq":{"light":"#ffffff","dark":"#000000","__typename":"YiqThemeSettings"},"colorLightness":{"primaryDark":0.36,"primaryLight":0.74,"primaryLighter":0.89,"primaryLightest":0.95,"infoDark":0.39,"infoLight":0.72,"infoLighter":0.85,"infoLightest":0.93,"successDark":0.24,"successLight":0.62,"successLighter":0.8,"successLightest":0.91,"warningDark":0.39,"warningLight":0.68,"warningLighter":0.84,"warningLightest":0.93,"dangerDark":0.41,"dangerLight":0.72,"dangerLighter":0.89,"dangerLightest":0.95,"__typename":"ColorLightnessThemeSettings"},"localOverride":false,"__typename":"Theme"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Loading/LoadingDot-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-1745505307000","value":{"title":"Loading..."},"localOverride":false},"CachedAsset:quilt:o365.prod:pages/blogs/BlogMessagePage:board:FastTrackforAzureBlog-1746740537110":{"__typename":"CachedAsset","id":"quilt:o365.prod:pages/blogs/BlogMessagePage:board:FastTrackforAzureBlog-1746740537110","value":{"id":"BlogMessagePage","container":{"id":"Common","headerProps":{"backgroundImageProps":null,"backgroundColor":null,"addComponents":null,"removeComponents":["community.widget.bannerWidget"],"componentOrder":null,"__typename":"QuiltContainerSectionProps"},"headerComponentProps":{"community.widget.breadcrumbWidget":{"disableLastCrumbForDesktop":false}},"footerProps":null,"footerComponentProps":null,"items":[{"id":"blog-article","layout":"ONE_COLUMN","bgColor":null,"showTitle":null,"showDescription":null,"textPosition":null,"textColor":null,"sectionEditLevel":"LOCKED","bgImage":null,"disableSpacing":null,"edgeToEdgeDisplay":null,"fullHeight":null,"showBorder":null,"__typename":"OneColumnQuiltSection","columnMap":{"main":[{"id":"blogs.widget.blogArticleWidget","className":"lia-blog-container","props":null,"__typename":"QuiltComponent"}],"__typename":"OneSectionColumns"}},{"id":"section-1729184836777","layout":"MAIN_SIDE","bgColor":"transparent","showTitle":false,"showDescription":false,"textPosition":"CENTER","textColor":"var(--lia-bs-body-color)","sectionEditLevel":null,"bgImage":null,"disableSpacing":null,"edgeToEdgeDisplay":null,"fullHeight":null,"showBorder":null,"__typename":"MainSideQuiltSection","columnMap":{"main":[],"side":[],"__typename":"MainSideSectionColumns"}}],"__typename":"QuiltContainer"},"__typename":"Quilt","localOverride":false},"localOverride":false},"CachedAsset:text:en_US-components/common/EmailVerification-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/common/EmailVerification-1745505307000","value":{"email.verification.title":"Email Verification Required","email.verification.message.update.email":"To participate in the community, you must first verify your email address. The verification email was sent to {email}. To change your email, visit My Settings.","email.verification.message.resend.email":"To participate in the community, you must first verify your email address. The verification email was sent to {email}. Resend email."},"localOverride":false},"CachedAsset:text:en_US-pages/blogs/BlogMessagePage-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-pages/blogs/BlogMessagePage-1745505307000","value":{"title":"{contextMessageSubject} | {communityTitle}","errorMissing":"This blog post cannot be found","name":"Blog Message Page","section.blog-article.title":"Blog Post","archivedMessageTitle":"This Content Has Been Archived","section.section-1729184836777.title":"","section.section-1729184836777.description":"","section.CncIde.title":"Blog Post","section.tifEmD.description":"","section.tifEmD.title":""},"localOverride":false},"CachedAsset:quiltWrapper:o365.prod:Common:1746797692508":{"__typename":"CachedAsset","id":"quiltWrapper:o365.prod:Common:1746797692508","value":{"id":"Common","header":{"backgroundImageProps":{"assetName":null,"backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"CENTER_CENTER","lastModified":null,"__typename":"BackgroundImageProps"},"backgroundColor":"transparent","items":[{"id":"community.widget.navbarWidget","props":{"showUserName":true,"showRegisterLink":true,"useIconLanguagePicker":true,"useLabelLanguagePicker":true,"className":"QuiltComponent_lia-component-edit-mode__0nCcm","links":{"sideLinks":[],"mainLinks":[{"children":[],"linkType":"INTERNAL","id":"gxcuf89792","params":{},"routeName":"CommunityPage"},{"children":[],"linkType":"EXTERNAL","id":"external-link","url":"/Directory","target":"SELF"},{"children":[{"linkType":"INTERNAL","id":"microsoft365","params":{"categoryId":"microsoft365"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"windows","params":{"categoryId":"Windows"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"Common-microsoft365-copilot-link","params":{"categoryId":"Microsoft365Copilot"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-teams","params":{"categoryId":"MicrosoftTeams"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-securityand-compliance","params":{"categoryId":"microsoft-security"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"azure","params":{"categoryId":"Azure"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"Common-content_management-link","params":{"categoryId":"Content_Management"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"exchange","params":{"categoryId":"Exchange"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"windows-server","params":{"categoryId":"Windows-Server"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"outlook","params":{"categoryId":"Outlook"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-endpoint-manager","params":{"categoryId":"microsoftintune"},"routeName":"CategoryPage"},{"linkType":"EXTERNAL","id":"external-link-2","url":"/Directory","target":"SELF"}],"linkType":"EXTERNAL","id":"communities","url":"/","target":"BLANK"},{"children":[{"linkType":"INTERNAL","id":"a-i","params":{"categoryId":"AI"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"education-sector","params":{"categoryId":"EducationSector"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"partner-community","params":{"categoryId":"PartnerCommunity"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"i-t-ops-talk","params":{"categoryId":"ITOpsTalk"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"healthcare-and-life-sciences","params":{"categoryId":"HealthcareAndLifeSciences"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-mechanics","params":{"categoryId":"MicrosoftMechanics"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"public-sector","params":{"categoryId":"PublicSector"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"s-m-b","params":{"categoryId":"MicrosoftforNonprofits"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"io-t","params":{"categoryId":"IoT"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"startupsat-microsoft","params":{"categoryId":"StartupsatMicrosoft"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"driving-adoption","params":{"categoryId":"DrivingAdoption"},"routeName":"CategoryPage"},{"linkType":"EXTERNAL","id":"external-link-1","url":"/Directory","target":"SELF"}],"linkType":"EXTERNAL","id":"communities-1","url":"/","target":"SELF"},{"children":[],"linkType":"EXTERNAL","id":"external","url":"/Blogs","target":"SELF"},{"children":[],"linkType":"EXTERNAL","id":"external-1","url":"/Events","target":"SELF"},{"children":[{"linkType":"INTERNAL","id":"microsoft-learn-1","params":{"categoryId":"MicrosoftLearn"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-learn-blog","params":{"boardId":"MicrosoftLearnBlog","categoryId":"MicrosoftLearn"},"routeName":"BlogBoardPage"},{"linkType":"EXTERNAL","id":"external-10","url":"https://learningroomdirectory.microsoft.com/","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-3","url":"https://docs.microsoft.com/learn/dynamics365/?WT.mc_id=techcom_header-webpage-m365","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-4","url":"https://docs.microsoft.com/learn/m365/?wt.mc_id=techcom_header-webpage-m365","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-5","url":"https://docs.microsoft.com/learn/topics/sci/?wt.mc_id=techcom_header-webpage-m365","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-6","url":"https://docs.microsoft.com/learn/powerplatform/?wt.mc_id=techcom_header-webpage-powerplatform","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-7","url":"https://docs.microsoft.com/learn/github/?wt.mc_id=techcom_header-webpage-github","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-8","url":"https://docs.microsoft.com/learn/teams/?wt.mc_id=techcom_header-webpage-teams","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-9","url":"https://docs.microsoft.com/learn/dotnet/?wt.mc_id=techcom_header-webpage-dotnet","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-2","url":"https://docs.microsoft.com/learn/azure/?WT.mc_id=techcom_header-webpage-m365","target":"BLANK"}],"linkType":"INTERNAL","id":"microsoft-learn","params":{"categoryId":"MicrosoftLearn"},"routeName":"CategoryPage"},{"children":[],"linkType":"INTERNAL","id":"community-info-center","params":{"categoryId":"Community-Info-Center"},"routeName":"CategoryPage"}]},"style":{"boxShadow":"var(--lia-bs-box-shadow-sm)","controllerHighlightColor":"hsla(30, 100%, 50%)","linkFontWeight":"400","dropdownDividerMarginBottom":"10px","hamburgerBorderHover":"none","linkBoxShadowHover":"none","linkFontSize":"14px","backgroundOpacity":0.8,"controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerBgColor":"transparent","hamburgerColor":"var(--lia-nav-controller-icon-color)","linkTextBorderBottom":"none","brandLogoHeight":"30px","linkBgHoverColor":"transparent","linkLetterSpacing":"normal","collapseMenuDividerOpacity":0.16,"dropdownPaddingBottom":"15px","paddingBottom":"15px","dropdownMenuOffset":"2px","hamburgerBgHoverColor":"transparent","borderBottom":"1px solid var(--lia-bs-border-color)","hamburgerBorder":"none","dropdownPaddingX":"10px","brandMarginRightSm":"10px","linkBoxShadow":"none","collapseMenuDividerBg":"var(--lia-nav-link-color)","linkColor":"var(--lia-bs-body-color)","linkJustifyContent":"flex-start","dropdownPaddingTop":"10px","controllerHighlightTextColor":"var(--lia-yiq-dark)","controllerTextColor":"var(--lia-nav-controller-icon-color)","background":{"imageAssetName":"","color":"var(--lia-bs-white)","size":"COVER","repeat":"NO_REPEAT","position":"CENTER_CENTER","imageLastModified":""},"linkBorderRadius":"var(--lia-bs-border-radius-sm)","linkHoverColor":"var(--lia-bs-body-color)","position":"FIXED","linkBorder":"none","linkTextBorderBottomHover":"2px solid var(--lia-bs-body-color)","brandMarginRight":"30px","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","linkBorderHover":"none","collapseMenuMarginLeft":"20px","linkFontStyle":"NORMAL","controllerTextHoverColor":"var(--lia-nav-controller-icon-hover-color)","linkPaddingX":"10px","linkPaddingY":"5px","paddingTop":"15px","linkTextTransform":"NONE","dropdownBorderColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","controllerBgHoverColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.1)","linkBgColor":"transparent","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkDropdownPaddingY":"9px","controllerIconColor":"var(--lia-bs-body-color)","dropdownDividerMarginTop":"10px","linkGap":"10px","controllerIconHoverColor":"var(--lia-bs-body-color)"},"showSearchIcon":false,"languagePickerStyle":"iconAndLabel"},"__typename":"QuiltComponent"},{"id":"community.widget.breadcrumbWidget","props":{"backgroundColor":"transparent","linkHighlightColor":"var(--lia-bs-primary)","visualEffects":{"showBottomBorder":true},"linkTextColor":"var(--lia-bs-gray-700)"},"__typename":"QuiltComponent"},{"id":"custom.widget.HeroBanner","props":{"widgetVisibility":"signedInOrAnonymous","usePageWidth":false,"useTitle":true,"cMax_items":3,"useBackground":false,"title":"","lazyLoad":false,"widgetChooser":"custom.widget.HeroBanner"},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"footer":{"backgroundImageProps":{"assetName":null,"backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"CENTER_CENTER","lastModified":null,"__typename":"BackgroundImageProps"},"backgroundColor":"transparent","items":[{"id":"custom.widget.MicrosoftFooter","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"__typename":"QuiltWrapper","localOverride":false},"localOverride":false},"CachedAsset:text:en_US-components/common/ActionFeedback-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/common/ActionFeedback-1745505307000","value":{"joinedGroupHub.title":"Welcome","joinedGroupHub.message":"You are now a member of this group and are subscribed to updates.","groupHubInviteNotFound.title":"Invitation Not Found","groupHubInviteNotFound.message":"Sorry, we could not find your invitation to the group. The owner may have canceled the invite.","groupHubNotFound.title":"Group Not Found","groupHubNotFound.message":"The grouphub you tried to join does not exist. It may have been deleted.","existingGroupHubMember.title":"Already Joined","existingGroupHubMember.message":"You are already a member of this group.","accountLocked.title":"Account Locked","accountLocked.message":"Your account has been locked due to multiple failed attempts. Try again in {lockoutTime} minutes.","editedGroupHub.title":"Changes Saved","editedGroupHub.message":"Your group has been updated.","leftGroupHub.title":"Goodbye","leftGroupHub.message":"You are no longer a member of this group and will not receive future updates.","deletedGroupHub.title":"Deleted","deletedGroupHub.message":"The group has been deleted.","groupHubCreated.title":"Group Created","groupHubCreated.message":"{groupHubName} is ready to use","accountClosed.title":"Account Closed","accountClosed.message":"The account has been closed and you will now be redirected to the homepage","resetTokenExpired.title":"Reset Password Link has Expired","resetTokenExpired.message":"Try resetting your password again","invalidUrl.title":"Invalid URL","invalidUrl.message":"The URL you're using is not recognized. Verify your URL and try again.","accountClosedForUser.title":"Account Closed","accountClosedForUser.message":"{userName}'s account is closed","inviteTokenInvalid.title":"Invitation Invalid","inviteTokenInvalid.message":"Your invitation to the community has been canceled or expired.","inviteTokenError.title":"Invitation Verification Failed","inviteTokenError.message":"The url you are utilizing is not recognized. Verify your URL and try again","pageNotFound.title":"Access Denied","pageNotFound.message":"You do not have access to this area of the community or it doesn't exist","eventAttending.title":"Responded as Attending","eventAttending.message":"You'll be notified when there's new activity and reminded as the event approaches","eventInterested.title":"Responded as Interested","eventInterested.message":"You'll be notified when there's new activity and reminded as the event approaches","eventNotFound.title":"Event Not Found","eventNotFound.message":"The event you tried to respond to does not exist.","redirectToRelatedPage.title":"Showing Related Content","redirectToRelatedPageForBaseUsers.title":"Showing Related Content","redirectToRelatedPageForBaseUsers.message":"The content you are trying to access is archived","redirectToRelatedPage.message":"The content you are trying to access is archived","relatedUrl.archivalLink.flyoutMessage":"The content you are trying to access is archived View Archived Content"},"localOverride":false},"CachedAsset:component:custom.widget.HeroBanner-en-us-1746740526861":{"__typename":"CachedAsset","id":"component:custom.widget.HeroBanner-en-us-1746740526861","value":{"component":{"id":"custom.widget.HeroBanner","template":{"id":"HeroBanner","markupLanguage":"REACT","style":null,"texts":{"searchPlaceholderText":"Search this community","followActionText":"Follow","unfollowActionText":"Following","searchOnHoverText":"Please enter your search term(s) and then press return key to complete a search.","blogs.sidebar.pagetitle":"Latest Blogs | Microsoft Tech Community","followThisNode":"Follow this node","unfollowThisNode":"Unfollow this node"},"defaults":{"config":{"applicablePages":[],"description":null,"fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[{"id":"max_items","dataType":"NUMBER","list":false,"defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"control":"INPUT","__typename":"PropDefinition"}],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.HeroBanner","form":{"fields":[{"id":"widgetChooser","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"title","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useTitle","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useBackground","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"widgetVisibility","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"moreOptions","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"cMax_items","validation":null,"noValidation":null,"dataType":"NUMBER","list":false,"control":"INPUT","defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"__typename":"FormField"}],"layout":{"rows":[{"id":"widgetChooserGroup","type":"fieldset","as":null,"items":[{"id":"widgetChooser","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"titleGroup","type":"fieldset","as":null,"items":[{"id":"title","className":null,"__typename":"FormFieldRef"},{"id":"useTitle","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"useBackground","type":"fieldset","as":null,"items":[{"id":"useBackground","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"widgetVisibility","type":"fieldset","as":null,"items":[{"id":"widgetVisibility","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"moreOptionsGroup","type":"fieldset","as":null,"items":[{"id":"moreOptions","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"componentPropsGroup","type":"fieldset","as":null,"items":[{"id":"cMax_items","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"}],"actionButtons":null,"className":"custom_widget_HeroBanner_form","formGroupFieldSeparator":"divider","__typename":"FormLayout"},"__typename":"Form"},"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":null,"fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[{"id":"max_items","dataType":"NUMBER","list":false,"defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"control":"INPUT","__typename":"PropDefinition"}],"__typename":"ComponentProperties"},"form":{"fields":[{"id":"widgetChooser","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"title","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useTitle","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useBackground","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"widgetVisibility","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"moreOptions","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"cMax_items","validation":null,"noValidation":null,"dataType":"NUMBER","list":false,"control":"INPUT","defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"__typename":"FormField"}],"layout":{"rows":[{"id":"widgetChooserGroup","type":"fieldset","as":null,"items":[{"id":"widgetChooser","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"titleGroup","type":"fieldset","as":null,"items":[{"id":"title","className":null,"__typename":"FormFieldRef"},{"id":"useTitle","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"useBackground","type":"fieldset","as":null,"items":[{"id":"useBackground","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"widgetVisibility","type":"fieldset","as":null,"items":[{"id":"widgetVisibility","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"moreOptionsGroup","type":"fieldset","as":null,"items":[{"id":"moreOptions","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"componentPropsGroup","type":"fieldset","as":null,"items":[{"id":"cMax_items","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"}],"actionButtons":null,"className":"custom_widget_HeroBanner_form","formGroupFieldSeparator":"divider","__typename":"FormLayout"},"__typename":"Form"},"__typename":"Component","localOverride":false},"globalCss":null,"form":{"fields":[{"id":"widgetChooser","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"title","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useTitle","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useBackground","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"widgetVisibility","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"moreOptions","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"cMax_items","validation":null,"noValidation":null,"dataType":"NUMBER","list":false,"control":"INPUT","defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"__typename":"FormField"}],"layout":{"rows":[{"id":"widgetChooserGroup","type":"fieldset","as":null,"items":[{"id":"widgetChooser","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"titleGroup","type":"fieldset","as":null,"items":[{"id":"title","className":null,"__typename":"FormFieldRef"},{"id":"useTitle","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"useBackground","type":"fieldset","as":null,"items":[{"id":"useBackground","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"widgetVisibility","type":"fieldset","as":null,"items":[{"id":"widgetVisibility","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"moreOptionsGroup","type":"fieldset","as":null,"items":[{"id":"moreOptions","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"componentPropsGroup","type":"fieldset","as":null,"items":[{"id":"cMax_items","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"}],"actionButtons":null,"className":"custom_widget_HeroBanner_form","formGroupFieldSeparator":"divider","__typename":"FormLayout"},"__typename":"Form"}},"localOverride":false},"CachedAsset:component:custom.widget.MicrosoftFooter-en-us-1746740526861":{"__typename":"CachedAsset","id":"component:custom.widget.MicrosoftFooter-en-us-1746740526861","value":{"component":{"id":"custom.widget.MicrosoftFooter","template":{"id":"MicrosoftFooter","markupLanguage":"HANDLEBARS","style":".context-uhf {\n min-width: 280px;\n font-size: 15px;\n box-sizing: border-box;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n & *,\n & *:before,\n & *:after {\n box-sizing: inherit;\n }\n a.c-uhff-link {\n color: #616161;\n word-break: break-word;\n text-decoration: none;\n }\n &a:link,\n &a:focus,\n &a:hover,\n &a:active,\n &a:visited {\n text-decoration: none;\n color: inherit;\n }\n & div {\n font-family: 'Segoe UI', SegoeUI, 'Helvetica Neue', Helvetica, Arial, sans-serif;\n }\n}\n.c-uhff {\n background: #f2f2f2;\n margin: -1.5625;\n width: auto;\n height: auto;\n}\n.c-uhff-nav {\n margin: 0 auto;\n max-width: calc(1600px + 10%);\n padding: 0 5%;\n box-sizing: inherit;\n &:before,\n &:after {\n content: ' ';\n display: table;\n clear: left;\n }\n @media only screen and (max-width: 1083px) {\n padding-left: 12px;\n }\n .c-heading-4 {\n color: #616161;\n word-break: break-word;\n font-size: 15px;\n line-height: 20px;\n padding: 36px 0 4px;\n font-weight: 600;\n }\n .c-uhff-nav-row {\n .c-uhff-nav-group {\n display: block;\n float: left;\n min-height: 1px;\n vertical-align: text-top;\n padding: 0 12px;\n width: 100%;\n zoom: 1;\n &:first-child {\n padding-left: 0;\n @media only screen and (max-width: 1083px) {\n padding-left: 12px;\n }\n }\n @media only screen and (min-width: 540px) and (max-width: 1082px) {\n width: 33.33333%;\n }\n @media only screen and (min-width: 1083px) {\n width: 16.6666666667%;\n }\n ul.c-list.f-bare {\n font-size: 11px;\n line-height: 16px;\n margin-top: 0;\n margin-bottom: 0;\n padding-left: 0;\n list-style-type: none;\n li {\n word-break: break-word;\n padding: 8px 0;\n margin: 0;\n }\n }\n }\n }\n}\n.c-uhff-base {\n background: #f2f2f2;\n margin: 0 auto;\n max-width: calc(1600px + 10%);\n padding: 30px 5% 16px;\n &:before,\n &:after {\n content: ' ';\n display: table;\n }\n &:after {\n clear: both;\n }\n a.c-uhff-ccpa {\n font-size: 11px;\n line-height: 16px;\n float: left;\n margin: 3px 0;\n }\n a.c-uhff-ccpa:hover {\n text-decoration: underline;\n }\n ul.c-list {\n font-size: 11px;\n line-height: 16px;\n float: right;\n margin: 3px 0;\n color: #616161;\n li {\n padding: 0 24px 4px 0;\n display: inline-block;\n }\n }\n .c-list.f-bare {\n padding-left: 0;\n list-style-type: none;\n }\n @media only screen and (max-width: 1083px) {\n display: flex;\n flex-wrap: wrap;\n padding: 30px 24px 16px;\n }\n}\n\n.social-share {\n position: fixed;\n top: 60%;\n transform: translateY(-50%);\n left: 0;\n z-index: 1000;\n}\n\n.sharing-options {\n list-style: none;\n padding: 0;\n margin: 0;\n display: block;\n flex-direction: column;\n background-color: white;\n width: 43px;\n border-radius: 0px 7px 7px 0px;\n}\n.linkedin-icon {\n border-top-right-radius: 7px;\n}\n.linkedin-icon:hover {\n border-radius: 0;\n}\n.social-share-rss-image {\n border-bottom-right-radius: 7px;\n}\n.social-share-rss-image:hover {\n border-radius: 0;\n}\n\n.social-link-footer {\n position: relative;\n display: block;\n margin: -2px 0;\n transition: all 0.2s ease;\n}\n.social-link-footer:hover .linkedin-icon {\n border-radius: 0;\n}\n.social-link-footer:hover .social-share-rss-image {\n border-radius: 0;\n}\n\n.social-link-footer img {\n width: 40px;\n height: auto;\n transition: filter 0.3s ease;\n}\n\n.social-share-list {\n width: 40px;\n}\n.social-share-rss-image {\n width: 40px;\n}\n\n.share-icon {\n border: 2px solid transparent;\n display: inline-block;\n position: relative;\n}\n\n.share-icon:hover {\n opacity: 1;\n border: 2px solid white;\n box-sizing: border-box;\n}\n\n.share-icon:hover .label {\n opacity: 1;\n visibility: visible;\n border: 2px solid white;\n box-sizing: border-box;\n border-left: none;\n}\n\n.label {\n position: absolute;\n left: 100%;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n transition: all 0.2s ease;\n color: white;\n border-radius: 0 10 0 10px;\n top: 50%;\n transform: translateY(-50%);\n height: 40px;\n border-radius: 0 6px 6px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px 5px 20px 8px;\n margin-left: -1px;\n}\n.linkedin {\n background-color: #0474b4;\n}\n.facebook {\n background-color: #3c5c9c;\n}\n.twitter {\n background-color: white;\n color: black;\n}\n.reddit {\n background-color: #fc4404;\n}\n.mail {\n background-color: #848484;\n}\n.bluesky {\n background-color: white;\n color: black;\n}\n.rss {\n background-color: #ec7b1c;\n}\n#RSS {\n width: 40px;\n height: 40px;\n}\n\n@media (max-width: 991px) {\n .social-share {\n display: none;\n }\n}\n","texts":{"New tab":"What's New","New 1":"Surface Laptop Studio 2","New 2":"Surface Laptop Go 3","New 3":"Surface Pro 9","New 4":"Surface Laptop 5","New 5":"Surface Studio 2+","New 6":"Copilot in Windows","New 7":"Microsoft 365","New 8":"Windows 11 apps","Store tab":"Microsoft Store","Store 1":"Account Profile","Store 2":"Download Center","Store 3":"Microsoft Store Support","Store 4":"Returns","Store 5":"Order tracking","Store 6":"Certified Refurbished","Store 7":"Microsoft Store Promise","Store 8":"Flexible Payments","Education tab":"Education","Edu 1":"Microsoft in education","Edu 2":"Devices for education","Edu 3":"Microsoft Teams for Education","Edu 4":"Microsoft 365 Education","Edu 5":"How to buy for your school","Edu 6":"Educator Training and development","Edu 7":"Deals for students and parents","Edu 8":"Azure for students","Business tab":"Business","Bus 1":"Microsoft Cloud","Bus 2":"Microsoft Security","Bus 3":"Dynamics 365","Bus 4":"Microsoft 365","Bus 5":"Microsoft Power Platform","Bus 6":"Microsoft Teams","Bus 7":"Microsoft Industry","Bus 8":"Small Business","Developer tab":"Developer & IT","Dev 1":"Azure","Dev 2":"Developer Center","Dev 3":"Documentation","Dev 4":"Microsoft Learn","Dev 5":"Microsoft Tech Community","Dev 6":"Azure Marketplace","Dev 7":"AppSource","Dev 8":"Visual Studio","Company tab":"Company","Com 1":"Careers","Com 2":"About Microsoft","Com 3":"Company News","Com 4":"Privacy at Microsoft","Com 5":"Investors","Com 6":"Diversity and inclusion","Com 7":"Accessiblity","Com 8":"Sustainibility"},"defaults":{"config":{"applicablePages":[],"description":"The Microsoft Footer","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.MicrosoftFooter","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"The Microsoft Footer","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":{"css":".custom_widget_MicrosoftFooter_context-uhf_105bp_1 {\n min-width: 17.5rem;\n font-size: 0.9375rem;\n box-sizing: border-box;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n & *,\n & *:before,\n & *:after {\n box-sizing: inherit;\n }\n a.custom_widget_MicrosoftFooter_c-uhff-link_105bp_12 {\n color: #616161;\n word-break: break-word;\n text-decoration: none;\n }\n &a:link,\n &a:focus,\n &a:hover,\n &a:active,\n &a:visited {\n text-decoration: none;\n color: inherit;\n }\n & div {\n font-family: 'Segoe UI', SegoeUI, 'Helvetica Neue', Helvetica, Arial, sans-serif;\n }\n}\n.custom_widget_MicrosoftFooter_c-uhff_105bp_12 {\n background: #f2f2f2;\n margin: -1.5625;\n width: auto;\n height: auto;\n}\n.custom_widget_MicrosoftFooter_c-uhff-nav_105bp_35 {\n margin: 0 auto;\n max-width: calc(100rem + 10%);\n padding: 0 5%;\n box-sizing: inherit;\n &:before,\n &:after {\n content: ' ';\n display: table;\n clear: left;\n }\n @media only screen and (max-width: 1083px) {\n padding-left: 0.75rem;\n }\n .custom_widget_MicrosoftFooter_c-heading-4_105bp_49 {\n color: #616161;\n word-break: break-word;\n font-size: 0.9375rem;\n line-height: 1.25rem;\n padding: 2.25rem 0 0.25rem;\n font-weight: 600;\n }\n .custom_widget_MicrosoftFooter_c-uhff-nav-row_105bp_57 {\n .custom_widget_MicrosoftFooter_c-uhff-nav-group_105bp_58 {\n display: block;\n float: left;\n min-height: 0.0625rem;\n vertical-align: text-top;\n padding: 0 0.75rem;\n width: 100%;\n zoom: 1;\n &:first-child {\n padding-left: 0;\n @media only screen and (max-width: 1083px) {\n padding-left: 0.75rem;\n }\n }\n @media only screen and (min-width: 540px) and (max-width: 1082px) {\n width: 33.33333%;\n }\n @media only screen and (min-width: 1083px) {\n width: 16.6666666667%;\n }\n ul.custom_widget_MicrosoftFooter_c-list_105bp_78.custom_widget_MicrosoftFooter_f-bare_105bp_78 {\n font-size: 0.6875rem;\n line-height: 1rem;\n margin-top: 0;\n margin-bottom: 0;\n padding-left: 0;\n list-style-type: none;\n li {\n word-break: break-word;\n padding: 0.5rem 0;\n margin: 0;\n }\n }\n }\n }\n}\n.custom_widget_MicrosoftFooter_c-uhff-base_105bp_94 {\n background: #f2f2f2;\n margin: 0 auto;\n max-width: calc(100rem + 10%);\n padding: 1.875rem 5% 1rem;\n &:before,\n &:after {\n content: ' ';\n display: table;\n }\n &:after {\n clear: both;\n }\n a.custom_widget_MicrosoftFooter_c-uhff-ccpa_105bp_107 {\n font-size: 0.6875rem;\n line-height: 1rem;\n float: left;\n margin: 0.1875rem 0;\n }\n a.custom_widget_MicrosoftFooter_c-uhff-ccpa_105bp_107:hover {\n text-decoration: underline;\n }\n ul.custom_widget_MicrosoftFooter_c-list_105bp_78 {\n font-size: 0.6875rem;\n line-height: 1rem;\n float: right;\n margin: 0.1875rem 0;\n color: #616161;\n li {\n padding: 0 1.5rem 0.25rem 0;\n display: inline-block;\n }\n }\n .custom_widget_MicrosoftFooter_c-list_105bp_78.custom_widget_MicrosoftFooter_f-bare_105bp_78 {\n padding-left: 0;\n list-style-type: none;\n }\n @media only screen and (max-width: 1083px) {\n display: flex;\n flex-wrap: wrap;\n padding: 1.875rem 1.5rem 1rem;\n }\n}\n.custom_widget_MicrosoftFooter_social-share_105bp_138 {\n position: fixed;\n top: 60%;\n transform: translateY(-50%);\n left: 0;\n z-index: 1000;\n}\n.custom_widget_MicrosoftFooter_sharing-options_105bp_146 {\n list-style: none;\n padding: 0;\n margin: 0;\n display: block;\n flex-direction: column;\n background-color: white;\n width: 2.6875rem;\n border-radius: 0 0.4375rem 0.4375rem 0;\n}\n.custom_widget_MicrosoftFooter_linkedin-icon_105bp_156 {\n border-top-right-radius: 7px;\n}\n.custom_widget_MicrosoftFooter_linkedin-icon_105bp_156:hover {\n border-radius: 0;\n}\n.custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162 {\n border-bottom-right-radius: 7px;\n}\n.custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162:hover {\n border-radius: 0;\n}\n.custom_widget_MicrosoftFooter_social-link-footer_105bp_169 {\n position: relative;\n display: block;\n margin: -0.125rem 0;\n transition: all 0.2s ease;\n}\n.custom_widget_MicrosoftFooter_social-link-footer_105bp_169:hover .custom_widget_MicrosoftFooter_linkedin-icon_105bp_156 {\n border-radius: 0;\n}\n.custom_widget_MicrosoftFooter_social-link-footer_105bp_169:hover .custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162 {\n border-radius: 0;\n}\n.custom_widget_MicrosoftFooter_social-link-footer_105bp_169 img {\n width: 2.5rem;\n height: auto;\n transition: filter 0.3s ease;\n}\n.custom_widget_MicrosoftFooter_social-share-list_105bp_188 {\n width: 2.5rem;\n}\n.custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162 {\n width: 2.5rem;\n}\n.custom_widget_MicrosoftFooter_share-icon_105bp_195 {\n border: 2px solid transparent;\n display: inline-block;\n position: relative;\n}\n.custom_widget_MicrosoftFooter_share-icon_105bp_195:hover {\n opacity: 1;\n border: 2px solid white;\n box-sizing: border-box;\n}\n.custom_widget_MicrosoftFooter_share-icon_105bp_195:hover .custom_widget_MicrosoftFooter_label_105bp_207 {\n opacity: 1;\n visibility: visible;\n border: 2px solid white;\n box-sizing: border-box;\n border-left: none;\n}\n.custom_widget_MicrosoftFooter_label_105bp_207 {\n position: absolute;\n left: 100%;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n transition: all 0.2s ease;\n color: white;\n border-radius: 0 10 0 0.625rem;\n top: 50%;\n transform: translateY(-50%);\n height: 2.5rem;\n border-radius: 0 0.375rem 0.375rem 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1.25rem 0.3125rem 1.25rem 0.5rem;\n margin-left: -0.0625rem;\n}\n.custom_widget_MicrosoftFooter_linkedin_105bp_156 {\n background-color: #0474b4;\n}\n.custom_widget_MicrosoftFooter_facebook_105bp_237 {\n background-color: #3c5c9c;\n}\n.custom_widget_MicrosoftFooter_twitter_105bp_240 {\n background-color: white;\n color: black;\n}\n.custom_widget_MicrosoftFooter_reddit_105bp_244 {\n background-color: #fc4404;\n}\n.custom_widget_MicrosoftFooter_mail_105bp_247 {\n background-color: #848484;\n}\n.custom_widget_MicrosoftFooter_bluesky_105bp_250 {\n background-color: white;\n color: black;\n}\n.custom_widget_MicrosoftFooter_rss_105bp_254 {\n background-color: #ec7b1c;\n}\n#custom_widget_MicrosoftFooter_RSS_105bp_1 {\n width: 2.5rem;\n height: 2.5rem;\n}\n@media (max-width: 991px) {\n .custom_widget_MicrosoftFooter_social-share_105bp_138 {\n display: none;\n }\n}\n","tokens":{"context-uhf":"custom_widget_MicrosoftFooter_context-uhf_105bp_1","c-uhff-link":"custom_widget_MicrosoftFooter_c-uhff-link_105bp_12","c-uhff":"custom_widget_MicrosoftFooter_c-uhff_105bp_12","c-uhff-nav":"custom_widget_MicrosoftFooter_c-uhff-nav_105bp_35","c-heading-4":"custom_widget_MicrosoftFooter_c-heading-4_105bp_49","c-uhff-nav-row":"custom_widget_MicrosoftFooter_c-uhff-nav-row_105bp_57","c-uhff-nav-group":"custom_widget_MicrosoftFooter_c-uhff-nav-group_105bp_58","c-list":"custom_widget_MicrosoftFooter_c-list_105bp_78","f-bare":"custom_widget_MicrosoftFooter_f-bare_105bp_78","c-uhff-base":"custom_widget_MicrosoftFooter_c-uhff-base_105bp_94","c-uhff-ccpa":"custom_widget_MicrosoftFooter_c-uhff-ccpa_105bp_107","social-share":"custom_widget_MicrosoftFooter_social-share_105bp_138","sharing-options":"custom_widget_MicrosoftFooter_sharing-options_105bp_146","linkedin-icon":"custom_widget_MicrosoftFooter_linkedin-icon_105bp_156","social-share-rss-image":"custom_widget_MicrosoftFooter_social-share-rss-image_105bp_162","social-link-footer":"custom_widget_MicrosoftFooter_social-link-footer_105bp_169","social-share-list":"custom_widget_MicrosoftFooter_social-share-list_105bp_188","share-icon":"custom_widget_MicrosoftFooter_share-icon_105bp_195","label":"custom_widget_MicrosoftFooter_label_105bp_207","linkedin":"custom_widget_MicrosoftFooter_linkedin_105bp_156","facebook":"custom_widget_MicrosoftFooter_facebook_105bp_237","twitter":"custom_widget_MicrosoftFooter_twitter_105bp_240","reddit":"custom_widget_MicrosoftFooter_reddit_105bp_244","mail":"custom_widget_MicrosoftFooter_mail_105bp_247","bluesky":"custom_widget_MicrosoftFooter_bluesky_105bp_250","rss":"custom_widget_MicrosoftFooter_rss_105bp_254","RSS":"custom_widget_MicrosoftFooter_RSS_105bp_1"}},"form":null},"localOverride":false},"CachedAsset:text:en_US-components/community/Breadcrumb-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/community/Breadcrumb-1745505307000","value":{"navLabel":"Breadcrumbs","dropdown":"Additional parent page navigation"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBanner-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBanner-1745505307000","value":{"messageMarkedAsSpam":"This post has been marked as spam","messageMarkedAsSpam@board:TKB":"This article has been marked as spam","messageMarkedAsSpam@board:BLOG":"This post has been marked as spam","messageMarkedAsSpam@board:FORUM":"This discussion has been marked as spam","messageMarkedAsSpam@board:OCCASION":"This event has been marked as spam","messageMarkedAsSpam@board:IDEA":"This idea has been marked as spam","manageSpam":"Manage Spam","messageMarkedAsAbuse":"This post has been marked as abuse","messageMarkedAsAbuse@board:TKB":"This article has been marked as abuse","messageMarkedAsAbuse@board:BLOG":"This post has been marked as abuse","messageMarkedAsAbuse@board:FORUM":"This discussion has been marked as abuse","messageMarkedAsAbuse@board:OCCASION":"This event has been marked as abuse","messageMarkedAsAbuse@board:IDEA":"This idea has been marked as abuse","preModCommentAuthorText":"This comment will be published as soon as it is approved","preModCommentModeratorText":"This comment is awaiting moderation","messageMarkedAsOther":"This post has been rejected due to other reasons","messageMarkedAsOther@board:TKB":"This article has been rejected due to other reasons","messageMarkedAsOther@board:BLOG":"This post has been rejected due to other reasons","messageMarkedAsOther@board:FORUM":"This discussion has been rejected due to other reasons","messageMarkedAsOther@board:OCCASION":"This event has been rejected due to other reasons","messageMarkedAsOther@board:IDEA":"This idea has been rejected due to other reasons","messageArchived":"This post was archived on {date}","relatedUrl":"View Related Content","relatedContentText":"Showing related content","archivedContentLink":"View Archived Content"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageView/MessageViewStandard-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageView/MessageViewStandard-1745505307000","value":{"anonymous":"Anonymous","author":"{messageAuthorLogin}","authorBy":"{messageAuthorLogin}","board":"{messageBoardTitle}","replyToUser":" to {parentAuthor}","showMoreReplies":"Show More","replyText":"Reply","repliesText":"Replies","markedAsSolved":"Marked as Solution","movedMessagePlaceholder.BLOG":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.TKB":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.FORUM":"{count, plural, =0 {This reply has been} other {These replies have been} }","movedMessagePlaceholder.IDEA":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.OCCASION":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholderUrlText":"moved.","messageStatus":"Status: ","statusChanged":"Status changed: {previousStatus} to {currentStatus}","statusAdded":"Status added: {status}","statusRemoved":"Status removed: {status}","labelExpand":"expand replies","labelCollapse":"collapse replies","unhelpfulReason.reason1":"Content is outdated","unhelpfulReason.reason2":"Article is missing information","unhelpfulReason.reason3":"Content is for a different Product","unhelpfulReason.reason4":"Doesn't match what I was searching for"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageDeepLink-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageDeepLink-1745505307000","value":{"viewFullDiscussion@board:FORUM":"View Full Discussion ({repliesCount} Replies)","viewFullDiscussion@board:BLOG":"View Full Discussion ({repliesCount} Comments)","viewFullDiscussion@board:TKB":"View Full Discussion ({repliesCount} Comments)","viewFullDiscussion@board:IDEA":"View Full Discussion ({repliesCount} Comments)","viewFullDiscussion@board:OCCASION":"View Full Discussion ({repliesCount} Comments)","showParents@board:FORUM":"Show Parent Replies","showParents@board:BLOG":"Show Parent Comments","showParents@board:TKB":"Show Parent Comments","showParents@board:IDEA":"Show Parent Comments","showParents@board:OCCASION":"Show Parent Comments"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageReplyCallToAction-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageReplyCallToAction-1745505307000","value":{"leaveReply":"Leave a reply...","leaveReply@board:BLOG@message:root":"Leave a comment...","leaveReply@board:TKB@message:root":"Leave a comment...","leaveReply@board:IDEA@message:root":"Leave a comment...","leaveReply@board:OCCASION@message:root":"Leave a comment...","repliesTurnedOff.FORUM":"Replies are turned off for this topic","repliesTurnedOff.BLOG":"Comments are turned off for this topic","repliesTurnedOff.TKB":"Comments are turned off for this topic","repliesTurnedOff.IDEA":"Comments are turned off for this topic","repliesTurnedOff.OCCASION":"Comments are turned off for this topic","infoText":"Stop poking me!"},"localOverride":false},"Category:category:Exchange":{"__typename":"Category","id":"category:Exchange","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Outlook":{"__typename":"Category","id":"category:Outlook","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Community-Info-Center":{"__typename":"Category","id":"category:Community-Info-Center","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:EducationSector":{"__typename":"Category","id":"category:EducationSector","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:DrivingAdoption":{"__typename":"Category","id":"category:DrivingAdoption","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Azure":{"__typename":"Category","id":"category:Azure","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Windows-Server":{"__typename":"Category","id":"category:Windows-Server","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:MicrosoftTeams":{"__typename":"Category","id":"category:MicrosoftTeams","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:PublicSector":{"__typename":"Category","id":"category:PublicSector","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:microsoft365":{"__typename":"Category","id":"category:microsoft365","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:IoT":{"__typename":"Category","id":"category:IoT","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:HealthcareAndLifeSciences":{"__typename":"Category","id":"category:HealthcareAndLifeSciences","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:ITOpsTalk":{"__typename":"Category","id":"category:ITOpsTalk","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:MicrosoftLearn":{"__typename":"Category","id":"category:MicrosoftLearn","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Blog:board:MicrosoftLearnBlog":{"__typename":"Blog","id":"board:MicrosoftLearnBlog","blogPolicies":{"__typename":"BlogPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:AI":{"__typename":"Category","id":"category:AI","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:MicrosoftMechanics":{"__typename":"Category","id":"category:MicrosoftMechanics","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:MicrosoftforNonprofits":{"__typename":"Category","id":"category:MicrosoftforNonprofits","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:StartupsatMicrosoft":{"__typename":"Category","id":"category:StartupsatMicrosoft","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:PartnerCommunity":{"__typename":"Category","id":"category:PartnerCommunity","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Microsoft365Copilot":{"__typename":"Category","id":"category:Microsoft365Copilot","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Windows":{"__typename":"Category","id":"category:Windows","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Content_Management":{"__typename":"Category","id":"category:Content_Management","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:microsoft-security":{"__typename":"Category","id":"category:microsoft-security","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:microsoftintune":{"__typename":"Category","id":"category:microsoftintune","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"CachedAsset:text:en_US-components/community/Navbar-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/community/Navbar-1745505307000","value":{"community":"Community Home","inbox":"Inbox","manageContent":"Manage Content","tos":"Terms of Service","forgotPassword":"Forgot Password","themeEditor":"Theme Editor","edit":"Edit Navigation Bar","skipContent":"Skip to content","gxcuf89792":"Tech Community","external-1":"Events","s-m-b":"Nonprofit Community","windows-server":"Windows Server","education-sector":"Education Sector","driving-adoption":"Driving Adoption","Common-content_management-link":"Content Management","microsoft-learn":"Microsoft Learn","s-q-l-server":"Content Management","partner-community":"Microsoft Partner Community","microsoft365":"Microsoft 365","external-9":".NET","external-8":"Teams","external-7":"Github","products-services":"Products","external-6":"Power Platform","communities-1":"Topics","external-5":"Microsoft Security","planner":"Outlook","external-4":"Microsoft 365","external-3":"Dynamics 365","azure":"Azure","healthcare-and-life-sciences":"Healthcare and Life Sciences","external-2":"Azure","microsoft-mechanics":"Microsoft Mechanics","microsoft-learn-1":"Community","external-10":"Learning Room Directory","microsoft-learn-blog":"Blog","windows":"Windows","i-t-ops-talk":"ITOps Talk","external-link-1":"View All","microsoft-securityand-compliance":"Microsoft Security","public-sector":"Public Sector","community-info-center":"Lounge","external-link-2":"View All","microsoft-teams":"Microsoft Teams","external":"Blogs","microsoft-endpoint-manager":"Microsoft Intune","startupsat-microsoft":"Startups at Microsoft","exchange":"Exchange","a-i":"AI and Machine Learning","io-t":"Internet of Things (IoT)","Common-microsoft365-copilot-link":"Microsoft 365 Copilot","outlook":"Microsoft 365 Copilot","external-link":"Community Hubs","communities":"Products"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarHamburgerDropdown-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarHamburgerDropdown-1745505307000","value":{"hamburgerLabel":"Side Menu"},"localOverride":false},"CachedAsset:text:en_US-components/community/BrandLogo-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/community/BrandLogo-1745505307000","value":{"logoAlt":"Khoros","themeLogoAlt":"Brand Logo"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarTextLinks-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarTextLinks-1745505307000","value":{"more":"More"},"localOverride":false},"CachedAsset:text:en_US-components/authentication/AuthenticationLink-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/authentication/AuthenticationLink-1745505307000","value":{"title.login":"Sign In","title.registration":"Register","title.forgotPassword":"Forgot Password","title.multiAuthLogin":"Sign In"},"localOverride":false},"CachedAsset:text:en_US-components/nodes/NodeLink-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/nodes/NodeLink-1745505307000","value":{"place":"Place {name}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageCoverImage-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageCoverImage-1745505307000","value":{"coverImageTitle":"Cover Image"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeTitle-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeTitle-1745505307000","value":{"nodeTitle":"{nodeTitle, select, community {Community} other {{nodeTitle}}} "},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageTimeToRead-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageTimeToRead-1745505307000","value":{"minReadText":"{min} MIN READ"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageSubject-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageSubject-1745505307000","value":{"noSubject":"(no subject)"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserLink-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserLink-1745505307000","value":{"authorName":"View Profile: {author}","anonymous":"Anonymous"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserRank-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserRank-1745505307000","value":{"rankName":"{rankName}","userRank":"Author rank {rankName}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageTime-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageTime-1745505307000","value":{"postTime":"Published: {time}","lastPublishTime":"Last Update: {time}","conversation.lastPostingActivityTime":"Last posting activity time: {time}","conversation.lastPostTime":"Last post time: {time}","moderationData.rejectTime":"Rejected time: {time}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBody-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBody-1745505307000","value":{"showMessageBody":"Show More","mentionsErrorTitle":"{mentionsType, select, board {Board} user {User} message {Message} other {}} No Longer Available","mentionsErrorMessage":"The {mentionsType} you are trying to view has been removed from the community.","videoProcessing":"Video is being processed. Please try again in a few minutes.","bannerTitle":"Video provider requires cookies to play the video. Accept to continue or {url} it directly on the provider's site.","buttonTitle":"Accept","urlText":"watch"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageCustomFields-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageCustomFields-1745505307000","value":{"CustomField.default.label":"Value of {name}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageRevision-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageRevision-1745505307000","value":{"lastUpdatedDatePublished":"{publishCount, plural, one{Published} other{Updated}} {date}","lastUpdatedDateDraft":"Created {date}","version":"Version {major}.{minor}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/QueryHandler-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/QueryHandler-1745505307000","value":{"title":"Query Handler"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageReplyButton-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageReplyButton-1745505307000","value":{"repliesCount":"{count}","title":"Reply","title@board:BLOG@message:root":"Comment","title@board:TKB@message:root":"Comment","title@board:IDEA@message:root":"Comment","title@board:OCCASION@message:root":"Comment"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageAuthorBio-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageAuthorBio-1745505307000","value":{"sendMessage":"Send Message","actionMessage":"Follow this blog board to get notified when there's new activity","coAuthor":"CO-PUBLISHER","contributor":"CONTRIBUTOR","userProfile":"View Profile","iconlink":"Go to {name} {type}"},"localOverride":false},"ModerationData:moderation_data:3787135":{"__typename":"ModerationData","id":"moderation_data:3787135","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"Revision:revision:3787135_1":{"__typename":"Revision","id":"revision:3787135_1","lastEditTime":"2023-04-04T06:57:57.602-07:00"},"QueryVariables:ReplyList:message:3787135:1":{"__typename":"QueryVariables","id":"ReplyList:message:3787135:1","value":{"id":"message:3787135","first":10,"sorts":{"postTime":{"direction":"DESC"}},"repliesFirst":3,"repliesFirstDepthThree":1,"repliesSorts":{"postTime":{"direction":"DESC"}},"useAvatar":true,"useAuthorLogin":true,"useAuthorRank":true,"useBody":true,"useKudosCount":true,"useTimeToRead":false,"useMedia":false,"useReadOnlyIcon":false,"useRepliesCount":true,"useSearchSnippet":false,"useAcceptedSolutionButton":false,"useSolvedBadge":false,"useAttachments":false,"attachmentsFirst":5,"useTags":false,"useNodeAncestors":false,"useUserHoverCard":false,"useNodeHoverCard":false,"useModerationStatus":true,"usePreviewSubjectModal":false,"useMessageStatus":true}},"ROOT_MUTATION":{"__typename":"Mutation"},"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarDropdownToggle-1745505307000","value":{"ariaLabelClosed":"Press the down arrow to open the menu"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserAvatar-1745505307000","value":{"altText":"{login}'s avatar","altTextGeneric":"User's avatar"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/ranks/UserRankLabel-1745505307000","value":{"altTitle":"Icon for {rankName} rank"},"localOverride":false},"CachedAsset:text:en_US-components/tags/TagView/TagViewChip-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/tags/TagView/TagViewChip-1745505307000","value":{"tagLabelName":"Tag name {tagName}"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserRegistrationDate-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserRegistrationDate-1745505307000","value":{"noPrefix":"{date}","withPrefix":"Joined {date}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeAvatar-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeAvatar-1745505307000","value":{"altTitle":"Node avatar for {nodeTitle}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeDescription-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeDescription-1745505307000","value":{"description":"{description}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/ThreadedReplyList-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/ThreadedReplyList-1745505307000","value":{"title":"{count, plural, one{# Reply} other{# Replies}}","title@board:BLOG":"{count, plural, one{# Comment} other{# Comments}}","title@board:TKB":"{count, plural, one{# Comment} other{# Comments}}","title@board:IDEA":"{count, plural, one{# Comment} other{# Comments}}","title@board:OCCASION":"{count, plural, one{# Comment} other{# Comments}}","noRepliesTitle":"No Replies","noRepliesTitle@board:BLOG":"No Comments","noRepliesTitle@board:TKB":"No Comments","noRepliesTitle@board:IDEA":"No Comments","noRepliesTitle@board:OCCASION":"No Comments","noRepliesDescription":"Be the first to reply","noRepliesDescription@board:BLOG":"Be the first to comment","noRepliesDescription@board:TKB":"Be the first to comment","noRepliesDescription@board:IDEA":"Be the first to comment","noRepliesDescription@board:OCCASION":"Be the first to comment","messageReadOnlyAlert:BLOG":"Comments have been turned off for this post","messageReadOnlyAlert:TKB":"Comments have been turned off for this article","messageReadOnlyAlert:IDEA":"Comments have been turned off for this idea","messageReadOnlyAlert:FORUM":"Replies have been turned off for this discussion","messageReadOnlyAlert:OCCASION":"Comments have been turned off for this event"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeIcon-1745505307000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeIcon-1745505307000","value":{"contentType":"Content Type {style, select, FORUM {Forum} BLOG {Blog} TKB {Knowledge Base} IDEA {Ideas} OCCASION {Events} other {}} icon"},"localOverride":false}}}},"page":"/blogs/BlogMessagePage/BlogMessagePage","query":{"boardId":"fasttrackforazureblog","messageSubject":"how-to-expose-nginx-ingress-controller-via-azure-front-door-and-azure-private-li","messageId":"3767535","replyId":"3787135"},"buildId":"-gVUpXaWnPcjlrLJZ92B7","runtimeConfig":{"buildInformationVisible":false,"logLevelApp":"info","logLevelMetrics":"info","openTelemetryClientEnabled":false,"openTelemetryConfigName":"o365","openTelemetryServiceVersion":"25.3.0","openTelemetryUniverse":"prod","openTelemetryCollector":"http://localhost:4318","openTelemetryRouteChangeAllowedTime":"5000","apolloDevToolsEnabled":false,"inboxMuteWipFeatureEnabled":false},"isFallback":false,"isExperimentalCompile":false,"dynamicIds":["./components/community/Navbar/NavbarWidget.tsx","./components/community/Breadcrumb/BreadcrumbWidget.tsx","./components/customComponent/CustomComponent/CustomComponent.tsx","./components/blogs/BlogArticleWidget/BlogArticleWidget.tsx","./components/messages/MessageView/MessageViewStandard/MessageViewStandard.tsx","./components/external/components/ExternalComponent.tsx","../shared/client/components/common/List/UnwrappedList/UnwrappedList.tsx","./components/tags/TagView/TagView.tsx","./components/tags/TagView/TagViewChip/TagViewChip.tsx","./components/customComponent/CustomComponentContent/TemplateContent.tsx"],"appGip":true,"scriptLoader":[{"id":"analytics","src":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/pagescripts/1730819800000/analytics.js?page.id=BlogReplyPage&entity.id=board%3Afasttrackforazureblog&entity.id=message%3A3787135","strategy":"afterInteractive"}]}