Skip to content

Commit

Permalink
[DC][OIDC] Check for both managed identity and role assignment write …
Browse files Browse the repository at this point in the history
…permissions on resource group (#7499) (#7501)

* [DC] Check for both managed identity and role assignment write permissions on resource group

* Update error message
  • Loading branch information
yoonaoh authored Dec 1, 2023
1 parent 9335a5a commit 8a9945d
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ export interface DeploymentCenterCommonFormData {
authType?: AuthType;
authIdentityClientId?: string;
authIdentity?: UserAssignedIdentity;
hasPermissionToAssignRBAC?: boolean;
hasPermissionToUseOIDC?: boolean;
hasOidcFlightEnabled?: boolean;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,17 +143,17 @@ export abstract class DeploymentCenterFormBuilder {
}),
externalRepoType: Yup.mixed().notRequired(),
devOpsProjectName: Yup.mixed().notRequired(),
authType: Yup.mixed().when('hasPermissionToAssignRBAC', {
authType: Yup.mixed().when('hasPermissionToUseOIDC', {
is: true,
then: Yup.mixed().test('authTypeRequired', this._t('deploymentCenterFieldRequiredMessage'), function(value) {
return this.parent.hasOidcFlightEnabled ? !!value : true;
}),
otherwise: Yup.mixed().test('authTypeIsNotOidc', '', function(value) {
otherwise: Yup.mixed().test('authTypeIsNotOidc', this._t('authenticationSettingsOidcPermissionsValidationError'), function(value) {
return value !== AuthType.Oidc;
}),
}),
authIdentity: Yup.mixed().notRequired(),
hasPermissionToAssignRBAC: Yup.boolean().notRequired(),
hasPermissionToUseOIDC: Yup.boolean().notRequired(),
hasOidcFlightEnabled: Yup.boolean().notRequired(),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import {
DeploymentCenterCodeFormData,
DeploymentCenterContainerFormData,
DeploymentCenterFieldProps,
UserAssignedIdentity,
} from '../DeploymentCenter.types';
import CustomBanner from '../../../../components/CustomBanner/CustomBanner';
import { DeploymentCenterLinks } from '../../../../utils/FwLinks';
import RadioButton from '../../../../components/form-controls/RadioButton';
import { Link } from '@fluentui/react/lib/Link';
import { learnMoreLinkStyle } from '../../../../components/form-controls/formControl.override.styles';
import RbacConstants from '../../../../utils/rbac-constants';
import { ArmResourceDescriptor } from '../../../../utils/resourceDescriptors';
import { getTelemetryInfo } from '../utility/DeploymentCenterUtility';

export const DeploymentCenterAuthenticationSettings = React.memo<
DeploymentCenterFieldProps<DeploymentCenterContainerFormData | DeploymentCenterCodeFormData>
Expand All @@ -26,7 +27,8 @@ export const DeploymentCenterAuthenticationSettings = React.memo<
const { formProps } = props;
const portalContext = React.useContext(PortalContext);
const deploymentCenterContext = React.useContext(DeploymentCenterContext);
const managedIdentityInfo = React.useRef<{ [key: string]: UserAssignedIdentity }>({});
const [hasRoleAssignmentWritePermission, setHasRoleAssignmentWritePermission] = React.useState<boolean>(false);
const [hasManagedIdentityWritePermission, setHasManagedIdentityWritePermission] = React.useState<boolean>(false);

const authTypeOptions = React.useMemo(() => {
return [
Expand All @@ -35,27 +37,62 @@ export const DeploymentCenterAuthenticationSettings = React.memo<
];
}, []);

const hasPermissionOverResource = React.useCallback(async () => {
const hasPermissionOverResourceGroup = React.useCallback(async () => {
if (deploymentCenterContext.resourceId) {
const hasRoleAssignmentWritePermission = await portalContext.hasPermission(deploymentCenterContext.resourceId, [
RbacConstants.roleAssignmentWriteScope,
]);
return formProps.setFieldValue('hasPermissionToAssignRBAC', hasRoleAssignmentWritePermission);
const armId = new ArmResourceDescriptor(deploymentCenterContext.resourceId);
const resourceGroup = `/subscriptions/${armId.subscription}/resourceGroups/${armId.resourceGroup}`;
const hasRoleAssignmentPermission = await portalContext.hasPermission(resourceGroup, [RbacConstants.roleAssignmentWriteScope]);
const hasManagedIdentityPermission = await portalContext.hasPermission(resourceGroup, [RbacConstants.identityWriteScope]);
portalContext.log(
getTelemetryInfo('info', 'hasPermissionToUseOIDC', 'check', {
hasRoleAssignmentWritePermission: hasRoleAssignmentPermission.toString(),
hasManagedIdentityWritePermission: hasManagedIdentityPermission.toString(),
})
);
formProps.setFieldValue('hasPermissionToUseOIDC', hasRoleAssignmentPermission && hasManagedIdentityPermission);
setHasRoleAssignmentWritePermission(hasRoleAssignmentPermission);
setHasManagedIdentityWritePermission(hasManagedIdentityPermission);
} else {
formProps.setFieldValue('hasPermissionToUseOIDC', false);
setHasRoleAssignmentWritePermission(false);
setHasManagedIdentityWritePermission(false);
}

return formProps.setFieldValue('hasPermissionToAssignRBAC', false);
}, [deploymentCenterContext.resourceId, formProps.values.hasPermissionToAssignRBAC]);
}, [deploymentCenterContext.resourceId]);

React.useEffect(() => {
const authIdentityClientId = formProps.values.authIdentityClientId;
if (authIdentityClientId && managedIdentityInfo.current[authIdentityClientId]) {
formProps.values.authIdentity = managedIdentityInfo.current[authIdentityClientId];
}
}, [formProps.values.authIdentityClientId]);
hasPermissionOverResourceGroup();
}, [hasPermissionOverResourceGroup]);

React.useEffect(() => {
hasPermissionOverResource();
}, [hasPermissionOverResource]);
const errorBanner = React.useMemo(() => {
if (formProps.values.authType === AuthType.Oidc && formProps.values.hasPermissionToUseOIDC === false) {
return (
<div className={deploymentCenterInfoBannerDiv}>
{!hasManagedIdentityWritePermission ? (
<CustomBanner
id="deployment-center-msi-permissions-error"
message={t('authenticationSettingsIdentityCreationPermissionsError')}
type={MessageBarType.blocked}
learnMoreLink={DeploymentCenterLinks.managedIdentityCreationPrereqs}
learnMoreLinkAriaLabel={t('authenticationSettingsIdentityCreationPrerequisitesLinkAriaLabel')}
/>
) : (
<CustomBanner
id="deployment-center-msi-permissions-error"
message={t('authenticationSettingsIdentityAssignmentPermissionsError')}
type={MessageBarType.blocked}
learnMoreLink={DeploymentCenterLinks.roleAssignmentPrereqs}
learnMoreLinkAriaLabel={t('authenticationSettingsRoleAssignmentPrerequisitesLinkAriaLabel')}
/>
)}
</div>
);
}
}, [
hasManagedIdentityWritePermission,
hasRoleAssignmentWritePermission,
formProps.values.authType,
formProps.values.hasPermissionToUseOIDC,
]);

return (
<div className={deploymentCenterContent}>
Expand All @@ -74,17 +111,7 @@ export const DeploymentCenterAuthenticationSettings = React.memo<
</p>
</>

{formProps.values.authType === AuthType.Oidc && formProps.values.hasPermissionToAssignRBAC === false && (
<div className={deploymentCenterInfoBannerDiv}>
<CustomBanner
id="deployment-center-msi-permissions-error"
message={t('authenticationSettingsIdentityPermissionsError')}
type={MessageBarType.blocked}
learnMoreLink={DeploymentCenterLinks.roleAssignmentPrereqs}
learnMoreLinkAriaLabel={t('authenticationSettingsRoleAssignmentPrerequisitesLinkAriaLabel')}
/>
</div>
)}
{errorBanner}

<Field
id="deployment-center-auth-type-option"
Expand Down
1 change: 1 addition & 0 deletions client-react/src/utils/FwLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,5 @@ export const DeploymentCenterLinks = {
azureDevOpsPortal: 'https://go.microsoft.com/fwlink/?linkid=2245703',
managedIdentityOidc: 'https://go.microsoft.com/fwlink/?linkid=2247951',
roleAssignmentPrereqs: 'https://go.microsoft.com/fwlink/?linkid=2249489',
managedIdentityCreationPrereqs: 'https://go.microsoft.com/fwlink/?linkid=2253463',
};
1 change: 1 addition & 0 deletions client-react/src/utils/rbac-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export default class RbacConstants {
public static configListActionScope = 'Microsoft.Web/sites/config/list/action';
public static configReadScope = 'Microsoft.Web/sites/config/read';
public static configWriteScope = 'Microsoft.Web/sites/config/write';
public static identityWriteScope = 'Microsoft.ManagedIdentity/userAssignedIdentities/write';
}
6 changes: 5 additions & 1 deletion client/src/app/shared/models/portal-resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2580,7 +2580,11 @@ export class PortalResources {
public static authenticationSettingsAuthenticationType = 'authenticationSettingsAuthenticationType';
public static authenticationSettingsAuthenticationPlaceholder = 'authenticationSettingsAuthenticationPlaceholder';
public static authenticationSettingsIdentity = 'authenticationSettingsIdentity';
public static authenticationSettingsIdentityPermissionsError = 'authenticationSettingsIdentityPermissionsError';
public static authenticationSettingsOidcPermissionsValidationError = 'authenticationSettingsOidcPermissionsValidationError';
public static authenticationSettingsIdentityCreationPermissionsError = 'authenticationSettingsIdentityCreationPermissionsError';
public static authenticationSettingsIdentityAssignmentPermissionsError = 'authenticationSettingsIdentityAssignmentPermissionsError';
public static authenticationSettingsIdentityCreationPrerequisitesLinkAriaLabel =
'authenticationSettingsIdentityCreationPrerequisitesLinkAriaLabel';
public static authenticationSettingsRoleAssignmentPrerequisitesLinkAriaLabel =
'authenticationSettingsRoleAssignmentPrerequisitesLinkAriaLabel';
public static authenticationSettingsFederatedCredentialsLinkAriaLabel = 'authenticationSettingsFederatedCredentialsLinkAriaLabel';
Expand Down
13 changes: 11 additions & 2 deletions server/Resources/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -7882,8 +7882,17 @@ Set to "External URL" to use an API definition that is hosted elsewhere.</value>
<data name="authenticationSettingsIdentity" xml:space="preserve">
<value>Identity</value>
</data>
<data name="authenticationSettingsIdentityPermissionsError" xml:space="preserve">
<value>You do not have sufficient permissions on this app to assign role-based access to a managed identity and configure federated credentials.</value>
<data name="authenticationSettingsOidcPermissionsValidationError" xml:space="preserve">
<value>You do not have sufficient permissions to authenticate with user-assigned identity.</value>
</data>
<data name="authenticationSettingsIdentityCreationPermissionsError" xml:space="preserve">
<value>You do not have sufficient permissions to create a managed identity within this resource group.</value>
</data>
<data name="authenticationSettingsIdentityAssignmentPermissionsError" xml:space="preserve">
<value>You do not have sufficient permissions to assign role-based access to a managed identity within this resource group and configure federated credentials.</value>
</data>
<data name="authenticationSettingsIdentityCreationPrerequisitesLinkAriaLabel" xml:space="preserve">
<value>Click here to learn more about how to create a managed identity.</value>
</data>
<data name="authenticationSettingsRoleAssignmentPrerequisitesLinkAriaLabel" xml:space="preserve">
<value>Click here to learn more about how to assign role-based access to a managed identity.</value>
Expand Down

0 comments on commit 8a9945d

Please sign in to comment.