From 4393c3da55af624dc99787dc641e459c69e37f96 Mon Sep 17 00:00:00 2001 From: Lance Galletti Date: Tue, 8 Aug 2023 11:28:47 -0400 Subject: [PATCH] Add support for Azure Workload Identity / Federated Identity based installs in OperatorHub --- .../locales/en/olm.json | 10 ++ .../src/components/operator-hub/index.ts | 2 +- .../operator-hub-item-details.tsx | 21 ++- .../operator-hub/operator-hub-items.tsx | 17 ++- .../operator-hub/operator-hub-page.tsx | 18 ++- .../operator-hub/operator-hub-subscribe.tsx | 129 ++++++++++++++++-- .../operator-hub/operator-hub-utils.ts | 14 +- 7 files changed, 185 insertions(+), 26 deletions(-) diff --git a/frontend/packages/operator-lifecycle-manager/locales/en/olm.json b/frontend/packages/operator-lifecycle-manager/locales/en/olm.json index d20a86bf1ba0..21fb9424ba4a 100644 --- a/frontend/packages/operator-lifecycle-manager/locales/en/olm.json +++ b/frontend/packages/operator-lifecycle-manager/locales/en/olm.json @@ -272,6 +272,8 @@ "Container image": "Container image", "Cluster in STS Mode": "Cluster in STS Mode", "This cluster is using AWS Security Token Service to reach the cloud API. In order for this operator to take the actions it requires directly with the cloud API, you will need to provide a role ARN (with an attached policy) during installation. Please see the operator description for more details.": "This cluster is using AWS Security Token Service to reach the cloud API. In order for this operator to take the actions it requires directly with the cloud API, you will need to provide a role ARN (with an attached policy) during installation. Please see the operator description for more details.", + "Cluster in Azure Workload Identity / Federated Identity Mode": "Cluster in Azure Workload Identity / Federated Identity Mode", + "This cluster is using Azure Workload Identity / Federated Identity to reach the cloud API. In order for this operator to take the actions it requires directly with the cloud API, provide the Client ID, Tenant ID, and Subscription ID during installation. See the operator description for more details.": "This cluster is using Azure Workload Identity / Federated Identity to reach the cloud API. In order for this operator to take the actions it requires directly with the cloud API, provide the Client ID, Tenant ID, and Subscription ID during installation. See the operator description for more details.", "Installed": "Installed", "Not Installed": "Not Installed", "provided by {{provider}}": "provided by {{provider}}", @@ -318,8 +320,16 @@ "Operator Installation": "Operator Installation", "Install your Operator by subscribing to one of the update channels to keep the Operator up to date. The strategy determines either manual or automatic updates.": "Install your Operator by subscribing to one of the update channels to keep the Operator up to date. The strategy determines either manual or automatic updates.", "This cluster is using AWS Security Token Service to reach the cloud API. In order for this operator to take the actions it requires directly with the cloud API, you will need to provide a role ARN (with an attached policy) during installation. Manual subscriptions are highly recommended as steps should be taken prior to upgrade to ensure that the permissions required by the next version are properly accounted for in the role. Please see the operator description for more details.": "This cluster is using AWS Security Token Service to reach the cloud API. In order for this operator to take the actions it requires directly with the cloud API, you will need to provide a role ARN (with an attached policy) during installation. Manual subscriptions are highly recommended as steps should be taken prior to upgrade to ensure that the permissions required by the next version are properly accounted for in the role. Please see the operator description for more details.", + "Cluster in Workload Identity / Federated Identity Mode": "Cluster in Workload Identity / Federated Identity Mode", + "This cluster is using Azure Workload Identity / Federated Identity to reach the cloud API. In order for this operator to take the actions it requires directly with the cloud API, provide the Client ID, Tenant ID, and Subscription ID during installation. Manual subscriptions are highly recommended as steps should be taken before upgrade to ensure that the permissions required by the next version are properly accounted for in the role. See the operator description for more details.": "This cluster is using Azure Workload Identity / Federated Identity to reach the cloud API. In order for this operator to take the actions it requires directly with the cloud API, provide the Client ID, Tenant ID, and Subscription ID during installation. Manual subscriptions are highly recommended as steps should be taken before upgrade to ensure that the permissions required by the next version are properly accounted for in the role. See the operator description for more details.", "role ARN": "role ARN", "The role ARN required for the operator to access the cloud API.": "The role ARN required for the operator to access the cloud API.", + "Azure Client ID": "Azure Client ID", + "The Azure Client ID required for the operator to access the cloud API.": "The Azure Client ID required for the operator to access the cloud API.", + "Azure Tenant ID": "Azure Tenant ID", + "The Azure Tenant ID required for the operator to access the cloud API.": "The Azure Tenant ID required for the operator to access the cloud API.", + "Azure Subscription ID": "Azure Subscription ID", + "The Azure Subscription ID required for the operator to access the cloud API.": "The Azure Subscription ID required for the operator to access the cloud API.", "Update channel": "Update channel", "The channel to track and receive the updates from.": "The channel to track and receive the updates from.", "Installation mode": "Installation mode", diff --git a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/index.ts b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/index.ts index 54d3cfc97965..9d21c5b4bb79 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/index.ts +++ b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/index.ts @@ -32,7 +32,7 @@ export enum InfraFeatures { csi = 'Container Storage Interface', sno = 'Single Node Clusters', // eslint-disable-next-line @typescript-eslint/naming-convention - 'Short-lived token authentication' = 'Short-lived token authentication', + TokenAuth = 'Short-lived token authentication', } export enum ValidSubscriptionValue { diff --git a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-item-details.tsx b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-item-details.tsx index 68b919759018..3b44a163c51d 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-item-details.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-item-details.tsx @@ -32,7 +32,7 @@ import { ClusterServiceVersionKind, SubscriptionKind } from '../../types'; import { MarkdownView } from '../clusterserviceversion'; import { defaultChannelNameFor } from '../index'; import { OperatorChannelSelect, OperatorVersionSelect } from './operator-channel-version-select'; -import { shortLivedTokenAuth, isAWSSTSCluster } from './operator-hub-utils'; +import { isAWSSTSCluster, isAzureWIFCluster } from './operator-hub-utils'; import { InfraFeatures, OperatorHubItem } from './index'; // t('olm~Basic Install'), @@ -364,7 +364,7 @@ export const OperatorHubItemDetails: React.FC = ({ {isAWSSTSCluster(cloudCredentials, infrastructure, authentication) && showWarn && !isClusterExternallyManaged() && - infraFeatures?.find((i) => i === InfraFeatures[shortLivedTokenAuth]) && ( + infraFeatures?.find((i) => i === InfraFeatures.TokenAuth) && ( = ({

)} + {isAzureWIFCluster(cloudCredentials, infrastructure, authentication) && + showWarn && + infraFeatures?.find((i) => i === InfraFeatures.TokenAuth) && ( + setShowWarn(false)} />} + className="pf-u-mb-lg" + > +

+ {t( + 'olm~This cluster is using Azure Workload Identity / Federated Identity to reach the cloud API. In order for this operator to take the actions it requires directly with the cloud API, provide the Client ID, Tenant ID, and Subscription ID during installation. See the operator description for more details.', + )} +

+
+ )} { return 1; case InfraFeatures.FipsMode: return 2; - case InfraFeatures[shortLivedTokenAuth]: + case InfraFeatures.TokenAuth: return 3; default: return 4; @@ -424,10 +424,21 @@ export const OperatorHubTileView: React.FC = (props) = currentItem.infrastructure, currentItem.authentication, ) && - currentItem.infraFeatures?.find((i) => i === InfraFeatures[shortLivedTokenAuth]) + currentItem.infraFeatures?.find((i) => i === InfraFeatures.TokenAuth) ) { setTokenizedAuth('AWS'); } + if ( + currentItem && + isAzureWIFCluster( + currentItem.cloudCredentials, + currentItem.infrastructure, + currentItem.authentication, + ) && + currentItem.infraFeatures?.find((i) => i === InfraFeatures.TokenAuth) + ) { + setTokenizedAuth('Azure'); + } }, [filteredItems]); const showCommunityOperator = (item: OperatorHubItem) => (ignoreWarning = false) => { diff --git a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-page.tsx b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-page.tsx index 29e5af75c1cc..c4c52976f455 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-page.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-page.tsx @@ -46,8 +46,8 @@ import { subscriptionFor } from '../operator-group'; import { OperatorHubTileView } from './operator-hub-items'; import { getCatalogSourceDisplayName, - shortLivedTokenAuth, isAWSSTSCluster, + isAzureWIFCluster, } from './operator-hub-utils'; import { OperatorHubItem, @@ -167,8 +167,8 @@ export const OperatorHubList: React.FC = ({ // tlsProfiles requires addtional changes // [OperatorHubCSVAnnotationKey.tlsProfiles]: tlsProfiles, [OperatorHubCSVAnnotationKey.tokenAuthAWS]: tokenAuthAWS, - // tokenAuthAzure and tokenAuthGCP require additional changes - // [OperatorHubCSVAnnotationKey.tokenAuthAzure]: tokenAuthAzure, + [OperatorHubCSVAnnotationKey.tokenAuthAzure]: tokenAuthAzure, + // tokenAuthGCP requires additional changes // [OperatorHubCSVAnnotationKey.tokenAuthGCP]: tokenAuthGCP, [OperatorHubCSVAnnotationKey.actionText]: marketplaceActionText, [OperatorHubCSVAnnotationKey.remoteWorkflow]: marketplaceRemoteWorkflow, @@ -188,6 +188,13 @@ export const OperatorHubList: React.FC = ({ (key) => InfraFeatures[key], ); + let tokenAuthSupport = 'false'; + if (tokenAuthAWS === 'true' && isAWSSTSCluster(cloudCredential, infra, auth)) { + tokenAuthSupport = 'true'; + } else if (tokenAuthAzure === 'true' && isAzureWIFCluster(cloudCredential, infra, auth)) { + tokenAuthSupport = 'true'; + } + const featuresAnnotationsObjects = [ { key: InfraFeatures.Disconnected, value: disconnected }, { key: InfraFeatures.FipsMode, value: fipsCompliant }, @@ -195,6 +202,7 @@ export const OperatorHubList: React.FC = ({ { key: InfraFeatures.cnf, value: cnf }, { key: InfraFeatures.cni, value: cni }, { key: InfraFeatures.csi, value: csi }, + { key: InfraFeatures.TokenAuth, value: tokenAuthSupport }, ]; featuresAnnotationsObjects.forEach(({ key, value }) => { @@ -206,10 +214,6 @@ export const OperatorHubList: React.FC = ({ } }); - if (tokenAuthAWS === 'true' && isAWSSTSCluster(cloudCredential, infra, auth)) { - infrastructureFeatures.push(InfraFeatures[shortLivedTokenAuth]); - } - const infraFeatures = _.uniq(_.compact(infrastructureFeatures)); const clusterServiceVersion = diff --git a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-subscribe.tsx b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-subscribe.tsx index 5bf2fc66c08c..17062bd6ac3c 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-subscribe.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-subscribe.tsx @@ -86,6 +86,9 @@ export const OperatorHubSubscribeForm: React.FC = const { pathname: url } = useLocation(); const [roleARNText, setRoleARNText] = React.useState(''); + const [azureTenantId, setAzureTenantId] = React.useState(''); + const [azureClientId, setAzureClientId] = React.useState(''); + const [azureSubscriptionId, setAzureSubscriptionId] = React.useState(''); const { catalogNamespace, channel, pkg, tokenizedAuth, version } = getURLSearchParams(); const [targetNamespace, setTargetNamespace] = React.useState(null); const [installMode, setInstallMode] = React.useState(null); @@ -256,7 +259,8 @@ export const OperatorHubSubscribeForm: React.FC = if ( version !== currentLatestVersion || manualSubscriptionsInNamespace?.length > 0 || - tokenizedAuth === 'AWS' + tokenizedAuth === 'AWS' || + tokenizedAuth === 'Azure' ) { setApproval(InstallPlanApproval.Manual); } else setApproval(InstallPlanApproval.Automatic); @@ -419,15 +423,37 @@ export const OperatorHubSubscribeForm: React.FC = }, }; - if (tokenizedAuth === 'AWS') { - subscription.spec.config = { - env: [ - { - name: 'ROLEARN', - value: roleARNText, - }, - ], - }; + switch (tokenizedAuth) { + case 'AWS': + subscription.spec.config = { + env: [ + { + name: 'ROLEARN', + value: roleARNText, + }, + ], + }; + break; + case 'Azure': + subscription.spec.config = { + env: [ + { + name: 'CLIENTID', + value: azureClientId, + }, + { + name: 'TENANTID', + value: azureTenantId, + }, + { + name: 'SUBSCRIPTIONID', + value: azureSubscriptionId, + }, + ], + }; + break; + default: + break; } try { @@ -477,7 +503,9 @@ export const OperatorHubSubscribeForm: React.FC = !namespaceSupports(selectedTargetNamespace)(selectedInstallMode) || (selectedTargetNamespace && cannotResolve) || !_.isEmpty(conflictingProvidedAPIs(selectedTargetNamespace)) || - (tokenizedAuth === 'AWS' && _.isEmpty(roleARNText)); + (tokenizedAuth === 'AWS' && _.isEmpty(roleARNText)) || + (tokenizedAuth === 'Azure' && + [azureClientId, azureTenantId, azureSubscriptionId].some((v) => _.isEmpty(v))); const formError = () => { return ( @@ -756,6 +784,21 @@ export const OperatorHubSubscribeForm: React.FC =

)} + {tokenizedAuth === 'Azure' && showSTSWarn && ( + setShowSTSWarn(false)} />} + className="pf-u-mb-lg" + > +

+ {t( + 'olm~This cluster is using Azure Workload Identity / Federated Identity to reach the cloud API. In order for this operator to take the actions it requires directly with the cloud API, provide the Client ID, Tenant ID, and Subscription ID during installation. Manual subscriptions are highly recommended as steps should be taken before upgrade to ensure that the permissions required by the next version are properly accounted for in the role. See the operator description for more details.', + )} +

+
+ )}
<> @@ -781,6 +824,70 @@ export const OperatorHubSubscribeForm: React.FC =
)} + {tokenizedAuth === 'Azure' && ( +
+
+ + + {t( + 'olm~The Azure Client ID required for the operator to access the cloud API.', + )} + +
+ { + setAzureClientId(value); + }} + /> +
+
+
+ + + {t( + 'olm~The Azure Tenant ID required for the operator to access the cloud API.', + )} + +
+ { + setAzureTenantId(value); + }} + /> +
+
+
+ + + {t( + 'olm~The Azure Subscription ID required for the operator to access the cloud API.', + )} + +
+ { + setAzureSubscriptionId(value); + }} + /> +
+
+
+ )}
diff --git a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-utils.ts b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-utils.ts index d10763560fb3..176e6533709b 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-utils.ts +++ b/frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-utils.ts @@ -20,8 +20,6 @@ export const getCatalogSourceDisplayName = (packageManifest: PackageManifestKind ); }; -export const shortLivedTokenAuth = 'Short-lived token authentication'; - export const isAWSSTSCluster = ( cloudcreds: CloudCredentialKind, infra: InfrastructureKind, @@ -33,3 +31,15 @@ export const isAWSSTSCluster = ( auth?.spec?.serviceAccountIssuer !== '' ); }; + +export const isAzureWIFCluster = ( + cloudcreds: CloudCredentialKind, + infra: InfrastructureKind, + auth: AuthenticationKind, +) => { + return ( + cloudcreds?.spec?.credentialsMode === 'Manual' && + infra?.status?.platform === 'Azure' && + auth?.spec?.serviceAccountIssuer !== '' + ); +};