Skip to content

Commit 59ab759

Browse files
liuqidakeQi Liu
and
Qi Liu
authoredNov 25, 2024··
AB#29764273 [Configuration]Disable optional interactive user for http 20 or min tls version 1.3 (#7917)
* disable optional interactive user for http 20 or min tls version 1.3 * update variable name --------- Co-authored-by: Qi Liu <[email protected]>
1 parent c174141 commit 59ab759

File tree

7 files changed

+100
-98
lines changed

7 files changed

+100
-98
lines changed
 

‎client-react/src/models/site/site.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export enum ClientCertMode {
5757
Required = 'Required',
5858
Optional = 'Optional',
5959
OptionalInteractiveUser = 'OptionalInteractiveUser',
60+
Ignore = 'Ignore',
6061
}
6162

6263
export enum MinTlsVersion {
@@ -139,7 +140,7 @@ export interface Site {
139140
clientAffinityEnabled: boolean;
140141
clientAffinityProxyEnabled: boolean;
141142
clientCertEnabled: boolean;
142-
clientCertMode: ClientCertMode;
143+
clientCertMode: string;
143144
clientCertExclusionPaths: string;
144145
hostNamesDisabled: boolean;
145146
domainVerificationIdentifiers: string;

‎client-react/src/pages/app/app-settings/AppSettingsDataLoader.tsx

+2-12
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ const AppSettingsDataLoader: React.FC<AppSettingsDataLoaderProps> = props => {
159159
}
160160

161161
const isLinux = isLinuxApp(site.data);
162-
const windowsContainer = isWindowsContainer(site.data);
162+
163163
// Get stacks response
164164
if (!loadingFailed) {
165165
if (isFunctionApp(site.data)) {
@@ -247,20 +247,10 @@ const AppSettingsDataLoader: React.FC<AppSettingsDataLoaderProps> = props => {
247247
setEditable(false);
248248
}
249249

250-
const sshEnabled = site.data.properties.sshEnabled;
251-
const functionsRuntimeAdminIsolationEnabled: boolean = !!site.data.properties.functionsRuntimeAdminIsolationEnabled;
252-
253250
setInitialValues({
254251
...convertStateToForm({
255252
// @note(krmitta): Manually over-writing since the api returns null when sshEnabled property is not set in the database but the default is true
256-
site: {
257-
...site.data,
258-
properties: {
259-
...site.data.properties,
260-
sshEnabled: (isLinux || windowsContainer) && sshEnabled === null ? true : sshEnabled,
261-
functionsRuntimeAdminIsolationEnabled: functionsRuntimeAdminIsolationEnabled,
262-
},
263-
},
253+
site: site.data,
264254
config: webConfig.data,
265255
metadata: metadata.metadata.success ? metadata.data : null,
266256
connectionStrings: connectionStrings.metadata.success ? connectionStrings.data : null,

‎client-react/src/pages/app/app-settings/AppSettingsFormData.ts

+36-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from './AppSettings.types';
1515
import { sortBy, isEqual } from 'lodash-es';
1616
import { ArmArray, ArmObj } from '../../../models/arm-obj';
17-
import { Site, PublishingCredentialPolicies, MinTlsVersion } from '../../../models/site/site';
17+
import { Site, PublishingCredentialPolicies, MinTlsVersion, ClientCertMode } from '../../../models/site/site';
1818
import {
1919
SiteConfig,
2020
ArmAzureStorageMount,
@@ -29,7 +29,7 @@ import { NameValuePair } from '../../../models/name-value-pair';
2929
import StringUtils from '../../../utils/string';
3030
import { CommonConstants } from '../../../utils/CommonConstants';
3131
import { KeyValue } from '../../../models/portal-models';
32-
import { isFlexConsumption, isFunctionApp, isWindowsCode } from '../../../utils/arm-utils';
32+
import { isFlexConsumption, isFunctionApp, isLinuxApp, isWindowsCode, isWindowsContainer } from '../../../utils/arm-utils';
3333
import { IconConstants } from '../../../utils/constants/IconConstants';
3434
import { ThemeExtended } from '../../../theme/SemanticColorsExtended';
3535
import { TFunction } from 'i18next';
@@ -93,7 +93,7 @@ export const convertStateToForm = (props: StateToFormParams): AppSettingsFormVal
9393
const formAppSetting = getFormAppSetting(appSettings, slotConfigNames);
9494

9595
return {
96-
site,
96+
site: getCleanedSite(site),
9797
basicPublishingCredentialsPolicies: getFormBasicPublishingCredentialsPolicies(basicPublishingCredentialsPolicies),
9898
config: getCleanedConfig(config),
9999
appSettings: formAppSetting,
@@ -113,6 +113,23 @@ export const convertStateToForm = (props: StateToFormParams): AppSettingsFormVal
113113
};
114114
};
115115

116+
export const getCleanedSite = (site: ArmObj<Site>) => {
117+
let sshEnabled = site.properties.sshEnabled;
118+
sshEnabled = (isLinuxApp(site) || isWindowsContainer(site)) && sshEnabled === null ? true : sshEnabled;
119+
const functionsRuntimeAdminIsolationEnabled = !!site.properties.functionsRuntimeAdminIsolationEnabled;
120+
const clientCertMode = site.properties.clientCertEnabled ? site.properties.clientCertMode : ClientCertMode.Ignore;
121+
122+
return {
123+
...site,
124+
properties: {
125+
...site.properties,
126+
sshEnabled,
127+
functionsRuntimeAdminIsolationEnabled,
128+
clientCertMode,
129+
},
130+
};
131+
};
132+
116133
export const getCleanedConfig = (config: ArmObj<SiteConfig>) => {
117134
// If Remote Debugging Version is set to VS2015, but Remote Debugging is disabled, just change it to VS2017 to prevent the PUT from failing
118135
const hasRemoteDebuggingDisabledWithVS2015 =
@@ -174,6 +191,13 @@ export const convertFormToState = (
174191
oldSlotConfigNames: ArmObj<SlotConfigNames>
175192
): ApiSetupReturn => {
176193
const site = { ...values.site };
194+
const { clientCertMode, ClientCertEnabled } = getClientCertValues(
195+
initialValues.site.properties.clientCertMode,
196+
values.site.properties.clientCertMode
197+
);
198+
site.properties.clientCertMode = clientCertMode;
199+
site.properties.clientCertEnabled = ClientCertEnabled;
200+
177201
const slotConfigNames = getStickySettings(values.appSettings, values.connectionStrings, values.azureStorageMounts, oldSlotConfigNames);
178202
const slotConfigNamesModified = isSlotConfigNamesModified(oldSlotConfigNames, slotConfigNames);
179203

@@ -239,6 +263,15 @@ export const getStorageMountAccessKey = (value: FormAzureStorageMounts) => {
239263
: accessKey;
240264
};
241265

266+
export function getClientCertValues(initialClientCertMode: string, currentClientCertMode: string) {
267+
const isClientCertModeIgnore = currentClientCertMode === ClientCertMode.Ignore;
268+
269+
return {
270+
clientCertMode: isClientCertModeIgnore ? initialClientCertMode : currentClientCertMode,
271+
ClientCertEnabled: !isClientCertModeIgnore,
272+
};
273+
}
274+
242275
export function getStickySettings(
243276
appSettings: FormAppSetting[],
244277
connectionStrings: FormConnectionString[],

‎client-react/src/pages/app/app-settings/GeneralSettings/ClientCert/ClientCert.tsx

+44-67
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
1-
import React, { useContext, useState } from 'react';
1+
import React, { useContext, useEffect, useState } from 'react';
22
import { settingsWrapper } from '../../AppSettingsForm';
33
import { Field, FormikProps } from 'formik';
4-
import RadioButtonNoFormik from '../../../../../components/form-controls/RadioButtonNoFormik';
54
import { useTranslation } from 'react-i18next';
65
import { PermissionsContext, SiteContext } from '../../Contexts';
76
import TextField from '../../../../../components/form-controls/TextField';
8-
import { Stack, PanelType, IChoiceGroupOption } from '@fluentui/react';
7+
import { Stack, PanelType, IChoiceGroupOption, MessageBarType } from '@fluentui/react';
98
import IconButton from '../../../../../components/IconButton/IconButton';
109
import EditClientExclusionPaths from './EditClientExclusionPaths';
1110
import { AppSettingsFormValues } from '../../AppSettings.types';
1211
import { ScenarioService } from '../../../../../utils/scenario-checker/scenario.service';
1312
import { ScenarioIds } from '../../../../../utils/scenario-checker/scenario-ids';
1413
import CustomPanel from '../../../../../components/CustomPanel/CustomPanel';
15-
import { ClientCertMode, Site } from '../../../../../models/site/site';
14+
import { ClientCertMode, MinTlsVersion, Site } from '../../../../../models/site/site';
1615
import { ArmObj } from '../../../../../models/arm-obj';
17-
18-
enum CompositeClientCertMode {
19-
Require = 'Require',
20-
Allow = 'Allow',
21-
Optional = 'Optional',
22-
Ignore = 'Ignore',
23-
}
16+
import RadioButton from '../../../../../components/form-controls/RadioButton';
17+
import CustomBanner from '../../../../../components/CustomBanner/CustomBanner';
2418

2519
const ClientCert: React.FC<FormikProps<AppSettingsFormValues>> = props => {
2620
const { values, setFieldValue, initialValues } = props;
@@ -29,53 +23,18 @@ const ClientCert: React.FC<FormikProps<AppSettingsFormValues>> = props => {
2923
const { app_write, editable, saving } = useContext(PermissionsContext);
3024
const disableAllControls = !app_write || !editable || saving;
3125
const [showPanel, setShowPanel] = useState(false);
32-
33-
const onClientCertModeChange = (e: any, newValue: IChoiceGroupOption) => {
34-
switch (newValue.key) {
35-
case CompositeClientCertMode.Require:
36-
setFieldValue('site.properties.clientCertEnabled', true);
37-
setFieldValue('site.properties.clientCertMode', ClientCertMode.Required);
38-
break;
39-
case CompositeClientCertMode.Allow:
40-
setFieldValue('site.properties.clientCertEnabled', true);
41-
setFieldValue('site.properties.clientCertMode', ClientCertMode.Optional);
42-
break;
43-
case CompositeClientCertMode.Optional:
44-
setFieldValue('site.properties.clientCertEnabled', true);
45-
setFieldValue('site.properties.clientCertMode', ClientCertMode.OptionalInteractiveUser);
46-
break;
47-
case CompositeClientCertMode.Ignore:
48-
setFieldValue('site.properties.clientCertEnabled', false);
49-
break;
50-
default:
51-
setFieldValue('site.properties.clientCertEnabled', false);
52-
break;
53-
}
54-
};
55-
56-
const getCompositeClientCertMode = (siteArm: ArmObj<Site>): CompositeClientCertMode => {
57-
if (siteArm.properties.clientCertEnabled) {
58-
return siteArm.properties.clientCertMode === ClientCertMode.Required
59-
? CompositeClientCertMode.Require
60-
: siteArm.properties.clientCertMode === ClientCertMode.Optional
61-
? CompositeClientCertMode.Allow
62-
: CompositeClientCertMode.Optional;
63-
}
64-
65-
return CompositeClientCertMode.Ignore;
66-
};
26+
const [disableOptionalInteractiveUserOption, setDisableOptionalInteractiveUserOption] = useState(false);
27+
const [clientCertWarningMessage, setClientCertWarningMessage] = useState('');
6728

6829
const getClientCertInfoBubbleMessage = (siteArm: ArmObj<Site>): string => {
69-
const mode = getCompositeClientCertMode(siteArm);
70-
71-
switch (mode) {
72-
case CompositeClientCertMode.Require:
30+
switch (siteArm.properties.clientCertMode) {
31+
case ClientCertMode.Required:
7332
return t('clientCertificateModeRequiredInfoBubbleMessage');
74-
case CompositeClientCertMode.Allow:
75-
return t('clientCertificateModeAllowInfoBubbleMessage');
76-
case CompositeClientCertMode.Optional:
33+
case ClientCertMode.Optional:
7734
return t('clientCertificateModeOptionalInfoBubbleMessage');
78-
case CompositeClientCertMode.Ignore:
35+
case ClientCertMode.OptionalInteractiveUser:
36+
return t('clientCertificateModeOptionalInteractiveUserInfoBubbleMessage');
37+
case ClientCertMode.Ignore:
7938
return t('clientCertificateModeIgnoreInfoBubbleMessage');
8039
default:
8140
return '';
@@ -84,6 +43,7 @@ const ClientCert: React.FC<FormikProps<AppSettingsFormValues>> = props => {
8443

8544
const scenarioChecker = new ScenarioService(t);
8645
const clientCertEnabled = scenarioChecker.checkScenario(ScenarioIds.incomingClientCertEnabled, { site });
46+
8747
const openClientExclusionPathPanel = () => {
8848
setShowPanel(true);
8949
};
@@ -95,38 +55,55 @@ const ClientCert: React.FC<FormikProps<AppSettingsFormValues>> = props => {
9555
setShowPanel(false);
9656
};
9757

58+
useEffect(() => {
59+
const http20EnabledOrMinTLSVersion13 =
60+
values.config.properties.http20Enabled || values.config.properties.minTlsVersion === MinTlsVersion.tLS13;
61+
const isClientCertModeOptionalInteractiveUser = values.site.properties.clientCertMode === ClientCertMode.OptionalInteractiveUser;
62+
63+
setDisableOptionalInteractiveUserOption(http20EnabledOrMinTLSVersion13);
64+
setClientCertWarningMessage(http20EnabledOrMinTLSVersion13 ? t('clientCertificateWarningMessage') : '');
65+
if (isClientCertModeOptionalInteractiveUser && http20EnabledOrMinTLSVersion13) {
66+
setFieldValue('site.properties.clientCertMode', ClientCertMode.Ignore);
67+
}
68+
// eslint-disable-next-line react-hooks/exhaustive-deps
69+
}, [values.config.properties.http20Enabled, values.config.properties.minTlsVersion, values.site.properties.clientCertMode]);
70+
9871
return scenarioChecker.checkScenario(ScenarioIds.incomingClientCertSupported, { site }).status !== 'disabled' ? (
9972
<>
10073
<h3>{t('incomingClientCertificates')}</h3>
10174
<div className={settingsWrapper}>
102-
<RadioButtonNoFormik
103-
dirty={getCompositeClientCertMode(values.site) !== getCompositeClientCertMode(initialValues.site)}
75+
{clientCertWarningMessage && (
76+
<CustomBanner id="clinet-cert-warning" message={clientCertWarningMessage} type={MessageBarType.warning} undocked={true} />
77+
)}
78+
<Field
79+
name={'site.properties.clientCertMode'}
80+
component={RadioButton}
81+
dirty={values.site.properties.clientCertMode !== initialValues.site.properties.clientCertMode}
10482
label={t('clientCertificateMode')}
10583
id="incoming-client-certificate-mode"
10684
ariaLabelledBy={`incoming-client-certificate-mode-label`}
107-
disabled={disableAllControls || clientCertEnabled.status === 'disabled' || values.config.properties.http20Enabled}
85+
disabled={disableAllControls || clientCertEnabled.status === 'disabled'}
10886
upsellMessage={clientCertEnabled.status === 'disabled' ? clientCertEnabled.data : ''}
109-
selectedKey={getCompositeClientCertMode(values.site)}
11087
infoBubbleMessage={getClientCertInfoBubbleMessage(values.site)}
11188
options={[
11289
{
113-
key: CompositeClientCertMode.Require,
114-
text: t('clientCertificateModeRequire'),
90+
key: ClientCertMode.Required,
91+
text: t('clientCertificateModeRequired'),
11592
},
11693
{
117-
key: CompositeClientCertMode.Allow,
118-
text: t('clientCertificateModeAllow'),
94+
key: ClientCertMode.Optional,
95+
text: t('clientCertificateModeOptional'),
11996
},
12097
{
121-
key: CompositeClientCertMode.Optional,
122-
text: t('clientCertificateModeOptional'),
98+
key: ClientCertMode.OptionalInteractiveUser,
99+
text: t('clientCertificateModeOptionalInteractiveUser'),
100+
disabled: disableOptionalInteractiveUserOption,
123101
},
124102
{
125-
key: CompositeClientCertMode.Ignore,
103+
key: ClientCertMode.Ignore,
126104
text: t('clientCertificateModeIgnore'),
127105
},
128106
]}
129-
onChange={onClientCertModeChange}
130107
/>
131108
<Stack horizontal>
132109
<Field
@@ -150,7 +127,7 @@ const ClientCert: React.FC<FormikProps<AppSettingsFormValues>> = props => {
150127
id={`edit-client-cert-exclusion-paths`}
151128
ariaLabel={t('editCertificateExlusionPaths')}
152129
title={t('editCertificateExlusionPaths')}
153-
disabled={disableAllControls || !values.site.properties.clientCertEnabled}
130+
disabled={disableAllControls || values.site.properties.clientCertMode === ClientCertMode.Ignore}
154131
onClick={openClientExclusionPathPanel}
155132
/>
156133
</Stack>

‎client-react/src/pages/app/app-settings/GeneralSettings/Platform.tsx

+2-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ScenarioService } from '../../../../utils/scenario-checker/scenario.ser
88
import { AppSettingsFormValues } from '../AppSettings.types';
99
import { PermissionsContext, SiteContext } from '../Contexts';
1010
import { Links } from '../../../../utils/FwLinks';
11-
import { IPMode, MinTlsVersion, SslState, VnetPrivatePortsCount } from '../../../../models/site/site';
11+
import { ClientCertMode, IPMode, MinTlsVersion, SslState, VnetPrivatePortsCount } from '../../../../models/site/site';
1212
import CustomBanner from '../../../../components/CustomBanner/CustomBanner';
1313
import { IDropdownOption, MessageBar, MessageBarType, mergeStyles } from '@fluentui/react';
1414
import { CommonConstants, ScmHosts } from '../../../../utils/CommonConstants';
@@ -111,12 +111,8 @@ const Platform: React.FC<FormikProps<AppSettingsFormValues>> = props => {
111111
[setFieldValue]
112112
);
113113

114-
const onHttp20EnabledChange = (event: React.FormEvent<HTMLDivElement>, option: { key: boolean }) => {
114+
const onHttp20EnabledChange = (_, option: { key: boolean }) => {
115115
props.setFieldValue('config.properties.http20ProxyFlag', 0);
116-
if (option.key) {
117-
props.setFieldValue('site.properties.clientCertEnabled', false);
118-
}
119-
120116
props.setFieldValue('config.properties.http20Enabled', option.key);
121117
};
122118

‎client/src/app/shared/models/portal-resources.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1686,14 +1686,16 @@ export class PortalResources {
16861686
public static incomingClientCertificates = 'incomingClientCertificates';
16871687
public static requireIncomingClientCertificates = 'requireIncomingClientCertificates';
16881688
public static clientCertificateMode = 'clientCertificateMode';
1689-
public static clientCertificateModeRequire = 'clientCertificateModeRequire';
1690-
public static clientCertificateModeAllow = 'clientCertificateModeAllow';
1689+
public static clientCertificateModeRequired = 'clientCertificateModeRequired';
16911690
public static clientCertificateModeOptional = 'clientCertificateModeOptional';
1691+
public static clientCertificateModeOptionalInteractiveUser = 'clientCertificateModeOptionalInteractiveUser';
16921692
public static clientCertificateModeIgnore = 'clientCertificateModeIgnore';
16931693
public static clientCertificateModeRequiredInfoBubbleMessage = 'clientCertificateModeRequiredInfoBubbleMessage';
1694-
public static clientCertificateModeAllowInfoBubbleMessage = 'clientCertificateModeAllowInfoBubbleMessage';
16951694
public static clientCertificateModeOptionalInfoBubbleMessage = 'clientCertificateModeOptionalInfoBubbleMessage';
1695+
public static clientCertificateModeOptionalInteractiveUserInfoBubbleMessage =
1696+
'clientCertificateModeOptionalInteractiveUserInfoBubbleMessage';
16961697
public static clientCertificateModeIgnoreInfoBubbleMessage = 'clientCertificateModeIgnoreInfoBubbleMessage';
1698+
public static clientCertificateWarningMessage = 'clientCertificateWarningMessage';
16971699
public static certificateExlusionPaths = 'certificateExlusionPaths';
16981700
public static editCertificateExlusionPaths = 'editCertificateExlusionPaths';
16991701
public static noExclusionRulesDefined = 'noExclusionRulesDefined';

‎server/Resources/Resources.resx

+9-6
Original file line numberDiff line numberDiff line change
@@ -5246,30 +5246,33 @@ Set to "External URL" to use an API definition that is hosted elsewhere.</value>
52465246
<data name="clientCertificateMode" xml:space="preserve">
52475247
<value>Client certificate mode</value>
52485248
</data>
5249-
<data name="clientCertificateModeRequire" xml:space="preserve">
5249+
<data name="clientCertificateModeRequired" xml:space="preserve">
52505250
<value>Require</value>
52515251
</data>
5252-
<data name="clientCertificateModeAllow" xml:space="preserve">
5253-
<value>Allow</value>
5254-
</data>
52555252
<data name="clientCertificateModeOptional" xml:space="preserve">
52565253
<value>Optional</value>
52575254
</data>
5255+
<data name="clientCertificateModeOptionalInteractiveUser" xml:space="preserve">
5256+
<value>Optional Interactive User</value>
5257+
</data>
52585258
<data name="clientCertificateModeIgnore" xml:space="preserve">
52595259
<value>Ignore</value>
52605260
</data>
52615261
<data name="clientCertificateModeRequiredInfoBubbleMessage" xml:space="preserve">
52625262
<value>All requests must be authenticated through a client certificate.</value>
52635263
</data>
5264-
<data name="clientCertificateModeAllowInfoBubbleMessage" xml:space="preserve">
5264+
<data name="clientCertificateModeOptionalInfoBubbleMessage" xml:space="preserve">
52655265
<value>Clients will be prompted for a certificate, if no certificate is provided fallback to SSO or other means of authentication. Unauthenticated requests will be blocked.</value>
52665266
</data>
5267-
<data name="clientCertificateModeOptionalInfoBubbleMessage" xml:space="preserve">
5267+
<data name="clientCertificateModeOptionalInteractiveUserInfoBubbleMessage" xml:space="preserve">
52685268
<value>Clients will not be prompted for a certificate by default. Unless the request can be authenticated through other means (like SSO), it will be blocked.</value>
52695269
</data>
52705270
<data name="clientCertificateModeIgnoreInfoBubbleMessage" xml:space="preserve">
52715271
<value>No client authentication is required. Unauthenticated requests will not be blocked.</value>
52725272
</data>
5273+
<data name="clientCertificateWarningMessage" xml:space="preserve">
5274+
<value>Client certificate mode "Optional Interactive User" and client certificate exclusion path regardless of client certificate mode are not compatible with Http version 2.0 or minimum inbound TLS Version 1.3</value>
5275+
</data>
52735276
<data name="certificateExlusionPaths" xml:space="preserve">
52745277
<value>Certificate exclusion paths</value>
52755278
</data>

0 commit comments

Comments
 (0)
Please sign in to comment.