github.com/argoproj/argo-cd/v2@v2.10.9/ui/src/app/settings/components/certs-list/certs-list.tsx (about)

     1  import {DropDownMenu, FormField, NotificationType, SlidingPanel} from 'argo-ui';
     2  import * as PropTypes from 'prop-types';
     3  import * as React from 'react';
     4  import {Form, FormApi, Text, TextArea} from 'react-form';
     5  import {RouteComponentProps} from 'react-router';
     6  
     7  import {DataLoader, EmptyState, ErrorNotification, Page} from '../../../shared/components';
     8  import {AppContext} from '../../../shared/context';
     9  import * as models from '../../../shared/models';
    10  import {services} from '../../../shared/services';
    11  
    12  require('./certs-list.scss');
    13  
    14  interface NewTLSCertParams {
    15      serverName: string;
    16      certType: string;
    17      certData: string;
    18  }
    19  
    20  interface NewSSHKnownHostParams {
    21      certData: string;
    22  }
    23  
    24  export class CertsList extends React.Component<RouteComponentProps<any>> {
    25      public static contextTypes = {
    26          router: PropTypes.object,
    27          apis: PropTypes.object,
    28          history: PropTypes.object
    29      };
    30  
    31      private formApiTLS: FormApi;
    32      private formApiSSH: FormApi;
    33      private loader: DataLoader;
    34  
    35      public render() {
    36          return (
    37              <Page
    38                  title='Repository certificates and known hosts'
    39                  toolbar={{
    40                      breadcrumbs: [{title: 'Settings', path: '/settings'}, {title: 'Repository certificates and known hosts'}],
    41                      actionMenu: {
    42                          className: 'fa fa-plus',
    43                          items: [
    44                              {
    45                                  title: 'Add TLS certificate',
    46                                  iconClassName: 'fa fa-plus',
    47                                  action: () => (this.showAddTLSCertificate = true)
    48                              },
    49                              {
    50                                  title: 'Add SSH known hosts',
    51                                  iconClassName: 'fa fa-plus',
    52                                  action: () => (this.showAddSSHKnownHosts = true)
    53                              }
    54                          ]
    55                      }
    56                  }}>
    57                  <div className='certs-list'>
    58                      <div className='argo-container'>
    59                          <DataLoader load={() => services.certs.list()} ref={loader => (this.loader = loader)}>
    60                              {(certs: models.RepoCert[]) =>
    61                                  (certs.length > 0 && (
    62                                      <div className='argo-table-list'>
    63                                          <div className='argo-table-list__head'>
    64                                              <div className='row'>
    65                                                  <div className='columns small-3'>SERVER NAME</div>
    66                                                  <div className='columns small-3'>CERT TYPE</div>
    67                                                  <div className='columns small-6'>CERT INFO</div>
    68                                              </div>
    69                                          </div>
    70                                          {certs.map(cert => (
    71                                              <div className='argo-table-list__row' key={cert.certType + '_' + cert.certSubType + '_' + cert.serverName}>
    72                                                  <div className='row'>
    73                                                      <div className='columns small-3'>
    74                                                          <i className='icon argo-icon-git' /> {cert.serverName}
    75                                                      </div>
    76                                                      <div className='columns small-3'>
    77                                                          {cert.certType} {cert.certSubType}
    78                                                      </div>
    79                                                      <div className='columns small-6'>
    80                                                          {cert.certInfo}
    81                                                          <DropDownMenu
    82                                                              anchor={() => (
    83                                                                  <button className='argo-button argo-button--light argo-button--lg argo-button--short'>
    84                                                                      <i className='fa fa-ellipsis-v' />
    85                                                                  </button>
    86                                                              )}
    87                                                              items={[
    88                                                                  {
    89                                                                      title: 'Remove',
    90                                                                      action: () => this.removeCert(cert.serverName, cert.certType, cert.certSubType)
    91                                                                  }
    92                                                              ]}
    93                                                          />
    94                                                      </div>
    95                                                  </div>
    96                                              </div>
    97                                          ))}
    98                                      </div>
    99                                  )) || (
   100                                      <EmptyState icon='argo-icon-git'>
   101                                          <h4>No certificates configured</h4>
   102                                          <h5>You can add further certificates below..</h5>
   103                                          <button className='argo-button argo-button--base' onClick={() => (this.showAddTLSCertificate = true)}>
   104                                              Add TLS certificates
   105                                          </button>{' '}
   106                                          <button className='argo-button argo-button--base' onClick={() => (this.showAddSSHKnownHosts = true)}>
   107                                              Add SSH known hosts
   108                                          </button>
   109                                      </EmptyState>
   110                                  )
   111                              }
   112                          </DataLoader>
   113                      </div>
   114                  </div>
   115                  <SlidingPanel
   116                      isShown={this.showAddTLSCertificate}
   117                      onClose={() => (this.showAddTLSCertificate = false)}
   118                      header={
   119                          <div>
   120                              <button className='argo-button argo-button--base' onClick={() => this.formApiTLS.submitForm(null)}>
   121                                  Create
   122                              </button>{' '}
   123                              <button onClick={() => (this.showAddTLSCertificate = false)} className='argo-button argo-button--base-o'>
   124                                  Cancel
   125                              </button>
   126                          </div>
   127                      }>
   128                      <Form
   129                          onSubmit={params => this.addTLSCertificate(params as NewTLSCertParams)}
   130                          getApi={api => (this.formApiTLS = api)}
   131                          preSubmit={(params: NewTLSCertParams) => ({
   132                              serverName: params.serverName,
   133                              certData: btoa(params.certData)
   134                          })}
   135                          validateError={(params: NewTLSCertParams) => ({
   136                              serverName: !params.serverName && 'Repository Server Name is required',
   137                              certData: !params.certData && 'TLS Certificate is required'
   138                          })}>
   139                          {formApiTLS => (
   140                              <form onSubmit={formApiTLS.submitForm} role='form' className='certs-list width-control' encType='multipart/form-data'>
   141                                  <div className='white-box'>
   142                                      <p>CREATE TLS REPOSITORY CERTIFICATE</p>
   143                                      <div className='argo-form-row'>
   144                                          <FormField formApi={formApiTLS} label='Repository Server Name' field='serverName' component={Text} />
   145                                      </div>
   146                                      <div className='argo-form-row'>
   147                                          <FormField formApi={formApiTLS} label='TLS Certificate (PEM format)' field='certData' component={TextArea} />
   148                                      </div>
   149                                  </div>
   150                              </form>
   151                          )}
   152                      </Form>
   153                  </SlidingPanel>
   154                  <SlidingPanel
   155                      isShown={this.showAddSSHKnownHosts}
   156                      onClose={() => (this.showAddSSHKnownHosts = false)}
   157                      header={
   158                          <div>
   159                              <button className='argo-button argo-button--base' onClick={() => this.formApiSSH.submitForm(null)}>
   160                                  Create
   161                              </button>{' '}
   162                              <button onClick={() => (this.showAddSSHKnownHosts = false)} className='argo-button argo-button--base-o'>
   163                                  Cancel
   164                              </button>
   165                          </div>
   166                      }>
   167                      <Form
   168                          onSubmit={params => this.addSSHKnownHosts(params as NewSSHKnownHostParams)}
   169                          getApi={api => (this.formApiSSH = api)}
   170                          preSubmit={(params: NewSSHKnownHostParams) => ({
   171                              certData: btoa(params.certData)
   172                          })}
   173                          validateError={(params: NewSSHKnownHostParams) => ({
   174                              certData: !params.certData && 'SSH known hosts data is required'
   175                          })}>
   176                          {formApiSSH => (
   177                              <form onSubmit={formApiSSH.submitForm} role='form' className='certs-list width-control' encType='multipart/form-data'>
   178                                  <div className='white-box'>
   179                                      <p>CREATE SSH KNOWN HOST ENTRIES</p>
   180                                      <p>
   181                                          Paste SSH known hosts data in the text area below, one entry per line. You can use output from <code>ssh-keyscan</code> or the contents on
   182                                          an <code>ssh_known_hosts</code> file verbatim. Lines starting with <code>#</code> will be treated as comments and ignored.
   183                                      </p>
   184                                      <p>
   185                                          <strong>Make sure there are no linebreaks in the keys.</strong>
   186                                      </p>
   187                                      <div className='argo-form-row'>
   188                                          <FormField formApi={formApiSSH} label='SSH known hosts data' field='certData' component={TextArea} />
   189                                      </div>
   190                                  </div>
   191                              </form>
   192                          )}
   193                      </Form>
   194                  </SlidingPanel>
   195              </Page>
   196          );
   197      }
   198  
   199      private clearForms() {
   200          this.formApiSSH.resetAll();
   201          this.formApiTLS.resetAll();
   202      }
   203  
   204      private async addTLSCertificate(params: NewTLSCertParams) {
   205          try {
   206              await services.certs.create({items: [{serverName: params.serverName, certType: 'https', certData: params.certData, certSubType: '', certInfo: ''}], metadata: null});
   207              this.showAddTLSCertificate = false;
   208              this.loader.reload();
   209          } catch (e) {
   210              this.appContext.apis.notifications.show({
   211                  content: <ErrorNotification title='Unable to add TLS certificate' e={e} />,
   212                  type: NotificationType.Error
   213              });
   214          }
   215      }
   216  
   217      private async addSSHKnownHosts(params: NewSSHKnownHostParams) {
   218          try {
   219              let knownHostEntries: models.RepoCert[] = [];
   220              atob(params.certData)
   221                  .split('\n')
   222                  .forEach(function processEntry(item, index) {
   223                      const trimmedLine = item.trimLeft();
   224                      if (trimmedLine.startsWith('#') === false) {
   225                          const knownHosts = trimmedLine.split(' ', 3);
   226                          if (knownHosts.length === 3) {
   227                              // Perform a little sanity check on the data - server
   228                              // checks too, but let's not send it invalid data in
   229                              // the first place.
   230                              const subType = knownHosts[1].match(/^(ssh\-[a-z0-9]+|ecdsa-[a-z0-9\-]+)$/gi);
   231                              if (subType != null) {
   232                                  // Key could be valid for multiple hosts
   233                                  const hostnames = knownHosts[0].split(',');
   234                                  for (const hostname of hostnames) {
   235                                      knownHostEntries = knownHostEntries.concat({
   236                                          serverName: hostname,
   237                                          certType: 'ssh',
   238                                          certSubType: knownHosts[1],
   239                                          certData: btoa(knownHosts[2]),
   240                                          certInfo: ''
   241                                      });
   242                                  }
   243                              } else {
   244                                  throw new Error('Invalid SSH subtype: ' + subType);
   245                              }
   246                          }
   247                      }
   248                  });
   249              if (knownHostEntries.length === 0) {
   250                  throw new Error('No valid known hosts data entered');
   251              }
   252              await services.certs.create({items: knownHostEntries, metadata: null});
   253              this.showAddSSHKnownHosts = false;
   254              this.loader.reload();
   255          } catch (e) {
   256              this.appContext.apis.notifications.show({
   257                  content: <ErrorNotification title='Unable to add SSH known hosts data' e={e} />,
   258                  type: NotificationType.Error
   259              });
   260          }
   261      }
   262  
   263      private async removeCert(serverName: string, certType: string, certSubType: string) {
   264          const confirmed = await this.appContext.apis.popup.confirm('Remove certificate', 'Are you sure you want to remove ' + certType + ' certificate for ' + serverName + '?');
   265          if (confirmed) {
   266              await services.certs.delete(serverName, certType, certSubType);
   267              this.loader.reload();
   268          }
   269      }
   270  
   271      private get showAddTLSCertificate() {
   272          return new URLSearchParams(this.props.location.search).get('addTLSCert') === 'true';
   273      }
   274  
   275      private set showAddTLSCertificate(val: boolean) {
   276          this.clearForms();
   277          this.appContext.router.history.push(`${this.props.match.url}?addTLSCert=${val}`);
   278      }
   279  
   280      private get showAddSSHKnownHosts() {
   281          return new URLSearchParams(this.props.location.search).get('addSSHKnownHosts') === 'true';
   282      }
   283  
   284      private set showAddSSHKnownHosts(val: boolean) {
   285          this.clearForms();
   286          this.appContext.router.history.push(`${this.props.match.url}?addSSHKnownHosts=${val}`);
   287      }
   288  
   289      private get appContext(): AppContext {
   290          return this.context as AppContext;
   291      }
   292  }