mirror of
https://github.com/portainer/portainer.git
synced 2025-07-25 00:09:40 +02:00
fix(docker/tls): update tls certs for Docker API env [EE-4286] (#9112)
This commit is contained in:
parent
f1f46f4da1
commit
f02ede00b3
13 changed files with 184 additions and 64 deletions
|
@ -1,15 +1,20 @@
|
|||
import { useFormikContext } from 'formik';
|
||||
import { FormikErrors } from 'formik';
|
||||
import { SchemaOf, boolean, object } from 'yup';
|
||||
|
||||
import { file, withFileSize } from '@@/form-components/yup-file-validation';
|
||||
import { FileUploadField } from '@@/form-components/FileUpload';
|
||||
import { SwitchField } from '@@/form-components/SwitchField';
|
||||
import { FormControl } from '@@/form-components/FormControl';
|
||||
|
||||
import { FormValues } from './types';
|
||||
import { TLSConfig } from './types';
|
||||
|
||||
export function TLSFieldset() {
|
||||
const { values, setFieldValue, errors } = useFormikContext<FormValues>();
|
||||
interface Props {
|
||||
values: TLSConfig;
|
||||
onChange: (value: Partial<TLSConfig>) => void;
|
||||
errors?: FormikErrors<TLSConfig>;
|
||||
}
|
||||
|
||||
export function TLSFieldset({ values, onChange, errors }: Props) {
|
||||
return (
|
||||
<>
|
||||
<div className="form-group">
|
||||
|
@ -18,7 +23,7 @@ export function TLSFieldset() {
|
|||
label="TLS"
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
checked={values.tls}
|
||||
onChange={(checked) => setFieldValue('tls', checked)}
|
||||
onChange={(checked) => handleChange({ tls: checked })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -30,7 +35,8 @@ export function TLSFieldset() {
|
|||
<SwitchField
|
||||
label="Skip Certification Verification"
|
||||
checked={!!values.skipVerify}
|
||||
onChange={(checked) => setFieldValue('skipVerify', checked)}
|
||||
onChange={(checked) => handleChange({ skipVerify: checked })}
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -40,33 +46,33 @@ export function TLSFieldset() {
|
|||
<FormControl
|
||||
label="TLS CA certificate"
|
||||
inputId="ca-cert-field"
|
||||
errors={errors.caCertFile}
|
||||
errors={errors?.caCertFile}
|
||||
>
|
||||
<FileUploadField
|
||||
inputId="ca-cert-field"
|
||||
onChange={(file) => setFieldValue('caCertFile', file)}
|
||||
onChange={(file) => handleChange({ caCertFile: file })}
|
||||
value={values.caCertFile}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="TLS certificate"
|
||||
inputId="cert-field"
|
||||
errors={errors.certFile}
|
||||
errors={errors?.certFile}
|
||||
>
|
||||
<FileUploadField
|
||||
inputId="cert-field"
|
||||
onChange={(file) => setFieldValue('certFile', file)}
|
||||
onChange={(file) => handleChange({ certFile: file })}
|
||||
value={values.certFile}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="TLS key"
|
||||
inputId="tls-key-field"
|
||||
errors={errors.keyFile}
|
||||
errors={errors?.keyFile}
|
||||
>
|
||||
<FileUploadField
|
||||
inputId="tls-key-field"
|
||||
onChange={(file) => setFieldValue('keyFile', file)}
|
||||
onChange={(file) => handleChange({ keyFile: file })}
|
||||
value={values.keyFile}
|
||||
/>
|
||||
</FormControl>
|
||||
|
@ -76,21 +82,29 @@ export function TLSFieldset() {
|
|||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
function handleChange(partialValue: Partial<TLSConfig>) {
|
||||
onChange(partialValue);
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_FILE_SIZE = 5_242_880; // 5MB
|
||||
|
||||
function certValidation() {
|
||||
function certValidation(optional?: boolean) {
|
||||
return withFileSize(file(), MAX_FILE_SIZE).when(['tls', 'skipVerify'], {
|
||||
is: (tls: boolean, skipVerify: boolean) => tls && !skipVerify,
|
||||
is: (tls: boolean, skipVerify: boolean) => tls && !skipVerify && !optional,
|
||||
then: (schema) => schema.required('File is required'),
|
||||
});
|
||||
}
|
||||
|
||||
export function validation() {
|
||||
return {
|
||||
caCertFile: certValidation(),
|
||||
certFile: certValidation(),
|
||||
keyFile: certValidation(),
|
||||
};
|
||||
export function tlsConfigValidation({
|
||||
optionalCert,
|
||||
}: { optionalCert?: boolean } = {}): SchemaOf<TLSConfig> {
|
||||
return object({
|
||||
tls: boolean().default(false),
|
||||
skipVerify: boolean().default(false),
|
||||
caCertFile: certValidation(optionalCert),
|
||||
certFile: certValidation(optionalCert),
|
||||
keyFile: certValidation(optionalCert),
|
||||
});
|
||||
}
|
1
app/react/components/TLSFieldset/index.ts
Normal file
1
app/react/components/TLSFieldset/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { TLSFieldset, tlsConfigValidation } from './TLSFieldset';
|
7
app/react/components/TLSFieldset/types.ts
Normal file
7
app/react/components/TLSFieldset/types.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export interface TLSConfig {
|
||||
tls: boolean;
|
||||
skipVerify?: boolean;
|
||||
caCertFile?: File;
|
||||
certFile?: File;
|
||||
keyFile?: File;
|
||||
}
|
|
@ -67,6 +67,13 @@ export function isLocalEnvironment(environment: Environment) {
|
|||
);
|
||||
}
|
||||
|
||||
export function isDockerAPIEnvironment(environment: Environment) {
|
||||
return (
|
||||
environment.URL.startsWith('tcp://') &&
|
||||
environment.Type === EnvironmentType.Docker
|
||||
);
|
||||
}
|
||||
|
||||
export function getDashboardRoute(environment: Environment) {
|
||||
if (isEdgeEnvironment(environment.Type)) {
|
||||
if (!environment.EdgeID) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
Environment,
|
||||
EnvironmentCreationTypes,
|
||||
} from '@/react/portainer/environments/types';
|
||||
import { TLSFieldset } from '@/react/components/TLSFieldset/TLSFieldset';
|
||||
|
||||
import { LoadingButton } from '@@/buttons/LoadingButton';
|
||||
import { FormControl } from '@@/form-components/FormControl';
|
||||
|
@ -19,7 +20,6 @@ import { MoreSettingsSection } from '../../shared/MoreSettingsSection';
|
|||
|
||||
import { useValidation } from './APIForm.validation';
|
||||
import { FormValues } from './types';
|
||||
import { TLSFieldset } from './TLSFieldset';
|
||||
|
||||
interface Props {
|
||||
onCreate(environment: Environment): void;
|
||||
|
@ -31,7 +31,10 @@ export function APIForm({ onCreate, isDockerStandalone }: Props) {
|
|||
const initialValues: FormValues = {
|
||||
url: '',
|
||||
name: '',
|
||||
tls: false,
|
||||
tlsConfig: {
|
||||
tls: false,
|
||||
skipVerify: false,
|
||||
},
|
||||
meta: {
|
||||
groupId: 1,
|
||||
tagIds: [],
|
||||
|
@ -52,7 +55,7 @@ export function APIForm({ onCreate, isDockerStandalone }: Props) {
|
|||
validateOnMount
|
||||
key={formKey}
|
||||
>
|
||||
{({ isValid, dirty }) => (
|
||||
{({ values, errors, setFieldValue, isValid, dirty }) => (
|
||||
<Form>
|
||||
<NameField />
|
||||
|
||||
|
@ -70,7 +73,15 @@ export function APIForm({ onCreate, isDockerStandalone }: Props) {
|
|||
/>
|
||||
</FormControl>
|
||||
|
||||
<TLSFieldset />
|
||||
<TLSFieldset
|
||||
values={values.tlsConfig}
|
||||
onChange={(value) =>
|
||||
Object.entries(value).forEach(([key, value]) =>
|
||||
setFieldValue(`tlsConfig.${key}`, value)
|
||||
)
|
||||
}
|
||||
errors={errors.tlsConfig}
|
||||
/>
|
||||
|
||||
<MoreSettingsSection>
|
||||
{isDockerStandalone && (
|
||||
|
@ -141,24 +152,24 @@ export function APIForm({ onCreate, isDockerStandalone }: Props) {
|
|||
}
|
||||
);
|
||||
function getTlsValues() {
|
||||
if (!values.tls) {
|
||||
if (!values.tlsConfig.tls) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
skipVerify: values.skipVerify,
|
||||
skipVerify: values.tlsConfig.skipVerify,
|
||||
...getCertFiles(),
|
||||
};
|
||||
|
||||
function getCertFiles() {
|
||||
if (values.skipVerify) {
|
||||
if (values.tlsConfig.skipVerify) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
caCertFile: values.caCertFile,
|
||||
certFile: values.certFile,
|
||||
keyFile: values.keyFile,
|
||||
caCertFile: values.tlsConfig.caCertFile,
|
||||
certFile: values.tlsConfig.certFile,
|
||||
keyFile: values.tlsConfig.keyFile,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import { boolean, object, SchemaOf, string } from 'yup';
|
||||
import { object, SchemaOf, string } from 'yup';
|
||||
|
||||
import { tlsConfigValidation } from '@/react/components/TLSFieldset/TLSFieldset';
|
||||
|
||||
import { metadataValidation } from '../../shared/MetadataFieldset/validation';
|
||||
import { useNameValidation } from '../../shared/NameField';
|
||||
|
||||
import { validation as certsValidation } from './TLSFieldset';
|
||||
import { FormValues } from './types';
|
||||
|
||||
export function useValidation(): SchemaOf<FormValues> {
|
||||
return object({
|
||||
name: useNameValidation(),
|
||||
url: string().required('This field is required.'),
|
||||
tls: boolean().default(false),
|
||||
skipVerify: boolean(),
|
||||
tlsConfig: tlsConfigValidation(),
|
||||
meta: metadataValidation(),
|
||||
...certsValidation(),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import { TLSConfig } from '@/react/components/TLSFieldset/types';
|
||||
import { EnvironmentMetadata } from '@/react/portainer/environments/environment.service/create';
|
||||
|
||||
export interface FormValues {
|
||||
name: string;
|
||||
url: string;
|
||||
tls: boolean;
|
||||
skipVerify?: boolean;
|
||||
caCertFile?: File;
|
||||
certFile?: File;
|
||||
keyFile?: File;
|
||||
tlsConfig: TLSConfig;
|
||||
meta: EnvironmentMetadata;
|
||||
}
|
||||
|
|
|
@ -126,6 +126,7 @@ function OverrideSocketFieldset() {
|
|||
checked={values.overridePath}
|
||||
onChange={(checked) => setFieldValue('overridePath', checked)}
|
||||
label="Override default socket path"
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue