mirror of https://github.com/portainer/portainer
				
				
				
			fix(docker/tls): update tls certs for Docker API env [EE-4286] (#9112)
							parent
							
								
									f1f46f4da1
								
							
						
					
					
						commit
						f02ede00b3
					
				| 
						 | 
				
			
			@ -4,6 +4,7 @@ import (
 | 
			
		|||
	"net/http"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	httperror "github.com/portainer/libhttp/error"
 | 
			
		||||
	"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)
 | 
			
		||||
		_, err = handler.ProxyManager.CreateAndRegisterEndpointProxy(endpoint)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -285,3 +289,22 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
 | 
			
		|||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,8 +8,9 @@ import { boxSelectorModule } from './BoxSelector';
 | 
			
		|||
import { beFeatureIndicator } from './BEFeatureIndicator';
 | 
			
		||||
import { InformationPanelAngular } from './InformationPanel';
 | 
			
		||||
import { gitFormModule } from './forms/git-form';
 | 
			
		||||
import { tlsFieldsetModule } from './tls-fieldset';
 | 
			
		||||
 | 
			
		||||
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('beFeatureIndicator', beFeatureIndicator).name;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
);
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +72,7 @@
 | 
			
		|||
  <div class="col-lg-12 col-md-12 col-xs-12">
 | 
			
		||||
    <rd-widget>
 | 
			
		||||
      <rd-widget-body>
 | 
			
		||||
        <form class="form-horizontal">
 | 
			
		||||
        <form class="form-horizontal" name="$ctrl.endpointForm">
 | 
			
		||||
          <div class="col-sm-12 form-section-title"> Configuration </div>
 | 
			
		||||
          <!-- name-input -->
 | 
			
		||||
          <div class="form-group">
 | 
			
		||||
| 
						 | 
				
			
			@ -124,6 +124,14 @@
 | 
			
		|||
          </div>
 | 
			
		||||
 | 
			
		||||
          <!-- !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
 | 
			
		||||
            ng-if="state.azureEndpoint"
 | 
			
		||||
            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>
 | 
			
		||||
 | 
			
		||||
          <!-- 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 -->
 | 
			
		||||
          <div ng-if="state.showAMTInfo">
 | 
			
		||||
            <div class="col-sm-12 form-section-title"> Open Active Management Technology </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -219,7 +221,7 @@
 | 
			
		|||
              <button
 | 
			
		||||
                type="button"
 | 
			
		||||
                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()"
 | 
			
		||||
                button-spinner="state.actionInProgress"
 | 
			
		||||
              >
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,11 +2,10 @@ import _ from 'lodash-es';
 | 
			
		|||
import uuidv4 from 'uuid/v4';
 | 
			
		||||
 | 
			
		||||
import { PortainerEndpointTypes } from '@/portainer/models/endpoint/models';
 | 
			
		||||
import { EndpointSecurityFormData } from '@/portainer/components/endpointSecurity/porEndpointSecurityModel';
 | 
			
		||||
import EndpointHelper from '@/portainer/helpers/endpointHelper';
 | 
			
		||||
import { getAMTInfo } from 'Portainer/hostmanagement/open-amt/open-amt.service';
 | 
			
		||||
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 { confirmDisassociate } from '@/react/portainer/environments/ItemView/ConfirmDisassociateModel';
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +32,8 @@ function EndpointController(
 | 
			
		|||
  $scope.onChangeCheckInInterval = onChangeCheckInInterval;
 | 
			
		||||
  $scope.setFieldValue = setFieldValue;
 | 
			
		||||
  $scope.onChangeTags = onChangeTags;
 | 
			
		||||
  $scope.onChangeTLSConfigFormValues = onChangeTLSConfigFormValues;
 | 
			
		||||
 | 
			
		||||
  const isBE = process.env.PORTAINER_EDITION === 'BE';
 | 
			
		||||
 | 
			
		||||
  $scope.state = {
 | 
			
		||||
| 
						 | 
				
			
			@ -53,6 +54,7 @@ function EndpointController(
 | 
			
		|||
    allowSelfSignedCerts: true,
 | 
			
		||||
    showAMTInfo: false,
 | 
			
		||||
    showNomad: isBE,
 | 
			
		||||
    showTLSConfig: false,
 | 
			
		||||
    edgeScriptCommands: {
 | 
			
		||||
      linux: _.compact([commandsTabs.k8sLinux, commandsTabs.swarmLinux, commandsTabs.standaloneLinux, isBE && commandsTabs.nomadLinux]),
 | 
			
		||||
      win: [commandsTabs.swarmWindows, commandsTabs.standaloneWindow],
 | 
			
		||||
| 
						 | 
				
			
			@ -100,7 +102,14 @@ function EndpointController(
 | 
			
		|||
  };
 | 
			
		||||
 | 
			
		||||
  $scope.formValues = {
 | 
			
		||||
    SecurityFormData: new EndpointSecurityFormData(),
 | 
			
		||||
    tlsConfig: {
 | 
			
		||||
      tls: false,
 | 
			
		||||
      skipVerify: false,
 | 
			
		||||
      skipClientVerify: false,
 | 
			
		||||
      caCertFile: null,
 | 
			
		||||
      certFile: null,
 | 
			
		||||
      keyFile: null,
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  $scope.onDisassociateEndpoint = async function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -134,6 +143,15 @@ function EndpointController(
 | 
			
		|||
    setFieldValue('TagIds', value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function onChangeTLSConfigFormValues(newValues) {
 | 
			
		||||
    return this.$async(async () => {
 | 
			
		||||
      $scope.formValues.tlsConfig = {
 | 
			
		||||
        ...$scope.formValues.tlsConfig,
 | 
			
		||||
        ...newValues,
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function setFieldValue(name, value) {
 | 
			
		||||
    return $scope.$evalAsync(() => {
 | 
			
		||||
      $scope.endpoint = {
 | 
			
		||||
| 
						 | 
				
			
			@ -158,11 +176,6 @@ function EndpointController(
 | 
			
		|||
 | 
			
		||||
  $scope.updateEndpoint = async function () {
 | 
			
		||||
    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) {
 | 
			
		||||
      let confirmed = await confirmDestructive({
 | 
			
		||||
| 
						 | 
				
			
			@ -182,12 +195,6 @@ function EndpointController(
 | 
			
		|||
      Gpus: endpoint.Gpus,
 | 
			
		||||
      GroupID: endpoint.GroupId,
 | 
			
		||||
      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,
 | 
			
		||||
      AzureTenantID: endpoint.AzureCredentials.TenantID,
 | 
			
		||||
      AzureAuthenticationKey: endpoint.AzureCredentials.AuthenticationKey,
 | 
			
		||||
| 
						 | 
				
			
			@ -201,6 +208,18 @@ function EndpointController(
 | 
			
		|||
      endpoint.Type !== PortainerEndpointTypes.AgentOnKubernetesEnvironment
 | 
			
		||||
    ) {
 | 
			
		||||
      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) {
 | 
			
		||||
| 
						 | 
				
			
			@ -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() {
 | 
			
		||||
    return $async(async () => {
 | 
			
		||||
      try {
 | 
			
		||||
        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
 | 
			
		||||
        const isDockerEnvironment = endpoint.Type === PortainerEndpointTypes.DockerEnvironment;
 | 
			
		||||
        if (isDockerEnvironment) {
 | 
			
		||||
| 
						 | 
				
			
			@ -305,6 +338,8 @@ function EndpointController(
 | 
			
		|||
 | 
			
		||||
        configureState();
 | 
			
		||||
 | 
			
		||||
        configureTLS(endpoint);
 | 
			
		||||
 | 
			
		||||
        if (EndpointHelper.isDockerEndpoint(endpoint) && $scope.state.edgeAssociated) {
 | 
			
		||||
          $scope.state.showAMTInfo = settings && settings.openAMTConfiguration && settings.openAMTConfiguration.enabled;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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),
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
export { TLSFieldset, tlsConfigValidation } from './TLSFieldset';
 | 
			
		||||
| 
						 | 
				
			
			@ -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…
	
		Reference in New Issue