fix(docker/tls): update tls certs for Docker API env [EE-4286] (#9112)

pull/9132/head
Oscar Zhou 2023-06-28 08:51:58 +12:00 committed by GitHub
parent f1f46f4da1
commit f02ede00b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 184 additions and 64 deletions

View File

@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"reflect" "reflect"
"strconv" "strconv"
"strings"
httperror "github.com/portainer/libhttp/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request" "github.com/portainer/libhttp/request"
@ -246,7 +247,10 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
} }
} }
if (payload.URL != nil && *payload.URL != endpoint.URL) || (payload.TLS != nil && endpoint.TLSConfig.TLS != *payload.TLS) || endpoint.Type == portainer.AzureEnvironment { if (payload.URL != nil && *payload.URL != endpoint.URL) ||
(payload.TLS != nil && endpoint.TLSConfig.TLS != *payload.TLS) ||
endpoint.Type == portainer.AzureEnvironment ||
shouldReloadTLSConfiguration(endpoint, &payload) {
handler.ProxyManager.DeleteEndpointProxy(endpoint.ID) handler.ProxyManager.DeleteEndpointProxy(endpoint.ID)
_, err = handler.ProxyManager.CreateAndRegisterEndpointProxy(endpoint) _, err = handler.ProxyManager.CreateAndRegisterEndpointProxy(endpoint)
if err != nil { if err != nil {
@ -285,3 +289,22 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
return response.JSON(w, endpoint) return response.JSON(w, endpoint)
} }
func shouldReloadTLSConfiguration(endpoint *portainer.Endpoint, payload *endpointUpdatePayload) bool {
// When updating Docker API environment, as long as TLS is true and TLSSkipVerify is false,
// we assume that new TLS files have been uploaded and we need to reload the TLS configuration.
if endpoint.Type != portainer.DockerEnvironment ||
!strings.HasPrefix(*payload.URL, "tcp://") ||
payload.TLS == nil || !*payload.TLS {
return false
}
if payload.TLSSkipVerify != nil && !*payload.TLSSkipVerify {
return true
}
if payload.TLSSkipClientVerify != nil && !*payload.TLSSkipClientVerify {
return true
}
return false
}

View File

@ -8,8 +8,9 @@ import { boxSelectorModule } from './BoxSelector';
import { beFeatureIndicator } from './BEFeatureIndicator'; import { beFeatureIndicator } from './BEFeatureIndicator';
import { InformationPanelAngular } from './InformationPanel'; import { InformationPanelAngular } from './InformationPanel';
import { gitFormModule } from './forms/git-form'; import { gitFormModule } from './forms/git-form';
import { tlsFieldsetModule } from './tls-fieldset';
export default angular export default angular
.module('portainer.app.components', [boxSelectorModule, widgetModule, gitFormModule, porAccessManagementModule, formComponentsModule]) .module('portainer.app.components', [boxSelectorModule, widgetModule, gitFormModule, porAccessManagementModule, formComponentsModule, tlsFieldsetModule])
.component('informationPanel', InformationPanelAngular) .component('informationPanel', InformationPanelAngular)
.component('beFeatureIndicator', beFeatureIndicator).name; .component('beFeatureIndicator', beFeatureIndicator).name;

View File

@ -0,0 +1,22 @@
import angular from 'angular';
import {
TLSFieldset,
tlsConfigValidation,
} from '@/react/components/TLSFieldset';
import { withFormValidation } from '@/react-tools/withFormValidation';
export const ngModule = angular.module(
'portainer.app.components.tls-fieldset',
[]
);
export const tlsFieldsetModule = ngModule.name;
withFormValidation(
ngModule,
TLSFieldset,
'tlsFieldset',
[],
tlsConfigValidation
);

View File

@ -72,7 +72,7 @@
<div class="col-lg-12 col-md-12 col-xs-12"> <div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget> <rd-widget>
<rd-widget-body> <rd-widget-body>
<form class="form-horizontal"> <form class="form-horizontal" name="$ctrl.endpointForm">
<div class="col-sm-12 form-section-title"> Configuration </div> <div class="col-sm-12 form-section-title"> Configuration </div>
<!-- name-input --> <!-- name-input -->
<div class="form-group"> <div class="form-group">
@ -124,6 +124,14 @@
</div> </div>
<!-- !endpoint-public-url-input --> <!-- !endpoint-public-url-input -->
<tls-fieldset
ng-if="!state.edgeEndpoint && endpoint.Status !== 4 && state.showTLSConfig"
values="formValues.tlsConfig"
on-change="(onChangeTLSConfigFormValues)"
validation-data="{optionalCert: true}"
></tls-fieldset>
<azure-endpoint-config <azure-endpoint-config
ng-if="state.azureEndpoint" ng-if="state.azureEndpoint"
application-id="endpoint.AzureCredentials.ApplicationID" application-id="endpoint.AzureCredentials.ApplicationID"
@ -142,12 +150,6 @@
<tag-selector ng-if="endpoint" value="endpoint.TagIds" allow-create="state.allowCreate" on-change="(onChangeTags)"></tag-selector> <tag-selector ng-if="endpoint" value="endpoint.TagIds" allow-create="state.allowCreate" on-change="(onChangeTags)"></tag-selector>
<!-- endpoint-security -->
<div ng-if="endpointType === 'remote' && !state.azureEndpoint && !state.kubernetesEndpoint && !state.edgeEndpoint && endpoint.Type !== 6">
<div class="col-sm-12 form-section-title"> Security </div>
<por-endpoint-security form-data="formValues.SecurityFormData" endpoint="endpoint"></por-endpoint-security>
</div>
<!-- !endpoint-security -->
<!-- open-amt info --> <!-- open-amt info -->
<div ng-if="state.showAMTInfo"> <div ng-if="state.showAMTInfo">
<div class="col-sm-12 form-section-title"> Open Active Management Technology </div> <div class="col-sm-12 form-section-title"> Open Active Management Technology </div>
@ -219,7 +221,7 @@
<button <button
type="button" type="button"
class="btn btn-primary btn-sm !ml-0" class="btn btn-primary btn-sm !ml-0"
ng-disabled="state.actionInProgress || !endpoint.Name || !endpoint.URL || (endpoint.TLS && ((endpoint.TLSVerify && !formValues.TLSCACert) || (endpoint.TLSClientCert && (!formValues.TLSCert || !formValues.TLSKey))))" ng-disabled="state.actionInProgress || !endpoint.Name || !endpoint.URL || !$ctrl.endpointForm.$valid"
ng-click="updateEndpoint()" ng-click="updateEndpoint()"
button-spinner="state.actionInProgress" button-spinner="state.actionInProgress"
> >

View File

@ -2,11 +2,10 @@ import _ from 'lodash-es';
import uuidv4 from 'uuid/v4'; import uuidv4 from 'uuid/v4';
import { PortainerEndpointTypes } from '@/portainer/models/endpoint/models'; import { PortainerEndpointTypes } from '@/portainer/models/endpoint/models';
import { EndpointSecurityFormData } from '@/portainer/components/endpointSecurity/porEndpointSecurityModel';
import EndpointHelper from '@/portainer/helpers/endpointHelper'; import EndpointHelper from '@/portainer/helpers/endpointHelper';
import { getAMTInfo } from 'Portainer/hostmanagement/open-amt/open-amt.service'; import { getAMTInfo } from 'Portainer/hostmanagement/open-amt/open-amt.service';
import { confirmDestructive } from '@@/modals/confirm'; import { confirmDestructive } from '@@/modals/confirm';
import { isEdgeEnvironment } from '@/react/portainer/environments/utils'; import { isEdgeEnvironment, isDockerAPIEnvironment } from '@/react/portainer/environments/utils';
import { commandsTabs } from '@/react/edge/components/EdgeScriptForm/scripts'; import { commandsTabs } from '@/react/edge/components/EdgeScriptForm/scripts';
import { confirmDisassociate } from '@/react/portainer/environments/ItemView/ConfirmDisassociateModel'; import { confirmDisassociate } from '@/react/portainer/environments/ItemView/ConfirmDisassociateModel';
@ -33,6 +32,8 @@ function EndpointController(
$scope.onChangeCheckInInterval = onChangeCheckInInterval; $scope.onChangeCheckInInterval = onChangeCheckInInterval;
$scope.setFieldValue = setFieldValue; $scope.setFieldValue = setFieldValue;
$scope.onChangeTags = onChangeTags; $scope.onChangeTags = onChangeTags;
$scope.onChangeTLSConfigFormValues = onChangeTLSConfigFormValues;
const isBE = process.env.PORTAINER_EDITION === 'BE'; const isBE = process.env.PORTAINER_EDITION === 'BE';
$scope.state = { $scope.state = {
@ -53,6 +54,7 @@ function EndpointController(
allowSelfSignedCerts: true, allowSelfSignedCerts: true,
showAMTInfo: false, showAMTInfo: false,
showNomad: isBE, showNomad: isBE,
showTLSConfig: false,
edgeScriptCommands: { edgeScriptCommands: {
linux: _.compact([commandsTabs.k8sLinux, commandsTabs.swarmLinux, commandsTabs.standaloneLinux, isBE && commandsTabs.nomadLinux]), linux: _.compact([commandsTabs.k8sLinux, commandsTabs.swarmLinux, commandsTabs.standaloneLinux, isBE && commandsTabs.nomadLinux]),
win: [commandsTabs.swarmWindows, commandsTabs.standaloneWindow], win: [commandsTabs.swarmWindows, commandsTabs.standaloneWindow],
@ -100,7 +102,14 @@ function EndpointController(
}; };
$scope.formValues = { $scope.formValues = {
SecurityFormData: new EndpointSecurityFormData(), tlsConfig: {
tls: false,
skipVerify: false,
skipClientVerify: false,
caCertFile: null,
certFile: null,
keyFile: null,
},
}; };
$scope.onDisassociateEndpoint = async function () { $scope.onDisassociateEndpoint = async function () {
@ -134,6 +143,15 @@ function EndpointController(
setFieldValue('TagIds', value); setFieldValue('TagIds', value);
} }
function onChangeTLSConfigFormValues(newValues) {
return this.$async(async () => {
$scope.formValues.tlsConfig = {
...$scope.formValues.tlsConfig,
...newValues,
};
});
}
function setFieldValue(name, value) { function setFieldValue(name, value) {
return $scope.$evalAsync(() => { return $scope.$evalAsync(() => {
$scope.endpoint = { $scope.endpoint = {
@ -158,11 +176,6 @@ function EndpointController(
$scope.updateEndpoint = async function () { $scope.updateEndpoint = async function () {
var endpoint = $scope.endpoint; var endpoint = $scope.endpoint;
var securityData = $scope.formValues.SecurityFormData;
var TLS = securityData.TLS;
var TLSMode = securityData.TLSMode;
var TLSSkipVerify = TLS && (TLSMode === 'tls_client_noca' || TLSMode === 'tls_only');
var TLSSkipClientVerify = TLS && (TLSMode === 'tls_ca' || TLSMode === 'tls_only');
if (isEdgeEnvironment(endpoint.Type) && _.difference($scope.initialTagIds, endpoint.TagIds).length > 0) { if (isEdgeEnvironment(endpoint.Type) && _.difference($scope.initialTagIds, endpoint.TagIds).length > 0) {
let confirmed = await confirmDestructive({ let confirmed = await confirmDestructive({
@ -182,12 +195,6 @@ function EndpointController(
Gpus: endpoint.Gpus, Gpus: endpoint.Gpus,
GroupID: endpoint.GroupId, GroupID: endpoint.GroupId,
TagIds: endpoint.TagIds, TagIds: endpoint.TagIds,
TLS: TLS,
TLSSkipVerify: TLSSkipVerify,
TLSSkipClientVerify: TLSSkipClientVerify,
TLSCACert: TLSSkipVerify || securityData.TLSCACert === endpoint.TLSConfig.TLSCACert ? null : securityData.TLSCACert,
TLSCert: TLSSkipClientVerify || securityData.TLSCert === endpoint.TLSConfig.TLSCert ? null : securityData.TLSCert,
TLSKey: TLSSkipClientVerify || securityData.TLSKey === endpoint.TLSConfig.TLSKey ? null : securityData.TLSKey,
AzureApplicationID: endpoint.AzureCredentials.ApplicationID, AzureApplicationID: endpoint.AzureCredentials.ApplicationID,
AzureTenantID: endpoint.AzureCredentials.TenantID, AzureTenantID: endpoint.AzureCredentials.TenantID,
AzureAuthenticationKey: endpoint.AzureCredentials.AuthenticationKey, AzureAuthenticationKey: endpoint.AzureCredentials.AuthenticationKey,
@ -201,6 +208,18 @@ function EndpointController(
endpoint.Type !== PortainerEndpointTypes.AgentOnKubernetesEnvironment endpoint.Type !== PortainerEndpointTypes.AgentOnKubernetesEnvironment
) { ) {
payload.URL = 'tcp://' + endpoint.URL; payload.URL = 'tcp://' + endpoint.URL;
if (endpoint.Type === PortainerEndpointTypes.DockerEnvironment) {
var tlsConfig = $scope.formValues.tlsConfig;
payload.TLS = tlsConfig.tls;
payload.TLSSkipVerify = tlsConfig.skipVerify;
if (tlsConfig.tls && !tlsConfig.skipVerify) {
payload.TLSSkipClientVerify = tlsConfig.skipClientVerify;
payload.TLSCACert = tlsConfig.caCertFile;
payload.TLSCert = tlsConfig.certFile;
payload.TLSKey = tlsConfig.keyFile;
}
}
} }
if (endpoint.Type === PortainerEndpointTypes.AgentOnKubernetesEnvironment) { if (endpoint.Type === PortainerEndpointTypes.AgentOnKubernetesEnvironment) {
@ -267,11 +286,25 @@ function EndpointController(
} }
} }
function configureTLS(endpoint) {
$scope.formValues = {
tlsConfig: {
tls: endpoint.TLSConfig.TLS || false,
skipVerify: endpoint.TLSConfig.TLSSkipVerify || false,
skipClientVerify: endpoint.TLSConfig.TLSSkipClientVerify || false,
},
};
}
async function initView() { async function initView() {
return $async(async () => { return $async(async () => {
try { try {
const [endpoint, groups, settings] = await Promise.all([EndpointService.endpoint($transition$.params().id), GroupService.groups(), SettingsService.settings()]); const [endpoint, groups, settings] = await Promise.all([EndpointService.endpoint($transition$.params().id), GroupService.groups(), SettingsService.settings()]);
if (isDockerAPIEnvironment(endpoint)) {
$scope.state.showTLSConfig = true;
}
// Check if the environment is docker standalone, to decide whether to show the GPU insights box // Check if the environment is docker standalone, to decide whether to show the GPU insights box
const isDockerEnvironment = endpoint.Type === PortainerEndpointTypes.DockerEnvironment; const isDockerEnvironment = endpoint.Type === PortainerEndpointTypes.DockerEnvironment;
if (isDockerEnvironment) { if (isDockerEnvironment) {
@ -305,6 +338,8 @@ function EndpointController(
configureState(); configureState();
configureTLS(endpoint);
if (EndpointHelper.isDockerEndpoint(endpoint) && $scope.state.edgeAssociated) { if (EndpointHelper.isDockerEndpoint(endpoint) && $scope.state.edgeAssociated) {
$scope.state.showAMTInfo = settings && settings.openAMTConfiguration && settings.openAMTConfiguration.enabled; $scope.state.showAMTInfo = settings && settings.openAMTConfiguration && settings.openAMTConfiguration.enabled;
} }

View File

@ -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 { file, withFileSize } from '@@/form-components/yup-file-validation';
import { FileUploadField } from '@@/form-components/FileUpload'; import { FileUploadField } from '@@/form-components/FileUpload';
import { SwitchField } from '@@/form-components/SwitchField'; import { SwitchField } from '@@/form-components/SwitchField';
import { FormControl } from '@@/form-components/FormControl'; import { FormControl } from '@@/form-components/FormControl';
import { FormValues } from './types'; import { TLSConfig } from './types';
export function TLSFieldset() { interface Props {
const { values, setFieldValue, errors } = useFormikContext<FormValues>(); values: TLSConfig;
onChange: (value: Partial<TLSConfig>) => void;
errors?: FormikErrors<TLSConfig>;
}
export function TLSFieldset({ values, onChange, errors }: Props) {
return ( return (
<> <>
<div className="form-group"> <div className="form-group">
@ -18,7 +23,7 @@ export function TLSFieldset() {
label="TLS" label="TLS"
labelClass="col-sm-3 col-lg-2" labelClass="col-sm-3 col-lg-2"
checked={values.tls} checked={values.tls}
onChange={(checked) => setFieldValue('tls', checked)} onChange={(checked) => handleChange({ tls: checked })}
/> />
</div> </div>
</div> </div>
@ -30,7 +35,8 @@ export function TLSFieldset() {
<SwitchField <SwitchField
label="Skip Certification Verification" label="Skip Certification Verification"
checked={!!values.skipVerify} checked={!!values.skipVerify}
onChange={(checked) => setFieldValue('skipVerify', checked)} onChange={(checked) => handleChange({ skipVerify: checked })}
labelClass="col-sm-3 col-lg-2"
/> />
</div> </div>
</div> </div>
@ -40,33 +46,33 @@ export function TLSFieldset() {
<FormControl <FormControl
label="TLS CA certificate" label="TLS CA certificate"
inputId="ca-cert-field" inputId="ca-cert-field"
errors={errors.caCertFile} errors={errors?.caCertFile}
> >
<FileUploadField <FileUploadField
inputId="ca-cert-field" inputId="ca-cert-field"
onChange={(file) => setFieldValue('caCertFile', file)} onChange={(file) => handleChange({ caCertFile: file })}
value={values.caCertFile} value={values.caCertFile}
/> />
</FormControl> </FormControl>
<FormControl <FormControl
label="TLS certificate" label="TLS certificate"
inputId="cert-field" inputId="cert-field"
errors={errors.certFile} errors={errors?.certFile}
> >
<FileUploadField <FileUploadField
inputId="cert-field" inputId="cert-field"
onChange={(file) => setFieldValue('certFile', file)} onChange={(file) => handleChange({ certFile: file })}
value={values.certFile} value={values.certFile}
/> />
</FormControl> </FormControl>
<FormControl <FormControl
label="TLS key" label="TLS key"
inputId="tls-key-field" inputId="tls-key-field"
errors={errors.keyFile} errors={errors?.keyFile}
> >
<FileUploadField <FileUploadField
inputId="tls-key-field" inputId="tls-key-field"
onChange={(file) => setFieldValue('keyFile', file)} onChange={(file) => handleChange({ keyFile: file })}
value={values.keyFile} value={values.keyFile}
/> />
</FormControl> </FormControl>
@ -76,21 +82,29 @@ export function TLSFieldset() {
)} )}
</> </>
); );
function handleChange(partialValue: Partial<TLSConfig>) {
onChange(partialValue);
}
} }
const MAX_FILE_SIZE = 5_242_880; // 5MB const MAX_FILE_SIZE = 5_242_880; // 5MB
function certValidation() { function certValidation(optional?: boolean) {
return withFileSize(file(), MAX_FILE_SIZE).when(['tls', 'skipVerify'], { 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'), then: (schema) => schema.required('File is required'),
}); });
} }
export function validation() { export function tlsConfigValidation({
return { optionalCert,
caCertFile: certValidation(), }: { optionalCert?: boolean } = {}): SchemaOf<TLSConfig> {
certFile: certValidation(), return object({
keyFile: certValidation(), tls: boolean().default(false),
}; skipVerify: boolean().default(false),
caCertFile: certValidation(optionalCert),
certFile: certValidation(optionalCert),
keyFile: certValidation(optionalCert),
});
} }

View File

@ -0,0 +1 @@
export { TLSFieldset, tlsConfigValidation } from './TLSFieldset';

View File

@ -0,0 +1,7 @@
export interface TLSConfig {
tls: boolean;
skipVerify?: boolean;
caCertFile?: File;
certFile?: File;
keyFile?: File;
}

View 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) { export function getDashboardRoute(environment: Environment) {
if (isEdgeEnvironment(environment.Type)) { if (isEdgeEnvironment(environment.Type)) {
if (!environment.EdgeID) { if (!environment.EdgeID) {

View File

@ -8,6 +8,7 @@ import {
Environment, Environment,
EnvironmentCreationTypes, EnvironmentCreationTypes,
} from '@/react/portainer/environments/types'; } from '@/react/portainer/environments/types';
import { TLSFieldset } from '@/react/components/TLSFieldset/TLSFieldset';
import { LoadingButton } from '@@/buttons/LoadingButton'; import { LoadingButton } from '@@/buttons/LoadingButton';
import { FormControl } from '@@/form-components/FormControl'; import { FormControl } from '@@/form-components/FormControl';
@ -19,7 +20,6 @@ import { MoreSettingsSection } from '../../shared/MoreSettingsSection';
import { useValidation } from './APIForm.validation'; import { useValidation } from './APIForm.validation';
import { FormValues } from './types'; import { FormValues } from './types';
import { TLSFieldset } from './TLSFieldset';
interface Props { interface Props {
onCreate(environment: Environment): void; onCreate(environment: Environment): void;
@ -31,7 +31,10 @@ export function APIForm({ onCreate, isDockerStandalone }: Props) {
const initialValues: FormValues = { const initialValues: FormValues = {
url: '', url: '',
name: '', name: '',
tls: false, tlsConfig: {
tls: false,
skipVerify: false,
},
meta: { meta: {
groupId: 1, groupId: 1,
tagIds: [], tagIds: [],
@ -52,7 +55,7 @@ export function APIForm({ onCreate, isDockerStandalone }: Props) {
validateOnMount validateOnMount
key={formKey} key={formKey}
> >
{({ isValid, dirty }) => ( {({ values, errors, setFieldValue, isValid, dirty }) => (
<Form> <Form>
<NameField /> <NameField />
@ -70,7 +73,15 @@ export function APIForm({ onCreate, isDockerStandalone }: Props) {
/> />
</FormControl> </FormControl>
<TLSFieldset /> <TLSFieldset
values={values.tlsConfig}
onChange={(value) =>
Object.entries(value).forEach(([key, value]) =>
setFieldValue(`tlsConfig.${key}`, value)
)
}
errors={errors.tlsConfig}
/>
<MoreSettingsSection> <MoreSettingsSection>
{isDockerStandalone && ( {isDockerStandalone && (
@ -141,24 +152,24 @@ export function APIForm({ onCreate, isDockerStandalone }: Props) {
} }
); );
function getTlsValues() { function getTlsValues() {
if (!values.tls) { if (!values.tlsConfig.tls) {
return undefined; return undefined;
} }
return { return {
skipVerify: values.skipVerify, skipVerify: values.tlsConfig.skipVerify,
...getCertFiles(), ...getCertFiles(),
}; };
function getCertFiles() { function getCertFiles() {
if (values.skipVerify) { if (values.tlsConfig.skipVerify) {
return {}; return {};
} }
return { return {
caCertFile: values.caCertFile, caCertFile: values.tlsConfig.caCertFile,
certFile: values.certFile, certFile: values.tlsConfig.certFile,
keyFile: values.keyFile, keyFile: values.tlsConfig.keyFile,
}; };
} }
} }

View File

@ -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 { metadataValidation } from '../../shared/MetadataFieldset/validation';
import { useNameValidation } from '../../shared/NameField'; import { useNameValidation } from '../../shared/NameField';
import { validation as certsValidation } from './TLSFieldset';
import { FormValues } from './types'; import { FormValues } from './types';
export function useValidation(): SchemaOf<FormValues> { export function useValidation(): SchemaOf<FormValues> {
return object({ return object({
name: useNameValidation(), name: useNameValidation(),
url: string().required('This field is required.'), url: string().required('This field is required.'),
tls: boolean().default(false), tlsConfig: tlsConfigValidation(),
skipVerify: boolean(),
meta: metadataValidation(), meta: metadataValidation(),
...certsValidation(),
}); });
} }

View File

@ -1,12 +1,9 @@
import { TLSConfig } from '@/react/components/TLSFieldset/types';
import { EnvironmentMetadata } from '@/react/portainer/environments/environment.service/create'; import { EnvironmentMetadata } from '@/react/portainer/environments/environment.service/create';
export interface FormValues { export interface FormValues {
name: string; name: string;
url: string; url: string;
tls: boolean; tlsConfig: TLSConfig;
skipVerify?: boolean;
caCertFile?: File;
certFile?: File;
keyFile?: File;
meta: EnvironmentMetadata; meta: EnvironmentMetadata;
} }

View File

@ -126,6 +126,7 @@ function OverrideSocketFieldset() {
checked={values.overridePath} checked={values.overridePath}
onChange={(checked) => setFieldValue('overridePath', checked)} onChange={(checked) => setFieldValue('overridePath', checked)}
label="Override default socket path" label="Override default socket path"
labelClass="col-sm-3 col-lg-2"
/> />
</div> </div>
</div> </div>