Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DC] Check on GHA BuildProvider, fix role assignment check, and punycode non-ASCII characters #7546

Merged
merged 5 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IStyleBaseArray } from '@fluentui/merge-styles';
import { IStyleBaseArray, IRawStyle } from '@fluentui/merge-styles';
import { IDropdownStyles, ITextFieldStyles, ITooltipHostStyles } from '@fluentui/react';
import { style } from 'typestyle';
import { ComboBoxStyles } from '../../theme/CustomOfficeFabric/AzurePortal/ComboBox.styles';
Expand Down Expand Up @@ -42,6 +42,10 @@ export const comboboxStyleOverrides = (theme: ThemeExtended, fullpage: boolean,
width: widthOverride || FORM_DEFAULT_WIDTH,
},
],
errorMessage: {
...(baseStyle.errorMessage as IRawStyle),
width: widthOverride || FORM_DEFAULT_WIDTH,
},
} as IDropdownStyles;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,10 @@ export abstract class DeploymentCenterFormBuilder {
externalRepoType: Yup.mixed().notRequired(),
devOpsProjectName: Yup.mixed().notRequired(),
authType: Yup.mixed().test('authTypeRequired', this._t('deploymentCenterFieldRequiredMessage'), function(value) {
return this.parent.sourceProvider === ScmType.GitHubAction ? !!value : true;
return this.parent.buildProvider === BuildProvider.GitHubAction ? !!value : true;
}),
authIdentity: Yup.mixed().test('authIdentityRequired', this._t('deploymentCenterFieldRequiredMessage'), function(value) {
return this.parent.sourceProvider === ScmType.GitHubAction && this.parent.authType === AuthType.Oidc ? !!value.resourceId : true;
return this.parent.buildProvider === BuildProvider.GitHubAction && this.parent.authType === AuthType.Oidc ? !!value.id : true;
}),
hasPermissionToUseOIDC: Yup.boolean().notRequired(),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const DeploymentCenterCodeForm: React.FC<DeploymentCenterCodeFormProps> = props
},
});
} else {
if (values.sourceProvider === ScmType.GitHubAction && values.authType === AuthType.Oidc) {
if (values.buildProvider === BuildProvider.GitHubAction && values.authType === AuthType.Oidc) {
const armId = new ArmResourceDescriptor(deploymentCenterContext.resourceId);
portalContext.log(getTelemetryInfo('info', 'registerManagedIdentityProvider', 'submit'));
const registerManagedIdentityProviderResponse = await deploymentCenterData.registerProvider(
Expand All @@ -86,50 +86,30 @@ const DeploymentCenterCodeForm: React.FC<DeploymentCenterCodeFormProps> = props
return errorResponse;
}

const getRoleAssignmentsWithScope = deploymentCenterData.getRoleAssignmentsWithScope(
deploymentCenterContext.resourceId,
values.authIdentity.properties.principalId
);
const listFederatedCredentials = deploymentCenterData.listFederatedCredentials(values.authIdentity.id);
const [getRoleAssignmentsWithScopeResponse, listFederatedCredentialsResponse] = await Promise.all([
getRoleAssignmentsWithScope,
const [identityHasRole, listFederatedCredentialsResponse] = await Promise.all([
checkRoleAssignmentsForIdentity(values.authIdentity.properties.principalId),
listFederatedCredentials,
]);

if (getRoleAssignmentsWithScopeResponse.metadata.success) {
const hasRoleAssignment = deploymentCenterData.hasRoleAssignment(
if (!identityHasRole && values.hasPermissionToUseOIDC) {
const putWebsiteContributorRoleResponse = await deploymentCenterData.putRoleAssignmentWithScope(
RBACRoleId.websiteContributor,
getRoleAssignmentsWithScopeResponse.data.value
deploymentCenterContext.resourceId,
values.authIdentity.properties.principalId,
PrincipalType.servicePrincipal
);
if (!hasRoleAssignment) {
const putWebsiteContributorRoleResponse = await deploymentCenterData.putRoleAssignmentWithScope(
RBACRoleId.websiteContributor,
deploymentCenterContext.resourceId,
values.authIdentity.properties.principalId,
PrincipalType.servicePrincipal
if (!putWebsiteContributorRoleResponse.metadata.success) {
portalContext.log(
getTelemetryInfo('error', 'putWebsiteContributorRoleResponse', 'failed', {
message: getErrorMessage(putWebsiteContributorRoleResponse.metadata.error),
errorAsString: putWebsiteContributorRoleResponse.metadata.error
? JSON.stringify(putWebsiteContributorRoleResponse.metadata.error)
: '',
})
);
if (!putWebsiteContributorRoleResponse.metadata.success) {
portalContext.log(
getTelemetryInfo('error', 'putWebsiteContributorRoleResponse', 'failed', {
message: getErrorMessage(putWebsiteContributorRoleResponse.metadata.error),
errorAsString: putWebsiteContributorRoleResponse.metadata.error
? JSON.stringify(putWebsiteContributorRoleResponse.metadata.error)
: '',
})
);
return putWebsiteContributorRoleResponse;
}
return putWebsiteContributorRoleResponse;
}
} else {
portalContext.log(
getTelemetryInfo('error', 'getRoleAssignmentsWithScopeResponse', 'failed', {
message: getErrorMessage(getRoleAssignmentsWithScopeResponse.metadata.error),
errorAsString: getRoleAssignmentsWithScopeResponse.metadata.error
? JSON.stringify(getRoleAssignmentsWithScopeResponse.metadata.error)
: '',
})
);
return getRoleAssignmentsWithScopeResponse;
}

if (listFederatedCredentialsResponse.metadata.success) {
Expand Down Expand Up @@ -287,6 +267,49 @@ const DeploymentCenterCodeForm: React.FC<DeploymentCenterCodeFormProps> = props
}
};

const checkRoleAssignmentsForIdentity = async (principalId?: string) => {
if (principalId) {
const armId = new ArmResourceDescriptor(deploymentCenterContext.resourceId);
const subscriptionId = `/subscriptions/${armId.subscription}`;
const resourceGroupId = `/subscriptions/${armId.subscription}/resourceGroups/${armId.resourceGroup}`;
const [roleAssignmentsOnSub, roleAssignmentsOnRg, roleAssignmentsOnApp] = await Promise.all([
deploymentCenterData.getRoleAssignmentsWithScope(subscriptionId, principalId),
deploymentCenterData.getRoleAssignmentsWithScope(resourceGroupId, principalId),
deploymentCenterData.getRoleAssignmentsWithScope(deploymentCenterContext.resourceId, principalId),
]);

if (roleAssignmentsOnSub.metadata.success && roleAssignmentsOnRg.metadata.success && roleAssignmentsOnApp.metadata.success) {
const hasOwnerAccess = deploymentCenterData.hasRoleAssignment(RBACRoleId.owner, [
...roleAssignmentsOnSub.data.value,
...roleAssignmentsOnRg.data.value,
...roleAssignmentsOnApp.data.value,
]);
spellegrino021 marked this conversation as resolved.
Show resolved Hide resolved
const hasContributorAccess = deploymentCenterData.hasRoleAssignment(RBACRoleId.contributor, [
...roleAssignmentsOnSub.data.value,
...roleAssignmentsOnRg.data.value,
...roleAssignmentsOnApp.data.value,
]);
const hasWebsiteContributorAccess = deploymentCenterData.hasRoleAssignment(RBACRoleId.websiteContributor, [
...roleAssignmentsOnSub.data.value,
...roleAssignmentsOnRg.data.value,
...roleAssignmentsOnApp.data.value,
]);

return hasOwnerAccess || hasContributorAccess || hasWebsiteContributorAccess;
} else {
portalContext.log(
getTelemetryInfo('error', 'checkRoleAssignmentsForIdentityForOidc', 'failed', {
message:
`roleAssignmentsOnSub: ${getErrorMessage(roleAssignmentsOnSub.metadata.error)}, ` +
`roleAssignmentsOnRg: ${getErrorMessage(roleAssignmentsOnRg.metadata.error)}, ` +
`roleAssignmentsOnApp: ${getErrorMessage(roleAssignmentsOnApp.metadata.error)} `,
})
);
}
}
return false;
};

const getKuduSourceControlsPayload = (values: DeploymentCenterFormData<DeploymentCenterCodeFormData>): SiteSourceControlRequestBody => {
return {
repoUrl: getRepoUrl(values),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ const DeploymentCenterCodeSettings: React.FC<DeploymentCenterFieldProps<Deployme
useEffect(() => {
if (
deploymentCenterContext.siteDescriptor &&
formProps.values.branch &&
(formProps.values.workflowOption === WorkflowOption.UseExistingWorkflowConfig ||
formProps.values.workflowOption === WorkflowOption.Add ||
formProps.values.workflowOption === WorkflowOption.Overwrite)
Expand All @@ -248,7 +249,7 @@ const DeploymentCenterCodeSettings: React.FC<DeploymentCenterFieldProps<Deployme
setWorkflowFilePath('');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formProps.values.workflowOption]);
}, [deploymentCenterContext.siteDescriptor, formProps.values.branch, formProps.values.workflowOption]);

const getSettingsControls = () => (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,14 @@ const DeploymentCenterCodeSourceAndBuild: React.FC<DeploymentCenterFieldProps<De
);
}, [selectedBuild, deploymentCenterPublishingContext.basicPublishingCredentialsPolicies?.scm.allow, formProps.values.authType]);

useEffect(() => {
if (selectedBuild === BuildProvider.GitHubAction) {
formProps.setFieldValue('authType', AuthType.Oidc);
} else {
formProps.setFieldValue('authType', AuthType.PublishProfile);
}
}, [selectedBuild]);

const showNoWritePermissionBanner = useMemo(() => {
return !deploymentCenterContext.hasWritePermission;
}, [deploymentCenterContext.hasWritePermission]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { ISiteState } from '../../../../SiteState';
import { Guid } from '../../../../utils/Guid';
import { truncate } from 'lodash-es';
import { isSameLocation } from '../../../../utils/location';
import { toASCII } from 'punycode';

export const getRuntimeStackSetting = (
isLinuxApp: boolean,
Expand Down Expand Up @@ -636,11 +637,12 @@ export const getFederatedCredentialName = (fullRepoName: string): string => {

export const getUserAssignedIdentityName = (appName: string): string => {
const guid = Guid.newTinyGuid();
if (`${appName}-id-${guid}`.length > 24) {
return `${truncate(appName, { length: 24 - `-id-${guid}`.length, omission: '' })}-id-${guid}`;
const encodedAppName = toASCII(appName);
if (`${encodedAppName}-id-${guid}`.length > 24) {
return `${truncate(encodedAppName, { length: 24 - `-id-${guid}`.length, omission: '' })}-id-${guid}`;
}

return `${appName}-id-${guid}`;
return `${encodedAppName}-id-${guid}`;
};

export const isScmTypeValidForContainers = (scmType: ScmType): boolean => {
Expand Down
4 changes: 2 additions & 2 deletions server/Resources/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -7895,10 +7895,10 @@ Set to "External URL" to use an API definition that is hosted elsewhere.</value>
<value>You do not have sufficient permissions to authenticate with user-assigned identity.</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. If you wish to continue, please select an existing identity below. The identity must have write permissions on the app.</value>
<value>You do not have sufficient permissions to assign role-based access to a managed identity within this resource group and configure federated credentials. If you wish to continue, please select an existing identity below. The identity must have a Website Contributor or higher role on the app.</value>
</data>
<data name="authenticationSettingsIdentityWritePermissionsError" xml:space="preserve">
<value>This identity does not have write permissions on this app. Please select a different identity, or work with your admin to grant the Website Contributor role to your identity on this app.</value>
<value>This identity does not have a Website Contributor or higher role on this app. Please select a different identity, or work with your admin to grant the Website Contributor role to your identity on this app.</value>
</data>
<data name="authenticationSettingsIdentityUnsupportedRegionError" xml:space="preserve">
<value>This identity is in a region that does not support creating federated identity credentials. Please select an identity from a different region.</value>
Expand Down
Loading