"}},"componentScriptGroups({\"componentId\":\"custom.widget.Beta_Footer\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"component({\"componentId\":\"custom.widget.Tag_Manager_Helper\"})":{"__typename":"Component","render({\"context\":{\"component\":{\"entities\":[],\"props\":{}},\"page\":{\"entities\":[],\"name\":\"TagPage\",\"props\":{},\"url\":\"https://community.f5.com/tag/Ansible\"}}})":{"__typename":"ComponentRenderResult","html":" "}},"componentScriptGroups({\"componentId\":\"custom.widget.Tag_Manager_Helper\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"component({\"componentId\":\"custom.widget.Consent_Blackbar\"})":{"__typename":"Component","render({\"context\":{\"component\":{\"entities\":[],\"props\":{}},\"page\":{\"entities\":[],\"name\":\"TagPage\",\"props\":{},\"url\":\"https://community.f5.com/tag/Ansible\"}}})":{"__typename":"ComponentRenderResult","html":""}},"componentScriptGroups({\"componentId\":\"custom.widget.Consent_Blackbar\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/community/NavbarDropdownToggle\"]})":[{"__ref":"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageListTabs\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageListTabs-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageView/MessageViewInline\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageView/MessageViewInline-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/common/Pager/PagerLoadMore\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/common/Pager/PagerLoadMore-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/customComponent/CustomComponent\"]})":[{"__ref":"CachedAsset:text:en_US-components/customComponent/CustomComponent-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/common/OverflowNav\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/common/OverflowNav-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/users/UserLink\"]})":[{"__ref":"CachedAsset:text:en_US-components/users/UserLink-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageSubject\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageSubject-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageBody\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageBody-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageTime\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageTime-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeIcon\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeIcon-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageUnreadCount\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageUnreadCount-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageViewCount\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageViewCount-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/kudos/KudosCount\"]})":[{"__ref":"CachedAsset:text:en_US-components/kudos/KudosCount-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageRepliesCount\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageRepliesCount-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/users/UserAvatar\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1744046271000"}]},"Theme:customTheme1":{"__typename":"Theme","id":"customTheme1"},"User:user:-1":{"__typename":"User","id":"user:-1","uid":-1,"login":"Former Member","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":"dd-MMM-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-1744706921165":{"__typename":"CachedAsset","id":"pages-1744706921165","value":[{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetInvolved.MvpProgram","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved/mvp-program","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"BlogViewAllPostsPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId/all-posts/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"CasePortalPage","type":"CASE_PORTAL","urlPath":"/caseportal","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"CreateGroupHubPage","type":"GROUP_HUB","urlPath":"/groups/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"CaseViewPage","type":"CASE_DETAILS","urlPath":"/case/:caseId/:caseNumber","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"InboxPage","type":"COMMUNITY","urlPath":"/inbox","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetInvolved.AdvocacyProgram","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved/advocacy-program","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetHelp.NonCustomer","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/non-customer","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HelpFAQPage","type":"COMMUNITY","urlPath":"/help","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetHelp.F5Customer","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/f5-customer","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"IdeaMessagePage","type":"IDEA_POST","urlPath":"/idea/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"IdeaViewAllIdeasPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/all-ideas/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"LoginPage","type":"USER","urlPath":"/signin","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"BlogPostPage","type":"BLOG","urlPath":"/category/:categoryId/blogs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetInvolved","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.Learn","type":"COMMUNITY","urlPath":"/c/how-do-i/learn","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1739501996000,"localOverride":null,"page":{"id":"Test","type":"CUSTOM","urlPath":"/custom-test-2","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ThemeEditorPage","type":"COMMUNITY","urlPath":"/designer/themes","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TkbViewAllArticlesPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId/all-articles/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"OccasionEditPage","type":"EVENT","urlPath":"/event/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"OAuthAuthorizationAllowPage","type":"USER","urlPath":"/auth/authorize/allow","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"PageEditorPage","type":"COMMUNITY","urlPath":"/designer/pages","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"PostPage","type":"COMMUNITY","urlPath":"/category/:categoryId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ForumBoardPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TkbBoardPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"EventPostPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"UserBadgesPage","type":"COMMUNITY","urlPath":"/users/:login/:userId/badges","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"GroupHubMembershipAction","type":"GROUP_HUB","urlPath":"/membership/join/:nodeId/:membershipType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"MaintenancePage","type":"COMMUNITY","urlPath":"/maintenance","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"IdeaReplyPage","type":"IDEA_REPLY","urlPath":"/idea/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"UserSettingsPage","type":"USER","urlPath":"/mysettings/:userSettingsTab","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"GroupHubsPage","type":"GROUP_HUB","urlPath":"/groups","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ForumPostPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"OccasionRsvpActionPage","type":"OCCASION","urlPath":"/event/:boardId/:messageSubject/:messageId/rsvp/:responseType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"VerifyUserEmailPage","type":"USER","urlPath":"/verifyemail/:userId/:verifyEmailToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"AllOccasionsPage","type":"OCCASION","urlPath":"/category/:categoryId/events/:boardId/all-events/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"EventBoardPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TkbReplyPage","type":"TKB_REPLY","urlPath":"/kb/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"IdeaBoardPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"CommunityGuideLinesPage","type":"COMMUNITY","urlPath":"/communityguidelines","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"CaseCreatePage","type":"SALESFORCE_CASE_CREATION","urlPath":"/caseportal/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TkbEditPage","type":"TKB","urlPath":"/kb/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ForgotPasswordPage","type":"USER","urlPath":"/forgotpassword","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"IdeaEditPage","type":"IDEA","urlPath":"/idea/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TagPage","type":"COMMUNITY","urlPath":"/tag/:tagName","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"BlogBoardPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"OccasionMessagePage","type":"OCCASION_TOPIC","urlPath":"/event/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ManageContentPage","type":"COMMUNITY","urlPath":"/managecontent","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ClosedMembershipNodeNonMembersPage","type":"GROUP_HUB","urlPath":"/closedgroup/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetHelp.Community","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/community","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"CommunityPage","type":"COMMUNITY","urlPath":"/","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetInvolved.ContributeCode","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved/contribute-code","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ForumMessagePage","type":"FORUM_TOPIC","urlPath":"/discussions/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"IdeaPostPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"BlogMessagePage","type":"BLOG_ARTICLE","urlPath":"/blog/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"RegistrationPage","type":"USER","urlPath":"/register","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"EditGroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ForumEditPage","type":"FORUM","urlPath":"/discussions/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ResetPasswordPage","type":"USER","urlPath":"/resetpassword/:userId/:resetPasswordToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TkbMessagePage","type":"TKB_ARTICLE","urlPath":"/kb/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.Learn.AboutIrules","type":"COMMUNITY","urlPath":"/c/how-do-i/learn/about-irules","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"BlogEditPage","type":"BLOG","urlPath":"/blog/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetHelp.F5Support","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/f5-support","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ManageUsersPage","type":"USER","urlPath":"/users/manage/:tab?/:manageUsersTab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ForumReplyPage","type":"FORUM_REPLY","urlPath":"/discussions/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"PrivacyPolicyPage","type":"COMMUNITY","urlPath":"/privacypolicy","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"NotificationPage","type":"COMMUNITY","urlPath":"/notifications","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"UserPage","type":"USER","urlPath":"/users/:login/:userId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HealthCheckPage","type":"COMMUNITY","urlPath":"/health","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"OccasionReplyPage","type":"OCCASION_REPLY","urlPath":"/event/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ManageMembersPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/manage/:tab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"SearchResultsPage","type":"COMMUNITY","urlPath":"/search","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"BlogReplyPage","type":"BLOG_REPLY","urlPath":"/blog/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"GroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TermsOfServicePage","type":"COMMUNITY","urlPath":"/termsofservice","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetHelp","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetHelp.SecurityIncident","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/security-incident","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"CategoryPage","type":"CATEGORY","urlPath":"/category/:categoryId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ForumViewAllTopicsPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/all-topics/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TkbPostPage","type":"TKB","urlPath":"/category/:categoryId/kbs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"GroupHubPostPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI","type":"COMMUNITY","urlPath":"/c/how-do-i","__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},"CachedAsset:theme:customTheme1-1744706920729":{"__typename":"CachedAsset","id":"theme:customTheme1-1744706920729","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":["custom"],"__typename":"AvatarThemeSettings"},"basics":{"browserIcon":{"imageAssetName":"JimmyPackets-512-1702592938213.png","imageLastModified":"1702592945815","__typename":"ThemeAsset"},"customerLogo":{"imageAssetName":"f5_logo_fix-1704824537976.svg","imageLastModified":"1704824540697","__typename":"ThemeAsset"},"maximumWidthOfPageContent":"1600px","oneColumnNarrowWidth":"800px","gridGutterWidthMd":"30px","gridGutterWidthXs":"10px","pageWidthStyle":"WIDTH_OF_PAGE_CONTENT","__typename":"BasicsThemeSettings"},"buttons":{"borderRadiusSm":"5px","borderRadius":"5px","borderRadiusLg":"5px","paddingY":"5px","paddingYLg":"7px","paddingYHero":"var(--lia-bs-btn-padding-y-lg)","paddingX":"12px","paddingXLg":"14px","paddingXHero":"42px","fontStyle":"NORMAL","fontWeight":"400","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-400)","secondaryBgHoverColor":"hsl(var(--lia-bs-gray-400-h), var(--lia-bs-gray-400-s), calc(var(--lia-bs-gray-400-l) * 0.96))","secondaryBgActiveColor":"hsl(var(--lia-bs-gray-400-h), var(--lia-bs-gray-400-s), calc(var(--lia-bs-gray-400-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-300)","destructiveBgHoverColor":"hsl(var(--lia-bs-gray-300-h), var(--lia-bs-gray-300-s), calc(var(--lia-bs-gray-300-l) * 0.96))","destructiveBgActiveColor":"hsl(var(--lia-bs-gray-300-h), var(--lia-bs-gray-300-s), calc(var(--lia-bs-gray-300-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":"NONE","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.08)","sm":"0 2px 4px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.06)","md":"0 5px 15px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.15)","lg":"0 10px 30px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.15)","__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-primary)","defaultMessageLinkDecoration":"none","defaultMessageLinkFontStyle":"NORMAL","defaultMessageLinkFontWeight":"400","defaultMessageFontStyle":"NORMAL","defaultMessageFontWeight":"400","defaultMessageFontFamily":"var(--lia-bs-font-family-base)","forumColor":"#0C5C8D","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":"#62C026","blogColor":"#730015","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":"#C20025","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":"#F3704B","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":"#EE4B5B","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":"#491B62","categoryColor":"#949494","communityColor":"#FFFFFF","productColor":"#949494","__typename":"CoreTypesThemeSettings"},"colors":{"black":"#000000","white":"#FFFFFF","gray100":"#F7F7F7","gray200":"#F7F7F7","gray300":"#E8E8E8","gray400":"#D9D9D9","gray500":"#CCCCCC","gray600":"#949494","gray700":"#707070","gray800":"#545454","gray900":"#333333","dark":"#545454","light":"#F7F7F7","primary":"#0C5C8D","secondary":"#333333","bodyText":"#222222","bodyBg":"#F5F5F5","info":"#1D9CD3","success":"#62C026","warning":"#FFD651","danger":"#C20025","alertSystem":"#FF6600","textMuted":"#707070","highlight":"#FFFCAD","outline":"var(--lia-bs-primary)","custom":["#C20025","#081B85","#009639","#B3C6D7","#7CC0EB","#F29A36"],"__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":"Inter","fontStyle":"NORMAL","fontWeight":"600","h1FontSize":"30px","h2FontSize":"25px","h3FontSize":"20px","h4FontSize":"18px","h5FontSize":"16px","h6FontSize":"16px","lineHeight":"1.2","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":null,"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":"14px","defaultMessageHeaderMarginBottom":"10px","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":"14px","specialMessageHeaderMarginBottom":"10px","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":"Atkinson Hyperlegible","fontStyleBase":"NORMAL","fontWeightBase":"400","fontWeightLight":"300","fontWeightNormal":"400","fontWeightMd":"500","fontWeightBold":"700","letterSpacingSm":"normal","letterSpacingXs":"normal","lineHeightBase":"1.3","fontSizeBase":"15px","fontSizeXxs":"11px","fontSizeXs":"12px","fontSizeSm":"13px","fontSizeLg":"20px","fontSizeXl":"24px","smallFontSize":"14px","customFonts":[],"__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-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-1744046271000","value":{"title":"Loading..."},"localOverride":false},"CachedAsset:text:en_US-components/common/EmailVerification-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/common/EmailVerification-1744046271000","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/tags/TagPage-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-pages/tags/TagPage-1744046271000","value":{"tagPageTitle":"Tag:\"{tagName}\" | {communityTitle}","tagPageForNodeTitle":"Tag:\"{tagName}\" in \"{title}\" | {communityTitle}","name":"Tags Page","tag":"Tag: {tagName}"},"localOverride":false},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bi0zNC0xM2k0MzE3N0Q2NjFBRDg5NDAy\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bi0zNC0xM2k0MzE3N0Q2NjFBRDg5NDAy","mimeType":"image/png"},"Category:category:Articles":{"__typename":"Category","id":"category:Articles","entityType":"CATEGORY","displayId":"Articles","nodeType":"category","depth":1,"title":"Articles","shortTitle":"Articles","parent":{"__ref":"Category:category:top"},"categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:top":{"__typename":"Category","id":"category:top","displayId":"top","nodeType":"category","depth":0,"title":"Top"},"Tkb:board:TechnicalArticles":{"__typename":"Tkb","id":"board:TechnicalArticles","entityType":"TKB","displayId":"TechnicalArticles","nodeType":"board","depth":2,"conversationStyle":"TKB","title":"Technical Articles","description":"F5 SMEs share good practice.","avatar":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bi0zNC0xM2k0MzE3N0Q2NjFBRDg5NDAy\"}"},"profileSettings":{"__typename":"ProfileSettings","language":null},"parent":{"__ref":"Category:category:Articles"},"ancestors":{"__typename":"CoreNodeConnection","edges":[{"__typename":"CoreNodeEdge","node":{"__ref":"Community:community:zihoc95639"}},{"__typename":"CoreNodeEdge","node":{"__ref":"Category:category:Articles"}}]},"userContext":{"__typename":"NodeUserContext","canAddAttachments":false,"canUpdateNode":false,"canPostMessages":false,"isSubscribed":false},"boardPolicies":{"__typename":"BoardPolicies","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":[]}},"canReadNode":{"__typename":"PolicyResult","failureReason":null}},"theme":{"__ref":"Theme:customTheme1"},"tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"shortTitle":"Technical Articles","tagPolicies":{"__typename":"TagPolicies","canSubscribeTagOnNode":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.labels.action.corenode.subscribe_labels.allow.accessDenied","key":"error.lithium.policies.labels.action.corenode.subscribe_labels.allow.accessDenied","args":[]}},"canManageTagDashboard":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.labels.action.corenode.admin_labels.allow.accessDenied","key":"error.lithium.policies.labels.action.corenode.admin_labels.allow.accessDenied","args":[]}}}},"CachedAsset:quilt:f5.prod:pages/tags/TagPage:board:TechnicalArticles-1744706918897":{"__typename":"CachedAsset","id":"quilt:f5.prod:pages/tags/TagPage:board:TechnicalArticles-1744706918897","value":{"id":"TagPage","container":{"id":"Common","headerProps":{"removeComponents":["community.widget.bannerWidget"],"__typename":"QuiltContainerSectionProps"},"items":[{"id":"tag-header-widget","layout":"ONE_COLUMN","bgColor":"var(--lia-bs-white)","showBorder":"BOTTOM","sectionEditLevel":"LOCKED","columnMap":{"main":[{"id":"tags.widget.TagsHeaderWidget","__typename":"QuiltComponent"}],"__typename":"OneSectionColumns"},"__typename":"OneColumnQuiltSection"},{"id":"messages-list-for-tag-widget","layout":"ONE_COLUMN","columnMap":{"main":[{"id":"messages.widget.messageListForNodeByRecentActivityWidget","props":{"viewVariant":{"type":"inline","props":{"useUnreadCount":true,"useViewCount":true,"useAuthorLogin":true,"clampBodyLines":3,"useAvatar":true,"useBoardIcon":false,"useKudosCount":true,"usePreviewMedia":true,"useTags":false,"useNode":true,"useNodeLink":true,"useTextBody":true,"truncateBodyLength":-1,"useBody":true,"useRepliesCount":true,"useSolvedBadge":true,"timeStampType":"conversation.lastPostingActivityTime","useMessageTimeLink":true,"clampSubjectLines":2}},"panelType":"divider","useTitle":false,"hideIfEmpty":false,"pagerVariant":{"type":"loadMore"},"style":"list","showTabs":true,"tabItemMap":{"default":{"mostRecent":true,"mostRecentUserContent":false,"newest":false},"additional":{"mostKudoed":true,"mostViewed":true,"mostReplies":false,"noReplies":false,"noSolutions":false,"solutions":false}}},"__typename":"QuiltComponent"}],"__typename":"OneSectionColumns"},"__typename":"OneColumnQuiltSection"}],"__typename":"QuiltContainer"},"__typename":"Quilt"},"localOverride":false},"CachedAsset:quiltWrapper:f5.prod:Common:1744732624144":{"__typename":"CachedAsset","id":"quiltWrapper:f5.prod:Common:1744732624144","value":{"id":"Common","header":{"backgroundImageProps":{"assetName":"header.jpg","backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"LEFT_CENTER","lastModified":"1702932449000","__typename":"BackgroundImageProps"},"backgroundColor":"transparent","items":[{"id":"custom.widget.Beta_MetaNav","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"},{"id":"community.widget.navbarWidget","props":{"showUserName":false,"showRegisterLink":true,"style":{"boxShadow":"var(--lia-bs-box-shadow-sm)","linkFontWeight":"700","controllerHighlightColor":"hsla(30, 100%, 50%)","dropdownDividerMarginBottom":"10px","hamburgerBorderHover":"none","linkFontSize":"15px","linkBoxShadowHover":"none","backgroundOpacity":0.4,"controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerBgColor":"transparent","linkTextBorderBottom":"none","hamburgerColor":"var(--lia-nav-controller-icon-color)","brandLogoHeight":"48px","linkLetterSpacing":"normal","linkBgHoverColor":"transparent","collapseMenuDividerOpacity":0.16,"paddingBottom":"10px","dropdownPaddingBottom":"15px","dropdownMenuOffset":"2px","hamburgerBgHoverColor":"transparent","borderBottom":"0","hamburgerBorder":"none","dropdownPaddingX":"10px","brandMarginRightSm":"10px","linkBoxShadow":"none","linkJustifyContent":"center","linkColor":"var(--lia-bs-primary)","collapseMenuDividerBg":"var(--lia-nav-link-color)","dropdownPaddingTop":"10px","controllerHighlightTextColor":"var(--lia-yiq-dark)","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-primary)","position":"FIXED","linkBorder":"none","linkTextBorderBottomHover":"2px solid #0C5C8D","brandMarginRight":"30px","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","linkBorderHover":"none","collapseMenuMarginLeft":"20px","linkFontStyle":"NORMAL","linkPaddingX":"10px","paddingTop":"10px","linkPaddingY":"5px","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)","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkBgColor":"transparent","linkDropdownPaddingY":"9px","controllerIconColor":"#0C5C8D","dropdownDividerMarginTop":"10px","linkGap":"10px","controllerIconHoverColor":"#0C5C8D"},"links":{"sideLinks":[],"mainLinks":[{"children":[{"linkType":"INTERNAL","id":"migrated-link-1","params":{"boardId":"TechnicalForum","categoryId":"Forums"},"routeName":"ForumBoardPage"},{"linkType":"INTERNAL","id":"migrated-link-2","params":{"boardId":"WaterCooler","categoryId":"Forums"},"routeName":"ForumBoardPage"}],"linkType":"INTERNAL","id":"migrated-link-0","params":{"categoryId":"Forums"},"routeName":"CategoryPage"},{"children":[{"linkType":"INTERNAL","id":"migrated-link-4","params":{"boardId":"codeshare","categoryId":"CrowdSRC"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"migrated-link-5","params":{"boardId":"communityarticles","categoryId":"CrowdSRC"},"routeName":"TkbBoardPage"}],"linkType":"INTERNAL","id":"migrated-link-3","params":{"categoryId":"CrowdSRC"},"routeName":"CategoryPage"},{"children":[{"linkType":"INTERNAL","id":"migrated-link-7","params":{"boardId":"TechnicalArticles","categoryId":"Articles"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"article-series","params":{"boardId":"article-series","categoryId":"Articles"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"security-insights","params":{"boardId":"security-insights","categoryId":"Articles"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"migrated-link-8","params":{"boardId":"DevCentralNews","categoryId":"Articles"},"routeName":"TkbBoardPage"}],"linkType":"INTERNAL","id":"migrated-link-6","params":{"categoryId":"Articles"},"routeName":"CategoryPage"},{"children":[{"linkType":"INTERNAL","id":"migrated-link-10","params":{"categoryId":"CommunityGroups"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"migrated-link-11","params":{"categoryId":"F5-Groups"},"routeName":"CategoryPage"}],"linkType":"INTERNAL","id":"migrated-link-9","params":{"categoryId":"GroupsCategory"},"routeName":"CategoryPage"},{"children":[],"linkType":"INTERNAL","id":"migrated-link-12","params":{"boardId":"Events","categoryId":"top"},"routeName":"EventBoardPage"},{"children":[],"linkType":"INTERNAL","id":"migrated-link-13","params":{"boardId":"Suggestions","categoryId":"top"},"routeName":"IdeaBoardPage"},{"children":[],"linkType":"EXTERNAL","id":"Common-external-link","url":"https://community.f5.com/c/how-do-i","target":"SELF"}]},"className":"QuiltComponent_lia-component-edit-mode__lQ9Z6","showSearchIcon":false},"__typename":"QuiltComponent"},{"id":"community.widget.bannerWidget","props":{"backgroundColor":"transparent","visualEffects":{"showBottomBorder":false},"backgroundImageProps":{"backgroundSize":"COVER","backgroundPosition":"CENTER_CENTER","backgroundRepeat":"NO_REPEAT"},"fontColor":"#222222"},"__typename":"QuiltComponent"},{"id":"community.widget.breadcrumbWidget","props":{"backgroundColor":"var(--lia-bs-primary)","linkHighlightColor":"#FFFFFF","visualEffects":{"showBottomBorder":false},"backgroundOpacity":60,"linkTextColor":"#FFFFFF"},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"footer":{"backgroundImageProps":{"assetName":null,"backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"CENTER_CENTER","lastModified":null,"__typename":"BackgroundImageProps"},"backgroundColor":"var(--lia-bs-body-color)","items":[{"id":"custom.widget.Beta_Footer","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"},{"id":"custom.widget.Tag_Manager_Helper","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"},{"id":"custom.widget.Consent_Blackbar","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-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/common/ActionFeedback-1744046271000","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.Beta_MetaNav-en-us-1744706939108":{"__typename":"CachedAsset","id":"component:custom.widget.Beta_MetaNav-en-us-1744706939108","value":{"component":{"id":"custom.widget.Beta_MetaNav","template":{"id":"Beta_MetaNav","markupLanguage":"HANDLEBARS","style":null,"texts":{},"defaults":{"config":{"applicablePages":[],"description":"MetaNav menu at the top of every page.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Beta_MetaNav","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"MetaNav menu at the top of every page.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:component:custom.widget.Beta_Footer-en-us-1744706939108":{"__typename":"CachedAsset","id":"component:custom.widget.Beta_Footer-en-us-1744706939108","value":{"component":{"id":"custom.widget.Beta_Footer","template":{"id":"Beta_Footer","markupLanguage":"HANDLEBARS","style":null,"texts":{},"defaults":{"config":{"applicablePages":[],"description":"DevCentral´s custom footer.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Beta_Footer","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"DevCentral´s custom footer.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:component:custom.widget.Tag_Manager_Helper-en-us-1744706939108":{"__typename":"CachedAsset","id":"component:custom.widget.Tag_Manager_Helper-en-us-1744706939108","value":{"component":{"id":"custom.widget.Tag_Manager_Helper","template":{"id":"Tag_Manager_Helper","markupLanguage":"HANDLEBARS","style":null,"texts":{},"defaults":{"config":{"applicablePages":[],"description":"Helper widget to inject Tag Manager scripts into head element","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Tag_Manager_Helper","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"Helper widget to inject Tag Manager scripts into head element","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:component:custom.widget.Consent_Blackbar-en-us-1744706939108":{"__typename":"CachedAsset","id":"component:custom.widget.Consent_Blackbar-en-us-1744706939108","value":{"component":{"id":"custom.widget.Consent_Blackbar","template":{"id":"Consent_Blackbar","markupLanguage":"HTML","style":null,"texts":{},"defaults":{"config":{"applicablePages":[],"description":"","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Consent_Blackbar","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"TEXTHTML","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:text:en_US-components/community/Breadcrumb-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/Breadcrumb-1744046271000","value":{"navLabel":"Breadcrumbs","dropdown":"Additional parent page navigation"},"localOverride":false},"CachedAsset:text:en_US-components/tags/TagsHeaderWidget-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/tags/TagsHeaderWidget-1744046271000","value":{"tag":"{tagName}","topicsCount":"{count} {count, plural, one {Topic} other {Topics}}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageListForNodeByRecentActivityWidget-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageListForNodeByRecentActivityWidget-1744046271000","value":{"title@userScope:other":"Recent Content","title@userScope:self":"Contributions","title@board:FORUM@userScope:other":"Recent Discussions","title@board:BLOG@userScope:other":"Recent Blogs","emptyDescription":"No content to show","MessageListForNodeByRecentActivityWidgetEditor.nodeScope.label":"Scope","title@instance:1706288370055":"Content Feed","title@instance:1743095186784":"Most Recent Updates","title@instance:1704317906837":"Content Feed","title@instance:1743095018194":"Most Recent Updates","title@instance:1702668293472":"Community Feed","title@instance:1743095117047":"Most Recent Updates","title@instance:1704319314827":"Blog Feed","title@instance:1743095235555":"Most Recent Updates","title@instance:1704320290851":"My Contributions","title@instance:1703720491809":"Forum Feed","title@instance:1743095311723":"Most Recent Updates","title@instance:1703028709746":"Group Content Feed","title@instance:VTsglH":"Content Feed"},"localOverride":false},"Category:category:Forums":{"__typename":"Category","id":"category:Forums","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Forum:board:TechnicalForum":{"__typename":"Forum","id":"board:TechnicalForum","forumPolicies":{"__typename":"ForumPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Forum:board:WaterCooler":{"__typename":"Forum","id":"board:WaterCooler","forumPolicies":{"__typename":"ForumPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:DevCentralNews":{"__typename":"Tkb","id":"board:DevCentralNews","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:GroupsCategory":{"__typename":"Category","id":"category:GroupsCategory","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:F5-Groups":{"__typename":"Category","id":"category:F5-Groups","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:CommunityGroups":{"__typename":"Category","id":"category:CommunityGroups","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Occasion:board:Events":{"__typename":"Occasion","id":"board:Events","boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"occasionPolicies":{"__typename":"OccasionPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Idea:board:Suggestions":{"__typename":"Idea","id":"board:Suggestions","boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"ideaPolicies":{"__typename":"IdeaPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:CrowdSRC":{"__typename":"Category","id":"category:CrowdSRC","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:codeshare":{"__typename":"Tkb","id":"board:codeshare","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:communityarticles":{"__typename":"Tkb","id":"board:communityarticles","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:security-insights":{"__typename":"Tkb","id":"board:security-insights","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:article-series":{"__typename":"Tkb","id":"board:article-series","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Conversation:conversation:339856":{"__typename":"Conversation","id":"conversation:339856","topic":{"__typename":"TkbTopicMessage","uid":339856},"lastPostingActivityTime":"2025-02-20T14:45:08.231-08:00","solved":false},"User:user:195328":{"__typename":"User","uid":195328,"login":"Matt_Mabis","registrationData":{"__typename":"RegistrationData","status":null},"deleted":false,"avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/images/dS0xOTUzMjgtZ1QwR2RM?image-coordinates=0%2C75%2C3024%2C3099"},"id":"user:195328"},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMzk4NTYtRWV6YkJZ?revision=2\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0zMzk4NTYtRWV6YkJZ?revision=2","title":"image.png","associationType":"BODY","width":801,"height":308,"altText":""},"TkbTopicMessage:message:339856":{"__typename":"TkbTopicMessage","subject":"Mastering Imperative and Declarative Automation with F5 BIG-IP – AppWorld 2025","conversation":{"__ref":"Conversation:conversation:339856"},"id":"message:339856","revisionNum":2,"uid":339856,"depth":0,"board":{"__ref":"Tkb:board:TechnicalArticles"},"author":{"__ref":"User:user:195328"},"teaser@stripHtml({\"removeProcessingText\":true,\"truncateLength\":-1})":"","introduction":"","metrics":{"__typename":"MessageMetrics","views":228},"postTime":"2025-02-20T14:45:08.231-08:00","lastPublishTime":"2025-02-20T14:45:08.231-08:00","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})":" At AppWorld 2025, Anton Varkalevich and I will be hosting a hands-on lab, \"Mastering Imperative and Declarative Automation with F5 BIG-IP.\" \n I'm Matt Mabis, a Senior Solutions Architect in the Business Development/Technical Alliances department at F5. I specialize in solution integrations with our partnerships with Red Hat (Ansible and OpenShift), VMware, and Omnissa Horizon. \n Anton Varkalevich is a Solutions Engineer III for Financial Services at F5, specializing in ensuring financial customers have the right solutions to meet their unique needs. Automation plays a critical role in financial institutions, enabling them to serve their customers quickly and effectively. \n This lab is the result of years of experience working with customers to streamline application deployments on F5 BIG-IP. We’ll use Ansible Automation Platform to demonstrate both imperative and declarative automation approaches. \n \n Participants will first deploy common use cases—such as HTTPS and SSL-delivered applications—using imperative automation with individual Ansible modules. Then, we’ll achieve the same outcomes using declarative automation, offering a side-by-side comparison to help attendees choose the best approach for their needs. \n By the end of this lab, you’ll have a solid understanding of both automation styles and how to apply them effectively in your environment. \n Join us at AppWorld 2025—we look forward to sharing our knowledge with you! ","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})@stringLength":"1464","kudosSumWeight":0,"repliesCount":0,"readOnly":false,"images":{"__typename":"AssociatedImageConnection","edges":[{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDE","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMzk4NTYtRWV6YkJZ?revision=2\"}"}}],"totalCount":1,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"videos":{"__typename":"VideoConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"Conversation:conversation:339397":{"__typename":"Conversation","id":"conversation:339397","topic":{"__typename":"TkbTopicMessage","uid":339397},"lastPostingActivityTime":"2025-02-05T10:56:57.505-08:00","solved":false},"User:user:275883":{"__typename":"User","uid":275883,"login":"Tony_Marfil","registrationData":{"__typename":"RegistrationData","status":null},"deleted":false,"avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/images/dS0yNzU4ODMtQ2o3aUZ6?image-coordinates=0%2C0%2C3022%2C3022"},"id":"user:275883"},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMzkzOTctT0t4a2hK?revision=8\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0zMzkzOTctT0t4a2hK?revision=8","title":"Terminal Demo.jpg","associationType":"TEASER","width":692,"height":610,"altText":""},"TkbTopicMessage:message:339397":{"__typename":"TkbTopicMessage","subject":"Five Ways to Automate F5OS with Ansible: A Practical Guide","conversation":{"__ref":"Conversation:conversation:339397"},"id":"message:339397","revisionNum":8,"uid":339397,"depth":0,"board":{"__ref":"Tkb:board:TechnicalArticles"},"author":{"__ref":"User:user:275883"},"teaser@stripHtml({\"removeProcessingText\":true,\"truncateLength\":-1})":" \n ","introduction":"","metrics":{"__typename":"MessageMetrics","views":673},"postTime":"2025-02-04T05:00:00.038-08:00","lastPublishTime":"2025-02-05T10:56:57.505-08:00","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})":" This article explores five methods for automating F5OS using Ansible, complete with real-world examples you can adapt to your own environment. The five methods, with working code examples, include F5OS collection modules, REST API calls, Linux shell access, F5OS CLI commands, and Bash scripts. \n Understanding F5OS Architecture \n Our example system is an F5OS appliance named r5900-2 . \n When you connect to an F5OS device terminal as the root user, you enter a Linux Bash shell: \n [root@appliance-1(r5900-2):Active] ~ # uname -r\n3.10.0-1160.71.1.F5.1.1.1.el7_8.x86_64\n[root@appliance-1(r5900-2):Active] ~ # cat /etc/centos-release \nCentOS Linux release 7.8.2003 (Core)\n[root@appliance-1(r5900-2):Active] ~ #\n \n Root access is rarely needed to manage an F5OS appliance. Most tasks can be accomplished via the F5OS CLI. \n If you 'substitute user' to the 'admin' user (via the su command) or SSH directly as admin@r5900-2, you enter the F5OS CLI. Here you configure low-level network components such as Link Aggregation Groups and VLANs, monitor the F5OS platform, and manage BIG-IP tenants. \n [root@appliance-1(r5900-2):Active] ~ # su admin\nWelcome to the Management CLI\nadmin connected from 172.18.7.92 using ssh on r5900-2 \nr5900-2# show system version\nsystem version os-version 1.8.0-16036\nsystem version service-version 1.8.0-16036 \nsystem version product F5OS-A\n \n The F5OS CLI is built on top of the F5OS API. With this same admin account, you can target the F5OS API to configure anything at the F5OS layer. A simple test with curl can confirm you are ready to automate via the F5OS API: \n $ curl -k -X GET \\\nf/data/openconfi> \"https://r5900-2:8888/restconf/data/openconfig-system:system/f5-system-version:version\" \\\nH \"Content-Type:> -H \"Content-Type: application/yang-data+json\" \\\n> -H \"Accept: application/yang-data+json\" \\\n> -u admin:'YOUR_PASSWORD'\n{\n \"f5-system-version:version\": {\n \"os-version\": \"1.8.0-16036\",\n \"service-version\": \"1.8.0-16036\",\n \"product\": \"F5OS-A\"\n }\n}\n \n When you create a BIG-IP tenant, it behaves like a traditional BIG-IP instance. The tenant has its own isolated management IP address and iControl REST API endpoint. Your existing BIG-IP Ansible playbooks will continue to work with minimal changes. \n However, automation workflows for BIG-IP are not compatible with F5OS. Let's explore how we can automate just about anything on F5OS using Ansible. \n \n Method 1: F5OS Ansible Collection Modules \n # Method 1: Using F5OS collection module\n- name: Get F5OS system information using F5OS Ansible module\n f5os_device_info:\n gather_subset:\n - system-info\n register: f5os_facts\n \n Mechanics: This method uses the official F5OS Ansible collection to communicate with the F5OS REST API over HTTPS. The modules handle authentication, request formatting, and response parsing. \n Pros: \n \n Purpose-built for F5OS operations \n Input validation and error handling \n Consistent interface across F5OS versions \n Simplified syntax for complex operations \n \n Cons: \n \n Limited to functionality provided by existing modules \n May need to wait for new modules as F5OS features evolve \n \n \n Method 2: Direct REST API Calls \n # Method 2: Using generic Ansible uri module for REST API\n- name: Get F5OS version information using REST API direct call\n uri:\n url: \"{{ api_base_url }}/data/openconfig-system:system/f5-system-version:version\"\n method: GET\n force_basic_auth: true \n user: \"{{ ansible_user }}\"\n password: \"{{ ansible_httpapi_password }}\"\n headers:\n Content-Type: \"application/yang-data+json\"\n Accept: \"application/yang-data+json\" \n validate_certs: \"{{ ansible_httpapi_validate_certs }}\"\n status_code: 200\n return_content: true\n register: f5os_version\n \n Mechanics: This approach uses Ansible's uri module to make direct REST API calls to F5OS. It provides maximum flexibility but requires detailed knowledge of the F5OS API. \n Pros: \n \n Access to all F5OS API endpoints \n No dependency on F5OS collection modules \n Useful for testing new F5OS features \n \n Cons: \n \n Requires understanding of F5OS API structure \n More verbose configuration \n Need to handle response parsing manually \n \n \n Method 3: Linux System Access \n # Method 3: Using generic Ansible SSH for Linux system access \n- name: Gather Linux system information via SSH\n setup:\n gather_subset:\n - hardware \n delegate_to: r5900-2-linux\n vars:\n ansible_user: root\n ansible_connection: ssh\n register: linux_facts\n \n Mechanics: This method establishes an SSH connection to access the underlying Linux operating system for system-level operations and diagnostics. \n Pros: \n \n Direct access to system resources \n Useful for troubleshooting \n Leverage existing Linux automation skills \n \n Cons: \n \n Requires root access \n Risk of affecting system stability \n \n \n Method 4: F5OS CLI via SSH \n # Method 4: Using generic Ansible shell module for F5OS CLI\n- name: Get F5OS version information via CLI \n shell: |\n sshpass -p \"{{ f5os_cli_password }}\" ssh -t -o StrictHostKeyChecking=no admin@{{ hostvars['r5900-2-cli']['ansible_host'] }} << 'EOF'\n show sys version\n EOF\n delegate_to: localhost\n register: cli_version\n \n Mechanics: This method uses SSH to connect directly to the F5OS CLI, similar to how you interact with the device manually. \n Pros: \n \n Familiar CLI interface \n Suitable for operational commands \n Easy to test and debug \n \n Cons: \n \n Output parsing can be complex \n Less idempotent than API methods \n Requires sshpass installation \n \n \n Method 5: Bash Scripts with f5sh \n # Method 5: Using Ansible copy and shell modules to run f5sh commands from Bash scripts\n- name: Transfer f5sh_example.sh script to F5OS device\n copy:\n content: |\n #!/bin/bash\n f5sh show sys version\n dest: \"/root/f5sh_example.sh\"\n mode: '0755'\n delegate_to: r5900-2-linux\n vars:\n ansible_user: root\n ansible_connection: ssh\n register: script_transfer\n\n- name: Execute f5sh_example.sh script on F5OS device\n shell: \"/root/f5sh_example.sh\"\n delegate_to: r5900-2-linux\n vars:\n ansible_user: root\n ansible_connection: ssh\n register: script_output \n \n Mechanics: This method combines Linux shell access with the f5sh command prefix introduced in F5OS 1.8.0, allowing F5OS CLI commands to be run from Bash scripts. \n Pros: \n \n Combine shell scripting with F5OS commands \n \n Cons: \n \n Requires root access \n \n \n Inventory and Connection Methods \n Each connection method requires specific inventory settings defined in 'hosts.yml': \n API Connections \n # hosts.yml \nf5os:\n hosts:\n r5900-2-api:\n ansible_host: r5900-2\n \n Used with httpapi connection in the playbook: \n # playbook.yml\n- name: F5OS Management Tasks\n connection: httpapi\n hosts: r5900-2-api\n vars:\n ansible_user: \"admin\" \n ansible_httpapi_password: \"{{ f5os_api_password }}\"\n ansible_network_os: \"f5networks.f5os.f5os\"\n ansible_httpapi_use_ssl: true\n ansible_httpapi_port: 8888\n \n You may send API calls to either port 8888 or port 443. The URI path will change slightly depending on which TCP port you choose to use: \n \n For port 443: Initial path will be /api \n For port 8888: Initial path will be /restconf \n \n F5OS also listens on port 80 and will redirect to TCP port 443. You can then configure distinct network access control policies for traffic to: \n \n Management interface user interface (443) \n Management interface REST API endpoint (8888) \n \n CLI Access \n # hosts.yml\nf5os_cli: \n hosts:\n r5900-2-cli:\n ansible_host: r5900-2\n ansible_user: admin\n ansible_connection: ssh\n ansible_become: no \n ansible_ssh_common_args: '-o StrictHostKeyChecking=no'\n \n Enables CLI commands through SSH: \n # playbook.yml \n- name: Get F5OS version information via CLI\n shell: |\n sshpass -p \"{{ f5os_cli_password }}\" ssh -t -o StrictHostKeyChecking=no admin@{{ hostvars['r5900-2-cli']['ansible_host'] }} << 'EOF' \n show sys version\n EOF\n delegate_to: localhost\n \n Linux Shell Access \n # hosts.yml\nlinux_hosts:\n hosts: \n r5900-2-linux:\n ansible_host: r5900-2\n ansible_user: root\n ansible_connection: ssh\n ansible_become: no\n ansible_python_interpreter: /usr/bin/python3\n ansible_ssh_common_args: '-o StrictHostKeyChecking=no' \n \n Used for system information gathering and script execution: \n # playbook.yml\n- name: Gather Linux system information via SSH\n setup:\n gather_subset:\n - hardware\n delegate_to: r5900-2-linux \n vars:\n ansible_user: root\n ansible_connection: ssh\n \n Notes on hosts.yml Inventory Entries \n The inventory defines three connections to the same device: \n \n r5900-2-api : HTTPS API access \n r5900-2-cli : SSH CLI access with admin user \n r5900-2-linux : SSH root access \n \n SSH configuration: \n \n StrictHostKeyChecking=no : Skips host key verification \n ansible_become: no : No privilege escalation \n Custom Python path for Linux shell access \n \n Run playbooks with --ask-pass to prompt for the root password when accessing the Linux shell: \n ansible-playbook -i hosts.yml playbook.yml --ask-pass\n \n See the complete hosts.yml and playbook.yml examples below as templates for your own environments. The latest versions are hosted on GitHub. \n # hosts.yml - Ansible inventory configuration\n# This inventory file defines multiple ways to connect to the same F5OS device\n\nall:\n children:\n # F5OS API group - For REST API interactions via HTTPS\n # This method is used for F5OS platform configuration and monitoring via REST\n # User credentials configured as vars in playbook.yml\n f5os:\n hosts:\n r5900-2-api:\n ansible_host: r5900-2\n\n # F5OS CLI group - For F5OS CLI access via SSH\n # This method is used to run F5OS CLI commands via SSH using admin account\n # User credentials prompted for at runtime\n f5os_cli:\n hosts:\n r5900-2-cli:\n ansible_host: r5900-2\n ansible_user: admin\n ansible_connection: ssh\n ansible_become: no\n ansible_ssh_common_args: '-o StrictHostKeyChecking=no'\n\n # Linux group - For direct Linux shell access via SSH\n # This method is used to access the underlying Linux OS using root account\n # User credentials prompted for at runtime using --ask-pass\n linux_hosts:\n hosts:\n r5900-2-linux:\n ansible_host: r5900-2\n ansible_user: root\n ansible_connection: ssh\n ansible_become: no\n ansible_python_interpreter: /usr/bin/python3\n ansible_ssh_common_args: '-o StrictHostKeyChecking=no'\n # playbook.yml\n#\n# This playbook demonstrates multiple methods to interact with F5OS devices using Ansible:\n# 1. Using F5OS-specific Ansible modules from the F5 Ansible collection\n# - Connects via HTTPS to F5OS REST API\n# - Uses F5OS maintained collection modules\n# - Best for F5OS platform operations with verified modules\n#\n# 2. Using Ansible uri module to make direct REST API calls\n# - Connects via HTTPS to F5OS REST API\n# - Uses Ansible built-in uri module\n# - Best for custom REST API operations or when collection modules unavailable\n#\n# 3. Using SSH connection to access the underlying Linux OS\n# - Connects via SSH as root to Linux shell\n# - Uses Ansible built-in setup modules\n# - Best for system-level operations and diagnostics\n#\n# 4. Using SSH to connect as admin and run F5OS CLI commands\n# - Connects via SSH as admin to F5OS CLI\n# - Uses Ansible built-in shell module with sshpass\n# - Best for running operational commands and gathering CLI output\n#\n# 5. Using SSH connection to access the underlying Linux OS\n# - Connects via SSH as root to Linux shell\n# - Uses Ansible built-in SSH modules\n# - Uses Ansible built-in copy module to transfer Bash script\n# - Uses Ansible built-in shell module to execute script and display output\n# - Best for automated script-based management via f5sh commands\n#\n# Requirements:\n# - sshpass must be installed on the Ansible control node for F5OS CLI access\n# Ubuntu/Debian: sudo apt-get install sshpass\n# RHEL/CentOS: sudo yum install sshpass\n#\n# References:\n# - F5OS Collection: https://clouddocs.f5.com/products/orchestration/ansible/devel/f5os/F5OS-index.html\n# - Ansible URI Module: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/uri_module.html\n# - F5OS-A/F5 rSeries - API: https://clouddocs.f5.com/api/rseries-api/rseries-api-index.html\n# - Ansible SSH Connection: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/ssh_connection.html\n# - F5OS-A/F5 rSeries - CLI: https://clouddocs.f5.com/api/rseries-api/rseries-cli-index.html\n#\n# Debug Notes:\n# - Each connection method has debug lines (commented out) that can be enabled\n# - Uncomment Debug Info lines when troubleshooting connection issues\n# - Use -vvvv verbosity for additional connection debugging\n# - CLI task can be modified to show stderr/stdout with no_log: false\n---\n- name: F5OS Management Tasks\n connection: httpapi\n hosts: r5900-2-api\n gather_facts: false\n collections:\n - f5networks.f5os\n any_errors_fatal: true\n\n vars_prompt:\n - name: f5os_api_password\n prompt: \"ENTER F5OS REST API password (admin user):\"\n private: yes\n - name: f5os_cli_password\n prompt: \"ENTER F5OS CLI SSH password (admin user):\"\n private: yes\n\n vars:\n ansible_user: \"admin\"\n ansible_httpapi_password: \"{{ f5os_api_password }}\"\n ansible_network_os: \"f5networks.f5os.f5os\"\n ansible_httpapi_use_ssl: true\n ansible_httpapi_use_proxy: false\n ansible_httpapi_validate_certs: \"no\"\n ansible_httpapi_port: 8888\n ansible_command_timeout: 1800\n persistent_log_messages: true\n api_base_url: \"https://{{ ansible_host }}:{{ ansible_httpapi_port }}/restconf\"\n\n tasks:\n # Method 1: Using F5OS maintained collection module\n - name: Get F5OS system information using F5OS Ansible module\n f5os_device_info:\n gather_subset:\n - system-info\n register: f5os_facts\n\n # Method 2: Using generic Ansible uri module for REST API\n - name: Get F5OS version information using REST API direct call\n uri:\n url: \"{{ api_base_url }}/data/openconfig-system:system/f5-system-version:version\"\n method: GET\n force_basic_auth: true\n user: \"{{ ansible_user }}\"\n password: \"{{ ansible_httpapi_password }}\"\n headers:\n Content-Type: \"application/yang-data+json\"\n Accept: \"application/yang-data+json\"\n validate_certs: \"{{ ansible_httpapi_validate_certs }}\"\n status_code: 200\n return_content: true\n register: f5os_version\n\n # Method 3: Using generic Ansible SSH for Linux system access\n - name: Gather Linux system information via SSH connection\n setup:\n gather_subset:\n - hardware\n delegate_to: r5900-2-linux\n vars:\n ansible_user: root\n ansible_connection: ssh\n register: linux_facts\n\n # Method 4: Using generic Ansible shell module for F5OS CLI\n - name: Get F5OS version information via CLI\n shell: |\n sshpass -p \"{{ f5os_cli_password }}\" ssh -t -o StrictHostKeyChecking=no admin@{{ hostvars['r5900-2-cli']['ansible_host'] }} << 'EOF'\n show sys version\n EOF\n delegate_to: localhost\n register: cli_version\n changed_when: false\n # no_log: false # Uncomment to show CLI output for debugging\n # failed_when: false # Uncomment to prevent CLI failures for debugging\n\n # Method 5: Using generic Ansible SSH connection to access the underlying Linux OS\n - name: Transfer f5sh_example.sh script to F5OS device\n copy:\n content: |\n #!/bin/bash\n f5sh show sys version\n dest: \"/root/f5sh_example.sh\"\n mode: '0755'\n delegate_to: r5900-2-linux\n vars:\n ansible_user: root\n ansible_connection: ssh\n register: script_transfer\n\n - name: Execute f5sh_example.sh script on F5OS device\n shell: \"/root/f5sh_example.sh\"\n delegate_to: r5900-2-linux\n vars:\n ansible_user: root\n ansible_connection: ssh\n register: script_output\n\n # Display combined information from all five methods\n - name: Show combined information from all sources\n vars:\n system_info:\n \"F5OS Version Info (via F5 maintained F5OS Ansible module collection, communicating over HTTPS REST API, using: admin account)\":\n \"Platform Type\": \"{{ f5os_facts.system_info.platform_type }}\"\n \"Software Version\": \"{{ f5os_facts.system_info.running_software.os_version }}\"\n # \"Debug Info\": \"{{ f5os_facts | default('f5os_facts not defined') }}\"\n \"F5OS Version Info (via built-in Ansible uri module, communicating over HTTPS REST API, using: admin account)\":\n \"OS Version\": \"{{ f5os_version.json['f5-system-version:version']['os-version'] }}\"\n \"Product\": \"{{ f5os_version.json['f5-system-version:version']['product'] }}\"\n \"Service Version\": \"{{ f5os_version.json['f5-system-version:version']['service-version'] }}\"\n # \"Debug Info\": \"{{ f5os_version | default('f5os_version not defined') }}\"\n \"Linux System Info (via built-in Ansible ssh module, communicating over SSH, using: root account and bash shell)\":\n \"Distribution\": \"{{ linux_facts.ansible_facts.ansible_distribution }}\"\n \"Kernel Version\": \"{{ linux_facts.ansible_facts.ansible_kernel }}\"\n \"Python Version\": \"{{ linux_facts.ansible_facts.ansible_python_version }}\"\n \"Total Memory\": \"{{ linux_facts.ansible_facts.ansible_memtotal_mb }} MB\"\n # \"Debug Info\": \"{{ linux_facts | default('linux_facts not defined') }}\"\n \"F5OS Version Info (via built-in Ansible ssh module, communicating over SSH, using: admin account and f5_confd_cli shell)\":\n \"OS Version\": \"{{ cli_version.stdout_lines[0].split()[-1] if cli_version is defined and cli_version.rc == 0 else 'Error getting CLI version' }}\"\n \"Service Version\": \"{{ cli_version.stdout_lines[1].split()[-1] if cli_version is defined and cli_version.rc == 0 else 'Error getting CLI version' }}\"\n \"Product\": \"{{ cli_version.stdout_lines[2].split()[-1] if cli_version is defined and cli_version.rc == 0 else 'Error getting CLI version' }}\"\n # \"CLI Return Code\": \"{{ cli_version.rc | default('rc not defined') }}\"\n # \"CLI STDOUT\": \"{{ cli_version.stdout_lines | default('stdout not defined') }}\"\n # \"CLI STDERR\": \"{{ cli_version.stderr_lines | default('stderr not defined') }}\"\n # \"Debug Info\": \"{{ cli_version | default('cli_version not defined') }}\"\n \"F5OS Version Info (via built-in Ansible script copy and execution modules, using: root account, bash shell, and f5sh commands)\":\n \"OS Version\": \"{{ script_output.stdout_lines[0].split()[-1] if script_output is defined and script_output.rc == 0 else 'Error executing script' }}\"\n \"Product\": \"{{ script_output.stdout_lines[2].split()[-1] if script_output is defined and script_output.rc == 0 else 'Error executing script' }}\"\n \"Service Version\": \"{{ script_output.stdout_lines[1].split()[-1] if script_output is defined and script_output.rc == 0 else 'Error executing script' }}\"\n # \"Script Return Code\": \"{{ script_output.rc | default('rc not defined') }}\"\n # \"Script STDOUT\": \"{{ script_output.stdout_lines | default('stdout not defined') }}\"\n # \"Script STDERR\": \"{{ script_output.stderr_lines | default('stderr not defined') }}\"\n # \"Debug Info\": \"{{ script_output | default('script_output not defined') }}\"\n debug:\n var: system_info \n \n Terminal Demo \n ansible-playbook -i hosts.yml playbook.yml --ask-pass\n \n \n \n Notes on Software Version Compatibility \n If using the F5-maintained F5OS collection, you'll need a compatible version of Ansible: https://github.com/F5Networks/f5-ansible-f5os \n If using an alternate method that depends on compatibility with the Python3 version on the remote host, confirm the Python3 version on your F5OS appliance: \n [root@appliance-1(r5900-2):Active] ~ # python3 --version\nPython 3.6.8\n \n ...against the ansible-core support matrix. \n \n Responsible Authentication \n This tutorial focused on simple examples using default local accounts (root, admin) and local authentication. \n In production, authentication is more complex, requiring: \n \n SSH key-based authentication instead of passwords \n API token authentication instead of basic auth \n Role-based access control with dedicated automation accounts \n Secure credential management (e.g. Ansible Vault) \n Regular credential rotation and audit logging \n Remote authentication (LDAP, TACACS+, RADIUS) \n Upgrading from self-signed and invalid certificates to Certificate Authority signed and validated certificates \n ","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})@stringLength":"21046","kudosSumWeight":0,"repliesCount":0,"readOnly":false,"images":{"__typename":"AssociatedImageConnection","edges":[{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDE","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMzkzOTctT0t4a2hK?revision=8\"}"}}],"totalCount":1,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"videos":{"__typename":"VideoConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"Conversation:conversation:328672":{"__typename":"Conversation","id":"conversation:328672","topic":{"__typename":"TkbTopicMessage","uid":328672},"lastPostingActivityTime":"2024-03-22T12:34:52.234-07:00","solved":false},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItd1V3VGdR?revision=4\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItd1V3VGdR?revision=4","title":"image.png","associationType":"BODY","width":589,"height":448,"altText":""},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzIteExwdEVC?revision=4\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzIteExwdEVC?revision=4","title":"image.png","associationType":"BODY","width":1433,"height":401,"altText":""},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItclVWOEpu?revision=4\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItclVWOEpu?revision=4","title":"image.png","associationType":"BODY","width":1432,"height":544,"altText":""},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItWnRNQWIw?revision=4\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItWnRNQWIw?revision=4","title":"image.png","associationType":"BODY","width":1443,"height":539,"altText":""},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItYWd5UmNk?revision=4\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItYWd5UmNk?revision=4","title":"image.png","associationType":"BODY","width":1422,"height":545,"altText":""},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItRDlRV0k0?revision=4\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItRDlRV0k0?revision=4","title":"image.png","associationType":"BODY","width":1448,"height":347,"altText":""},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItTXAzVGZK?revision=4\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItTXAzVGZK?revision=4","title":"image.png","associationType":"BODY","width":1447,"height":681,"altText":""},"TkbTopicMessage:message:328672":{"__typename":"TkbTopicMessage","subject":"Infrastructure as Code: Automating F5 Distributed Cloud CEs with Ansible","conversation":{"__ref":"Conversation:conversation:328672"},"id":"message:328672","revisionNum":4,"uid":328672,"depth":0,"board":{"__ref":"Tkb:board:TechnicalArticles"},"author":{"__ref":"User:user:195328"},"teaser@stripHtml({\"removeProcessingText\":true,\"truncateLength\":-1})":"","introduction":"","metrics":{"__typename":"MessageMetrics","views":617},"postTime":"2024-03-22T12:34:52.234-07:00","lastPublishTime":"2024-03-22T12:34:52.234-07:00","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})":" Introduction \n Welcome to the first installment of our Infrastructure as Code (IaC) series, focusing on F5 products and Ansible. This series has been a long-standing desire of mine to showcase the ability of IaC utilizing Ansible Automation Platform to deliver Day 0 through Day 2 operations with multiple F5 virtualized platforms. \n Over time, I've encountered numerous financial clients expressing interest in this topic. For many of these clients, the prospect of leveraging IaC to redeploy an environment outweighs the traditional approach of performing upgrades. This series will hopefully provide insight, documentation, and code for anyone embarking on this journey. \n \n Why Ansible Automation Platform? \n Like most people, I started my journey with community editions of Ansible. As my coding became more complex, so did the need to ensure that my lab infrastructure adhered to the best security guidelines required by my company (my goal being to mimic how customers would/should do things in real life). I began utilizing Ansible Automation Platform to ensure my credentials were protected, as well as to organize and share my code with the rest of my team (following the 'just in case you got hit by a bus' theory). \n Ansible Automation Platform utilizes execution environments (EE) to ensure code runs efficiently and cleanly every time. Now, I am also creating Execution Environments via GitHub with workflows and pushing them up to Quay.io (https://github.com/VDI-Tech-Guy/f5-execution-engines). Huge thanks to Colin McNaughton at Red Hat for making my life so much easier with building EEs! \n \n Why deploy F5 Distributed Cloud on VMware vSphere? \n As I mentioned before, I had this desire to build this Infrastructure as Code (IaC) code a while back. This was prior to the Broadcom acquisition of VMware. Being an ex-VMware employee, I had a lot of knowledge of virtualization platform infrastructure going into this project, and I started my focus on deploying on VMware vSphere. \n F5 Distributed Cloud can be deployed in any cloud, anywhere. However, I really wanted to focus on on-premises deployments because not every customer can afford the cloud. Moreover, there's always a back-and-forth battle between on-premises and the cloud, which has evolved into the Hybrid Cloud and the Multi-Cloud. \n I do intend to extend this series to the Multi-Cloud, but these initial deployments will be focused on VMware vSphere, as it is still utilized in many organizations across the globe. \n Information about the Setup in the Demo Video \n If you watch the video (down below) on how the deployment works, you can see i did a bunch of the pre-work prior to launching the deployment, in the git repostory (link in Resources). \n Here are some Prework items i did \n \n Had a fully functional Ansible Automation Platform 2.4+ enviornment setup and working. (at the time the controller version was 4.4.4) \n Execution Environment was imported into Ansible Automation Platform Controller \n \n \n The Project was setup to import the Playbooks from the Git Repository (In Resources Section below) and setup the Default Execution Environment \n Demo Inventory was setup (in our usecase we only needed the vCenter Host) \n We Setup Network Credentials for the vCenter \n The Template was setup and had Variables populated in it (Note the API Key was hidden). \n As mentioned in the Video (Below) The variables were populated to my environment, this contains all the information, i have provided a Demo Example in the git repository for anyone to mimic my settings to their environment, also the example has comments about each field or area of a field and the purpose of the variable. {\n \"rhel_location\": \"https://vesio.blob.core.windows.net/releases/rhel/9/x86_64/images/vmware/rhel-9.2023.29-20231212012955-single-nic.ova\",\n \"xc_api_credential\": \"_____________________________________\",\n \"xc_namespace\": \"mmabis-automation\",\n \"xc_console_host\": \"f5-bd\",\n \"xc_user\": \"admin\",\n \"xc_pass\": \"Ansible123!\",\n \"vcenter_hostname\": \"{{ ansible_host }}\",\n \"vcenter_username\": \"{{ ansible_env.ANSIBLE_NET_USERNAME }}\",\n \"vcenter_password\": \"{{ ansible_env.ANSIBLE_NET_PASSWORD }}\",\n \"vcenter_validate_certs\": false,\n \"datacenter_name\": \"Apex\",\n \"cluster_name\": \"Worlds-Edge\",\n \"datastore\": \"TrueNAS-SSD\",\n \"dvs_switch_name\": \"DSC-DVS\",\n \"dns_name_servers\": [\n \"192.168.192.20\",\n \"192.168.192.1\"\n ],\n \"dns_name_search\": [\n \"dsc-services.local\",\n \"localdomain\"\n ],\n \"ntp_servers\": [\n \"0.pool.ntp.org\",\n \"1.pool.ntp.org\",\n \"2.pool.ntp.org\"\n ],\n \"domain_fqdn\": \"dsc-services.local\",\n \"DVS_Name\": \"{{dvs_switch_name}}\",\n \"Internal_Network\": \"DVS-Server-vLan\",\n \"External_Network\": \"DVS-DMZ-vLan\",\n \"resource_pool_name\": \"Lab-XC\",\n \"waiting_period\": 2,\n \"temp_download_location\": \"/tmp/xc-ova-download.ova\",\n \"xc_ova_builds\": [\n {\n \"hostname\": \"xc-automation-rhel-demo\",\n \"tmpl_name\": \"xc-automation-rhel-demo\",\n \"admin_password\": \"Ansible123!\",\n \"cluster_name\": \"xc-automation-cluster-rhel-demo\",\n \"dhcp\": \"no\",\n \"external_ip\": \"172.16.192.170\",\n \"external_ip_subnet_prefix\": \"24\",\n \"external_ip_gw\": \"172.16.192.1\",\n \"external_ip_route\": \"0.0.0.0/0\",\n \"internal_ip\": \"192.168.192.170\",\n \"internal_ip_subnet_prefix\": \"22\",\n \"internal_ip_gw\": \"192.168.192.1\",\n \"certified_hw\": \"vmware-regular-nic-voltmesh\",\n \"latitude\": \"39.51833126\",\n \"longitude\": \"-104.759496962\",\n \"build_count\": 3,\n \"nic_config\": \"rhel-multi\"\n }\n ]\n} \n \n Launching the Code \n With all of that prework Handled it was as easy as launch the code, there were a few caviats i learned over time when dealing with the atuomation that i wanted to share. \n \n Never re-use a cluster name in F5 Distributed Cloud, especially if it was used in a different version of the CE (there were communications issues with the CEs and previous cluster information that was stored in F5 Distributred Cloud Console) \n The Api Credentials are system level when trying to accept registration or create the token for importing in to the environment. This code is designed to check for \"{{ xc-namespace}}-token\" if it exists then it will utilize the existing token, if not it will try to create it so you need system level permissions to do this. \n Build Count should be 3 by default (still needs to be defined) or an ODD number based on recomendations i have heard from our F5 Field. \n \n If there are more that i think of ill definatly edit the post and make sure its up-to-date. When launching the code i was able to get the lab to build up correctly multiple times, so please if there is an issue or something i might not have documented well, feel free to let me know and give it a shot for yourself! \n \n YouTube Video now on DevCentral Channel \n \n Resources \n \n \n https://github.com/f5devcentral/f5-bd-ansible-day0-automation - The Code utilized for this deployment \n https://github.com/VDI-Tech-Guy/f5-execution-engines - Building Execution Environments with Github and Workflows \n \n Conclusion \n I do hope that this series will help everyone who wants to embrace IaC and if you have any questions feel free to reach out! ","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})@stringLength":"7375","kudosSumWeight":3,"repliesCount":0,"readOnly":false,"images":{"__typename":"AssociatedImageConnection","edges":[{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDE","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItd1V3VGdR?revision=4\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDI","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzIteExwdEVC?revision=4\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDM","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItclVWOEpu?revision=4\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDQ","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItWnRNQWIw?revision=4\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDU","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItYWd5UmNk?revision=4\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDY","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItRDlRV0k0?revision=4\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDc","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0zMjg2NzItTXAzVGZK?revision=4\"}"}}],"totalCount":7,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"videos":{"__typename":"VideoConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"Conversation:conversation:280278":{"__typename":"Conversation","id":"conversation:280278","topic":{"__typename":"TkbTopicMessage","uid":280278},"lastPostingActivityTime":"2023-06-05T22:59:55.330-07:00","solved":false},"User:user:217977":{"__typename":"User","uid":217977,"login":"Tim_Rupp","registrationData":{"__typename":"RegistrationData","status":null},"deleted":false,"avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/m_assets/avatars/default/avatar-9.svg?time=0"},"id":"user:217977"},"TkbTopicMessage:message:280278":{"__typename":"TkbTopicMessage","subject":"Getting started with Ansible","conversation":{"__ref":"Conversation:conversation:280278"},"id":"message:280278","revisionNum":2,"uid":280278,"depth":0,"board":{"__ref":"Tkb:board:TechnicalArticles"},"author":{"__ref":"User:user:217977"},"teaser@stripHtml({\"removeProcessingText\":true,\"truncateLength\":-1})":"","introduction":"","metrics":{"__typename":"MessageMetrics","views":2853},"postTime":"2016-01-20T10:32:23.000-08:00","lastPublishTime":"2023-06-05T22:59:55.330-07:00","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})":" Ansible is an orchestration and automation engine. It provides a means for you to automate the administration of different devices, from Linux to Windows and different special purpose appliances in-between. Ansible falls into the world of DevOps related tools. You may have heard of others that play in this area as well including. \n\n Chef Puppet Saltstack \n\n In this article I'm going to briefly skim the surface of what Ansible is and how you can get started using it. I've been toying around with it for some years now, and (most recently at F5) using it to streamline some development work I've been involved in. \n\n If you, like me, are a fan of dabbling with interesting tools and swear by the \"Automate all the Things!\" catch-phrase, then you might take an interest in Ansible. \n\n We're going to start small though and build upon what we learn. My goal here is to eventually bring you all to the point where we're doing some crazy awesome things with Ansible and F5 products. I'll also go into some brief detail on features of Ansible that make it relatively painless to interoperate with existing F5 products. Let's get started! \n\n So why Ansible? \n\n Any time that it comes to adopting some new technology for your everyday use, inevitably you need to ask yourself \"what's in it for me?\". Why not just use some custom shell scripts and pssh to do everything? Here are my reasons for using Ansible. \n\n It is agent-less The only dependencies (on the remote device) are SSH and python; and even python is not really a dependency The language that you \"do\" stuff in is YAML. No CS degree or programming language expertise is required (Perl, Ruby, Python, etc) Extending it is simple (in my opinion) Actions are idempotent Order of operations is well-defined and work is performed top-down \n\n Many of the original tools in the DevOps space were agent-based tools. This is a major problem for environments where it's literally (due to technology or politics) impossible to install an agent. Your SLA may prohibit you from installing software on the box. Or, you might legitimately not be able to install the software due to older libraries or other missing dependencies. Ansible has no agent requirement; a plus in my book. Most of the systems that you will come across can be, today, manipulated by Ansible. It is agent-less by design. \n\n Dependency wise you need to be able to connect to the machine you want to orchestrate, so it makes sense that SSH is a dependency. Also, you would like to be able to do higher-order \"stuff\" to a machine. That's where the python dependency comes into play. I say dependency loosely though, because Ansible provides a way to run raw commands on remote systems regardless of whether Python is installed. For professional Ansible development though, this method of orchestrating devices is largely not recommended except in very edge cases. \n\n Ansible's configuration language is YAML. If you have never seen YAML before, this is what it looks like \n\n \n - name: Deploy common hosts files settings\n hosts: all\n connection: ssh\n gather_facts: true\n \n tasks:\n - name: Install required packages\n apt:\n name: \"{{ item }}\"\n state: \"present\"\n with_items:\n - ntp\n - ubuntu-cloud-keyring\n - python-mysqldb \n\n YAML is generally composed of simple key/value pairs, lists, and dictionaries. Contrast this with the Puppet configuration language; a special DSL that resembles a real programming language. \n\n \n class sso {\n case $::lsbdistcodename {\n default: {\n $ssh_version = 'latest'\n }\n }\n \n class { '::sso':\n ldap_uri => $::ldap_uri,\n dev_env => true,\n ssh_version => $ssh_version,\n sshd_allow_groups => $::sshd_allow_groups,\n }\n} \n\n Or contrast this with Chef, in which you must know Ruby to be able to use. \n\n \n servers = search(\n :node,\n \"is_server:true AND chef_environment:#{node.chef_environment}\"\n).sort! do\n |a, b| a.name <=> b.name\nend\n \nbegin\n resources('service[mysql]')\nrescue Chef::Exceptions::ResourceNotFound\n service 'mysql'\nend\n \ntemplate \"#{mysql_dir}/etc/my.conf\" do\n source 'my.conf.erb'\n mode 0644\n variables :servers => servers, :mysql_conf => node['mysql']['mysql_conf']\n notifies :restart, 'service[mysql]'\nend \n\n In Ansible, work that is performed is idempotent. That's a buzzword. What does it mean? \n\n It means that an operation can be performed multiple times without changing the result beyond its initial application. If I try to add the same line to a file a thousand times, it will be added once and then will not be added again 999 times. Another example is adding user accounts. They would be added once, not many times (which might raise errors on the system). \n\n Finally, Ansible's workflow is well defined. Work starts at the top of a playbook and makes its way to the bottom. Done. End of story. There are other tools that have a declarative model. These tools attempt to read your mind. \"You declare to me how the node should look at the end of a run, and I will determine the order that steps should be run to meet that declaration.\" \n\n Contrast this with Ansible which only operates top-down. We start at the first task, then move to the second, then the third, etc. \n\n This removes much of the \"magic\" from the equation. Often times an error might occur in a declarative tool due specifically to how that tool arranges its dependency graph. When that happens, it's difficult to determine what exactly the tool was doing at the time of failure. \n\n That magic doesn't exist in Ansible; work is always top-down whether it be tasks, roles, dependencies, etc. You start at the top and you work your way down. \n\n Installation \n\n Let's now take a moment to install Ansible itself. Ansible is distributed in different ways depending on your operating system, but one tried and true method to install it is via pip ; the recommended tool for installing python packages. \n\n I'll be working on a vanilla installation of Ubuntu 15.04.2 (vivid) for the remaining commands. \n\n Ubuntu includes a pip package that should work for you without issue. You can install it via apt-get . \n\n \n sudo apt-get install python-pip python-dev \n\n Afterwards, you can install Ansible. \n\n \n sudo pip install markupsafe ansible==1.9.4 \n\n You might ask \"why not ansible 2.0\". Well, because 2.0 was just released and the community is busy ironing out some new-release bugs. I prefer to give these things some time to simmer before diving in. Lucky for us, when we are ready to dive in, upgrading is a simple task. \n\n So now you should have Ansible available to you. \n\n \nSEA-ML-RUPP1:~ trupp$ ansible --version\nansible 1.9.4\n configured module search path = None\nSEA-ML-RUPP1:~ trupp$ \n\n Your first playbook \n\n Depending on the tool, the body of work is called different things. \n\n Puppet calls them manifests Chef calls them recipes and cookbooks Ansible calls them plays and playbooks Saltstack calls them formulas and states \n\n They're all the same idea. You have a system configuration you need to apply, you put it in a file, the tool interprets the file and applies the configuration to the system. We will write a very simple playbook here to illustrate some concepts. It will create a file on the system. Booooooring. I know, terribly boring. We need to start somewhere though, and your eyes might roll back into your head if we were to start off with a more complicated example like bootstrapping a BIG-IP or dynamically creating cloud formation infrastructure in AWS and configuring HA pairs, pools, and injecting dynamically created members into those pools. \n\n So we are going to create a single file. We will call it site.yaml . Inside of that file paste in the following. \n\n \n- name: My first play\n hosts: localhost\n connection: local\n gather_facts: true\n \n tasks:\n - name: Create a file\n copy:\n dest: \"/tmp/test.txt\"\n content: \"This is some content\" \n\n This file is what Ansible refers to as a Playbook. Inside of this playbook file we have a single Play (My first play). There can be multiple Plays in a Playbook. \n\n Let's explore what's going on here, as well as touch upon the details of the play itself. First, that Play. \n\n Our play is composed of a preamble that contains the following \n\n name hosts connection gather_facts \n\n The name is an arbitrary name that we give to our Play so that we will know what is being executed if we need to debug something or otherwise generate a reasonable status message. ALWAYS provide a name for your Plays, Tasks, everything that supports the name syntax. \n\n Next, the hosts line specifies which hosts we want to target in our Play. For this Play we have a single host; localhost . We can get much more complicated than this though, to include \n\n patterns of hosts groups of hosts groups of groups of hosts dynamically created hosts hosts that are not even real \n\n You get the point. \n\n Next, the connection line tells Ansible how to connect to the hosts. Usually this is the default value ssh . In this case though, because I am operating on the localhost, I can skip SSH altogether and simply say local . \n\n After that, I used the gather_facts line to tell Ansible that it should interrogate the remote system (in this case the system localhost) to gather tidbits of information about it. These tidbits can include the installed operating system, the version of the OS, what sort of hardware is installed, etc. \n\n After the preamble is written, you can see that I began a new block of \"stuff\". In this case, the tasks associated with this Play. Tasks are Ansible's way of performing work on the system. The task that I am running here is using the copy module. As I did with my Play earlier, I provide a name for this task. Always name things! After that, the body of the module is written. There are two arguments that I have provided to this module (which are documented more in the References section below) \n\n dest content \n\n I won't go into great deal here because the module documentation is very clear, but suffice it to say that dest is where I want the file written and content is what I want written in the file. \n\n Running the playbook \n\n We can run this playbook using the ansible-playbook command. For example. \n\n \nSEA-ML-RUPP1:~ trupp$ ansible-playbook -i notahost, site.yaml \n\n The output of the command should resemble the following \n\n \nPLAY [My first play] ******************************************************\n \nGATHERING FACTS ***************************************************************\nok: [localhost]\n \nTASK: [Create a file] *********************************************************\nchanged: [localhost]\n \nPLAY RECAP ********************************************************************\nlocalhost : ok=2 changed=1 unreachable=0 failed=0 \n\n We can also see that the file we created has the content that we expected. \n\n \nSEA-ML-RUPP1:~ trupp$ cat /tmp/test.txt\nThis is some content \n\n A brief aside on the syntax to run the command. Ansible requires that you specify an inventory file to provide hosts that it can orchestrate. In this specific example, we are not specifying a file. Instead we are doing the following \n\n Specifying an arbitrary string (notahost) Followed by a comma \n\n In Ansible, this is a short-hand trick to skip the requirement that an inventory file be specified. The comma is the key part of the argument. Without it, Ansible will look for a file called notahost and (hopefully) not find it; raising an error otherwise. \n\n The output of the command is shown next. The output is actually fairly straight-forward to read. It lists the PLAY s and TASK s that are running (as well as their names...see, I told you you wanted to have names). The status of the Tasks is also shown. This can be values such as \n\n changed ok failed skipped unreachable \n\n Finally, all Ansible Playbook runs end with a PLAY RECAP where Ansible will tell you what the status of the various plays on your hosts were. It is at this point where a Playbook will be considered successful or not. In this case, the Playbook was completely successful because there were not unreachable hosts nor failed hosts. \n\n Summary \n\n This was a brief introduction to the orchestration and automation system Ansible. \n\n There are far more complex subjects related to Ansible that I will touch upon in future posts. If you found this information useful, rate it as such. If you would like to see more advanced topics covered, videos demo'd, code samples written, or anything else on the subject, let me know in the comments below. \n\n Many organizations, both large and small, use DevOps tools like the one presented in this post. Ansible has several features, per design, that make it attractive to these organizations (such as being agent-less, and having minimum requirements). \n\n If you'd like to see crazy sophisticated examples of Ansible in use...well...we'll get there. You need to rate and comment on my posts though to let me know that you want to see more. \n\n References \n\n copy - Copies files to remote locations. — Ansible Documentation raw - Executes a low-down and dirty SSH command — Ansible Documentation Variables — Ansible Documentation ","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})@stringLength":"13466","kudosSumWeight":0,"repliesCount":12,"readOnly":false,"images":{"__typename":"AssociatedImageConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"videos":{"__typename":"VideoConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"Conversation:conversation:280332":{"__typename":"Conversation","id":"conversation:280332","topic":{"__typename":"TkbTopicMessage","uid":280332},"lastPostingActivityTime":"2023-06-05T22:59:20.470-07:00","solved":false},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMTEyMDdpMTM1MDFBRjU3NjI4RDRFNg?revision=2\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMTEyMDdpMTM1MDFBRjU3NjI4RDRFNg?revision=2","title":"0151T000003d6l3QAA.png","associationType":"BODY","width":854,"height":446,"altText":null},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMTc3OWk1RDBFOEZFNDU2MzE5MTNB?revision=2\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMTc3OWk1RDBFOEZFNDU2MzE5MTNB?revision=2","title":"0151T000003d6l4QAA.png","associationType":"BODY","width":1326,"height":1198,"altText":null},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItODcxMWk3NTFFMzdBRjc2MUNCOTI1?revision=2\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItODcxMWk3NTFFMzdBRjc2MUNCOTI1?revision=2","title":"0151T000003d6l5QAA.png","associationType":"BODY","width":746,"height":432,"altText":null},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMzA1OGkyRkY4RTRCNTA1QzAxOUYz?revision=2\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMzA1OGkyRkY4RTRCNTA1QzAxOUYz?revision=2","title":"0151T000003d6l6QAA.png","associationType":"BODY","width":672,"height":422,"altText":null},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItOTkyOWlDNDg1ODNDMUYyOURCMzAz?revision=2\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItOTkyOWlDNDg1ODNDMUYyOURCMzAz?revision=2","title":"0151T000003d6l7QAA.png","associationType":"BODY","width":670,"height":488,"altText":null},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMTQ0NDRpM0YzRDEzMkM5NEMxOTI5OA?revision=2\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMTQ0NDRpM0YzRDEzMkM5NEMxOTI5OA?revision=2","title":"0151T000003d6l8QAA.png","associationType":"BODY","width":926,"height":764,"altText":null},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMTEyMTBpRDgxM0RBOUFGQTYyRDhCOQ?revision=2\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMTEyMTBpRDgxM0RBOUFGQTYyRDhCOQ?revision=2","title":"0151T000003d6l9QAA.png","associationType":"BODY","width":916,"height":720,"altText":null},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItNjg0MGk1NjAyODZDRjIxRkE4RDI4?revision=2\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItNjg0MGk1NjAyODZDRjIxRkE4RDI4?revision=2","title":"0151T000003d6lAQAQ.png","associationType":"BODY","width":648,"height":472,"altText":null},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItNjU4OGlGRjJFMTEzQTU2NjQxQ0Yx?revision=2\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItNjU4OGlGRjJFMTEzQTU2NjQxQ0Yx?revision=2","title":"0151T000003d6lGQAQ.png","associationType":"BODY","width":934,"height":712,"altText":null},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItNTI5MmkwMzMyNkU3NDhBQjk0RUFC?revision=2\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItNTI5MmkwMzMyNkU3NDhBQjk0RUFC?revision=2","title":"0151T000003d6lHQAQ.png","associationType":"BODY","width":936,"height":714,"altText":null},"TkbTopicMessage:message:280332":{"__typename":"TkbTopicMessage","subject":"Existing Ansible BIG-IP modules","conversation":{"__ref":"Conversation:conversation:280332"},"id":"message:280332","revisionNum":2,"uid":280332,"depth":0,"board":{"__ref":"Tkb:board:TechnicalArticles"},"author":{"__ref":"User:user:217977"},"teaser@stripHtml({\"removeProcessingText\":true,\"truncateLength\":-1})":"","introduction":"","metrics":{"__typename":"MessageMetrics","views":1664},"postTime":"2016-01-29T06:00:00.000-08:00","lastPublishTime":"2023-06-05T22:59:20.470-07:00","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})":" Right around the time that I started at F5, I was at the pinnacle of my exposure to Ansible. So imagine my surprise when I saw BIG-IP modules in the Ansible core product! I immediately wanted to know which one of my colleagues I could go talk Ansible-fu with! \n\n I cracked open the source code, found the names of contributors, made haste to the corporate phone book to look up where they sat, and...wait a second... \n\n ...no entries in the phone book? \n\n ...that curious look in colleagues eyes that says \"I haven't the foggiest idea what you are talking about\". \n\n Then it hit me...the BIG-IP modules didn't originate at F5. \n\n Upon further investigation, it became clear to me that, indeed, we had no skin in this game. These modules originated from two enterprising individuals who I found on DevCentral, and I would be remiss to exclude their valuable contributions to the Ansible+F5 cause, so in this post I want to highlight those contributions and bring others up-to-speed on current Ansible+BIG-IP functionality. \n\n Credit where it is due \n\n As far as I can tell from perusing Ansible's git logs, the existing BIG-IP modules in Ansible are the work of Matt Hite and Serge van Ginderachter. Both are DevCentral contributors, and have had a presence in the Ansible community from (at least) 2013 when their modules landed in the tool. \n\n Among the modules that they had a hand in are \n\n bigip_facts bigip_node bigip_pool bigip_pool_member bigip_monitor_http bigip_monitor_tcp \n\n Since that time, I've seen even more folks step up to the plate with Ansible modules that will be making their appearance in upcoming releases. \n\n Well, I wanted to have a hand in this work too. I figured I had a perfect opportunity to help because, \n\n I had some background in Ansible I had access to unlimited BIG-IP technical resources \n\n It was only a question of how to get involved. \n\n And then, Matt posted this in a pull request (PR)... \n\n \n Unfortunately I don't have GTM to smoke test this with. Can you solicit some testers on the mailing list? \n \n\n And Serge followed with \n\n \n So same here, not tested as no GTM gear, but an offline code review. \n \n\n Bingo...I was in. \n\n If there's one area that I figured I could have the greatest impact, it's the area of technical resources. There is BIG-IP stuff all over the place at F5, and I sit next to, or at least near, many of the people who have knowledge of the products; far more advanced knowledge than I do. Many of those same people hang out and contribute on DevCentral. Paired with my knowledge of Ansible, I figured I could help test BIG-IP related PRs to validate their functionality beyond what a code review offered. \n\n So that's what I did (and what we're about to do). \n\n Before you begin \n\n If you were around for the earlier introductory article to Ansible that I posted a couple days ago, pull up a terminal to that machine. We need to install one more dependency that is common to all of the BIG-IP modules in Ansible; bigsuds . \n\n \n pip install bigsuds \n\n With that in place, you're ready to work with the Ansible modules. \n\n Additionally, I am going to be using a very minimal inventory file that just includes a single BIG-IP. You will want to adjust yours for your environment, but here is mine. \n\n \n [test] \n big-ip01.internal \n\n Note that the name above is available in my local DNS. If you only have an IP address to work with, you can just add that to your inventory file. \n\n For example \n\n \n[test]\n192.168.10.2 \n\n Also note that I included a line called [test] . In Ansible this is referred to as a group. We will be revisiting this in the future as we begin orchestrating multiple BIG-IPs. \n\n Let's dive in to some of Matt and Serge's BIG-IP modules! \n\n bigip_facts \n\n In Ansible, there is a pattern you often see over and over. This is the pattern of modules that provide information, and modules that change settings. For this tutorial I'll refer to them as, \n\n reader modules writer modules \n\n Without exception, reader modules are suffixed with an _facts string. While writer modules, are not. \n\n So, with that in mind...pop quiz! \n\n If I asked you whether the bigip_pool module was a reader module or a writer module, you would say...writer! If I asked you whether the bigip_pool_facts module was a reader module or a writer module, you would say...reader! \n\n The bigip_facts module will return to you a number of facts about the BIG-IP in question. Let's take a look. First, my playbook. \n\n \n- name: Test bigip_facts\n hosts: test\n connection: local\n \n tasks:\n - name: Get all of the facts from my BIG-IP\n bigip_facts:\n server: \"{{ inventory_hostname }}\"\n user: \"admin\"\n password: \"admin\"\n include: \"system_info\" \n\n And now, let's just run it to see what sort of output it generates. \n\n \nansible-playbook -i hosts site.yaml -vvvv \n\n You should be presented with information resembling the following. \n\n \nroot@debian:~# ansible-playbook -i hosts site.yaml -vvvv\nPLAY [Test bigip_facts] *******************************************************\nTASK: [Get all of the facts from my BIG-IP] ***********************************\n...\nok: [big-ip01.internal] => {\"ansible_facts\": {\"system_info\": {\"base_mac_address\": \"8A:83:8B:B1:AE:F7\",\n\"blade_temperature\": [], \"chassis_slot_information\": [], \"globally_unique_identifier\": \"8A:83:8B:B1:AE:F7\",\n\"group_id\": \"DefaultGroup\", \"hardware_information\": [{\"model\": \"Common KVM processor\", \"name\": \"cpus\",\n\"slot\": 0, \"type\": \"HARDWARE_BASE_BOARD\", \"versions\": [{\"name\": \"cache size\", \"value\": \"4096 KB\"}, {\"name\":\n\"cores\", \"value\": \"2\"}, {\"name\": \"cpu MHz\", \"value\": \"2199.994\"}]}],\n...\n\"os_version\": \"#1 SMP Mon Aug 11 19:54:07 PDT 2014\", \"platform\": \"Z100\", \"product_category\": \"Virtual Edition\",\n\"switch_board_part_revision\": null, \"switch_board_serial\": null, \"system_name\": \"Linux\"}, \"time\": {\"day\": 28,\n\"hour\": 22, \"minute\": 1, \"month\": 1, \"second\": 7, \"year\": 2016}, \"time_zone\": {\"gmt_offset\": -8,\n\"is_daylight_saving_time\": false, \"time_zone\": \"PST\"}, \"uptime\": 854}}, \"changed\": false}\nroot@debian:~# \n\n This module can output a lot of information that you can use in later tasks. Note that I just asked for the system_info facts, but there are a number of them that you can ask for, including \n\n address_class certificate client_ssl_profile device device_group interface key node pool rule self_ip software system_info traffic_group trunk virtual_address virtual_server vlan \n\n You can include multiple types of facts by separating them with a comma. For example \n\n \n- name: Test bigip_facts\n hosts: test\n connection: local\n \n tasks:\n - name: Get all of the facts from my BIG-IP\n bigip_facts:\n server: \"{{ inventory_hostname }}\"\n user: \"admin\"\n password: \"admin\"\n include: \"system_info,software,self_ip\" \n\n Returns facts representing the system_info , software , and self IPs. \n\n You can use the generated facts in later tasks by referencing their JSON keys. For example \n\n \n- name: Test bigip_facts\n hosts: test\n connection: local\n \n tasks:\n - name: Get all of the facts from my BIG-IP\n bigip_facts:\n server: \"{{ inventory_hostname }}\"\n user: \"admin\"\n password: \"admin\"\n include: \"system_info,software,self_ip\"\n \n - name: Mention the Self IP\n debug:\n msg: \"I have self IP {{ self_ip['/Common/net1'].address }}\"\n \n - name: Mention the software\n debug:\n msg: \"I have software version {{ software[0].version }}\" \n\n And the (truncated) output \n\n \n...\nTASK: [Mention the Self IP] ***************************************************\nok: [big-ip01.internal] => {\n \"msg\": \"I have self IP 10.2.2.2\"\n}\n \nTASK: [Mention the software] **************************************************\nok: [big-ip01.internal] => {\n \"msg\": \"I have software version 11.6.0\"\n}\n... \n\n bigip_node \n\n This module allows you to manipulate nodes in a BIG-IP. Nodes are logical objects on your BIG-IP that identify the IP address of a physical node on your network. In terms of what we can do with them with the existing BIG-IP modules, you can use this module to create nodes that you can later assign to pool members. \n\n First, let's show you the playbook that I'm going to run. \n\n \n- name: Node shenanigans\n hosts: test\n connection: local\n \n tasks:\n - name: Add a new node\n bigip_node:\n server: \"{{ inventory_hostname }}\"\n user: \"admin\"\n password: \"admin\"\n host: \"10.2.1.1\"\n name: \"member1\" \n\n This is a simple example of creating a node in my BIG-IP. As is probably apparent if I am going to be working with pools, I would want to create many nodes. I'll hold off on that until a future example, but hopefully this clarifies the point of how to create the object that we can later assign to pool members. \n\n Let's run it! \n\n \nansible-playbook -i hosts site.yaml \n\n And the output we should expect to see looks like this \n\n \nroot@debian:~# ansible-playbook -i hosts site.yaml\n \nPLAY [Node shenanigans] *******************************************************\n \nTASK: [Add a new node] ********************************************************\nchanged: [big-ip01.internal]\n \nPLAY RECAP ********************************************************************\nbig-ip01.internal : ok=1 changed=1 unreachable=0 failed=0\n \nroot@debian:~# \n\n Now, just to clarify what gets dropped where, and where these nodes can be used, let's first look at the Local Traffic > Nodes screen. \n\n \n\n Now, let's navigate over to the pool screen and try to create a new pool. On the new pool creation screen, we have the option of specifying members of that pool. If we click on Node List, well, look at that. Our new node. \n\n \n\n bigip_pool \n\n This module allows you to create pools on your BIG-IPs. To these pools we can later add pool members. With this and the bigip_pool_member module, you can control the basic load balancing functionality of the BIG-IP. \n\n First, just to prove that I have nothing up my BIG-IP sleeve, here's my current pool list. \n\n \n\n And now, for my playbook \n\n \n- name: Create a pool\n hosts: test\n connection: local\n \n tasks:\n - name: Create the pool1 pool\n bigip_pool:\n server: \"{{ inventory_hostname }}\"\n user: \"admin\"\n password: \"admin\"\n name: \"pool1\" \n\n With a wave of my magic wand... \n\n \nroot@debian:~# ansible-playbook -i hosts site.yaml\n \nPLAY [Create a pool] *******************************************************\n \nTASK: [Create the pool1 pool] ***********************************\n...\n \nchanged: [big-ip01.internal] => {\"changed\": true}\n \nPLAY RECAP ********************************************************************\nbig-ip01.internal : ok=1 changed=1 unreachable=0 failed=0\n \nroot@debian:~# \n\n ...and my pool list has been changed. \n\n \n\n The bigip_pool module has a number of other options that let you adjust settings of the pool. They are all documented on the module's page. \n\n bigip_pool_member \n\n With the bigip_pool_member module, you can manipulate the members of any of the pools on your BIG-IP. This module, in particular, is a crucial part of a rolling upgrade strategy that you may undertake when upgrading software on members of the pool. \n\n Consider the following scenario. \n\n Remove (or disable) the member from the pool Upgrade the member's software (you pick the software, but let's say Apache for example) Verify the software upgrade Add (or enable) the member back to the old pool, or, add the member to a new pool \n\n This module can be used for steps 1 and 4. Let's have a look at my playbook. \n\n \n- name: Pool member manipulation\n hosts: test\n connection: local\n \n tasks:\n - name: Drop members out of pool1\n bigip_pool_member:\n server: \"{{ inventory_hostname }}\"\n user: \"admin\"\n password: \"admin\"\n state: \"absent\"\n host: \"{{ item }}\"\n pool: \"pool1\"\n port: \"22\"\n with_items:\n - member1\n - member2\n \n - name: Intermediate processing\n debug:\n msg: \"Upgrade some software\"\n \n - name: More intermediate processing\n debug:\n msg: \"Install some configuration\"\n \n - name: Add member1 node back\n bigip_node:\n server: \"{{ inventory_hostname }}\"\n user: \"admin\"\n password: \"admin\"\n name: \"{{ item }}\"\n host: \"10.2.1.1\"\n state: \"present\"\n with_items:\n - member1\n \n - name: Add member2 node back\n bigip_node:\n server: \"{{ inventory_hostname }}\"\n user: \"admin\"\n password: \"admin\"\n name: \"{{ item }}\"\n host: \"10.2.1.2\"\n state: \"present\"\n with_items:\n - member2\n \n - name: Add member1 back to pool1\n bigip_pool_member:\n server: \"{{ inventory_hostname }}\"\n user: \"admin\"\n password: \"admin\"\n host: \"{{ item }}\"\n pool: \"pool1\"\n port: \"22\"\n state: \"present\"\n with_items:\n - member1\n \n - name: Add member2 to pool2\n bigip_pool_member:\n server: \"{{ inventory_hostname }}\"\n user: \"admin\"\n password: \"admin\"\n host: \"{{ item }}\"\n pool: \"pool2\"\n port: \"22\"\n state: \"present\"\n with_items:\n - member2 \n\n And before we run it, let's just take a peek at my current pools. \n\n \n\n As well as the members of pool1 \n\n \n\n And the members of pool2 \n\n \n\n Now, let's have a go at running the playbook. \n\n \nansible-playbook -i hosts site2.yaml \n\n After it runs, you should have output that resembles something like this. \n\n \nroot@debian:~# ansible-playbook -i hosts site.yaml\n \nPLAY [Pool member manipulation] ***********************************************\n \nTASK: [Drop members out of pool1] *********************************************\nchanged: [big-ip01.internal] => (item=member1)\nok: [big-ip01.internal] => (item=member2)\n \nTASK: [Intermediate processing] ***********************************************\nok: [big-ip01.internal] => {\n \"msg\": \"Upgrade some software\"\n}\n \nTASK: [More intermediate processing] ******************************************\nok: [big-ip01.internal] => {\n \"msg\": \"Install some configuration\"\n}\n \nTASK: [Add member1 node back] *************************************************\nchanged: [big-ip01.internal] => (item=member1)\n \nTASK: [Add member2 node back] *************************************************\nok: [big-ip01.internal] => (item=member2)\n \nTASK: [Add member1 back to pool1] *********************************************\nchanged: [big-ip01.internal] => (item=member1)\n \nTASK: [Add member2 to pool2] **************************************************\nok: [big-ip01.internal] => (item=member2)\n \nPLAY RECAP ********************************************************************\nbig-ip01.internal : ok=7 changed=3 unreachable=0 failed=0\n \nroot@debian:~# \n\n And your pool members should be different, check out the screenshots below. \n\n The pool list \n\n \n\n Members in pool1 \n\n \n\n Members in pool2 \n\n \n\n To Summarize \n\n BIG-IP would not have a presence in Ansible if it were not for Matt and Serge's initiative. \n\n Based on the work they started, I'm happy to assist with testing and contributing new modules moving forward. In my entirely too biased opinion, Ansible fits in well with the tools and products I work on. Perhaps it will also fit in well with your workflow. If we've piqued your interest, then the Ansible mailing list is a great place to ask more Ansible related questions and further solidify your understanding. \n\n If you liked this article and want to see more like it, rate it as such. Have comments? Questions? Something else? Leave a comment below! \n\n References \n\n http://docs.ansible.com/ansible/bigip_facts_module.html http://docs.ansible.com/ansible/bigip_node_module.html http://docs.ansible.com/ansible/bigip_pool_module.html http://docs.ansible.com/ansible/bigip_pool_member_module.html https://support.f5.com/kb/en-us/products/big-ip_ltm/manuals/product/ltm_configuration_guide_10_0_0/ltm_nodes.html#1188710 https://groups.google.com/forum/#!forum/ansible-project ","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})@stringLength":"16677","kudosSumWeight":0,"repliesCount":14,"readOnly":false,"images":{"__typename":"AssociatedImageConnection","edges":[{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDE","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMTEyMDdpMTM1MDFBRjU3NjI4RDRFNg?revision=2\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDI","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMTc3OWk1RDBFOEZFNDU2MzE5MTNB?revision=2\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDM","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItODcxMWk3NTFFMzdBRjc2MUNCOTI1?revision=2\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDQ","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMzA1OGkyRkY4RTRCNTA1QzAxOUYz?revision=2\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDU","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItOTkyOWlDNDg1ODNDMUYyOURCMzAz?revision=2\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDY","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMTQ0NDRpM0YzRDEzMkM5NEMxOTI5OA?revision=2\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDc","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItMTEyMTBpRDgxM0RBOUFGQTYyRDhCOQ?revision=2\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDg","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItNjg0MGk1NjAyODZDRjIxRkE4RDI4?revision=2\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDk","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItNjU4OGlGRjJFMTEzQTU2NjQxQ0Yx?revision=2\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDEw","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODAzMzItNTI5MmkwMzMyNkU3NDhBQjk0RUFC?revision=2\"}"}}],"totalCount":10,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"videos":{"__typename":"VideoConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"Conversation:conversation:290998":{"__typename":"Conversation","id":"conversation:290998","topic":{"__typename":"TkbTopicMessage","uid":290998},"lastPostingActivityTime":"2023-04-14T15:17:23.504-07:00","solved":false},"TkbTopicMessage:message:290998":{"__typename":"TkbTopicMessage","subject":"F5 Automation with Ansible Tips and Tricks","conversation":{"__ref":"Conversation:conversation:290998"},"id":"message:290998","revisionNum":3,"uid":290998,"depth":0,"board":{"__ref":"Tkb:board:TechnicalArticles"},"author":{"__ref":"User:user:195328"},"teaser@stripHtml({\"removeProcessingText\":true,\"truncateLength\":-1})":"","introduction":"","metrics":{"__typename":"MessageMetrics","views":6137},"postTime":"2021-12-20T09:21:37.000-08:00","lastPublishTime":"2023-04-14T15:17:23.504-07:00","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})":" Getting Started with Ansible and F5 \n In this article we are going to provide you with a simple set of videos that demonstrate step by step how to implement automation with Ansible. In the last video, however we will demonstrate how telemetry and automation may be used in combination to address potential performance bottlenecks and ensure application availability. \n To start, we will provide you with details on how to get started with Ansible automation using the Ansible Automation Platform®: \n \n Backing up your F5 device \n Once a user has installed and configured Ansible Automation Platform, we will now transition to a basic maintenance function – an automated backup of a BIG-IP hardware device or Virtual Edition (VE). This is always recommended before major changes are made to our BIG-IP devices \n \n Configuring a Virtual Server \n Next, we will use Ansible to configure a Virtual Server, a task that is most frequently performed via manual functions via the BIG-IP. When changes to a BIG-IP are infrequent, manual intervention may not be so cumbersome. However large enterprise customers may need to perform these tasks hundreds of times: \n \n Replace an SSL Certificate \n The next video will demonstrate how to use Ansible to replace an SSL certificate on a BIG-IP. It is important to note that this video will show the certificate being applied on a BIG-IP and then validated by browsing to the application website: \n \n Configure and Deploy an iRule \n The next administrative function will demonstrate how to configure and push an iRule using the Ansible Automation Platform® onto a BIG-IP device. Again this is a standard administrative task that can be simply automated via Ansible: \n \n Delete the Existing Virtual Server \n Ok so now we have to delete the above configuration to roll back to a steady state. This is a common administrative task when an application is retired. We again demonstrate how Ansible automation may be used to perform these simple administrative tasks: \n \n Telemetry and Automation: Using Threshold Triggers to Automate Tasks and Fix Performance Bottlenecks \n Now you have a clear demonstration as to how to utilize Ansible automation to perform routine tasks on a BIG-IP platform. Once you have become proficient with more routine Ansible tasks, we can explore more high-level, sophisticated automation tasks. In the below demonstration we show how BIG-IP administrators using SSL Orchestrator® (SSLO) can combine telemetry with automation to address performance bottlenecks in an application environment: \n \n Resources: \n So that is a short series of tutorials on how to perform routine tasks using automation plus a preview of a more sophisticated use of automation based upon telemetry and automatic thresholds. For more detail on our partnership, please visit our F5/Ansible page or visit the Red Hat Automation Hub for information on the F5 Ansible certified collections. \n https://www.f5.com/ansible \n https://www.ansible.com/products/automation-hub \n https://galaxy.ansible.com/f5networks/f5_modules ","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})@stringLength":"3072","kudosSumWeight":2,"repliesCount":1,"readOnly":false,"images":{"__typename":"AssociatedImageConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"videos":{"__typename":"VideoConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"Conversation:conversation:291479":{"__typename":"Conversation","id":"conversation:291479","topic":{"__typename":"TkbTopicMessage","uid":291479},"lastPostingActivityTime":"2022-05-13T03:50:50.230-07:00","solved":false},"User:user:321184":{"__typename":"User","uid":321184,"login":"Paul_Deakin","registrationData":{"__typename":"RegistrationData","status":null},"deleted":false,"avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/m_assets/avatars/default/avatar-6.svg?time=0"},"id":"user:321184"},"TkbTopicMessage:message:291479":{"__typename":"TkbTopicMessage","subject":"Using CryptoNice as a Sanity-Checking Tool for Automated Application Deployments with Ansible","conversation":{"__ref":"Conversation:conversation:291479"},"id":"message:291479","revisionNum":1,"uid":291479,"depth":0,"board":{"__ref":"Tkb:board:TechnicalArticles"},"author":{"__ref":"User:user:321184"},"teaser@stripHtml({\"removeProcessingText\":true,\"truncateLength\":-1})":"","introduction":"","metrics":{"__typename":"MessageMetrics","views":1318},"postTime":"2021-04-27T09:13:54.000-07:00","lastPublishTime":"2021-04-27T09:13:54.000-07:00","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})":" Any good automation pipeline should have some validation built in to perform sanity checks on what it should have done. This article describes how you can use CryptoNice as a simple and easy way of sanity checking the SSL/TLS configuration of your automated application deployments, by integrating CryptoNice into your pipeline directly after your automation solution has deployed the application. \n\n I am going to show a simple Ansible playbook that illustrates this point. The Ansible playbook deploys an application to a BIG-IP that resides in AWS, but it could be any BIG-IP on premises or in any cloud. After the deployment is complete, I use CryptoNice to validate that the application is reachable via TLS and that the deployed VIP is using best practices for SSL deployments. \n\n What Is CryptoNice? \n\n CryptoNice is both a command line tool and Python library that is developed by F5 Labs and is publicly available; it provides the ability to scan and report on the configuration of SSL/TLS for your internet or internal-facing web services. Built using the sslyze API and SSL, http-client, and DNS libraries, CryptoNice collects data on a given domain and performs a series of tests to check TLS configuration. \n\n You can get CryptoNice here: https://github.com/F5-Labs/cryptonice \n\n What Is Ansible? \n\n Ansible is an open-source software-provisioning, configuration-management, and application-deployment tool enabling infrastructure as code. It runs on many Unix-like systems, and can configure both Unix-like systems as well as Microsoft Windows and also F5 BIG-IPs. \n\n You can learn how to install Ansible here: https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html \n\n F5 publishes instructions on how to integrate the Ansible plugins here: https://clouddocs.f5.com/products/orchestration/ansible/devel/ \n\n \n\n In this article I use the published version 1 F5 Ansible plugin that uses the iControl REST API. If we take a look briefly under the hood here, this plugin is using an imperative API. As of today, F5 has a version 2 plugin in preview that focuses on managing F5 BIG-IP/BIG-IQ through declarative APIs such as AS3, DO, TS, and CFE. You can also check the version 2 plugin preview out on F5 Cloud Docs. \n\n I used a stock Ubuntu image as a basis for my Ansible server and followed the instructions for installing Ansible on Ubuntu, then followed the instructions for installing the F5 plugin for Ansible referenced above. \n\n You can take a look at the F5/Ansible 1.0 plugin that is published on the Ansible Galaxy Hub here: https://galaxy.ansible.com/f5networks/f5_modules \n\n \n\n For my demonstration, I also installed CryptoNice on the same server where Ansible and the F5 Ansible plugin are installed; at that point you are off to the races and you can build a simple Ansible script and begin to automate a BIG-IP. \n\n The F5 Ansible Module provides you with a great deal of programmability for the BIG-IP basic onboarding, WAF, APM, and LTM. The following is a great reference to give you an idea of the scope of capabilities with Ansible examples that this module provides. You can also automate the infrastructure creation if you so choose, meaning you could stand up a BIG-IP in AWS and then configure everything required to get the device onboarded, and then after that, configure traffic-management objects like VIPs/pools, etc. \n\n https://clouddocs.f5.com/products/orchestration/ansible/devel/modules/module_index.html \n\n For my test, I created a simple Ansible script that automates the creation of a VIP with an iRule to respond to http get requests. The example also associates a pool and pool members to the VIP to give you an idea of what a simple application deployment may look like. After that, I run a CryptoNice to test the quality of the SSL/TLS. \n\n My simple Ansible playbook looks like this: \n\n \n---\n\n\n- name: Create a VIP, pool and pool members\n hosts: f5\n connection: local\n\n\n vars:\n provider:\n password: notmypassword\n server: f5cove.me\n user: auser\n validate_certs: no\n server_port: 8443\n\n\n tasks:\n - name: Create a pool\n bigip_pool:\n provider: \"{{ provider }}\"\n lb_method: ratio-member\n name: examplepool\n slow_ramp_time: 120\n delegate_to: localhost\n\n\n - name: Add members to pool\n bigip_pool_member:\n provider: \"{{ provider }}\"\n description: \"webserver {{ item.name }}\"\n host: \"{{ item.host }}\"\n name: \"{{ item.name }}\"\n pool: examplepool\n port: '80'\n with_items:\n - host: 10.0.0.68\n name: web01\n - host: 10.0.0.67\n name: web02\n delegate_to: localhost\n\n\n - name: Create a VIP\n bigip_virtual_server:\n provider: \"{{ provider }}\"\n description: avip\n destination: 10.0.0.66\n name: vip-1\n irules:\n - responder\n pool: examplepool\n port: '443'\n snat: Automap\n profiles:\n - http\n - f5cove\n delegate_to: localhost\n\n\n - name: Create Ridirect\n bigip_virtual_server:\n provider: \"{{ provider }}\"\n description: avipredirect\n destination: 10.0.0.66\n name: vip-1-redirect\n irules:\n - _sys_https_redirect\n port: '80'\n snat: None\n profiles:\n - http\n delegate_to: localhost \n\n\n- name: play cryptonice nicely\n hosts: 127.0.0.1\n connection: local\n tasks:\n - pause:\n seconds: 30\n\n\n - name: run cryptonice\n shell: \"cryptonice f5cove.me\"\n register: output\n\n\n - debug: var=output.stdout_lines\n \n\n \n\n My output from running the playbook looks like this: \n\n \nPLAY [Create a VIP, pool and pool members] *************************************************************************************************************************************************************************************************\n\nTASK [Gathering Facts] *********************************************************************************************************************************************************************************************************************\nok: [1.2.3.4]\n\nTASK [Create a pool] ***********************************************************************************************************************************************************************************************************************\nok: [1.2.3.4 -> localhost]\n\nTASK [Add members to pool] *****************************************************************************************************************************************************************************************************************\nok: [1.2.3.4 -> localhost] => (item={'host': '10.0.0.68', 'name': 'web01'})\nok: [1.2.3.4 -> localhost] => (item={'host': '10.0.0.67', 'name': 'web02'})\n\nTASK [Create a VIP] ************************************************************************************************************************************************************************************************************************\nchanged: [1.2.3.4 -> localhost]\n\nTASK [Create Ridirect] *********************************************************************************************************************************************************************************************************************\nok: [1.2.3.4 -> localhost]\n\nPLAY [play cryptonice nicely] **************************************************************************************************************************************************************************************************************\n\nTASK [Gathering Facts] *********************************************************************************************************************************************************************************************************************\nok: [localhost]\n\nTASK [pause] *******************************************************************************************************************************************************************************************************************************\nPausing for 30 seconds\n(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)\nok: [localhost]\n\nTASK [run cryptonice] **********************************************************************************************************************************************************************************************************************\nchanged: [localhost]\n\n\nTASK [debug] *******************************************************************************************************************************************************************************************************************************\nok: [localhost] => {\n \"output.stdout_lines\": [\n \"Pre-scan checks\",\n \"-------------------------------------\",\n \"Scanning f5cove.me on port 443...\",\n \"Analyzing DNS data for f5cove.me\",\n \"Fetching additional records for f5cove.me\",\n \"f5cove.me resolves to 34.217.132.104\",\n \"34.217.132.104:443: OPEN\",\n \"TLS is available: True\",\n \"Connecting to port 443 using HTTPS\",\n \"Queueing TLS scans (this might take a little while...)\",\n \"Looking for HTTP/2\",\n \"\",\n \"\",\n \"RESULTS\",\n \"-------------------------------------\",\n \"Hostname:\\t\\t\\t f5cove.me\",\n \"\",\n \"Selected Cipher Suite:\\t\\t ECDHE-RSA-AES128-GCM-SHA256\",\n \"Selected TLS Version:\\t\\t TLS_1_2\",\n \"\",\n \"Supported protocols:\",\n \"TLS 1.2:\\t\\t\\t Yes\",\n \"TLS 1.1:\\t\\t\\t Yes\",\n \"TLS 1.0:\\t\\t\\t Yes\",\n \"\",\n \"TLS fingerprint:\\t\\t 29d29d15d29d29d21c29d29d29d29d930c599f185259cdd20fafb488f63f34\",\n \"\",\n \"\",\n \"\",\n \"CERTIFICATE\",\n \"Common Name:\\t\\t\\t f5cove.me\",\n \"Issuer Name:\\t\\t\\t R3\",\n \"Public Key Algorithm:\\t\\t RSA\",\n \"Public Key Size:\\t\\t 2048\",\n \"Signature Algorithm:\\t\\t sha256\",\n \"\",\n \"Certificate is trusted:\\t\\t False (Mozilla not trusted)\",\n \"Hostname Validation:\\t\\t OK - Certificate matches server hostname\",\n \"Extended Validation:\\t\\t False\",\n \"Certificate is in date:\\t\\t True\",\n \"Days until expiry:\\t\\t 29\",\n \"Valid From:\\t\\t\\t 2021-02-12 23:28:14\",\n \"Valid Until:\\t\\t\\t 2021-05-13 23:28:14\",\n \"\",\n \"OCSP Response:\\t\\t\\t Successful\",\n \"Must Staple Extension:\\t\\t False\",\n \"\",\n \"Subject Alternative Names:\",\n \"\\t f5cove.me\",\n \"\",\n \"Vulnerability Tests:\",\n \"No vulnerability tests were run\",\n \"\",\n \"HTTP to HTTPS redirect:\\t\\t False\",\n \"None\",\n \"\",\n \"RECOMMENDATIONS\",\n \"-------------------------------------\",\n \"HIGH - TLSv1.0 Major browsers are disabling TLS 1.0 imminently. Carefully monitor if clients still use this protocol. \",\n \"HIGH - TLSv1.1 Major browsers are disabling this TLS 1.1 immenently. Carefully monitor if clients still use this protocol. \",\n \"Low - CAA Consider creating DNS CAA records to prevent accidental or malicious certificate issuance.\",\n \"\",\n \"Scans complete\",\n \"-------------------------------------\",\n \"Total run time: 0:00:05.688562\"\n ]\n}\n\n\n \n\n Note that after the playbook is complete, CryptoNice is telling me that I need to improve the quality of my SSL profile on my BIG-IP. \n\n As a result, I create a second SSL profile and \n\n Disable TLS 1.0 and 1.1. Upgrade the certificate from a 2048 bit key to a 4096 bit key. Make sure that the certificate bundle is configured correctly to ensure that the certificate is properly trusted. Create a cipher rule and group and use the following cipher string to improve the quality of the SSL connections: \n\n \n\n !EXPORT:!DHE+AES-GCM:!DHE+AES:ECDHE+AES-GCM:ECDHE+AES:RSA+AES-GCM:RSA+AES:-MD5:-SSLv3:-RC4:!3DES \n\n \n\n I then alter the playbook to reference the new SSL profile, and then re-run the Ansible playbook. \n\n Here is the relevant playbook snippet: \n\n \n - name: Create a VIP\n bigip_virtual_server:\n provider: \"{{ provider }}\"\n description: avip\n destination: 10.0.0.66\n name: vip-1\n irules:\n - responder\n pool: examplepool\n port: '443'\n snat: Automap\n profiles:\n - http\n - f5cove <change this to the new SSL Profile>\n delegate_to: localhost\n \n\n \n\n \n\n The resulting output from CryptoNice after running the playbook again to switch the SSL profile addresses all of the HIGH recommendations. \n\n \nTASK [debug] **********************************************************************************************************************************************************************\nok: [localhost] => {\n \"output.stdout_lines\": [\n \"Pre-scan checks\",\n \"-------------------------------------\",\n \"Scanning f5cove.me on port 443...\",\n \"Analyzing DNS data for f5cove.me\",\n \"Fetching additional records for f5cove.me\",\n \"f5cove.me resolves to 34.217.132.104\",\n \"34.217.132.104:443: OPEN\",\n \"TLS is available: True\",\n \"Connecting to port 443 using HTTPS\",\n \"Queueing TLS scans (this might take a little while...)\",\n \"Looking for HTTP/2\",\n \"\",\n \"\",\n \"RESULTS\",\n \"-------------------------------------\",\n \"Hostname:\\t\\t\\t f5cove.me\",\n \"\",\n \"Selected Cipher Suite:\\t\\t ECDHE-RSA-AES256-GCM-SHA384\",\n \"Selected TLS Version:\\t\\t TLS_1_2\",\n \"\",\n \"Supported protocols:\",\n \"TLS 1.2:\\t\\t\\t Yes\",\n \"\",\n \"TLS fingerprint:\\t\\t 2ad2ad0002ad2ad0002ad2ad2ad2adcb09dd549309271837f87ac5dad15fa7\",\n \"\",\n \"\",\n \"HTTP/2 supported:\\t\\t False\",\n \"\",\n \"\",\n \"CERTIFICATE\",\n \"Common Name:\\t\\t\\t f5cove.me\",\n \"Issuer Name:\\t\\t\\t R3\",\n \"Public Key Algorithm:\\t\\t RSA\",\n \"Public Key Size:\\t\\t 4096\",\n \"Signature Algorithm:\\t\\t sha256\",\n \"\",\n \"Certificate is trusted:\\t\\t True (No errors)\",\n \"Hostname Validation:\\t\\t OK - Certificate matches server hostname\",\n \"Extended Validation:\\t\\t False\",\n \"Certificate is in date:\\t\\t True\",\n \"Days until expiry:\\t\\t 88\",\n \"Valid From:\\t\\t\\t 2021-04-13 00:55:09\",\n \"Valid Until:\\t\\t\\t 2021-07-12 00:55:09\",\n \"\",\n \"OCSP Response:\\t\\t\\t Successful\",\n \"Must Staple Extension:\\t\\t False\",\n \"\",\n \"Subject Alternative Names:\",\n \"\\t f5cove.me\",\n \"\",\n \"Vulnerability Tests:\",\n \"No vulnerability tests were run\",\n \"\",\n \"HTTP to HTTPS redirect:\\t\\t False\",\n \"None\",\n \"\",\n \"RECOMMENDATIONS\",\n \"-------------------------------------\",\n \"Low - CAA Consider creating DNS CAA records to prevent accidental or malicious certificate issuance.\",\n \"\",\n \"Scans complete\",\n \"-------------------------------------\",\n \"Total run time: 0:00:05.638186\"\n ]\n}\n \n\n \n\n Now you can see in the CryptoNice output that \n\n There are no trust issues with the certificate/certificate bundle. I have upgraded from 2048 bits to 4096 bit key length. I have disabled TLS1.0 and TLS1.1. The connection is handshaking with a more secure cryptographic algorithm. I no longer have any HIGH recommendations for improvement to the quality of TLS connection. \n\n Conclusion \n\n It is always best practice to perform a sanity check on the services that you create in your automation pipelines. This article describes using CryptoNice as part of a simple Ansible playbook to run an SSL/TLS sanity check on the application in order to improve the security of your application-delivery automation. Ultimately you should not just be checking the SSL/TLS; another good idea would be to introduce application-vulnerability scanners too as part of the automation pipeline. ","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})@stringLength":"20331","kudosSumWeight":2,"repliesCount":1,"readOnly":false,"images":{"__typename":"AssociatedImageConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"videos":{"__typename":"VideoConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"Conversation:conversation:285094":{"__typename":"Conversation","id":"conversation:285094","topic":{"__typename":"TkbTopicMessage","uid":285094},"lastPostingActivityTime":"2021-03-23T11:08:04.000-07:00","solved":false},"User:user:332812":{"__typename":"User","uid":332812,"login":"Payal_S","registrationData":{"__typename":"RegistrationData","status":null},"deleted":false,"avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/m_assets/avatars/default/avatar-11.svg?time=0"},"id":"user:332812"},"TkbTopicMessage:message:285094":{"__typename":"TkbTopicMessage","subject":"Power of tmsh commands using Ansible","conversation":{"__ref":"Conversation:conversation:285094"},"id":"message:285094","revisionNum":1,"uid":285094,"depth":0,"board":{"__ref":"Tkb:board:TechnicalArticles"},"author":{"__ref":"User:user:332812"},"teaser@stripHtml({\"removeProcessingText\":true,\"truncateLength\":-1})":"","introduction":"","metrics":{"__typename":"MessageMetrics","views":10792},"postTime":"2021-02-09T08:34:05.000-08:00","lastPublishTime":"2021-02-09T08:34:05.000-08:00","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})":" Why is data important \n\n Having accurate data has become an integral part of decision making. The data could be for making simple decisions like purchasing the newest electronic gadget in the market or for complex decisions on what hardware and/or software platform works best for your highly demanding application which would provide the best user experience for your customer. In either case research and data collection becomes essential. \n\n Using what kind of F5 hardware and/or software in your environment follows the same principals where your IT team would require data to make the right decision. Data could vary from CPU, Throughput and/or Memory utilization etc. of your F5 gear. It could also be data just for a period of a day, a month or a year depending the application usage patterns. \n\n Ansible to the rescue \n\n Your environment could have 10's or maybe 100 or even 1000's of F5 BIG-IP's in your environment, manually logging into each one to gather data would be a highly inefficient method. One way which is a great and simple way could be to use Ansible as an automation framework to perform this task, relieving you to perform your other job functions. Let's take a look at some of the components needed to use Ansible. \n\n An inventory file in Ansible defines the hosts against which your playbook is going to run. Below is an example of a file defining F5 hosts which can be expanded to represent your 10'/100's or 1000's of BIG-IP's. \n\n Inventory file: 'inventory.yml' \n\n \n[f5]\nltm01 password=admin server=10.192.73.xxx user=admin validate_certs=no server_port=443\nltm02 password=admin server=10.192.73.xxx user=admin validate_certs=no server_port=443\nltm03 password=admin server=10.192.73.xxx user=admin validate_certs=no server_port=443\nltm04 password=admin server=10.192.73.xxx user=admin validate_certs=no server_port=443\nltm05 password=admin server=10.192.73.xxx user=admin validate_certs=no server_port=443\n \n\n A playbook defines the tasks that are going to be executed. In this playbook we are using the bigip_command module which can take as input any BIG-IP tmsh command and provide the output. Here we are going to use the tmsh commands to gather performance data from the BIG-IP's. The output from each of the BIG-IP's is going to be stored in a file that can be referenced after the playbook finished execution. \n\n Playbook: 'performance-data/yml' \n\n \n---\n- name: Create empty file\n hosts: localhost\n gather_facts: false\n\n tasks:\n\n - name: Creating an empty file\n file:\n path: \"./{{filename}}\"\n state: touch\n\n- name: Gather stats using tmsh command\n hosts: f5\n connection: local\n gather_facts: false\n serial: 1\n\n tasks:\n\n - name: Gather performance stats\n bigip_command:\n provider:\n server: \"{{server}}\"\n user: \"{{user}}\"\n password: \"{{password}}\"\n server_port: \"{{server_port}}\"\n validate_certs: \"{{validate_certs}}\"\n commands:\n - show sys performance throughput historical\n - show sys performance system historical\n register: result\n\n - lineinfile:\n line: \"\\n###BIG-IP hostname => {{ inventory_hostname }} ###\\n\"\n insertafter: EOF\n dest: \"./{{filename}}\"\n\n - lineinfile:\n line: \"{{ result.stdout_lines }}\"\n insertafter: EOF\n dest: \"./{{filename}}\"\n\n - name: Format the file\n shell:\n cmd: sed 's/,/\\n/g' ./{{filename}} > ./{{filename}}_formatted\n\n - pause:\n seconds: 10\n\n- name: Delete file\n hosts: localhost\n gather_facts: false\n\n tasks:\n\n - name: Delete extra file created (delete file)\n file:\n path: ./{{filename}}\n state: absent\n \n\n Execution: \n\n The execution command will take as input the playbook name, the inventory file as well as the filename where the output will be stored. (There are different ways of defining and passing parameters to a playbook, below is one such example) \n\n \nansible-playbook performance_data.yml -i inventory.yml --extra-vars \"filename=perf_output\"\n \n\n Snippet of expected output: \n\n \n###BIG-IP hostname => ltm01 ###\n\n[['Sys::Performance Throughput'\n '-----------------------------------------------------------------------'\n 'Throughput(bits)(bits/sec) Current 3 hrs 24 hrs 7 days 30 days'\n '-----------------------------------------------------------------------'\n 'Service 223.8K 258.8K 279.2K 297.4K 112.5K'\n 'In 212.1K 209.7K 210.5K 243.6K 89.5K'\n 'Out 21.4K 21.0K 21.1K 57.4K 30.1K'\n ' '\n '-----------------------------------------------------------------------'\n 'SSL Transactions Current 3 hrs 24 hrs 7 days 30 days'\n '-----------------------------------------------------------------------'\n 'SSL TPS 0 0 0 0 0'\n ' '\n '-----------------------------------------------------------------------'\n 'Throughput(packets)(pkts/sec) Current 3 hrs 24 hrs 7 days 30 days'\n '-----------------------------------------------------------------------'\n 'Service 79 82 83 63 62'\n 'In 41 40 40 34 32'\n 'Out 41 40 40 32 34']\n ['Sys::Performance System'\n '------------------------------------------------------------'\n 'System CPU Usage(%) Current 3 hrs 24 hrs 7 days 30 days'\n '------------------------------------------------------------'\n 'Utilization 17 18 18 18 17'\n ' '\n '------------------------------------------------------------'\n 'Memory Used(%) Current 3 hrs 24 hrs 7 days 30 days'\n '------------------------------------------------------------'\n 'TMM Memory Used 10 10 10 10 10'\n 'Other Memory Used 55 55 54 54 53'\n 'Swap Used 0 0 0 0 0']]\n\n###BIG-IP hostname => ltm02 ###\n\n[['Sys::Performance Throughput'\n '-----------------------------------------------------------------------'\n 'Throughput(bits)(bits/sec) Current 3 hrs 24 hrs 7 days 30 days'\n '-----------------------------------------------------------------------'\n 'Service 202.3K 258.7K 279.2K 297.4K 112.5K'\n 'In 190.8K 209.7K 210.5K 243.6K 89.5K'\n 'Out 19.6K 21.0K 21.1K 57.4K 30.1K'\n ' '\n '-----------------------------------------------------------------------'\n 'SSL Transactions Current 3 hrs 24 hrs 7 days 30 days'\n '-----------------------------------------------------------------------'\n 'SSL TPS 0 0 0 0 0'\n ' '\n '-----------------------------------------------------------------------'\n 'Throughput(packets)(pkts/sec) Current 3 hrs 24 hrs 7 days 30 days'\n '-----------------------------------------------------------------------'\n 'Service 77 82 83 63 62'\n 'In 39 40 40 34 32'\n 'Out 37 40 40 32 34']\n ['Sys::Performance System'\n '------------------------------------------------------------'\n 'System CPU Usage(%) Current 3 hrs 24 hrs 7 days 30 days'\n '------------------------------------------------------------'\n 'Utilization 21 18 18 18 17'\n ' '\n '------------------------------------------------------------'\n 'Memory Used(%) Current 3 hrs 24 hrs 7 days 30 days'\n '------------------------------------------------------------'\n 'TMM Memory Used 10 10 10 10 10'\n 'Other Memory Used 55 55 54 54 53'\n 'Swap Used 0 0 0 0 0']]\n \n\n The data obtained is historical data over a period of time. Sometimes it is also important to gather the peak usage of throughout/memory/cpu over time and not the average. Stay tuned as we will discuss on how to obtain that information in a upcoming article. \n\n Conclusion \n\n Use the output of the data to learn the traffic patterns and propose the most appropriate BIG-IP hardware/software in your environment. This could be data collected directly in your production environment or a staging environment, which would help you make the decision on what purchasing strategy gives you the most value from your BIG-IP's. \n\n For reference: https://www.f5.com/pdf/products/big-ip-local-traffic-manager-ds.pdf \n\n The above is one example of how you can get started with using Ansible and tmsh commands. Using this method you can potentially achieve close to 100% automation on the BIG-IP. ","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})@stringLength":"12657","kudosSumWeight":4,"repliesCount":3,"readOnly":false,"images":{"__typename":"AssociatedImageConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"videos":{"__typename":"VideoConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"Conversation:conversation:284881":{"__typename":"Conversation","id":"conversation:284881","topic":{"__typename":"TkbTopicMessage","uid":284881},"lastPostingActivityTime":"2021-01-07T17:44:15.000-08:00","solved":false},"User:user:50253":{"__typename":"User","uid":50253,"login":"James_Jinwon_Lee","registrationData":{"__typename":"RegistrationData","status":null},"deleted":false,"avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/images/dS01MDI1My0xNzYxOGk4RjhGNUMyMDJERTBBRjhE"},"id":"user:50253"},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODQ4ODEtNDQxMWk0MjNBMDUzRDNBOUVCOUZC?revision=1\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODQ4ODEtNDQxMWk0MjNBMDUzRDNBOUVCOUZC?revision=1","title":"0151T000003q4rrQAA.png","associationType":"BODY","width":835,"height":441,"altText":null},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODQ4ODEtMTA3MjBpNDY0OUQ2OUY5NkU0MjQ3Mw?revision=1\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODQ4ODEtMTA3MjBpNDY0OUQ2OUY5NkU0MjQ3Mw?revision=1","title":"0151T000003q7PbQAI.png","associationType":"BODY","width":3010,"height":1516,"altText":null},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODQ4ODEtMTE2NTFpODc4Q0ZFOEFBNzkwMEIwRg?revision=1\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODQ4ODEtMTE2NTFpODc4Q0ZFOEFBNzkwMEIwRg?revision=1","title":"0151T000003q4s1QAA.png","associationType":"BODY","width":941,"height":427,"altText":null},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODQ4ODEtNjM3N2k5QUU4MjE3ODE0RTc1OEZC?revision=1\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODQ4ODEtNjM3N2k5QUU4MjE3ODE0RTc1OEZC?revision=1","title":"0151T000003q4s6QAA.png","associationType":"BODY","width":941,"height":252,"altText":null},"TkbTopicMessage:message:284881":{"__typename":"TkbTopicMessage","subject":"Protecting Critical Apps against EastWest Attack","conversation":{"__ref":"Conversation:conversation:284881"},"id":"message:284881","revisionNum":1,"uid":284881,"depth":0,"board":{"__ref":"Tkb:board:TechnicalArticles"},"author":{"__ref":"User:user:50253"},"teaser@stripHtml({\"removeProcessingText\":true,\"truncateLength\":-1})":"","introduction":"","metrics":{"__typename":"MessageMetrics","views":937},"postTime":"2021-01-07T17:44:15.000-08:00","lastPublishTime":"2021-01-07T17:44:15.000-08:00","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})":" In the previous article, we explained how NetSecOps and DevSecOps could manage their application security policies to prevent advanced attacks from external organization networks. But in advanced persistent hacking, hackers sometimes exploit application vulnerabilities and use advanced malware with phishing emails to the operators. This is an old technique but still valid and utilized by many APT (Advanced Persistent Threat) Hacking Groups. And if the advanced hackers obtain a DevOps operator's ID and password using the malware, they could access a Kubernetes or OpenShift cluster through the normal login process and easily bypass advanced WAF(Web Application Firewall) solutions deployed in front of the cluster. Once the attacker can get a user ID and password of the Kubernetes or OpenShift cluster, the attacker also can access each application that is running inside of the cluster. \nSince most people on the SecOps team normally install very basic security functions inside the Kubernetes or OpenShift cluster, the hacker who logged in to the cluster can attack other applications in the same cluster without any security barrier. F5 Container Ingress Service is not designed to stop these sort of attacks within the cluster. To overcome this challenge, we have another tool, NGINX App Protect. NGINX App Protect delivers Layer 7 visibility and granular control for the applications while enabling an advanced application security policies. With an NGINX App Protect deployment, DevSecOps can ensure only legitimate traffic is allowed while all other unwanted traffic is blocked. NGINX App Protect can monitor the traffic traversing namespace boundaries between pods and provide advanced application protection at layer 7 for East-West traffic. \nSolution Overview This article will cover how NGINX App Protect can protect the critical applications in an OpenShift environment against an attack originating within the same cluster.\n\n Detecting advanced application attacks inside the cluster is beneficial for the DevSecOps team but this can increase the complexity of security operations. To provide a certain level of protection for the critical application the NGINX App Protect instance should be installed as a ‘PoD Proxy’ or a ‘Service Proxy’ for the application. This means the customer may need multiple NGINX App Protect instances to have the required level of protection for their applications. On the face of it this might seem like a dramatic increase in the complexity of security related operations. Security automation is the recommended solution to overcome the increased complexity of this security operations challenge. In this use case, we use Red Hat Ansible as our security automation tool. With Red Hat Ansible, the user can automate their incident response process with their existing security solutions. This can dramatically reduce the security team’s response time from hours to minutes. We use Ansible and Elasticsearch to provide all the required ‘security automation’ processes in this demo. \n \n\n With all these combined technologies, the solution provides WAF protection for the critical applications deployed in the OpenShift cluster. Once it detects the application-based attack from the same cluster subnet, it immediately blocks the attack and deletes the compromised pod with a pre-defined security automation playbook. The workflow is organized as shown below: The malware of 'Phishing email' infects the developer's laptop. The attacker steals the ID/PW of the developer using the malware. In this demo, the stolen ID is 'dev_user.' The attacker logs in the 'Test App' on the 'dev-test01' namespace, owned by the 'dev_user'. The attacker starts the network-scanning process on the internal subnet of the OpenShift cluster. And the attacker finds the 'critical-app' application pod. The attacker starts the web-based attack against 'critical-app'. NGINX App Protect protects the 'critical-app'; thus, the attack traffic is blocked immediately. NGINX exports the alert details to the external Elasticsearch. If this specific alert meets a pre-defined condition, Elasticsearch will trigger the pre-defined Ansible playbook. Ansible playbook accesses OpenShift and deletes the compromised 'Test App’ pod automatically. * Since this demo focuses on an attack inside the OpenShift cluster, the demo does not include the 'Step#1' and 'Step#2' (Phishing email).\n \n Understanding of the ‘Security Automation’ process The ‘Security Automation’ is the key part of this demo because the organizations don’t want to respond to each WAF alert manually, one by one. Manual incident-response processes are a time-consuming job and inefficient, especially in a modern-app environment with hundreds of container-based applications. In this demo, Red Hat Ansible and Elasticsearch take the security automation. Below is the brief workflow of the security automation of this use case. \n\n In this use case, the F5 Advanced WAF has been deployed in front of the OpenShift cluster and has inserted the X-Forwarded-For header value at each session. Since F5 Advanced WAF inserts the X-Forwarded-For header into the packet that comes from the external, if the packet doesn’t include the X-Forwarded-For header, it is likely coming from the internal network. NGINX App Protect installed as a pod proxy’ with the critical application we want to protect. Because NGINX App Protect runs as a pod proxy, all the traffic must be sent through this to reach the ‘critical-application.’ If the NGINX App Protect detects any malicious activities, it sends the alert details to the external Elasticsearch System. When any new alerts come from the NGINX App Protect, Elasticsearch analyzes the details of the alerts. If the alert meets the below conditions, Elasticsearch triggers the notification to the Logstash. \n If the source IP address of the alert is a part of the OpenShift cluster subnet… If the WAF alert severity is Critical… \nOnce the Logstash system receives the notification from the Elasticsearch, it creates the ip.txt file, which includes the source IP address of the attack and executes the pre-defined Ansible playbook. Ansible playbook reads the ip.txt file and extracts the IP address from the file. And Ansible accesses the OpenShift and finds the compromised pod using that Source IP Address from the ip.txt file. Then Ansible deletes the compromised pod and ip.txt files automatically. \n\n Creates Ansible Playbook Red Hat Ansible is the automation tool that enables network and security automation for users with enterprise-ready functions. F5 and Red Hat have a strategic partnership and deliver the joint use cases for our customer base. With Ansible integration with F5 solutions, organizations can have the single pane of glass management for network and security automation. In this use-case, we implement an automated security response process with the Ansible playbook when the F5 NGINX App Protect detects malicious activities in the OpenShift cluster. Below is the Ansible playbook to execute the incident response process for the attacker's compromised pod.\n ansible_ocp.yaml\n \n---\n- hosts: localhost\n gather_facts: false\n \n tasks:\n - name: Login to OCP cluster\n k8s_auth:\n host: https://yourocpdomain:6443\n username: kubeadmin\n password: your_ocp_password\n validate_certs: no\n register: k8s_auth_result\n \n - name: Extract IP Address\n command: cat /yourpath/ip.txt\n register: badpod_ip\n \n - name: Extract App Label from OpenShift\n shell: |\n sudo oc get pods -A -o json --field-selector status.podIP={{ badpod_ip.stdout }} |\n grep \"\\\"app\\\":\" |\n awk '{print $2}' |\n sed 's/,//'\n register: app_label\n \n - name: Delete Malicious Deployments\n shell: |\n sudo oc delete all --selector app={{ app_label.stdout }} -A\n register: delete_pod\n \n - name: Delete IP and Info File\n command: rm -rf /yourpath/ip.txt\n \n - name: OCP Service Deletion Completed\n debug:\n msg: \"{{ delete_pod.stdout }}\"\n \n Configuring Elasticsearch Watcher and Logstash To trigger the Ansible playbook for the Security Automation, SOC analysts need to validate the alert from the NGINX App Protect first. And based on the difference of the alert details, the SOC analyst might want to execute a different playbook. For example, if the alert is related to a Credential Stuffing Attack, the SOC analysts may want to block the user's application access. But if the alert is related to the known IP Blacklist, the analyst might want to block that IP address in the firewall. To support these requirements, the security team needs to have a tool that can monitor the security alerts and trigger the required actions based on them. Elasticsearch Watcher is the feature of the commercial version of Elasticsearch that users can use to create actions based on conditions, which are periodically evaluated using queries on the data. \n\n Configuring the Watcher of Kibana * You need an Elastic Platinum license or Eval license to use this feature on the Kibana. * Go to Kibana UI. * Management -> Watcher -> Create -> Create advanced watcher * Copy and paste below JSON code\n\n watcher_ocp.json\n \n{\n \"trigger\": {\n \"schedule\": {\n \"interval\": \"1m\"\n }\n },\n \"input\": {\n \"search\": {\n \"request\": {\n \"search_type\": \"query_then_fetch\",\n \"indices\": [\n \"nginx-*\"\n ],\n \"rest_total_hits_as_int\": true,\n \"body\": {\n \"query\": {\n \"bool\": {\n \"must\": [\n {\n \"match\": {\n \"outcome_reason\": \"SECURITY_WAF_VIOLATION\"\n }\n },\n {\n \"match\": {\n \"x_forwarded_for_header_value\": \"N/A\"\n }\n },\n {\n \"range\": {\n \"@timestamp\": {\n \"gte\": \"now-1h\",\n \"lte\": \"now\"\n }\n }\n }\n ]\n }\n }\n }\n }\n }\n },\n \"condition\": {\n \"compare\": {\n \"ctx.payload.hits.total\": {\n \"gt\": 0\n }\n }\n },\n \"actions\": {\n \"logstash_logging\": {\n \"webhook\": {\n \"scheme\": \"http\",\n \"host\": \"localhost\",\n \"port\": 1234,\n \"method\": \"post\",\n \"path\": \"/{{watch_id}}\",\n \"params\": {},\n \"headers\": {},\n \"body\": \"{{ctx.payload.hits.hits.0._source.ip_client}}\"\n }\n },\n \"logstash_exec\": {\n \"webhook\": {\n \"scheme\": \"http\",\n \"host\": \"localhost\",\n \"port\": 9001,\n \"method\": \"post\",\n \"path\": \"/{{watch_id}}\",\n \"params\": {},\n \"headers\": {},\n \"body\": \"{{ctx.payload.hits.hits[0].total}}\"\n }\n }\n }\n}\n \n2. Configuring 'logstash.conf' file. Below is the final version of the 'logstash.conf' file. Please note that you have to start the logstash with 'sudo' privilege\n logstash.conf\n \ninput {\n syslog {\n port => 5003\n type => nginx\n }\n \n http {\n port => 1234\n type => watcher1\n }\n \n http {\n port => 9001\n type => ansible1\n }\n}\n \nfilter {\nif [type] == \"nginx\" {\n \n grok {\n match => {\n \"message\" => [\n \",attack_type=\\\"%{DATA:attack_type}\\\"\",\n \",blocking_exception_reason=\\\"%{DATA:blocking_exception_reason}\\\"\",\n \",date_time=\\\"%{DATA:date_time}\\\"\",\n \",dest_port=\\\"%{DATA:dest_port}\\\"\",\n \",ip_client=\\\"%{DATA:ip_client}\\\"\",\n \",is_truncated=\\\"%{DATA:is_truncated}\\\"\",\n \",method=\\\"%{DATA:method}\\\"\",\n \",policy_name=\\\"%{DATA:policy_name}\\\"\",\n \",protocol=\\\"%{DATA:protocol}\\\"\",\n \",request_status=\\\"%{DATA:request_status}\\\"\",\n \",response_code=\\\"%{DATA:response_code}\\\"\",\n \",severity=\\\"%{DATA:severity}\\\"\",\n \",sig_cves=\\\"%{DATA:sig_cves}\\\"\",\n \",sig_ids=\\\"%{DATA:sig_ids}\\\"\",\n \",sig_names=\\\"%{DATA:sig_names}\\\"\",\n \",sig_set_names=\\\"%{DATA:sig_set_names}\\\"\",\n \",src_port=\\\"%{DATA:src_port}\\\"\",\n \",sub_violations=\\\"%{DATA:sub_violations}\\\"\",\n \",support_id=\\\"%{DATA:support_id}\\\"\",\n \",unit_hostname=\\\"%{DATA:unit_hostname}\\\"\",\n \",uri=\\\"%{DATA:uri}\\\"\",\n \",violation_rating=\\\"%{DATA:violation_rating}\\\"\",\n \",vs_name=\\\"%{DATA:vs_name}\\\"\",\n \",x_forwarded_for_header_value=\\\"%{DATA:x_forwarded_for_header_value}\\\"\",\n \",outcome=\\\"%{DATA:outcome}\\\"\",\n \",outcome_reason=\\\"%{DATA:outcome_reason}\\\"\",\n \",violations=\\\"%{DATA:violations}\\\"\",\n \",violation_details=\\\"%{DATA:violation_details}\\\"\",\n \",request=\\\"%{DATA:request}\\\"\"\n ]\n }\n break_on_match => false\n }\n \n mutate {\n split => { \"attack_type\" => \",\" }\n split => { \"sig_ids\" => \",\" }\n split => { \"sig_names\" => \",\" }\n split => { \"sig_cves\" => \",\" }\n split => { \"sig_set_names\" => \",\" }\n split => { \"threat_campaign_names\" => \",\" }\n split => { \"violations\" => \",\" }\n split => { \"sub_violations\" => \",\" }\n \n remove_field => [ \"date_time\", \"message\" ]\n }\n \n if [x_forwarded_for_header_value] != \"N/A\" {\n mutate { add_field => { \"source_host\" => \"%{x_forwarded_for_header_value}\"}}\n } else {\n mutate { add_field => { \"source_host\" => \"%{ip_client}\"}}\n }\n \n geoip {\n source => \"source_host\"\n database => \"/etc/logstash/GeoLite2-City.mmdb\"\n}\n}\n}\n \noutput {\n \nif [type] == 'nginx' {\n elasticsearch {\n hosts => [\"127.0.0.1:9200\"]\n index => \"nginx-%{+YYYY.MM.dd}\"\n }\n}\n \nif [type] == 'watcher1' {\n file {\n path => \"/yourpath/ip.txt\"\n codec => line { format => \"%{message}\"}\n }\n}\n \nif [type] == 'ansible1' {\n exec {\n command => \"ansible-playbook /yourpath/ansible_ocp.yaml\"\n }\n}\n}\n \nSimulate the demo You should start the Kibana watcher and logstash services first before proceeding with this step. Kubeadmin Console Please make sure you're logged in to the OCP cluster using a cluster-admin account. And confirm the 'critical-app' is running correctly.\n j.lee$ oc whoami\nkube:admin\n\nj.lee$\nj.lee$ oc get projects\n\nNAME DISPLAY NAME STATUS\n\ncritical-app Active\ndefault Active\ndev-test02 Active\nkube-node-lease Active\nkube-public Active\nkube-system Active\nopenshift Active\nopenshift-apiserver Active\nopenshift-apiserver-operator Active\nopenshift-authentication Active\nopenshift-authentication-operator Active\nopenshift-cloud-credential-operator Active\n\nj.lee$ oc get pods -o wide\n\nNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES\n\ncritical-app-v1-5c6546765f-wjhl9 2/2 Running 1 85m 10.129.2.71 ip-10-0-180-68.ap-southeast-1.compute.internal <none> <none>\n\nj.lee$\n \ndev_user Console Please make sure you're logged in to the OCP cluster using 'dev_user' account on the compromised pod and confirm the 'dev-test-app' is running correctly.\n PS C:\\Users\\ljwca\\Documents\\ocp> oc whoami\ndev_user\n\nPS C:\\Users\\ljwca\\Documents\\ocp>\nPS C:\\Users\\ljwca\\Documents\\ocp> oc get projects\n\nNAME DISPLAY NAME STATUS\n\ndev-test02 Active\n\nPS C:\\Users\\ljwca\\Documents\\ocp>\nPS C:\\Users\\ljwca\\Documents\\ocp> oc get pods -o wide\n\nNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES\n\ndev-test-v1-674f467644-t94dc 1/1 Running 0 6s 10.128.2.38 ip-10-0-155-159.ap-southeast-1.compute.internal <none> <none>\n \n 2. Login to 'dev-test' container using remote shell command of the OCP\n PS C:\\Users\\ljwca\\Documents\\ocp> oc rsh dev-test-v1-674f467644-t94dc\n$\n$ uname -a\nLinux dev-test-v1-674f467644-t94dc 4.18.0-193.14.3.el8_2.x86_64 #1 SMP Mon Jul 20 15:02:29 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux\n \n 3. Network scanning This step takes 1~2 hours to complete all scanning.\n $ nmap -sP 10.128.0.0/14\nStarting Nmap 7.80 ( https://nmap.org ) at 2020-09-29 17:20 UTC\nNmap scan report for ip-10-128-0-1.ap-southeast-1.compute.internal (10.128.0.1)\nHost is up (0.0025s latency).\nNmap scan report for ip-10-128-0-2.ap-southeast-1.compute.internal (10.128.0.2)\nHost is up (0.0024s latency).\nNmap scan report for 10-128-0-3.metrics.openshift-authentication-operator.svc.cluster.local (10.128.0.3)\nHost is up (0.0023s latency).\nNmap scan report for 10-128-0-4.metrics.openshift-kube-scheduler-operator.svc.cluster.local (10.128.0.4)\nHost is up (0.0027s latency).\n.\n.\n.\n After completion of the scanning, you will be able to find the 'critical-app' on the list. 4. Application Scanning for the target You can find the open service ports on the target using nmap.\n $ nmap 10.129.2.71\nStarting Nmap 7.80 ( https://nmap.org ) at 2020-09-29 17:23 UTC\nNmap scan report for 10-129-2-71.critical-app.critical-app.svc.cluster.local (10.129.2.71)\nHost is up (0.0012s latency).\nNot shown: 998 closed ports\nPORT STATE SERVICE\n80/tcp open http\n8888/tcp open sun-answerbook\n \nNmap done: 1 IP address (1 host up) scanned in 0.12 seconds\n$\n But you will see the 403 error when you try to access the server using port 80. This happens because the default Apache access control only allows the traffic from the NGINX App Protect.\n $ curl http://10.129.2.71/\n<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<html><head>\n<title>403 Forbidden</title>\n</head><body>\n<h1>Forbidden</h1>\n<p>You don't have permission to access this resource.</p>\n<hr>\n<address>Apache/2.4.46 (Debian) Server at 10.129.2.71 Port 80</address>\n</body></html>\n$\n \n Now, you can see the response through port 8888.\n $ curl http://10.129.2.71:8888/\n<html>\n<head>\n<title>\nNetwork Operation Utility - NSLOOKUP\n</title>\n</head>\n<body>\n <font color=blue size=12>NSLOOKUP TOOL</font><br><br>\n <h2>Please type the domain name into the below box.</h2>\n <h1>\n <form action=\"/index.php\" method=\"POST\">\n <p>\n <label for=\"target\">DNS lookup:</label>\n <input type=\"text\" id=\"target\" name=\"target\" value=\"www.f5.com\">\n <button type=\"submit\" name=\"form\" value=\"submit\">Lookup</button>\n </p>\n </form>\n </h1>\n <font color=red>This site is vulnerable to Web Exploit. Please use this site as a test purpose only.</font>\n</body>\n</html>\n$\n \n 5. Performing the Command Injection attack.\n $ curl -d \"target=www.f5.com|cat /etc/passwd&form=submit\" -X POST http://10.129.2.71:8888/index.php\n<html><head><title>SRE DevSecOps - East-West Attack Blocking</title></head><body><font color=green size=10>NGINX App Protect Blocking Page</font><br><br>Please consult with your administrator.<br><br>Your support ID is: 878077205548544462<br><br><a href='javascript:history.back();'>[Go Back]</a></body></html>$\n$\n 6. Verify the logs in Kibana dashboard You should be able to see the NGINX App Protect alerts on your Elasticsearch. \n\n You should be able to see the NGINX App Protect alerts on your ELK.\n \n7. Verify the Ansible terminates the compromised pod Ansible deletes the compromised pod.\n \n Summary Today’s cyber based threats are getting more and more sophisticated. Attackers keep attempting to find out the weakest link in the company’s infrastructure and finally move from there to the data in the company using that link. In most cases, the weakest link of the organization is the human and the company stores its critical data in the application. This is why the attackers use the phishing email to compromise the user’s laptop and leverage it to access the application. While F5 is working very closely with our key alliance partners such as Cisco and FireEye to stop the advanced malware at the first stage, our NGINX App Protect can work as another layer of defence for the application to protect the organization's data. F5, Red Hat, and Elastic have developed this new protection mechanism, which is an automated process. This use case allows the DevSecOps team to easily deploy the advanced security layer in their OpenShift cluster. If you want to learn more about this use case, please visit the F5 Business Development official Github link here. ","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})@stringLength":"34438","kudosSumWeight":0,"repliesCount":0,"readOnly":false,"images":{"__typename":"AssociatedImageConnection","edges":[{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDE","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODQ4ODEtNDQxMWk0MjNBMDUzRDNBOUVCOUZC?revision=1\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDI","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODQ4ODEtMTA3MjBpNDY0OUQ2OUY5NkU0MjQ3Mw?revision=1\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDM","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODQ4ODEtMTE2NTFpODc4Q0ZFOEFBNzkwMEIwRg?revision=1\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDQ","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODQ4ODEtNjM3N2k5QUU4MjE3ODE0RTc1OEZC?revision=1\"}"}}],"totalCount":4,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"videos":{"__typename":"VideoConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"Conversation:conversation:285110":{"__typename":"Conversation","id":"conversation:285110","topic":{"__typename":"TkbTopicMessage","uid":285110},"lastPostingActivityTime":"2020-11-20T13:05:39.000-08:00","solved":false},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODUxMTAtNTIxNGkwQzhGRTI4MTMwMUZFRjYw?revision=1\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODUxMTAtNTIxNGkwQzhGRTI4MTMwMUZFRjYw?revision=1","title":"0151T000003pyoNQAQ.png","associationType":"BODY","width":1689,"height":657,"altText":null},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODUxMTAtNDQxNWkxOTQ4MTYyNzFCMkE5Qjcw?revision=1\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bS0yODUxMTAtNDQxNWkxOTQ4MTYyNzFCMkE5Qjcw?revision=1","title":"0151T000003pyoOQAQ.png","associationType":"BODY","width":1802,"height":601,"altText":null},"TkbTopicMessage:message:285110":{"__typename":"TkbTopicMessage","subject":"F5 and Cisco ACI Essentials: Automate automate automate !!!","conversation":{"__ref":"Conversation:conversation:285110"},"id":"message:285110","revisionNum":1,"uid":285110,"depth":0,"board":{"__ref":"Tkb:board:TechnicalArticles"},"author":{"__ref":"User:user:332812"},"teaser@stripHtml({\"removeProcessingText\":true,\"truncateLength\":-1})":"","introduction":"","metrics":{"__typename":"MessageMetrics","views":1624},"postTime":"2020-11-20T13:05:39.000-08:00","lastPublishTime":"2020-11-20T13:05:39.000-08:00","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})":" This article will focus on automation support by BIG-IP and Cisco ACI and how automation tools specifically Ansible can be used to automate different use cases. Before getting into the weeds let's discuss and understand BIG-IP's and Cisco ACI's automation strategies. BIG-IP automation strategy BIG-IP automation strategy is simple-abstract as much complexity as possible from the user, give an easy button to the user to deploy their BIG-IP configuration. This could honestly mean different methods to different people, some prefer sending a single API call to perform one action( A one-to-one mapping between your API<->Configuration). Others prefer a more declarative approach where one API call performs multiple actions, basically a one-to -many mapping between your API(1)<->Configuration(N). A great link to refresh and learn about the different options: https://www.f5.com/products/automation-and-orchestration Cisco ACI automation strategy Cisco Application Policy Infrastructure Controller (APIC) is the network controller for the ACI fabric. APIC is the unified point of automation and management for the Cisco ACI fabric, policy enforcement, and health monitoring. The Cisco ACI programmability model provides complete programmatic access using APIC. Click here to learn more https://developer.cisco.com/site/aci/ Automation tools There are a lot of automation tools that are being talked for network automation BUT the one that comes up in every customer conversation is Ansible. Its simplicity, maturity and community adoption has made it very popular. In this article we are going to focus on using Ansible to automate a service discovery use case. Use Case: Dynamic EP attach/detach Let’s take an example of a simple http web service being made highly available and secure using the BIG-IP Virtual IP address. This web service has a bunch of backend web servers hosting the application, the IP of this web servers is configured on the BIG-IP as pool members. These same web server IP’s are learned as endpoints in the ACI fabric and are part of an End Point Group (EPG) on the APIC. Hence there is a logical mapping between a EPG on APIC and a pool on the BIG-IP. Now if the application is adding or deleting web servers that is hosting the application maybe to save cost or maybe to deal with increase/decrease of traffic, what happens is that the web server IP will be automatically learned/unlearned on APIC. BUT an admin will still have to add/remove that web server IP from the pool on BIG-IP. This can be a burden on the network admin specially if this happens very often. Here is where automation can help and let’s look at how in the next section More details on the use case can be found at https://devcentral.f5.com/s/articles/F5-Cisco-ACI-Essentials-Dynamic-pool-sizing-using-the-F5-ACI-ServiceCenter Automation of Use Case: Dynamic EP attach/detach Automation can be achieved by using Ansible and Ansible tower where API calls are made directly to the BIG-IP. Another option it to use the F5 ACI ServiceCenter (a native F5 ACI integration) to automate this particular use case. Ansible and Ansible tower To learn more about Ansible and Ansible tower: https://www.ansible.com/products/tower Using this method of automation separate API calls are made directly to the ACI and the BIG-IP. Sample playbook to perform the addition and deletion of pool members to a BIG-IP pool based on members in a particular EPG. The mapping of pool to EPG is provided as input to the playbook. - name: Dynamic end point attach/dettach \n hosts: aci\n connection: local\n gather_facts: false\n\n vars:\n epg_members: []\n pool_members: []\n pool_members_ip: []\n bigip_ip: 10.192.73.xx\n bigip_password: admin\n bigip_username: admin\n # Here we are mapping pool 'dynamic_pool' to EPG 'internalEPG' which belongs to APIC tenant 'TenantDemo'\n app_profile_name: AppProfile\n epg_name: internalEPG\n partition: Common\n pool_name: dynamic_pool\n pool_port: 80\n tenant_name: TenantDemo\n\n tasks:\n - name: Setup provider\n set_fact:\n provider:\n server: \"{{bigip_ip}}\"\n user: \"{{bigip_username}}\"\n password: \"{{bigip_password}}\"\n server_port: \"443\"\n validate_certs: \"false\"\n\n - name: Get end points learned from End Point group\n aci_rest:\n action: \"get\"\n uri: \"/api/node/mo/uni/tn-{{tenant_name}}/ap-{{app_profile_name}}/epg-{{epg_name}}.json?query-target=subtree&target-subtree-class=fvIp\"\n host: \"{{inventory_hostname}}\"\n username: \"{{ lookup('env', 'ANSIBLE_NET_USERNAME') }}\"\n password: \"{{ lookup('env', 'ANSIBLE_NET_PASSWORD') }}\"\n validate_certs: \"false\"\n register: eps\n \n - name: Get the IP's of the servers part of the EPG\n set_fact: \n epg_members=\"{{epg_members + [item]}}\"\n loop: \"{{eps | json_query(query_string)}}\"\n vars:\n query_string: \"imdata[*].fvIp.attributes.addr\"\n no_log: True\n\n - name: Get only the IPv4 IP's\n set_fact:\n epg_members=\"{{epg_members | ipv4}}\"\n \n - name: Adding Pool members to the BIG-IP\n bigip_pool_member:\n provider: \"{{provider}}\"\n state: \"present\"\n name: \"{{item}}\"\n host: \"{{item}}\"\n port: \"{{pool_port}}\"\n pool: \"{{pool_name}}\"\n partition: \"{{partition}}\"\n loop: \"{{epg_members}}\"\n\n - name: Query BIG-IP facts\n bigip_device_facts:\n provider: \"{{provider}}\"\n gather_subset:\n - ltm-pools\n register: bigip_facts\n\n - name: \"Show members belonging to pool {{pool_name}}\"\n set_fact:\n pool_members=\"{{pool_members + [item]}}\"\n loop: \"{{bigip_facts.ltm_pools | json_query(query_string)}}\"\n vars:\n query_string: \"[?name=='{{pool_name}}'].members[*].name[]\"\n\n - set_fact:\n pool_members_ip: \"{{pool_members_ip + [item.split(':')[0]]}}\"\n loop: \"{{pool_members}}\"\n\n - debug: \"msg={{pool_members_ip}}\"\n\n #If there are any membeers on the BIG-IP that are not present in the EPG,then delete them\n - name: Find the members to be deleted if any \n set_fact:\n members_to_be_deleted: \"{{ pool_members_ip | difference(epg_members) }}\"\n\n - debug: \"msg={{members_to_be_deleted}}\"\n\n - name: Delete Pool members from the BIG-IP\n bigip_pool_member:\n provider: \"{{provider}}\"\n state: \"absent\"\n name: \"{{item}}\"\n port: \"{{pool_port}}\"\n pool: \"{{pool_name}}\"\n preserve_node: yes\n partition: \"{{partition}}\"\n loop: \"{{members_to_be_deleted}}\"\n Ansible tower's scheduling feature can be used to schedule this playbook to be run every minute, every hour or once per day based on how often an application is expected to change and how important is it for the configuration on both the Cisco ACI and the BIG-IP to be in sync. F5 ACI ServiceCenter To learn more about the integration : https://www.f5.com/cisco The F5 ACI ServiceCenter is installed on the APIC controller. Here automation can be used to create the initial EPG to Pool mapping. Once the mapping is created the F5 ACI ServiceCenter handles the dynamic sizing of pools based on events generated by APIC. Events are generated when a server is learned/unlearned on an EPG which is what the F5 ACI ServiceCenter listens to and accordingly adds or removes pool members from the BIG-IP. Sample playbook to deploy the mapping configuration on the BIG-IP through the F5 ACI ServiceCenter ---\n- name: Deploy EPG to Pool mapping\n hosts: localhost\n gather_facts: false\n connection: local\n\n vars:\n apic_ip: \"10.192.73.xx\"\n big_ip: \"10.192.73.xx\"\n partition: \"Dynamic\"\n\n tasks:\n\n - name: Login to APIC\n uri:\n url: https://{{apic_ip}}/api/aaaLogin.json\n method: POST\n validate_certs: no\n body_format: json\n body:\n aaaUser:\n attributes:\n name: \"admin\"\n pwd: \"******\"\n headers:\n content_type: \"application/json\"\n return_content: yes\n register: cookie\n\n - debug: msg=\"{{cookie['cookies']['APIC-cookie']}}\"\n\n - set_fact:\n token: \"{{cookie['cookies']['APIC-cookie']}}\"\n\n - name: Login to BIG-IP\n uri:\n url: https://{{apic_ip}}/appcenter/F5Networks/F5ACIServiceCenter/loginbigip.json\n method: POST\n validate_certs: no\n body:\n url: \"{{big_ip}}\"\n user: \"admin\"\n password: \"admin\"\n body_format: json\n headers:\n DevCookie: \"{{token}}\"\n\n #The body of this request defines the mapping of Pool to EPG\n #Here we are mapping pool 'web_pool' to EPG 'internalEPG' which belongs to APIC tenant 'TenantDemo'\n - name: Deploy AS3 dynamic EP mapping\n uri:\n url: https://{{apic_ip}}/appcenter/F5Networks/F5ACIServiceCenter/updateas3data.json\n method: POST\n validate_certs: no\n body:\n url: \"{{big_ip}}\"\n partition: \"{{partition}}\"\n application: \"DemoApp1\"\n json:\n class: Application\n template: http\n serviceMain:\n class: Service_HTTP\n virtualAddresses:\n - 10.168.56.100\n pool: web_pool\n web_pool:\n class: Pool\n monitors:\n - http\n members:\n - servicePort: 80\n serverAddresses: []\n - addressDiscovery: event\n servicePort: 80\n constants:\n class: Constants\n serviceCenterEPG:\n web_pool:\n tenant: TenantDemo\n application: AppProfile\n epg: internalEPG\n body_format: json\n status_code:\n - 202\n - 200\n headers:\n DevCookie: \"{{token}}\"\n return_content: yes\n register: complete_info\n\n - name: Get task ID of above request\n set_fact:\n task_id: \"{{ complete_info.json.message.taskId}}\"\n when: complete_info.json.code == 202\n\n - name: Get deployment status\n uri:\n url: https://{{apic_ip}}/appcenter/F5Networks/F5ACIServiceCenter/getasynctaskresponse.json\n method: POST\n validate_certs: no\n body:\n taskId: \"{{task_id}}\"\n body_format: json\n headers:\n DevCookie: \"{{token}}\"\n return_content: yes\n register: result\n until: result.json.message.message != \"in progress\"\n retries: 5\n delay: 2\n when: task_id is defined\n\n - name: Display final result\n debug:\n var: result\n After deploying this configuration, adding/deleting pool members and making sure the configuration is in sync is the responsibility of the F5 ACI ServiceCenter. Takeaways Both methods are highly effective and usable. The choice of which one to use comes down to the operational model in your environment. Some pros and cons to help made the decision on which platform to use for automation. Ansible Tower Pros No dependency on any other tools Fits in better with bigger company automation strategy to use Ansible for ALL network automation Cons Have to manage playbook execution and scheduling using Ansible Tower If more logic is needed besides what’s described above playbooks will have to be written and maintained Execution of playbook is based on scheduling and is not event driven F5 ACI ServiceCenter Pros Only pool-epg mapping has to be deployed using automation, rest all is handled by the application User interface to view pool member to EPG mapping once deployed and view discrepancies if any Limited automation knowledge is needed, heavy lifting is being done by the application Dynamically adding/deleting pool members is event driven, as members are learned/unlearned by the F5 ACI ServiceCenter an action is taken Cons Another tool is required Customization of pool to EPG mapping is not present. Only one-to-one EPG to pool mapping is present. References Learn about the F5 ACI ServiceCenter and other Cisco integrations: https://f5.com/cisco Download the F5 ACI ServiceCenter: https://dcappcenter.cisco.com/f5-aci-servicecenter.html Lab to execute Ansible playbooks: https://dcloud.cisco.com (Lab name: F5 and Ansible ) ","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":-1})@stringLength":"11991","kudosSumWeight":1,"repliesCount":0,"readOnly":false,"images":{"__typename":"AssociatedImageConnection","edges":[{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDE","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODUxMTAtNTIxNGkwQzhGRTI4MTMwMUZFRjYw?revision=1\"}"}},{"__typename":"AssociatedImageEdge","cursor":"MjUuM3wyLjF8b3wyNXxfTlZffDI","node":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bS0yODUxMTAtNDQxNWkxOTQ4MTYyNzFCMkE5Qjcw?revision=1\"}"}}],"totalCount":2,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"videos":{"__typename":"VideoConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"CachedAsset:text:en_US-components/community/Navbar-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/Navbar-1744046271000","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","migrated-link-9":"Groups","migrated-link-7":"Technical Articles","migrated-link-8":"DevCentral News","migrated-link-1":"Technical Forum","migrated-link-10":"Community Groups","migrated-link-2":"Water Cooler","migrated-link-11":"F5 Groups","Common-external-link":"How Do I...?","migrated-link-0":"Forums","article-series":"Article Series","migrated-link-5":"Community Articles","migrated-link-6":"Articles","security-insights":"Security Insights","migrated-link-3":"CrowdSRC","migrated-link-4":"CodeShare","migrated-link-12":"Events","migrated-link-13":"Suggestions"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarHamburgerDropdown-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarHamburgerDropdown-1744046271000","value":{"hamburgerLabel":"Side Menu"},"localOverride":false},"CachedAsset:text:en_US-components/community/BrandLogo-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/BrandLogo-1744046271000","value":{"logoAlt":"Khoros","themeLogoAlt":"Brand Logo"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarTextLinks-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarTextLinks-1744046271000","value":{"more":"More"},"localOverride":false},"CachedAsset:text:en_US-components/authentication/AuthenticationLink-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/authentication/AuthenticationLink-1744046271000","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-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/nodes/NodeLink-1744046271000","value":{"place":"Place {name}"},"localOverride":false},"CachedAsset:text:en_US-components/tags/TagSubscriptionAction-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/tags/TagSubscriptionAction-1744046271000","value":{"success.follow.title":"Following Tag","success.unfollow.title":"Unfollowed Tag","success.follow.message.followAcrossCommunity":"You will be notified when this tag is used anywhere across the community","success.unfollowtag.message":"You will no longer be notified when this tag is used anywhere in this place","success.unfollowtagAcrossCommunity.message":"You will no longer be notified when this tag is used anywhere across the community","unexpected.error.title":"Error - Action Failed","unexpected.error.message":"An unidentified problem occurred during the action you took. Please try again later.","buttonTitle":"{isSubscribed, select, true {Unfollow} false {Follow} other{}}","unfollow":"Unfollow"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/QueryHandler-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/QueryHandler-1744046271000","value":{"title":"Query Handler"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarDropdownToggle-1744046271000","value":{"ariaLabelClosed":"Press the down arrow to open the menu"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageListTabs-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageListTabs-1744046271000","value":{"mostKudoed":"{value, select, IDEA {Most Votes} other {Most Likes}}","mostReplies":"Most Replies","mostViewed":"Most Viewed","newest":"{value, select, IDEA {Newest Ideas} OCCASION {Newest Events} other {Newest Topics}}","newestOccasions":"Newest Events","mostRecent":"Most Recent","noReplies":"No Replies Yet","noSolutions":"No Solutions Yet","solutions":"Solutions","mostRecentUserContent":"Most Recent","trending":"Trending","draft":"Drafts","spam":"Spam","abuse":"Abuse","moderation":"Moderation","tags":"Tags","PAST":"Past","UPCOMING":"Upcoming","sortBymostRecent":"Sort By Most Recent","sortBymostRecentUserContent":"Sort By Most Recent","sortBymostKudoed":"Sort By Most Likes","sortBymostReplies":"Sort By Most Replies","sortBymostViewed":"Sort By Most Viewed","sortBynewest":"Sort By Newest Topics","sortBynewestOccasions":"Sort By Newest Events","otherTabs":" Messages list in the {tab} for {conversationStyle}","guides":"Guides","archives":"Archives"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageView/MessageViewInline-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageView/MessageViewInline-1744046271000","value":{"bylineAuthor":"{bylineAuthor}","bylineBoard":"{bylineBoard}","anonymous":"Anonymous","place":"Place {bylineBoard}","gotoParent":"Go to parent {name}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Pager/PagerLoadMore-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Pager/PagerLoadMore-1744046271000","value":{"loadMore":"Show More"},"localOverride":false},"CachedAsset:text:en_US-components/customComponent/CustomComponent-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/customComponent/CustomComponent-1744046271000","value":{"errorMessage":"Error rendering component id: {customComponentId}","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-shared/client/components/common/OverflowNav-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/OverflowNav-1744046271000","value":{"toggleText":"More"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserLink-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserLink-1744046271000","value":{"authorName":"View Profile: {author}","anonymous":"Anonymous"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageSubject-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageSubject-1744046271000","value":{"noSubject":"(no subject)"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBody-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBody-1744046271000","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/MessageTime-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageTime-1744046271000","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-shared/client/components/nodes/NodeIcon-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeIcon-1744046271000","value":{"contentType":"Content Type {style, select, FORUM {Forum} BLOG {Blog} TKB {Knowledge Base} IDEA {Ideas} OCCASION {Events} other {}} icon"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageUnreadCount-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageUnreadCount-1744046271000","value":{"unread":"{count} unread","comments":"{count, plural, one { unread comment} other{ unread comments}}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageViewCount-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageViewCount-1744046271000","value":{"textTitle":"{count, plural,one {View} other{Views}}","views":"{count, plural, one{View} other{Views}}"},"localOverride":false},"CachedAsset:text:en_US-components/kudos/KudosCount-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/kudos/KudosCount-1744046271000","value":{"textTitle":"{count, plural,one {{messageType, select, IDEA{Vote} other{Like}}} other{{messageType, select, IDEA{Votes} other{Likes}}}}","likes":"{count, plural, one{like} other{likes}}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageRepliesCount-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageRepliesCount-1744046271000","value":{"textTitle":"{count, plural,one {{conversationStyle, select, IDEA{Comment} OCCASION{Comment} other{Reply}}} other{{conversationStyle, select, IDEA{Comments} OCCASION{Comments} other{Replies}}}}","comments":"{count, plural, one{Comment} other{Comments}}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserAvatar-1744046271000","value":{"altText":"{login}'s avatar","altTextGeneric":"User's avatar"},"localOverride":false}}}},"page":"/tags/TagPage/TagPage","query":{"nodeId":"board:TechnicalArticles","tagName":"Ansible"},"buildId":"FP3zsFp6DJl70wDujOvtu","runtimeConfig":{"buildInformationVisible":false,"logLevelApp":"info","logLevelMetrics":"info","openTelemetryClientEnabled":false,"openTelemetryConfigName":"f5","openTelemetryServiceVersion":"25.3.0","openTelemetryUniverse":"prod","openTelemetryCollector":"http://localhost:4318","openTelemetryRouteChangeAllowedTime":"5000","apolloDevToolsEnabled":false,"inboxMuteWipFeatureEnabled":false},"isFallback":false,"isExperimentalCompile":false,"dynamicIds":["./components/customComponent/CustomComponent/CustomComponent.tsx","./components/community/Navbar/NavbarWidget.tsx","./components/community/Breadcrumb/BreadcrumbWidget.tsx","./components/tags/TagsHeaderWidget/TagsHeaderWidget.tsx","./components/messages/MessageListForNodeByRecentActivityWidget/MessageListForNodeByRecentActivityWidget.tsx","./components/tags/TagSubscriptionAction/TagSubscriptionAction.tsx","./components/customComponent/CustomComponentContent/TemplateContent.tsx","../shared/client/components/common/List/ListGroup/ListGroup.tsx","./components/messages/MessageView/MessageView.tsx","./components/messages/MessageView/MessageViewInline/MessageViewInline.tsx","../shared/client/components/common/Pager/PagerLoadMore/PagerLoadMore.tsx","./components/customComponent/CustomComponentContent/HtmlContent.tsx","./components/customComponent/CustomComponentContent/CustomComponentScripts.tsx"],"appGip":true,"scriptLoader":[]}