SSO LDAP: Add configuration for root ca, client key and client certificate (#92866)

* Add missing properties with `_value` prefix

* Add RadioButtonGrpoup

* Add file/value section for certs

* Add mapping for keys and certificates

* Rename base and file types

* Add styles

* Add base64 values configuration flags

* Add function to render root ca certificate contents

* generate i18n files
This commit is contained in:
linoman 2024-09-04 12:19:45 +02:00 committed by GitHub
parent 13c8f4d212
commit b213ecc2dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 352 additions and 178 deletions

View File

@ -1,5 +1,5 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { useId } from 'react'; import { Dispatch, SetStateAction, useEffect, useId, useState } from 'react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { GrafanaTheme2, SelectableValue } from '@grafana/data';
@ -12,25 +12,41 @@ import {
Icon, Icon,
Input, Input,
Label, Label,
MultiSelect,
Select, Select,
Stack, Stack,
Switch, Switch,
Text, Text,
TextLink, TextLink,
Tooltip, Tooltip,
RadioButtonGroup,
SecretInput,
} from '@grafana/ui'; } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization'; import { t, Trans } from 'app/core/internationalization';
import { LdapPayload } from 'app/types'; import { LdapPayload, MapKeyCertConfigured } from 'app/types';
interface Props { interface Props {
onClose: () => void; onClose: () => void;
mapKeyCertConfigured: MapKeyCertConfigured;
setMapKeyCertConfigured: Dispatch<SetStateAction<MapKeyCertConfigured>>;
} }
const serverConfig = 'settings.config.servers.0';
const tlsOptions: Array<SelectableValue<string>> = ['TLS1.2', 'TLS1.3'].map((v) => ({ label: v, value: v })); const tlsOptions: Array<SelectableValue<string>> = ['TLS1.2', 'TLS1.3'].map((v) => ({ label: v, value: v }));
enum EncryptionProvider {
Base64 = 'base64',
FilePath = 'path',
}
export const LdapDrawerComponent = ({
onClose,
mapKeyCertConfigured: mapCertConfigured,
setMapKeyCertConfigured: setMapCertConfigured,
}: Props) => {
const [encryptionProvider, setEncryptionProvider] = useState(EncryptionProvider.Base64);
export const LdapDrawerComponent = ({ onClose }: Props) => {
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const { register, setValue, watch } = useFormContext<LdapPayload>(); const { getValues, register, setValue, watch } = useFormContext<LdapPayload>();
const nameId = useId(); const nameId = useId();
const surnameId = useId(); const surnameId = useId();
@ -38,6 +54,22 @@ export const LdapDrawerComponent = ({ onClose }: Props) => {
const memberOfId = useId(); const memberOfId = useId();
const emailId = useId(); const emailId = useId();
useEffect(() => {
const { client_cert, client_key, root_ca_cert } = getValues(serverConfig);
setEncryptionProvider(
!client_cert.length && !client_key.length && !root_ca_cert?.length
? EncryptionProvider.Base64
: EncryptionProvider.FilePath
);
}, [getValues]);
const renderMultiSelectLabel = (value: string) => {
if (value.length >= 5) {
return `${value.slice(0, 2)}...${value.slice(-2)}`;
}
return value;
};
const groupMappingsLabel = ( const groupMappingsLabel = (
<Label <Label
className={styles.sectionLabel} className={styles.sectionLabel}
@ -48,7 +80,7 @@ export const LdapDrawerComponent = ({ onClose }: Props) => {
); );
const useTlsDescription = ( const useTlsDescription = (
<Trans i18nKey="ldap-drawer.extra-security-section.use-ssl.tooltip"> <Trans i18nKey="ldap-drawer.extra-security-section.use-ssl-tooltip">
For a complete list of supported ciphers and TLS versions, refer to:{' '} For a complete list of supported ciphers and TLS versions, refer to:{' '}
{ {
<TextLink style={{ fontSize: 'inherit' }} href="https://go.dev/src/crypto/tls/cipher_suites.go" external> <TextLink style={{ fontSize: 'inherit' }} href="https://go.dev/src/crypto/tls/cipher_suites.go" external>
@ -62,18 +94,18 @@ export const LdapDrawerComponent = ({ onClose }: Props) => {
<Drawer title={t('ldap-drawer.title', 'Advanced settings')} onClose={onClose}> <Drawer title={t('ldap-drawer.title', 'Advanced settings')} onClose={onClose}>
<CollapsableSection label={t('ldap-drawer.misc-section.label', 'Misc')} isOpen={true}> <CollapsableSection label={t('ldap-drawer.misc-section.label', 'Misc')} isOpen={true}>
<Field <Field
label={t('ldap-drawer.misc-section.allow-sign-up.label', 'Allow sign up')} label={t('ldap-drawer.misc-section.allow-sign-up-label', 'Allow sign up')}
description={t( description={t(
'ldap-drawer.misc-section.allow-sign-up.descrition', 'ldap-drawer.misc-section.allow-sign-up-descrition',
'If not enabled, only existing Grafana users can log in using LDAP' 'If not enabled, only existing Grafana users can log in using LDAP'
)} )}
> >
<Switch id="allow-sign-up" {...register('settings.allowSignUp')} /> <Switch id="allow-sign-up" {...register('settings.allowSignUp')} />
</Field> </Field>
<Field <Field
label={t('ldap-drawer.misc-section.port.label', 'Port')} label={t('ldap-drawer.misc-section.port-label', 'Port')}
description={t( description={t(
'ldap-drawer.misc-section.port.description', 'ldap-drawer.misc-section.port-description',
'Default port is 389 without SSL or 636 with SSL' 'Default port is 389 without SSL or 636 with SSL'
)} )}
> >
@ -81,13 +113,13 @@ export const LdapDrawerComponent = ({ onClose }: Props) => {
id="port" id="port"
placeholder="389" placeholder="389"
type="number" type="number"
{...register('settings.config.servers.0.port', { valueAsNumber: true })} {...register(`${serverConfig}.port`, { valueAsNumber: true })}
/> />
</Field> </Field>
<Field <Field
label={t('ldap-drawer.misc-section.timeout.label', 'Timeout')} label={t('ldap-drawer.misc-section.timeout-label', 'Timeout')}
description={t( description={t(
'ldap-drawer.misc-section.timeout.description', 'ldap-drawer.misc-section.timeout-description',
'Timeout in seconds for the connection to the LDAP server' 'Timeout in seconds for the connection to the LDAP server'
)} )}
> >
@ -95,7 +127,7 @@ export const LdapDrawerComponent = ({ onClose }: Props) => {
id="timeout" id="timeout"
placeholder="10" placeholder="10"
type="number" type="number"
{...register('settings.config.servers.0.timeout', { valueAsNumber: true })} {...register(`${serverConfig}.timeout`, { valueAsNumber: true })}
/> />
</Field> </Field>
</CollapsableSection> </CollapsableSection>
@ -106,72 +138,70 @@ export const LdapDrawerComponent = ({ onClose }: Props) => {
the application correctly retrieves and displays user information. the application correctly retrieves and displays user information.
</Trans> </Trans>
</Text> </Text>
<Field label={t('ldap-drawer.attributes-section.name.label', 'Name')}> <Field label={t('ldap-drawer.attributes-section.name-label', 'Name')}>
<Input id={nameId} {...register('settings.config.servers.0.attributes.name')} /> <Input id={nameId} {...register(`${serverConfig}.attributes.name`)} />
</Field> </Field>
<Field label={t('ldap-drawer.attributes-section.surname.label', 'Surname')}> <Field label={t('ldap-drawer.attributes-section.surname-label', 'Surname')}>
<Input id={surnameId} {...register('settings.config.servers.0.attributes.surname')} /> <Input id={surnameId} {...register(`${serverConfig}.attributes.surname`)} />
</Field> </Field>
<Field label={t('ldap-drawer.attributes-section.username.label', 'Username')}> <Field label={t('ldap-drawer.attributes-section.username-label', 'Username')}>
<Input id={usernameId} {...register('settings.config.servers.0.attributes.username')} /> <Input id={usernameId} {...register(`${serverConfig}.attributes.username`)} />
</Field> </Field>
<Field label={t('ldap-drawer.attributes-section.member-of.label', 'Member Of')}> <Field label={t('ldap-drawer.attributes-section.member-of-label', 'Member Of')}>
<Input id={memberOfId} {...register('settings.config.servers.0.attributes.member_of')} /> <Input id={memberOfId} {...register(`${serverConfig}.attributes.member_of`)} />
</Field> </Field>
<Field label={t('ldap-drawer.attributes-section.email.label', 'Email')}> <Field label={t('ldap-drawer.attributes-section.email-label', 'Email')}>
<Input id={emailId} {...register('settings.config.servers.0.attributes.email')} /> <Input id={emailId} {...register(`${serverConfig}.attributes.email`)} />
</Field> </Field>
</CollapsableSection> </CollapsableSection>
<CollapsableSection label={groupMappingsLabel} isOpen={true}> <CollapsableSection label={groupMappingsLabel} isOpen={true}>
<Field <Field
htmlFor="skip-org-role-sync" htmlFor="skip-org-role-sync"
label={t('ldap-drawer.group-mapping-section.skip-org-role-sync.label', 'Skip organization role sync')} label={t('ldap-drawer.group-mapping-section.skip-org-role-sync-label', 'Skip organization role sync')}
description={t( description={t(
'ldap-drawer.group-mapping-section.skip-org-role-sync.description', 'ldap-drawer.group-mapping-section.skip-org-role-sync-description',
'Prevent synchronizing users organization roles from your IdP' 'Prevent synchronizing users organization roles from your IdP'
)} )}
> >
<Switch id="skip-org-role-sync" {...register('settings.config.servers.0.skip_org_role_sync')} /> <Switch id="skip-org-role-sync" {...register(`${serverConfig}.skip_org_role_sync`)} />
</Field> </Field>
<Field <Field
htmlFor="group-search-filter" htmlFor="group-search-filter"
label={t('ldap-drawer.group-mapping-section.group-search-filter.label', 'Group search filter')} label={t('ldap-drawer.group-mapping-section.group-search-filter-label', 'Group search filter')}
description={t( description={t(
'ldap-drawer.group-mapping-section.group-search-filter.description', 'ldap-drawer.group-mapping-section.group-search-filter-description',
'Used to filter and identify group entries within the directory' 'Used to filter and identify group entries within the directory'
)} )}
> >
<Input id="group-search-filter" {...register('settings.config.servers.0.group_search_filter')} /> <Input id="group-search-filter" {...register(`${serverConfig}.group_search_filter`)} />
</Field> </Field>
<Field <Field
htmlFor="group-search-base-dns" htmlFor="group-search-base-dns"
label={t('ldap-drawer.group-mapping-section.group-search-base-dns.label', 'Group search base DNS')} label={t('ldap-drawer.group-mapping-section.group-search-base-dns-label', 'Group search base DNS')}
description={t( description={t(
'ldap-drawer.group-mapping-section.group-search-base-dns.description', 'ldap-drawer.group-mapping-section.group-search-base-dns-description',
'Separate by commas or spaces' 'Separate by commas or spaces'
)} )}
> >
<Input <Input
id="group-search-base-dns" id="group-search-base-dns"
onChange={({ currentTarget: { value } }) => onChange={({ currentTarget: { value } }) => setValue(`${serverConfig}.group_search_base_dns`, [value])}
setValue('settings.config.servers.0.group_search_base_dns', [value])
}
/> />
</Field> </Field>
<Field <Field
htmlFor="group-search-filter-user-attribute" htmlFor="group-search-filter-user-attribute"
label={t( label={t(
'ldap-drawer.group-mapping-section.group-search-filter-user-attribute.label', 'ldap-drawer.group-mapping-section.group-search-filter-user-attribute-label',
'Group name attribute' 'Group name attribute'
)} )}
description={t( description={t(
'ldap-drawer.group-mapping-section.group-search-filter-user-attribute.description', 'ldap-drawer.group-mapping-section.group-search-filter-user-attribute-description',
'Identifies users within group entries for filtering purposes' 'Identifies users within group entries for filtering purposes'
)} )}
> >
<Input <Input
id="group-search-filter-user-attribute" id="group-search-filter-user-attribute"
{...register('settings.config.servers.0.group_search_filter_user_attribute')} {...register(`${serverConfig}.group_search_filter_user_attribute`)}
/> />
</Field> </Field>
<Divider /> <Divider />
@ -181,67 +211,203 @@ export const LdapDrawerComponent = ({ onClose }: Props) => {
isOpen={true} isOpen={true}
> >
<Field <Field
label={t('ldap-drawer.extra-security-section.use-ssl.label', 'Use SSL')} label={t('ldap-drawer.extra-security-section.use-ssl-label', 'Use SSL')}
description={t( description={t(
'ldap-drawer.extra-security-section.use-ssl.description', 'ldap-drawer.extra-security-section.use-ssl-description',
'Set to true if LDAP server should use TLS connection (either with STARTTLS or LDAPS)' 'Set to true if LDAP server should use TLS connection (either with STARTTLS or LDAPS)'
)} )}
> >
<Stack> <Stack>
<Switch id="use-ssl" {...register('settings.config.servers.0.use_ssl')} /> <Switch id="use-ssl" {...register(`${serverConfig}.use_ssl`)} />
<Tooltip content={useTlsDescription} interactive> <Tooltip content={useTlsDescription} interactive>
<Icon name="info-circle" /> <Icon name="info-circle" />
</Tooltip> </Tooltip>
</Stack> </Stack>
</Field> </Field>
{watch('settings.config.servers.0.use_ssl') && ( {watch(`${serverConfig}.use_ssl`) && (
<> <>
<Field <Field
label={t('ldap-drawer.extra-security-section.start-tls.label', 'Start TLS')} label={t('ldap-drawer.extra-security-section.start-tls-label', 'Start TLS')}
description={t( description={t(
'ldap-drawer.extra-security-section.start-tls.description', 'ldap-drawer.extra-security-section.start-tls-description',
'If set to true, use LDAP with STARTTLS instead of LDAPS' 'If set to true, use LDAP with STARTTLS instead of LDAPS'
)} )}
> >
<Switch id="start-tls" {...register('settings.config.servers.0.start_tls')} /> <Switch id="start-tls" {...register(`${serverConfig}.start_tls`)} />
</Field> </Field>
<Field <Field
htmlFor="min-tls-version" htmlFor="min-tls-version"
label={t('ldap-drawer.extra-security-section.min-tls-version.label', 'Min TLS version')} label={t('ldap-drawer.extra-security-section.min-tls-version-label', 'Min TLS version')}
description={t( description={t(
'ldap-drawer.extra-security-section.min-tls-version.description', 'ldap-drawer.extra-security-section.min-tls-version-description',
'This is the minimum TLS version allowed. Accepted values are: TLS1.2, TLS1.3.' 'This is the minimum TLS version allowed. Accepted values are: TLS1.2, TLS1.3.'
)} )}
> >
<Select <Select
id="min-tls-version" id="min-tls-version"
options={tlsOptions} options={tlsOptions}
value={watch('settings.config.servers.0.min_tls_version')} value={watch(`${serverConfig}.min_tls_version`)}
onChange={({ value }) => setValue('settings.config.servers.0.min_tls_version', value)} onChange={({ value }) => setValue(`${serverConfig}.min_tls_version`, value)}
/> />
</Field> </Field>
<Field <Field
label={t('ldap-drawer.extra-security-section.tls-ciphers.label', 'TLS ciphers')} label={t('ldap-drawer.extra-security-section.tls-ciphers-label', 'TLS ciphers')}
description={t( description={t(
'ldap-drawer.extra-security-section.tls-ciphers.description', 'ldap-drawer.extra-security-section.tls-ciphers-description',
'List of comma- or space-separated ciphers' 'List of comma- or space-separated ciphers'
)} )}
> >
<Input <Input
id="tls-ciphers" id="tls-ciphers"
placeholder={t( placeholder={t(
'ldap-drawer.extra-security-section.tls-ciphers.placeholder', 'ldap-drawer.extra-security-section.tls-ciphers-placeholder',
'e.g. ["TLS_AES_256_GCM_SHA384"]' 'e.g. ["TLS_AES_256_GCM_SHA384"]'
)} )}
value={watch('settings.config.servers.0.tls_ciphers')} value={watch(`${serverConfig}.tls_ciphers`) || ''}
onChange={({ currentTarget: { value } }) => onChange={({ currentTarget: { value } }) =>
setValue( setValue(
'settings.config.servers.0.tls_ciphers', `${serverConfig}.tls_ciphers`,
value?.split(/,|\s/).map((v: string) => v.trim()) value?.split(/,|\s/).map((v) => v.trim())
) )
} }
/> />
</Field> </Field>
<Field
label={t(
'ldap-drawer.extra-security-section.encryption-provider-label',
'Encryption key and certificate provision specification.'
)}
description={t(
'ldap-drawer.extra-security-section.encryption-provider-description',
'X.509 certificate provides the public part, while the private key issued in a PKCS#8 format provides the private part of the asymmetric encryption.'
)}
>
<RadioButtonGroup
id="encryption-provider"
options={[
{
label: t(
'ldap-drawer.extra-security-section.encryption-provider-base-64',
'Base64-encoded content'
),
value: EncryptionProvider.Base64,
},
{
label: t('ldap-drawer.extra-security-section.encryption-provider-file-path', 'Path to files'),
value: EncryptionProvider.FilePath,
},
]}
value={encryptionProvider}
onChange={setEncryptionProvider}
/>
</Field>
{encryptionProvider === EncryptionProvider.Base64 && (
<>
<Field
label={t(
'ldap-drawer.extra-security-section.root-ca-cert-value-label',
'Root CA certificate content'
)}
>
<MultiSelect
id="root-ca-cert"
allowCustomValue
onChange={(v) => {
setValue(
`${serverConfig}.root_ca_cert_value`,
v.filter(({ v }) => typeof v === 'string')?.map(({ v }) => v)
);
}}
value={watch(`${serverConfig}.root_ca_cert_value`)?.map((v) => ({
label: renderMultiSelectLabel(v),
value: v,
}))}
/>
</Field>
<Field
label={t('ldap-drawer.extra-security-section.client-cert-value-label', 'Client certificate content')}
>
<SecretInput
id="client-cert"
placeholder={t(
'ldap-drawer.extra-security-section.client-cert-value-placeholder',
'Client certificate content in base64'
)}
isConfigured={mapCertConfigured.clientCertValue}
onReset={() => {
setValue(`${serverConfig}.client_cert_value`, '');
setMapCertConfigured({ ...mapCertConfigured, clientCertValue: false });
}}
/>
</Field>
<Field label={t('ldap-drawer.extra-security-section.client-key-value-label', 'Client key content')}>
<SecretInput
id="client-key"
placeholder={t(
'ldap-drawer.extra-security-section.client-key-value-placeholder',
'Client key content in base64'
)}
isConfigured={mapCertConfigured.clientKeyCertValue}
onReset={() => {
setValue(`${serverConfig}.client_key_value`, '');
setMapCertConfigured({ ...mapCertConfigured, clientKeyCertValue: false });
}}
/>
</Field>
</>
)}
{encryptionProvider === EncryptionProvider.FilePath && (
<>
<Field label={t('ldap-drawer.extra-security-section.root-ca-cert-label', 'Root CA certificate path')}>
<SecretInput
id="root-ca-cert"
placeholder={t(
'ldap-drawer.extra-security-section.root-ca-cert-placeholder',
'/path/to/root_ca_cert.pem'
)}
isConfigured={mapCertConfigured.rootCaCertPath}
onReset={() => {
setValue(`${serverConfig}.root_ca_cert`, '');
setMapCertConfigured({ ...mapCertConfigured, rootCaCertPath: false });
}}
value={watch(`${serverConfig}.root_ca_cert`)}
onChange={({ currentTarget: { value } }) => setValue(`${serverConfig}.root_ca_cert`, value)}
/>
</Field>
<Field label={t('ldap-drawer.extra-security-section.client-cert-label', 'Client certificate path')}>
<SecretInput
id="client-cert"
placeholder={t(
'ldap-drawer.extra-security-section.client-cert-placeholder',
'/path/to/client_cert.pem'
)}
isConfigured={mapCertConfigured.clientCertPath}
onReset={() => {
setValue(`${serverConfig}.client_cert`, '');
setMapCertConfigured({ ...mapCertConfigured, clientCertPath: false });
}}
value={watch(`${serverConfig}.client_cert`)}
onChange={({ currentTarget: { value } }) => setValue(`${serverConfig}.client_cert`, value)}
/>
</Field>
<Field label={t('ldap-drawer.extra-security-section.client-key-label', 'Client key path')}>
<SecretInput
id="client-key"
placeholder={t(
'ldap-drawer.extra-security-section.client-key-placeholder',
'/path/to/client_key.pem'
)}
isConfigured={mapCertConfigured.clientKeyCertPath}
onReset={() => {
setValue(`${serverConfig}.client_key`, '');
setMapCertConfigured({ ...mapCertConfigured, clientKeyCertPath: false });
}}
value={watch(`${serverConfig}.client_key`)}
onChange={({ currentTarget: { value } }) => setValue(`${serverConfig}.client_key`, value)}
/>
</Field>
</>
)}
</> </>
)} )}
</CollapsableSection> </CollapsableSection>

View File

@ -10,7 +10,7 @@ import { Page } from 'app/core/components/Page/Page';
import config from 'app/core/config'; import config from 'app/core/config';
import { t, Trans } from 'app/core/internationalization'; import { t, Trans } from 'app/core/internationalization';
import { Loader } from 'app/features/plugins/admin/components/Loader'; import { Loader } from 'app/features/plugins/admin/components/Loader';
import { LdapPayload, StoreState } from 'app/types'; import { LdapPayload, MapKeyCertConfigured, StoreState } from 'app/types';
import { LdapDrawerComponent } from './LdapDrawer'; import { LdapDrawerComponent } from './LdapDrawer';
@ -44,7 +44,9 @@ const emptySettings: LdapPayload = {
bind_dn: '', bind_dn: '',
bind_password: '', bind_password: '',
client_cert: '', client_cert: '',
client_cert_value: '',
client_key: '', client_key: '',
client_key_value: '',
group_mappings: [], group_mappings: [],
group_search_base_dns: [], group_search_base_dns: [],
group_search_filter: '', group_search_filter: '',
@ -53,6 +55,7 @@ const emptySettings: LdapPayload = {
min_tls_version: '', min_tls_version: '',
port: 389, port: 389,
root_ca_cert: '', root_ca_cert: '',
root_ca_cert_value: [],
search_base_dns: [], search_base_dns: [],
search_filter: '', search_filter: '',
skip_org_role_sync: false, skip_org_role_sync: false,
@ -75,6 +78,17 @@ export const LdapSettingsPage = () => {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [mapKeyCertConfigured, setMapKeyCertConfigured] = useState<MapKeyCertConfigured>({
// values
rootCaCertValue: false,
clientCertValue: false,
clientKeyCertValue: false,
// paths
rootCaCertPath: false,
clientCertPath: false,
clientKeyCertPath: false,
});
const methods = useForm<LdapPayload>({ defaultValues: emptySettings }); const methods = useForm<LdapPayload>({ defaultValues: emptySettings });
const { getValues, handleSubmit, register, reset } = methods; const { getValues, handleSubmit, register, reset } = methods;
@ -91,6 +105,16 @@ export const LdapSettingsPage = () => {
return; return;
} }
const serverConfig = payload.settings.config.servers[0];
setMapKeyCertConfigured({
rootCaCertValue: serverConfig.root_ca_cert_value?.length > 0,
clientCertValue: serverConfig.client_cert_value !== '',
clientKeyCertValue: serverConfig.client_key_value !== '',
rootCaCertPath: serverConfig.root_ca_cert !== '',
clientCertPath: serverConfig.client_cert !== '',
clientKeyCertPath: serverConfig.client_key !== '',
});
reset(payload); reset(payload);
setIsLoading(false); setIsLoading(false);
} }
@ -300,7 +324,13 @@ export const LdapSettingsPage = () => {
</Box> </Box>
</section> </section>
)} )}
{isDrawerOpen && <LdapDrawerComponent onClose={() => setIsDrawerOpen(false)} />} {isDrawerOpen && (
<LdapDrawerComponent
onClose={() => setIsDrawerOpen(false)}
mapKeyCertConfigured={mapKeyCertConfigured}
setMapKeyCertConfigured={setMapKeyCertConfigured}
/>
)}
</form> </form>
</FormProvider> </FormProvider>
</Page.Contents> </Page.Contents>

View File

@ -84,7 +84,9 @@ export interface LdapServerConfig {
bind_dn: string; bind_dn: string;
bind_password?: string; bind_password?: string;
client_cert: string; client_cert: string;
client_cert_value: string;
client_key: string; client_key: string;
client_key_value: string;
group_mappings: GroupMapping[]; group_mappings: GroupMapping[];
group_search_base_dns: string[]; group_search_base_dns: string[];
group_search_filter: string; group_search_filter: string;
@ -93,6 +95,7 @@ export interface LdapServerConfig {
min_tls_version?: string; min_tls_version?: string;
port: number; port: number;
root_ca_cert: string; root_ca_cert: string;
root_ca_cert_value: string[];
search_base_dns: string[]; search_base_dns: string[];
search_filter: string; search_filter: string;
skip_org_role_sync: boolean; skip_org_role_sync: boolean;
@ -135,3 +138,12 @@ export interface LdapPayload {
settings: LdapSettings; settings: LdapSettings;
source: string; source: string;
} }
export interface MapKeyCertConfigured {
rootCaCertValue: boolean;
clientCertValue: boolean;
clientKeyCertValue: boolean;
rootCaCertPath: boolean;
clientCertPath: boolean;
clientKeyCertPath: boolean;
}

View File

@ -1005,78 +1005,61 @@
"ldap-drawer": { "ldap-drawer": {
"attributes-section": { "attributes-section": {
"description": "Specify the LDAP attributes that map to the user&lsquo;s given name, surname, and email address, ensuring the application correctly retrieves and displays user information.", "description": "Specify the LDAP attributes that map to the user&lsquo;s given name, surname, and email address, ensuring the application correctly retrieves and displays user information.",
"email": { "email-label": "Email",
"label": "Email"
},
"label": "Attributes", "label": "Attributes",
"member-of": { "member-of-label": "Member Of",
"label": "Member Of" "name-label": "Name",
}, "surname-label": "Surname",
"name": { "username-label": "Username"
"label": "Name"
},
"surname": {
"label": "Surname"
},
"username": {
"label": "Username"
}
}, },
"extra-security-section": { "extra-security-section": {
"client-cert-label": "Client certificate path",
"client-cert-placeholder": "/path/to/client_cert.pem",
"client-cert-value-label": "Client certificate content",
"client-cert-value-placeholder": "Client certificate content in base64",
"client-key-label": "Client key path",
"client-key-placeholder": "/path/to/client_key.pem",
"client-key-value-label": "Client key content",
"client-key-value-placeholder": "Client key content in base64",
"encryption-provider-base-64": "Base64-encoded content",
"encryption-provider-description": "X.509 certificate provides the public part, while the private key issued in a PKCS#8 format provides the private part of the asymmetric encryption.",
"encryption-provider-file-path": "Path to files",
"encryption-provider-label": "Encryption key and certificate provision specification.",
"label": "Extra security measures", "label": "Extra security measures",
"min-tls-version": { "min-tls-version-description": "This is the minimum TLS version allowed. Accepted values are: TLS1.2, TLS1.3.",
"description": "This is the minimum TLS version allowed. Accepted values are: TLS1.2, TLS1.3.", "min-tls-version-label": "Min TLS version",
"label": "Min TLS version" "root-ca-cert-label": "Root CA certificate path",
}, "root-ca-cert-placeholder": "/path/to/root_ca_cert.pem",
"start-tls": { "root-ca-cert-value-label": "Root CA certificate content",
"description": "If set to true, use LDAP with STARTTLS instead of LDAPS", "start-tls-description": "If set to true, use LDAP with STARTTLS instead of LDAPS",
"label": "Start TLS" "start-tls-label": "Start TLS",
}, "tls-ciphers-description": "List of comma- or space-separated ciphers",
"tls-ciphers": { "tls-ciphers-label": "TLS ciphers",
"description": "List of comma- or space-separated ciphers", "tls-ciphers-placeholder": "e.g. [\"TLS_AES_256_GCM_SHA384\"]",
"label": "TLS ciphers", "use-ssl-description": "Set to true if LDAP server should use TLS connection (either with STARTTLS or LDAPS)",
"placeholder": "e.g. [\"TLS_AES_256_GCM_SHA384\"]" "use-ssl-label": "Use SSL",
}, "use-ssl-tooltip": "For a complete list of supported ciphers and TLS versions, refer to: {\n <TextLink style={{ fontSize: 'inherit' }} href=\"https://go.dev/src/crypto/tls/cipher_suites.go\" external>\n https://go.dev/src/crypto/tls/cipher_suites.go\n </TextLink>}"
"use-ssl": {
"description": "Set to true if LDAP server should use TLS connection (either with STARTTLS or LDAPS)",
"label": "Use SSL",
"tooltip": "For a complete list of supported ciphers and TLS versions, refer to: {\n <TextLink style={{ fontSize: 'inherit' }} href=\"https://go.dev/src/crypto/tls/cipher_suites.go\" external>\n https://go.dev/src/crypto/tls/cipher_suites.go\n </TextLink>}"
}
}, },
"group-mapping-section": { "group-mapping-section": {
"description": "Map LDAP groups to Grafana org roles", "description": "Map LDAP groups to Grafana org roles",
"group-search-base-dns": { "group-search-base-dns-description": "Separate by commas or spaces",
"description": "Separate by commas or spaces", "group-search-base-dns-label": "Group search base DNS",
"label": "Group search base DNS" "group-search-filter-description": "Used to filter and identify group entries within the directory",
}, "group-search-filter-label": "Group search filter",
"group-search-filter": { "group-search-filter-user-attribute-description": "Identifies users within group entries for filtering purposes",
"description": "Used to filter and identify group entries within the directory", "group-search-filter-user-attribute-label": "Group name attribute",
"label": "Group search filter"
},
"group-search-filter-user-attribute": {
"description": "Identifies users within group entries for filtering purposes",
"label": "Group name attribute"
},
"label": "Group mapping", "label": "Group mapping",
"skip-org-role-sync": { "skip-org-role-sync-description": "Prevent synchronizing users organization roles from your IdP",
"description": "Prevent synchronizing users organization roles from your IdP", "skip-org-role-sync-label": "Skip organization role sync"
"label": "Skip organization role sync"
}
}, },
"misc-section": { "misc-section": {
"allow-sign-up": { "allow-sign-up-descrition": "If not enabled, only existing Grafana users can log in using LDAP",
"descrition": "If not enabled, only existing Grafana users can log in using LDAP", "allow-sign-up-label": "Allow sign up",
"label": "Allow sign up"
},
"label": "Misc", "label": "Misc",
"port": { "port-description": "Default port is 389 without SSL or 636 with SSL",
"description": "Default port is 389 without SSL or 636 with SSL", "port-label": "Port",
"label": "Port" "timeout-description": "Timeout in seconds for the connection to the LDAP server",
}, "timeout-label": "Timeout"
"timeout": {
"description": "Timeout in seconds for the connection to the LDAP server",
"label": "Timeout"
}
}, },
"title": "Advanced settings" "title": "Advanced settings"
}, },

View File

@ -1005,78 +1005,61 @@
"ldap-drawer": { "ldap-drawer": {
"attributes-section": { "attributes-section": {
"description": "Ŝpęčįƒy ŧĥę ĿĐÅP äŧŧřįþūŧęş ŧĥäŧ mäp ŧő ŧĥę ūşęř&ľşqūő;ş ģįvęʼn ʼnämę, şūřʼnämę, äʼnđ ęmäįľ äđđřęşş, ęʼnşūřįʼnģ ŧĥę äppľįčäŧįőʼn čőřřęčŧľy řęŧřįęvęş äʼnđ đįşpľäyş ūşęř įʼnƒőřmäŧįőʼn.", "description": "Ŝpęčįƒy ŧĥę ĿĐÅP äŧŧřįþūŧęş ŧĥäŧ mäp ŧő ŧĥę ūşęř&ľşqūő;ş ģįvęʼn ʼnämę, şūřʼnämę, äʼnđ ęmäįľ äđđřęşş, ęʼnşūřįʼnģ ŧĥę äppľįčäŧįőʼn čőřřęčŧľy řęŧřįęvęş äʼnđ đįşpľäyş ūşęř įʼnƒőřmäŧįőʼn.",
"email": { "email-label": "Ēmäįľ",
"label": "Ēmäįľ"
},
"label": "Åŧŧřįþūŧęş", "label": "Åŧŧřįþūŧęş",
"member-of": { "member-of-label": "Męmþęř ؃",
"label": "Męmþęř ؃" "name-label": "Ńämę",
}, "surname-label": "Ŝūřʼnämę",
"name": { "username-label": "Ůşęřʼnämę"
"label": "Ńämę"
},
"surname": {
"label": "Ŝūřʼnämę"
},
"username": {
"label": "Ůşęřʼnämę"
}
}, },
"extra-security-section": { "extra-security-section": {
"client-cert-label": "Cľįęʼnŧ čęřŧįƒįčäŧę päŧĥ",
"client-cert-placeholder": "/päŧĥ/ŧő/čľįęʼnŧ_čęřŧ.pęm",
"client-cert-value-label": "Cľįęʼnŧ čęřŧįƒįčäŧę čőʼnŧęʼnŧ",
"client-cert-value-placeholder": "Cľįęʼnŧ čęřŧįƒįčäŧę čőʼnŧęʼnŧ įʼn þäşę64",
"client-key-label": "Cľįęʼnŧ ĸęy päŧĥ",
"client-key-placeholder": "/päŧĥ/ŧő/čľįęʼnŧ_ĸęy.pęm",
"client-key-value-label": "Cľįęʼnŧ ĸęy čőʼnŧęʼnŧ",
"client-key-value-placeholder": "Cľįęʼnŧ ĸęy čőʼnŧęʼnŧ įʼn þäşę64",
"encryption-provider-base-64": "ßäşę64-ęʼnčőđęđ čőʼnŧęʼnŧ",
"encryption-provider-description": "X.509 čęřŧįƒįčäŧę přővįđęş ŧĥę pūþľįč päřŧ, ŵĥįľę ŧĥę přįväŧę ĸęy įşşūęđ įʼn ä PĶCŜ#8 ƒőřmäŧ přővįđęş ŧĥę přįväŧę päřŧ őƒ ŧĥę äşymmęŧřįč ęʼnčřypŧįőʼn.",
"encryption-provider-file-path": "Päŧĥ ŧő ƒįľęş",
"encryption-provider-label": "Ēʼnčřypŧįőʼn ĸęy äʼnđ čęřŧįƒįčäŧę přővįşįőʼn şpęčįƒįčäŧįőʼn.",
"label": "Ēχŧřä şęčūřįŧy męäşūřęş", "label": "Ēχŧřä şęčūřįŧy męäşūřęş",
"min-tls-version": { "min-tls-version-description": "Ŧĥįş įş ŧĥę mįʼnįmūm ŦĿŜ vęřşįőʼn äľľőŵęđ. Åččępŧęđ väľūęş äřę: ŦĿŜ1.2, ŦĿŜ1.3.",
"description": "Ŧĥįş įş ŧĥę mįʼnįmūm ŦĿŜ vęřşįőʼn äľľőŵęđ. Åččępŧęđ väľūęş äřę: ŦĿŜ1.2, ŦĿŜ1.3.", "min-tls-version-label": "Mįʼn ŦĿŜ vęřşįőʼn",
"label": "Mįʼn ŦĿŜ vęřşįőʼn" "root-ca-cert-label": "Ŗőőŧ CÅ čęřŧįƒįčäŧę päŧĥ",
}, "root-ca-cert-placeholder": "/päŧĥ/ŧő/řőőŧ_čä_čęřŧ.pęm",
"start-tls": { "root-ca-cert-value-label": "Ŗőőŧ CÅ čęřŧįƒįčäŧę čőʼnŧęʼnŧ",
"description": "Ĩƒ şęŧ ŧő ŧřūę, ūşę ĿĐÅP ŵįŧĥ ŜŦÅŖŦŦĿŜ įʼnşŧęäđ őƒ ĿĐÅPŜ", "start-tls-description": "Ĩƒ şęŧ ŧő ŧřūę, ūşę ĿĐÅP ŵįŧĥ ŜŦÅŖŦŦĿŜ įʼnşŧęäđ őƒ ĿĐÅPŜ",
"label": "Ŝŧäřŧ ŦĿŜ" "start-tls-label": "Ŝŧäřŧ ŦĿŜ",
}, "tls-ciphers-description": "Ŀįşŧ őƒ čőmmä- őř şpäčę-şępäřäŧęđ čįpĥęřş",
"tls-ciphers": { "tls-ciphers-label": "ŦĿŜ čįpĥęřş",
"description": "Ŀįşŧ őƒ čőmmä- őř şpäčę-şępäřäŧęđ čįpĥęřş", "tls-ciphers-placeholder": "ę.ģ. [\"ŦĿŜ_ÅĒŜ_256_ĞCM_ŜĦÅ384\"]",
"label": "ŦĿŜ čįpĥęřş", "use-ssl-description": "Ŝęŧ ŧő ŧřūę įƒ ĿĐÅP şęřvęř şĥőūľđ ūşę ŦĿŜ čőʼnʼnęčŧįőʼn (ęįŧĥęř ŵįŧĥ ŜŦÅŖŦŦĿŜ őř ĿĐÅPŜ)",
"placeholder": "ę.ģ. [\"ŦĿŜ_ÅĒŜ_256_ĞCM_ŜĦÅ384\"]" "use-ssl-label": "Ůşę ŜŜĿ",
}, "use-ssl-tooltip": "Főř ä čőmpľęŧę ľįşŧ őƒ şūppőřŧęđ čįpĥęřş äʼnđ ŦĿŜ vęřşįőʼnş, řęƒęř ŧő: {\n <ŦęχŧĿįʼnĸ şŧyľę={{ fontSize: 'inherit' }} ĥřęƒ=\"ĥŧŧpş://ģő.đęv/şřč/čřypŧő/ŧľş/čįpĥęř_şūįŧęş.ģő\" ęχŧęřʼnäľ>\n ĥŧŧpş://ģő.đęv/şřč/čřypŧő/ŧľş/čįpĥęř_şūįŧęş.ģő\n </ŦęχŧĿįʼnĸ>}"
"use-ssl": {
"description": "Ŝęŧ ŧő ŧřūę įƒ ĿĐÅP şęřvęř şĥőūľđ ūşę ŦĿŜ čőʼnʼnęčŧįőʼn (ęįŧĥęř ŵįŧĥ ŜŦÅŖŦŦĿŜ őř ĿĐÅPŜ)",
"label": "Ůşę ŜŜĿ",
"tooltip": "Főř ä čőmpľęŧę ľįşŧ őƒ şūppőřŧęđ čįpĥęřş äʼnđ ŦĿŜ vęřşįőʼnş, řęƒęř ŧő: {\n <ŦęχŧĿįʼnĸ şŧyľę={{ fontSize: 'inherit' }} ĥřęƒ=\"ĥŧŧpş://ģő.đęv/şřč/čřypŧő/ŧľş/čįpĥęř_şūįŧęş.ģő\" ęχŧęřʼnäľ>\n ĥŧŧpş://ģő.đęv/şřč/čřypŧő/ŧľş/čįpĥęř_şūįŧęş.ģő\n </ŦęχŧĿįʼnĸ>}"
}
}, },
"group-mapping-section": { "group-mapping-section": {
"description": "Mäp ĿĐÅP ģřőūpş ŧő Ğřäƒäʼnä őřģ řőľęş", "description": "Mäp ĿĐÅP ģřőūpş ŧő Ğřäƒäʼnä őřģ řőľęş",
"group-search-base-dns": { "group-search-base-dns-description": "Ŝępäřäŧę þy čőmmäş őř şpäčęş",
"description": "Ŝępäřäŧę þy čőmmäş őř şpäčęş", "group-search-base-dns-label": "Ğřőūp şęäřčĥ þäşę ĐŃŜ",
"label": "Ğřőūp şęäřčĥ þäşę ĐŃŜ" "group-search-filter-description": "Ůşęđ ŧő ƒįľŧęř äʼnđ įđęʼnŧįƒy ģřőūp ęʼnŧřįęş ŵįŧĥįʼn ŧĥę đįřęčŧőřy",
}, "group-search-filter-label": "Ğřőūp şęäřčĥ ƒįľŧęř",
"group-search-filter": { "group-search-filter-user-attribute-description": "Ĩđęʼnŧįƒįęş ūşęřş ŵįŧĥįʼn ģřőūp ęʼnŧřįęş ƒőř ƒįľŧęřįʼnģ pūřpőşęş",
"description": "Ůşęđ ŧő ƒįľŧęř äʼnđ įđęʼnŧįƒy ģřőūp ęʼnŧřįęş ŵįŧĥįʼn ŧĥę đįřęčŧőřy", "group-search-filter-user-attribute-label": "Ğřőūp ʼnämę äŧŧřįþūŧę",
"label": "Ğřőūp şęäřčĥ ƒįľŧęř"
},
"group-search-filter-user-attribute": {
"description": "Ĩđęʼnŧįƒįęş ūşęřş ŵįŧĥįʼn ģřőūp ęʼnŧřįęş ƒőř ƒįľŧęřįʼnģ pūřpőşęş",
"label": "Ğřőūp ʼnämę äŧŧřįþūŧę"
},
"label": "Ğřőūp mäppįʼnģ", "label": "Ğřőūp mäppįʼnģ",
"skip-org-role-sync": { "skip-org-role-sync-description": "Přęvęʼnŧ şyʼnčĥřőʼnįžįʼnģ ūşęřş’ őřģäʼnįžäŧįőʼn řőľęş ƒřőm yőūř ĨđP",
"description": "Přęvęʼnŧ şyʼnčĥřőʼnįžįʼnģ ūşęřş’ őřģäʼnįžäŧįőʼn řőľęş ƒřőm yőūř ĨđP", "skip-org-role-sync-label": "Ŝĸįp őřģäʼnįžäŧįőʼn řőľę şyʼnč"
"label": "Ŝĸįp őřģäʼnįžäŧįőʼn řőľę şyʼnč"
}
}, },
"misc-section": { "misc-section": {
"allow-sign-up": { "allow-sign-up-descrition": "Ĩƒ ʼnőŧ ęʼnäþľęđ, őʼnľy ęχįşŧįʼnģ Ğřäƒäʼnä ūşęřş čäʼn ľőģ įʼn ūşįʼnģ ĿĐÅP",
"descrition": "Ĩƒ ʼnőŧ ęʼnäþľęđ, őʼnľy ęχįşŧįʼnģ Ğřäƒäʼnä ūşęřş čäʼn ľőģ įʼn ūşįʼnģ ĿĐÅP", "allow-sign-up-label": "Åľľőŵ şįģʼn ūp",
"label": "Åľľőŵ şįģʼn ūp"
},
"label": "Mįşč", "label": "Mįşč",
"port": { "port-description": "Đęƒäūľŧ pőřŧ įş 389 ŵįŧĥőūŧ ŜŜĿ őř 636 ŵįŧĥ ŜŜĿ",
"description": "Đęƒäūľŧ pőřŧ įş 389 ŵįŧĥőūŧ ŜŜĿ őř 636 ŵįŧĥ ŜŜĿ", "port-label": "Pőřŧ",
"label": "Pőřŧ" "timeout-description": "Ŧįmęőūŧ įʼn şęčőʼnđş ƒőř ŧĥę čőʼnʼnęčŧįőʼn ŧő ŧĥę ĿĐÅP şęřvęř",
}, "timeout-label": "Ŧįmęőūŧ"
"timeout": {
"description": "Ŧįmęőūŧ įʼn şęčőʼnđş ƒőř ŧĥę čőʼnʼnęčŧįőʼn ŧő ŧĥę ĿĐÅP şęřvęř",
"label": "Ŧįmęőūŧ"
}
}, },
"title": "Åđväʼnčęđ şęŧŧįʼnģş" "title": "Åđväʼnčęđ şęŧŧįʼnģş"
}, },