]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: add a new configuration page in side nav bar Object > 58114/head
authorAashish Sharma <aasharma@li-e74156cc-2f67-11b2-a85c-e98659a63c5c.ibm.com>
Tue, 18 Jun 2024 11:13:07 +0000 (16:43 +0530)
committerAashish Sharma <aasharma@li-e74156cc-2f67-11b2-a85c-e98659a63c5c.ibm.com>
Mon, 8 Jul 2024 05:09:16 +0000 (10:39 +0530)
Configuration

Fixes: https://tracker.ceph.com/issues/66543
Signed-off-by: Aashish Sharma <aasharma@redhat.com>
21 files changed:
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.e2e-spec.ts
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.po.ts
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/configuration.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/configuration.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-bucket-encryption.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-config-details/rgw-config-details.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-config-details/rgw-config-details.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-config-details/rgw-config-details.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-config-details/rgw-config-details.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-config-modal/rgw-config-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-config-modal/rgw-config-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-configuration-page/rgw-configuration-page.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-configuration-page/rgw-configuration-page.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-configuration-page/rgw-configuration-page.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-configuration-page/rgw-configuration-page.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/models/rgw-encryption-config-keys.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/services/ceph_service.py

index 8b05c309f695a5928dbc33fb05c6677ed2a7d1c9..4bfc672ccf206830128ef5967ef657e4cacaa19d 100644 (file)
@@ -31,11 +31,6 @@ describe('RGW buckets page', () => {
       buckets.delete(bucket_name);
     });
 
-    it('should check default encryption is SSE-S3', () => {
-      buckets.navigateTo('create');
-      buckets.checkForDefaultEncryption();
-    });
-
     it('should create bucket with object locking enabled', () => {
       buckets.navigateTo('create');
       buckets.create(bucket_name, BucketsPageHelper.USERS[0], true);
index 32f89c263a439b8520fd591fe7fba23e7473b2f7..069b48f888d6a06683f3e4d1dc7240db5b65421d 100644 (file)
@@ -50,14 +50,6 @@ export class BucketsPageHelper extends PageHelper {
     this.getFirstTableCell(name).should('exist');
   }
 
-  @PageHelper.restrictTo(pages.create.url)
-  checkForDefaultEncryption() {
-    cy.get("a[aria-label='click here']").click();
-    cy.get('cd-modal').within(() => {
-      cy.get('input[id=s3Enabled]').should('be.checked');
-    });
-  }
-
   @PageHelper.restrictTo(pages.index.url)
   edit(name: string, new_owner: string, isLocking = false) {
     this.navigateEdit(name);
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/configuration.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/configuration.e2e-spec.ts
new file mode 100644 (file)
index 0000000..d1e4836
--- /dev/null
@@ -0,0 +1,36 @@
+import { ConfigurationPageHelper } from './configuration.po';
+
+describe('RGW configuration page', () => {
+  const configurations = new ConfigurationPageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    configurations.navigateTo();
+  });
+
+  describe('breadcrumb and tab tests', () => {
+    it('should open and show breadcrumb', () => {
+      configurations.expectBreadcrumbText('Configuration');
+    });
+
+    it('should show one tab', () => {
+      configurations.getTabsCount().should('eq', 1);
+    });
+
+    it('should show Server-side Encryption Config list tab at first', () => {
+      configurations.getTabText(0).should('eq', 'Server-side Encryption');
+    });
+  });
+
+  describe('create and edit encryption configuration', () => {
+    it('should create configuration', () => {
+      configurations.create('vault', 'agent', 'transit', 'https://localhost:8080');
+      configurations.getFirstTableCell('SSE_KMS').should('exist');
+    });
+
+    it('should edit configuration', () => {
+      configurations.edit('https://localhost:9090');
+      configurations.getDataTables().should('contain.text', 'https://localhost:9090');
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/configuration.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/configuration.po.ts
new file mode 100644 (file)
index 0000000..a1f4a9f
--- /dev/null
@@ -0,0 +1,52 @@
+import { PageHelper } from '../page-helper.po';
+
+export class ConfigurationPageHelper extends PageHelper {
+  pages = {
+    index: { url: '#/rgw/configuration', id: 'cd-rgw-configuration-page' }
+  };
+
+  columnIndex = {
+    address: 4
+  };
+
+  create(provider: string, auth_method: string, secret_engine: string, address: string) {
+    cy.contains('button', 'Create').click();
+    this.selectKmsProvider(provider);
+    cy.get('#kms_provider').should('have.class', 'ng-valid');
+    this.selectAuthMethod(auth_method);
+    cy.get('#auth_method').should('have.class', 'ng-valid');
+    this.selectSecretEngine(secret_engine);
+    cy.get('#secret_engine').should('have.class', 'ng-valid');
+    cy.get('#address').type(address);
+    cy.get('#address').should('have.class', 'ng-valid');
+    cy.contains('button', 'Submit').click();
+    this.getFirstTableCell('SSE_KMS').should('exist');
+  }
+
+  edit(new_address: string) {
+    this.navigateEdit('SSE_KMS', true, false);
+    cy.get('#address').clear().type(new_address);
+    cy.get('#address').should('have.class', 'ng-valid');
+    cy.get('#kms_provider').should('be.disabled');
+    cy.contains('button', 'Submit').click();
+    this.getTableCell(this.columnIndex.address, new_address)
+      .parent()
+      .find(`datatable-body-cell:nth-child(${this.columnIndex.address})`)
+      .should(($elements) => {
+        const address = $elements.text();
+        expect(address).to.eq(new_address);
+      });
+  }
+
+  private selectKmsProvider(provider: string) {
+    return this.selectOption('kms_provider', provider);
+  }
+
+  private selectAuthMethod(auth_method: string) {
+    return this.selectOption('auth_method', auth_method);
+  }
+
+  private selectSecretEngine(secret_engine: string) {
+    return this.selectOption('secret_engine', secret_engine);
+  }
+}
index e4f81f643c445f47a930782644b60ae2c57dd653..5dd7c51de6b4614b17b50efa558b71be478a3a02 100644 (file)
@@ -1,7 +1,37 @@
-export class RgwBucketEncryptionModel {
-  kmsProviders = ['vault'];
-  authMethods = ['token', 'agent'];
-  secretEngines = ['kv', 'transit'];
-  sse_s3 = 'AES256';
-  sse_kms = 'aws:kms';
+enum KmsProviders {
+  Vault = 'vault'
 }
+
+enum AuthMethods {
+  Token = 'token',
+  Agent = 'agent'
+}
+
+enum SecretEngines {
+  KV = 'kv',
+  Transit = 'transit'
+}
+
+enum sseS3 {
+  SSE_S3 = 'AES256'
+}
+
+enum sseKms {
+  SSE_KMS = 'aws:kms'
+}
+
+interface RgwBucketEncryptionModel {
+  kmsProviders: KmsProviders[];
+  authMethods: AuthMethods[];
+  secretEngines: SecretEngines[];
+  SSE_S3: sseS3;
+  SSE_KMS: sseKms;
+}
+
+export const rgwBucketEncryptionModel: RgwBucketEncryptionModel = {
+  kmsProviders: [KmsProviders.Vault],
+  authMethods: [AuthMethods.Token, AuthMethods.Agent],
+  secretEngines: [SecretEngines.KV, SecretEngines.Transit],
+  SSE_S3: sseS3.SSE_S3,
+  SSE_KMS: sseKms.SSE_KMS
+};
index b25d47fecf33aaee9ebb7ab0eb0f432aa3393996..f77526be779b2549c2d5804692c589440e5d9b56 100644 (file)
                      name="encryption_enabled"
                      formControlName="encryption_enabled"
                      type="checkbox"
-                     [attr.disabled]="!kmsVaultConfig && !s3VaultConfig ? true : null"/>
+                     [attr.disabled]="!kmsConfigured && !s3Configured ? true : null"/>
               <cd-help-text aria-label="encryption helper">
                 <span i18n>Enables encryption for the objects in the bucket.
                     To enable encryption on a bucket you need to set the configuration values for SSE-S3 or SSE-KMS.
-                    To set the configuration values <a href="#/rgw/bucket/create"
-                                                       (click)="openConfigModal()"
+                    To set the configuration values <a href="#/rgw/configuration"
                                                        aria-label="click here">Click here</a></span>
               </cd-help-text>
             </div>
                          type="radio"
                          name="encryption_type"
                          value="AES256"
-                         [attr.disabled]="!s3VaultConfig ? true : null">
+                         [attr.disabled]="!s3Configured ? true : null">
                   <label class="form-control-label"
+                         [ngClass]="{'text-muted': !s3Configured}"
                          for="sse_S3_enabled"
-                         i18n>SSE-S3 Encryption</label>
+                         i18n>SSE-S3</label>
                 </div>
               </div>
             </div>
                          id="kms_enabled"
                          name="encryption_type"
                          value="aws:kms"
-                         [attr.disabled]="!kmsVaultConfig ? true : null"
+                         [attr.disabled]="!kmsConfigured ? true : null"
                          type="radio">
                   <label class="form-control-label"
+                         [ngClass]="{'text-muted': !kmsConfigured}"
                          for="kms_enabled"
                          i18n>Connect to an external key management service</label>
                 </div>
index cc2b5206517e08b52ef54e259e567c8e29aa184c..d82c71e3cf74f1c80d1cc31b4d9221d73576cb84 100644 (file)
@@ -25,7 +25,7 @@ import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { CdValidators } from '~/app/shared/forms/cd-validators';
 import { ModalService } from '~/app/shared/services/modal.service';
 import { NotificationService } from '~/app/shared/services/notification.service';
-import { RgwBucketEncryptionModel } from '../models/rgw-bucket-encryption';
+import { rgwBucketEncryptionModel } from '../models/rgw-bucket-encryption';
 import { RgwBucketMfaDelete } from '../models/rgw-bucket-mfa-delete';
 import {
   AclPermissionsType,
@@ -33,7 +33,6 @@ import {
   RgwBucketAclGrantee as Grantee
 } from './rgw-bucket-acl-permissions.enum';
 import { RgwBucketVersioning } from '../models/rgw-bucket-versioning';
-import { RgwConfigModalComponent } from '../rgw-config-modal/rgw-config-modal.component';
 import { BucketTagModalComponent } from '../bucket-tag-modal/bucket-tag-modal.component';
 import { TextAreaJsonFormatterService } from '~/app/shared/services/text-area-json-formatter.service';
 import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
@@ -44,8 +43,7 @@ import { TextAreaXmlFormatterService } from '~/app/shared/services/text-area-xml
 @Component({
   selector: 'cd-rgw-bucket-form',
   templateUrl: './rgw-bucket-form.component.html',
-  styleUrls: ['./rgw-bucket-form.component.scss'],
-  providers: [RgwBucketEncryptionModel]
+  styleUrls: ['./rgw-bucket-form.component.scss']
 })
 export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewChecked {
   @ViewChild('bucketPolicyTextArea')
@@ -64,8 +62,8 @@ export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewC
   isVersioningAlreadyEnabled = false;
   isMfaDeleteAlreadyEnabled = false;
   icons = Icons;
-  kmsVaultConfig = false;
-  s3VaultConfig = false;
+  kmsConfigured = false;
+  s3Configured = false;
   tags: Record<string, string>[] = [];
   dirtyTags = false;
   tagConfig = [
@@ -97,7 +95,6 @@ export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewC
     private modalService: ModalService,
     private rgwUserService: RgwUserService,
     private notificationService: NotificationService,
-    private rgwEncryptionModal: RgwBucketEncryptionModel,
     private textAreaJsonFormatterService: TextAreaJsonFormatterService,
     private textAreaXmlFormatterService: TextAreaXmlFormatterService,
     public actionLabels: ActionLabelsI18n,
@@ -187,15 +184,20 @@ export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewC
       )
     );
 
-    this.kmsProviders = this.rgwEncryptionModal.kmsProviders;
+    this.kmsProviders = rgwBucketEncryptionModel.kmsProviders;
     this.rgwBucketService.getEncryptionConfig().subscribe((data) => {
-      this.kmsVaultConfig = data[0];
-      this.s3VaultConfig = data[1];
-      if (this.kmsVaultConfig && this.s3VaultConfig) {
+      if (data['SSE_KMS']?.length > 0) {
+        this.kmsConfigured = true;
+      }
+      if (data['SSE_S3']?.length > 0) {
+        this.s3Configured = true;
+      }
+      // Set the encryption type based on the configurations
+      if (this.kmsConfigured && this.s3Configured) {
         this.bucketForm.get('encryption_type').setValue('');
-      } else if (this.kmsVaultConfig) {
+      } else if (this.kmsConfigured) {
         this.bucketForm.get('encryption_type').setValue('aws:kms');
-      } else if (this.s3VaultConfig) {
+      } else if (this.s3Configured) {
         this.bucketForm.get('encryption_type').setValue('AES256');
       } else {
         this.bucketForm.get('encryption_type').setValue('');
@@ -459,13 +461,6 @@ export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewC
     this.bucketForm.updateValueAndValidity();
   }
 
-  openConfigModal() {
-    const modalRef = this.modalService.show(RgwConfigModalComponent, null, { size: 'lg' });
-    modalRef.componentInstance.configForm
-      .get('encryptionType')
-      .setValue(this.bucketForm.getValue('encryption_type') || 'AES256');
-  }
-
   showTagModal(index?: number) {
     const modalRef = this.modalService.show(BucketTagModalComponent);
     const modalComponent = modalRef.componentInstance as BucketTagModalComponent;
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-config-details/rgw-config-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-config-details/rgw-config-details.component.html
new file mode 100644 (file)
index 0000000..ed79ed2
--- /dev/null
@@ -0,0 +1,17 @@
+<ng-container *ngIf="selection">
+  <nav ngbNav
+       #nav="ngbNav"
+       id="tabset-config-details"
+       class="nav-tabs"
+       cdStatefulTab="config-details">
+    <ng-container ngbNavItem="details">
+      <a ngbNavLink
+         i18n>Details</a>
+      <ng-template ngbNavContent>
+        <cd-table-key-value [data]="transformedData">
+        </cd-table-key-value>
+      </ng-template>
+    </ng-container>
+  </nav>
+  <div [ngbNavOutlet]="nav"></div>
+</ng-container>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-config-details/rgw-config-details.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-config-details/rgw-config-details.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-config-details/rgw-config-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-config-details/rgw-config-details.component.spec.ts
new file mode 100644 (file)
index 0000000..8f52256
--- /dev/null
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwConfigDetailsComponent } from './rgw-config-details.component';
+
+describe('RgwConfigDetailsComponent', () => {
+  let component: RgwConfigDetailsComponent;
+  let fixture: ComponentFixture<RgwConfigDetailsComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [RgwConfigDetailsComponent]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RgwConfigDetailsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-config-details/rgw-config-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-config-details/rgw-config-details.component.ts
new file mode 100644 (file)
index 0000000..689330f
--- /dev/null
@@ -0,0 +1,37 @@
+import { Component, Input, OnChanges } from '@angular/core';
+import { rgwEncryptionConfigKeys } from '~/app/shared/models/rgw-encryption-config-keys';
+
+@Component({
+  selector: 'cd-rgw-config-details',
+  templateUrl: './rgw-config-details.component.html',
+  styleUrls: ['./rgw-config-details.component.scss']
+})
+export class RgwConfigDetailsComponent implements OnChanges {
+  transformedData: {};
+  @Input()
+  selection: any;
+
+  @Input()
+  excludeProps: any[] = [];
+  filteredEncryptionConfigValues: {};
+
+  ngOnChanges(): void {
+    if (this.selection) {
+      this.filteredEncryptionConfigValues = Object.keys(this.selection)
+        .filter((key) => !this.excludeProps.includes(key))
+        .reduce((obj, key) => {
+          obj[key] = this.selection[key];
+          return obj;
+        }, {});
+      const transformedData = {};
+      for (const key in this.filteredEncryptionConfigValues) {
+        if (rgwEncryptionConfigKeys[key]) {
+          transformedData[rgwEncryptionConfigKeys[key]] = this.filteredEncryptionConfigValues[key];
+        } else {
+          transformedData[key] = this.filteredEncryptionConfigValues[key];
+        }
+      }
+      this.transformedData = transformedData;
+    }
+  }
+}
index a8ed17838347630500ea72fa2a9f22c2332220cf..7205665a7a72e7820f3f5c9a5f20b673b686a4fe 100644 (file)
@@ -1,6 +1,6 @@
 <cd-modal [modalRef]="activeModal">
   <ng-container i18n="form title"
-                class="modal-title">Update RGW Encryption Configurations</ng-container>
+                class="modal-title">{{ action | titlecase }} RGW Encryption Configurations</ng-container>
 
   <ng-container class="modal-content">
     <form name="configForm"
                  id="s3Enabled"
                  type="radio"
                  name="encryptionType"
+                 (change)="checkKmsProviders()"
+                 [attr.disabled]="editing && configForm.getValue('encryptionType') !== 'AES256' ? true : null"
                  value="AES256">
           <label class="custom-check-label"
+                 [ngClass]="{'text-muted': editing && configForm.getValue('encryptionType') !== 'AES256'}"
                  for="s3Enabled"
-                 i18n>SSE-S3 Encryption</label>
+                 i18n>SSE-S3</label>
         </div>
 
         <div class="col-md-auto custom-checkbox form-check-inline">
                  formControlName="encryptionType"
                  id="kmsEnabled"
                  name="encryptionType"
+                 (change)="checkKmsProviders()"
                  value="aws:kms"
+                 [attr.disabled]="editing && configForm.getValue('encryptionType') !== 'aws:kms' ? true : null"
                  type="radio">
           <label class="custom-check-label"
+                 [ngClass]="{'text-muted': editing && configForm.getValue('encryptionType') !== 'aws:kms'}"
                  for="kmsEnabled"
-                 i18n>SSE-KMS Encryption</label>
+                 i18n>SSE-KMS</label>
         </div>
       </div>
 
                     name="kms_provider"
                     class="form-select"
                     formControlName="kms_provider">
-              <option i18n
-                      *ngIf="kmsProviders !== null"
-                      [ngValue]="null">-- Select a provider --</option>
+              <option *ngIf="kmsProviders !== null && kmsProviders.length === 0"
+                      ngValue="null"
+                      i18n>-- No kms providers available --</option>
+              <option *ngIf="kmsProviders !== null && kmsProviders.length > 0"
+                      ngValue=""
+                      i18n>-- Select a provider --</option>
               <option *ngFor="let provider of kmsProviders"
                       [value]="provider">{{ provider }}</option>
             </select>
         </div>
       </div>
 
-      <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
-        <div class="form-group row">
-          <label class="cd-col-form-label required"
-                 for="auth_method"
-                 i18n>Authentication Method</label>
-          <div class="cd-col-form-input">
-            <select id="auth_method"
-                    name="auth_method"
-                    class="form-select"
-                    formControlName="auth_method">
-              <option *ngFor="let auth_method of authMethods"
-                      [value]="auth_method">{{ auth_method }}</option>
-            </select>
-            <span class="invalid-feedback"
-                  *ngIf="configForm.showError('auth_method', frm, 'required')"
-                  i18n>This field is required.</span>
+      <div *ngIf="kmsProviders.length !== 0 && configForm.getValue('kms_provider') !== ''">
+        <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
+          <div class="form-group row">
+            <label class="cd-col-form-label required"
+                   for="auth_method"
+                   i18n>Authentication Method</label>
+            <div class="cd-col-form-input">
+              <select id="auth_method"
+                      name="auth_method"
+                      class="form-select"
+                      formControlName="auth_method">
+                <option *ngFor="let auth_method of authMethods"
+                        [value]="auth_method">{{ auth_method }}</option>
+              </select>
+              <span class="invalid-feedback"
+                    *ngIf="configForm.showError('auth_method', frm, 'required')"
+                    i18n>This field is required.</span>
+            </div>
           </div>
         </div>
-      </div>
 
-      <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
-        <div class="form-group row">
-          <label class="cd-col-form-label required"
-                 for="secret_engine"
-                 i18n>Secret Engine</label>
-          <div class="cd-col-form-input">
-            <select id="secret_engine"
-                    name="secret_engine"
-                    class="form-select"
-                    formControlName="secret_engine">
-              <option *ngFor="let secret_engine of secretEngines"
-                      [value]="secret_engine">{{ secret_engine }}</option>
-            </select>
-            <span class="invalid-feedback"
-                  *ngIf="configForm.showError('secret_engine', frm, 'required')"
-                  i18n>This field is required.</span>
+        <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
+          <div class="form-group row">
+            <label class="cd-col-form-label required"
+                   for="secret_engine"
+                   i18n>Secret Engine</label>
+            <div class="cd-col-form-input">
+              <select id="secret_engine"
+                      name="secret_engine"
+                      class="form-select"
+                      formControlName="secret_engine">
+                <option *ngFor="let secret_engine of secretEngines"
+                        [value]="secret_engine">{{ secret_engine }}</option>
+              </select>
+              <span class="invalid-feedback"
+                    *ngIf="configForm.showError('secret_engine', frm, 'required')"
+                    i18n>This field is required.</span>
+            </div>
           </div>
         </div>
-      </div>
 
-      <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 for="secret_path"
-                 i18n>Secret Path
-          </label>
-          <div class="cd-col-form-input">
-            <input id="secret_path"
-                   name="secret_path"
-                   class="form-control"
-                   type="text"
-                   formControlName="secret_path">
-            <span class="invalid-feedback"
-                  *ngIf="configForm.showError('secret_path', frm, 'required')"
-                  i18n>This field is required.</span>
+        <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
+          <div class="form-group row">
+            <label class="cd-col-form-label"
+                   for="secret_path"
+                   i18n>Secret Path
+            </label>
+            <div class="cd-col-form-input">
+              <input id="secret_path"
+                     name="secret_path"
+                     class="form-control"
+                     type="text"
+                     formControlName="secret_path">
+              <span class="invalid-feedback"
+                    *ngIf="configForm.showError('secret_path', frm, 'required')"
+                    i18n>This field is required.</span>
+            </div>
           </div>
         </div>
-      </div>
 
-      <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 for="namespace"
-                 i18n>Namespace
-          </label>
-          <div class="cd-col-form-input">
-            <input id="namespace"
-                   name="namespace"
-                   class="form-control"
-                   type="text"
-                   formControlName="namespace">
+        <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
+          <div class="form-group row">
+            <label class="cd-col-form-label"
+                   for="namespace"
+                   i18n>Namespace
+            </label>
+            <div class="cd-col-form-input">
+              <input id="namespace"
+                     name="namespace"
+                     class="form-control"
+                     type="text"
+                     formControlName="namespace">
+            </div>
           </div>
         </div>
-      </div>
 
-      <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
-        <div class="form-group row">
-          <label class="cd-col-form-label required"
-                 for="address"
-                 i18n>Vault Address
-          </label>
-          <div class="cd-col-form-input">
-            <input id="address"
-                   name="address"
-                   class="form-control"
-                   formControlName="address"
-                   placeholder="http://127.0.0.1:8000">
-            <span class="invalid-feedback"
-                  *ngIf="configForm.showError('address', frm, 'required')"
-                  i18n>This field is required.</span>
+        <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
+          <div class="form-group row">
+            <label class="cd-col-form-label required"
+                   for="address"
+                   i18n>Vault Address
+            </label>
+            <div class="cd-col-form-input">
+              <input id="address"
+                     name="address"
+                     class="form-control"
+                     formControlName="address"
+                     placeholder="http://127.0.0.1:8000">
+              <span class="invalid-feedback"
+                    *ngIf="configForm.showError('address', frm, 'required')"
+                    i18n>This field is required.</span>
+            </div>
           </div>
         </div>
-      </div>
-
-      <div *ngIf="configForm.getValue('auth_method') === 'token'"
-           class="form-group row">
-        <label class="cd-col-form-label required"
-               for="token">
-        <span i18n>Token</span>
-        <cd-helper i18n>
-          The token authentication method expects a Vault token to be present in a plaintext file.
-        </cd-helper>
-        </label>
-        <div class="cd-col-form-input">
-          <input type="file"
-                 formControlName="token"
-                 (change)="fileUpload($event.target.files, 'token')">
-          <span class="invalid-feedback"
-                *ngIf="configForm.showError('token', frm, 'required')"
-                i18n>This field is required.</span>
-        </div>
-      </div>
 
-      <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 for="ssl_cert">
-          <span i18n>CA Certificate</span>
-          <cd-helper i18n>The SSL certificate in PEM format.</cd-helper>
+        <div *ngIf="configForm.getValue('auth_method') === 'token'"
+             class="form-group row">
+          <label class="cd-col-form-label required"
+                 for="token">
+          <span i18n>Token</span>
+          <cd-helper i18n>
+            The token authentication method expects a Vault token to be present in a plaintext file.
+          </cd-helper>
           </label>
           <div class="cd-col-form-input">
             <input type="file"
-                   formControlName="ssl_cert"
-                   (change)="fileUpload($event.target.files, 'ssl_cert')">
+                   formControlName="token"
+                   (change)="fileUpload($event.target.files, 'token')">
             <span class="invalid-feedback"
-                  *ngIf="configForm.showError('ssl_cert', frm, 'required')"
+                  *ngIf="configForm.showError('token', frm, 'required')"
                   i18n>This field is required.</span>
           </div>
         </div>
-      </div>
 
-      <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 for="client_cert">
-          <span i18n>Client Certificate</span>
-          <cd-helper i18n>The Client certificate in PEM format.</cd-helper>
-          </label>
-          <div class="cd-col-form-input">
-            <input type="file"
-                   formControlName="client_cert"
-                   (change)="fileUpload($event.target.files, 'client_cert')">
-            <span class="invalid-feedback"
-                  *ngIf="configForm.showError('client_cert', frm, 'required')"
-                  i18n>This field is required.</span>
+        <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
+          <div class="form-group row">
+            <label class="cd-col-form-label"
+                   for="ssl_cert">
+            <span i18n>CA Certificate</span>
+            <cd-helper i18n>The SSL certificate in PEM format.</cd-helper>
+            </label>
+            <div class="cd-col-form-input">
+              <input type="file"
+                     formControlName="ssl_cert"
+                     (change)="fileUpload($event.target.files, 'ssl_cert')">
+              <span class="invalid-feedback"
+                    *ngIf="configForm.showError('ssl_cert', frm, 'required')"
+                    i18n>This field is required.</span>
+            </div>
           </div>
         </div>
-      </div>
 
-      <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
-        <div class="form-group row">
-          <label class="cd-col-form-label"
-                 for="client_key">
-          <span i18n>Client Private Key</span>
-          <cd-helper i18n>The Client Private Key in PEM format.</cd-helper>
-          </label>
-          <div class="cd-col-form-input">
-            <input type="file"
-                   (change)="fileUpload($event.target.files, 'client_key')">
-            <span class="invalid-feedback"
-                  *ngIf="configForm.showError('client_key', frm, 'required')"
-                  i18n>This field is required.</span>
+        <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
+          <div class="form-group row">
+            <label class="cd-col-form-label"
+                   for="client_cert">
+            <span i18n>Client Certificate</span>
+            <cd-helper i18n>The Client certificate in PEM format.</cd-helper>
+            </label>
+            <div class="cd-col-form-input">
+              <input type="file"
+                     formControlName="client_cert"
+                     (change)="fileUpload($event.target.files, 'client_cert')">
+              <span class="invalid-feedback"
+                    *ngIf="configForm.showError('client_cert', frm, 'required')"
+                    i18n>This field is required.</span>
+            </div>
+          </div>
+        </div>
+
+        <div *ngIf="configForm.getValue('encryptionType') === 'aws:kms' || configForm.getValue('encryptionType') === 'AES256'">
+          <div class="form-group row">
+            <label class="cd-col-form-label"
+                   for="client_key">
+            <span i18n>Client Private Key</span>
+            <cd-helper i18n>The Client Private Key in PEM format.</cd-helper>
+            </label>
+            <div class="cd-col-form-input">
+              <input type="file"
+                     (change)="fileUpload($event.target.files, 'client_key')">
+              <span class="invalid-feedback"
+                    *ngIf="configForm.showError('client_key', frm, 'required')"
+                    i18n>This field is required.</span>
+            </div>
           </div>
         </div>
       </div>
index f2a0959109fbb72d609accbf1e0055f40b75f6e0..892916e86b5ead0e75193ceec03243c5f3708c76 100644 (file)
@@ -12,13 +12,12 @@ import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { CdValidators } from '~/app/shared/forms/cd-validators';
 import { NotificationService } from '~/app/shared/services/notification.service';
-import { RgwBucketEncryptionModel } from '../models/rgw-bucket-encryption';
+import { rgwBucketEncryptionModel } from '../models/rgw-bucket-encryption';
 
 @Component({
   selector: 'cd-rgw-config-modal',
   templateUrl: './rgw-config-modal.component.html',
-  styleUrls: ['./rgw-config-modal.component.scss'],
-  providers: [RgwBucketEncryptionModel]
+  styleUrls: ['./rgw-config-modal.component.scss']
 })
 export class RgwConfigModalComponent implements OnInit {
   readonly vaultAddress = /^((https?:\/\/)|(www.))(?:([a-zA-Z]+)|(\d+\.\d+.\d+.\d+)):\d{4}$/;
@@ -32,21 +31,75 @@ export class RgwConfigModalComponent implements OnInit {
   authMethods: string[];
   secretEngines: string[];
 
+  selectedEncryptionConfigValues: any = {};
+  allEncryptionConfigValues: any = [];
+  editing = false;
+  action: string;
+
   constructor(
     private formBuilder: CdFormBuilder,
     public activeModal: NgbActiveModal,
     private router: Router,
     public actionLabels: ActionLabelsI18n,
     private rgwBucketService: RgwBucketService,
-    private rgwEncryptionModal: RgwBucketEncryptionModel,
     private notificationService: NotificationService
   ) {
     this.createForm();
   }
   ngOnInit(): void {
-    this.kmsProviders = this.rgwEncryptionModal.kmsProviders;
-    this.authMethods = this.rgwEncryptionModal.authMethods;
-    this.secretEngines = this.rgwEncryptionModal.secretEngines;
+    this.kmsProviders = rgwBucketEncryptionModel.kmsProviders;
+    this.authMethods = rgwBucketEncryptionModel.authMethods;
+    this.secretEngines = rgwBucketEncryptionModel.secretEngines;
+    if (this.editing && this.selectedEncryptionConfigValues) {
+      const patchValues = {
+        address: this.selectedEncryptionConfigValues['addr'],
+        encryptionType:
+          rgwBucketEncryptionModel[this.selectedEncryptionConfigValues['encryption_type']],
+        kms_provider: this.selectedEncryptionConfigValues['backend'],
+        auth_method: this.selectedEncryptionConfigValues['auth'],
+        secret_engine: this.selectedEncryptionConfigValues['secret_engine'],
+        secret_path: this.selectedEncryptionConfigValues['prefix'],
+        namespace: this.selectedEncryptionConfigValues['namespace']
+      };
+      this.configForm.patchValue(patchValues);
+      this.configForm.get('kms_provider').disable();
+    }
+    this.checkKmsProviders();
+  }
+
+  checkKmsProviders() {
+    this.kmsProviders = rgwBucketEncryptionModel.kmsProviders;
+    if (
+      this.allEncryptionConfigValues &&
+      this.allEncryptionConfigValues.hasOwnProperty('SSE_KMS') &&
+      !this.editing
+    ) {
+      const sseKmsBackends = this.allEncryptionConfigValues['SSE_KMS'].map(
+        (config: any) => config.backend
+      );
+      if (this.configForm.get('encryptionType').value === rgwBucketEncryptionModel.SSE_KMS) {
+        this.kmsProviders = this.kmsProviders.filter(
+          (provider) => !sseKmsBackends.includes(provider)
+        );
+      }
+    }
+    if (
+      this.allEncryptionConfigValues &&
+      this.allEncryptionConfigValues.hasOwnProperty('SSE_S3') &&
+      !this.editing
+    ) {
+      const sseS3Backends = this.allEncryptionConfigValues['SSE_S3'].map(
+        (config: any) => config.backend
+      );
+      if (this.configForm.get('encryptionType').value === rgwBucketEncryptionModel.SSE_S3) {
+        this.kmsProviders = this.kmsProviders.filter(
+          (provider) => !sseS3Backends.includes(provider)
+        );
+      }
+    }
+    if (this.kmsProviders.length > 0 && !this.kmsProviders.includes('vault')) {
+      this.configForm.get('kms_provider').setValue('');
+    }
   }
 
   createForm() {
@@ -98,7 +151,7 @@ export class RgwConfigModalComponent implements OnInit {
   }
 
   onSubmit() {
-    const values = this.configForm.value;
+    const values = this.configForm.getRawValue();
     this.rgwBucketService
       .setEncryptionConfig(
         values['encryptionType'],
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-configuration-page/rgw-configuration-page.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-configuration-page/rgw-configuration-page.component.html
new file mode 100644 (file)
index 0000000..c33c8db
--- /dev/null
@@ -0,0 +1,32 @@
+<nav ngbNav
+     #nav="ngbNav"
+     class="nav-tabs">
+  <ng-container ngbNavItem>
+    <a ngbNavLink
+       i18n>Server-side Encryption</a>
+    <ng-template ngbNavContent>
+      <cd-table #table
+                [data]="encryptionConfigValues"
+                [columns]="columns"
+                identifier="unique_id"
+                [forceIdentifier]="true"
+                [hasDetails]="true"
+                (updateSelection)="updateSelection($event)"
+                (setExpandedRow)="setExpandedRow($event)"
+                columnMode="flex"
+                selectionType="single">
+        <cd-table-actions class="table-actions"
+                          [permission]="permissions.configOpt"
+                          [selection]="selection"
+                          [tableActions]="tableActions">
+        </cd-table-actions>
+        <cd-rgw-config-details cdTableDetail
+                               [selection]="expandedRow"
+                               [excludeProps]="excludeProps">
+        </cd-rgw-config-details>
+      </cd-table>
+    </ng-template>
+  </ng-container>
+</nav>
+
+<div [ngbNavOutlet]="nav"></div>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-configuration-page/rgw-configuration-page.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-configuration-page/rgw-configuration-page.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-configuration-page/rgw-configuration-page.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-configuration-page/rgw-configuration-page.component.spec.ts
new file mode 100644 (file)
index 0000000..a487050
--- /dev/null
@@ -0,0 +1,28 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwConfigurationPageComponent } from './rgw-configuration-page.component';
+import { NgbActiveModal, NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { SharedModule } from '~/app/shared/shared.module';
+import { RgwModule } from '../rgw.module';
+
+describe('RgwConfigurationPageComponent', () => {
+  let component: RgwConfigurationPageComponent;
+  let fixture: ComponentFixture<RgwConfigurationPageComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [RgwConfigurationPageComponent],
+      providers: [NgbActiveModal],
+      imports: [HttpClientTestingModule, SharedModule, NgbNavModule, RgwModule]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RgwConfigurationPageComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-configuration-page/rgw-configuration-page.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-configuration-page/rgw-configuration-page.component.ts
new file mode 100644 (file)
index 0000000..12e1a36
--- /dev/null
@@ -0,0 +1,148 @@
+import { Component, EventEmitter, OnInit, Output } from '@angular/core';
+
+import { NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import _ from 'lodash';
+
+import { Permissions } from '~/app/shared/models/permissions';
+
+import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { ModalService } from '~/app/shared/services/modal.service';
+import { RgwConfigModalComponent } from '../rgw-config-modal/rgw-config-modal.component';
+import { rgwBucketEncryptionModel } from '../models/rgw-bucket-encryption';
+
+@Component({
+  selector: 'cd-rgw-configuration-page',
+  templateUrl: './rgw-configuration-page.component.html',
+  styleUrls: ['./rgw-configuration-page.component.scss']
+})
+export class RgwConfigurationPageComponent extends ListWithDetails implements OnInit {
+  readonly vaultAddress = /^((https?:\/\/)|(www.))(?:([a-zA-Z]+)|(\d+\.\d+.\d+.\d+)):\d{4}$/;
+
+  kmsProviders: string[];
+
+  columns: Array<CdTableColumn> = [];
+
+  configForm: CdFormGroup;
+  permissions: Permissions;
+  encryptionConfigValues: any = [];
+  selection: CdTableSelection = new CdTableSelection();
+
+  @Output()
+  submitAction = new EventEmitter();
+  authMethods: string[];
+  secretEngines: string[];
+  tableActions: CdTableAction[];
+  bsModalRef: NgbModalRef;
+  filteredEncryptionConfigValues: {};
+  excludeProps: any[] = [];
+  disableCreate = false;
+  allEncryptionValues: any;
+
+  constructor(
+    public activeModal: NgbActiveModal,
+    public actionLabels: ActionLabelsI18n,
+    private rgwBucketService: RgwBucketService,
+    public authStorageService: AuthStorageService,
+    private modalService: ModalService
+  ) {
+    super();
+    this.permissions = this.authStorageService.getPermissions();
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Encryption Type`,
+        prop: 'encryption_type',
+        flexGrow: 1
+      },
+      {
+        name: $localize`Key Management Service Provider`,
+        prop: 'backend',
+        flexGrow: 1
+      },
+      {
+        name: $localize`Address`,
+        prop: 'addr',
+        flexGrow: 1
+      }
+    ];
+    this.tableActions = [
+      {
+        permission: 'create',
+        icon: Icons.add,
+        name: this.actionLabels.CREATE,
+        click: () => this.openRgwConfigModal(false),
+        disable: () => this.disableCreate
+      },
+      {
+        permission: 'update',
+        icon: Icons.edit,
+        name: this.actionLabels.EDIT,
+        click: () => this.openRgwConfigModal(true)
+      }
+    ];
+
+    this.rgwBucketService.getEncryptionConfig().subscribe((data: any) => {
+      this.allEncryptionValues = data;
+      const allowedBackends = rgwBucketEncryptionModel.kmsProviders;
+
+      const kmsBackends = this.getBackend(data, 'SSE_KMS');
+      const s3Backends = this.getBackend(data, 'SSE_S3');
+
+      const allKmsBackendsPresent = this.areAllAllowedBackendsPresent(allowedBackends, kmsBackends);
+      const allS3BackendsPresent = this.areAllAllowedBackendsPresent(allowedBackends, s3Backends);
+
+      this.disableCreate = allKmsBackendsPresent && allS3BackendsPresent;
+      this.encryptionConfigValues = Object.values(data).flat();
+    });
+
+    this.excludeProps = this.columns.map((column) => column.prop);
+    this.excludeProps.push('unique_id');
+  }
+
+  getBackend(encryptionData: { [x: string]: any[] }, encryptionType: string) {
+    return new Set(encryptionData[encryptionType].map((item) => item.backend));
+  }
+
+  areAllAllowedBackendsPresent(allowedBackends: any[], backendsSet: Set<any>) {
+    return allowedBackends.every((backend) => backendsSet.has(backend));
+  }
+
+  openRgwConfigModal(edit: boolean) {
+    if (edit) {
+      const initialState = {
+        action: 'edit',
+        editing: true,
+        selectedEncryptionConfigValues: this.selection.first()
+      };
+      this.bsModalRef = this.modalService.show(RgwConfigModalComponent, initialState, {
+        size: 'lg'
+      });
+    } else {
+      const initialState = {
+        action: 'create',
+        allEncryptionConfigValues: this.allEncryptionValues
+      };
+      this.bsModalRef = this.modalService.show(RgwConfigModalComponent, initialState, {
+        size: 'lg'
+      });
+    }
+  }
+
+  updateSelection(selection: CdTableSelection) {
+    this.selection = selection;
+  }
+
+  setExpandedRow(expandedRow: any) {
+    super.setExpandedRow(expandedRow);
+  }
+}
index 803e3c5bdf83df5f972bf450865dc05a53a54a32..dde6cff4866be202da95ec7ccf7a4b4586031d16 100644 (file)
@@ -56,6 +56,8 @@ import { NfsListComponent } from '../nfs/nfs-list/nfs-list.component';
 import { NfsFormComponent } from '../nfs/nfs-form/nfs-form.component';
 import { RgwMultisiteSyncPolicyComponent } from './rgw-multisite-sync-policy/rgw-multisite-sync-policy.component';
 import { RgwMultisiteSyncPolicyFormComponent } from './rgw-multisite-sync-policy-form/rgw-multisite-sync-policy-form.component';
+import { RgwConfigurationPageComponent } from './rgw-configuration-page/rgw-configuration-page.component';
+import { RgwConfigDetailsComponent } from './rgw-config-details/rgw-config-details.component';
 
 @NgModule({
   imports: [
@@ -116,7 +118,9 @@ import { RgwMultisiteSyncPolicyFormComponent } from './rgw-multisite-sync-policy
     RgwSyncDataInfoComponent,
     BucketTagModalComponent,
     RgwMultisiteSyncPolicyComponent,
-    RgwMultisiteSyncPolicyFormComponent
+    RgwMultisiteSyncPolicyFormComponent,
+    RgwConfigDetailsComponent,
+    RgwConfigurationPageComponent
   ],
   providers: [TitleCasePipe]
 })
@@ -253,6 +257,11 @@ const routes: Routes = [
         data: { breadcrumbs: ActionLabels.EDIT }
       }
     ]
+  },
+  {
+    path: 'configuration',
+    data: { breadcrumbs: 'Configuration' },
+    children: [{ path: '', component: RgwConfigurationPageComponent }]
   }
 ];
 
index f238946963d5825003e2d3e3ef4384b41813af49..c6464fe5e44492eb6826422d6f621b2987f8494d 100644 (file)
                             i18n-title
                             *ngIf="permissions.nfs.read && enabledFeature.nfs"
                             class="tc_submenuitem tc_submenuitem_rgw_nfs"><span i18n>NFS</span></cds-sidenav-item>
+          <cds-sidenav-item route="/rgw/configuration"
+                            [useRouter]="true"
+                            title="Configuration"
+                            i18n-title
+                            class="tc_submenuitem tc_submenuitem_rgw_configuration"><span i18n>Configuration</span></cds-sidenav-item>
         </cds-sidenav-menu>
         <!-- Filesystem -->
         <cds-sidenav-menu title="File"
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/rgw-encryption-config-keys.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/rgw-encryption-config-keys.ts
new file mode 100644 (file)
index 0000000..90fccb8
--- /dev/null
@@ -0,0 +1,21 @@
+export enum rgwEncryptionConfigKeys {
+  auth = 'Authentication Method',
+  encryption_type = 'Encryption Type',
+  backend = 'Backend',
+  prefix = 'Prefix',
+  namespace = 'Namespace',
+  secret_engine = 'Secret Engine',
+  addr = 'Address',
+  token_file = 'Token File',
+  ssl_cacert = 'SSL CA Certificate',
+  ssl_clientcert = 'SSL Client Certificate',
+  ssl_clientkey = 'SSL Client Key',
+  verify_ssl = 'Verify SSL',
+  ca_path = 'CA Path',
+  client_cert = 'Client Certificate',
+  client_key = 'Client Key',
+  kms_key_template = 'KMS Key Template',
+  password = 'Password',
+  s3_key_template = 'S3 Key Template',
+  username = 'Username'
+}
index 53cd0e7ad936a124a73239a0e915e1fcf7568570..0a34e39a7139c792bdf136b789bff0018215bb73 100644 (file)
@@ -2,6 +2,7 @@
 
 import json
 import logging
+from abc import ABC, abstractmethod
 
 import rados
 from mgr_module import CommandResult
@@ -10,7 +11,7 @@ from mgr_util import get_most_recent_rate, get_time_series_rates, name_to_config
 from .. import mgr
 
 try:
-    from typing import Any, Dict, Optional, Union
+    from typing import Any, Dict, List, Optional, Union
 except ImportError:
     pass  # For typing only
 
@@ -24,6 +25,45 @@ class SendCommandError(rados.Error):
         super(SendCommandError, self).__init__(err, errno)
 
 
+class BackendConfig(ABC):
+    @abstractmethod
+    def get_config_keys(self) -> List[str]:
+        pass
+
+    @abstractmethod
+    def get_required_keys(self) -> List[str]:
+        pass
+
+    @abstractmethod
+    def get_key_pattern(self, enc_type: str) -> str:
+        pass
+
+
+class VaultConfig(BackendConfig):
+    def get_config_keys(self) -> List[str]:
+        return ['addr', 'auth', 'namespace', 'prefix', 'secret_engine',
+                'token_file', 'ssl_cacert', 'ssl_clientcert', 'ssl_clientkey',
+                'verify_ssl']
+
+    def get_required_keys(self) -> List[str]:
+        return ['auth', 'prefix', 'secret_engine', 'addr']
+
+    def get_key_pattern(self, enc_type: str) -> str:
+        return 'rgw_crypt_{backend}_{key}' if enc_type == 'SSE_KMS' else 'rgw_crypt_sse_s3_{backend}_{key}'  # noqa E501  #pylint: disable=line-too-long
+
+
+class KmipConfig(BackendConfig):
+    def get_config_keys(self) -> List[str]:
+        return ['addr', 'ca_path', 'client_cert', 'client_key', 'kms_key_template',
+                'password', 's3_key_template', 'username']
+
+    def get_required_keys(self) -> List[str]:
+        return ['addr', 'username', 'password']
+
+    def get_key_pattern(self, enc_type: str) -> str:
+        return 'rgw_crypt_{backend}_{key}' if enc_type == 'SSE_KMS' else 'rgw_crypt_sse_s3_{backend}_{key}'  # noqa E501  #pylint: disable=line-too-long
+
+
 # pylint: disable=too-many-public-methods
 class CephService(object):
 
@@ -183,64 +223,59 @@ class CephService(object):
         return None
 
     @classmethod
-    def get_encryption_config(cls, daemon_name):
-        kms_vault_configured = False
-        s3_vault_configured = False
-        kms_backend: str = ''
-        sse_s3_backend: str = ''
-        vault_stats = []
-        full_daemon_name = 'rgw.' + daemon_name
+    def get_encryption_config(cls, daemon_name: str) -> Dict[str, List[Dict[str, Any]]]:
+        # Define backends with their respective configuration classes
+        backends: Dict[str, Dict[str, BackendConfig]] = {
+            'SSE_KMS': {
+                'vault': VaultConfig(),
+                'kmip': KmipConfig()
+            },
+            'SSE_S3': {
+                'vault': VaultConfig()
+            }
+        }
 
-        kms_backend = CephService.send_command('mon', 'config get',
-                                               who=name_to_config_section(full_daemon_name),
-                                               key='rgw_crypt_s3_kms_backend')
-        sse_s3_backend = CephService.send_command('mon', 'config get',
-                                                  who=name_to_config_section(full_daemon_name),
-                                                  key='rgw_crypt_sse_s3_backend')
-
-        if kms_backend.strip() == 'vault':
-            kms_vault_auth: str = CephService.send_command('mon', 'config get',
-                                                           who=name_to_config_section(full_daemon_name),  # noqa E501 #pylint: disable=line-too-long
-                                                           key='rgw_crypt_vault_auth')
-            kms_vault_engine: str = CephService.send_command('mon', 'config get',
-                                                             who=name_to_config_section(full_daemon_name),  # noqa E501 #pylint: disable=line-too-long
-                                                             key='rgw_crypt_vault_secret_engine')
-            kms_vault_address: str = CephService.send_command('mon', 'config get',
-                                                              who=name_to_config_section(full_daemon_name),  # noqa E501 #pylint: disable=line-too-long
-                                                              key='rgw_crypt_vault_addr')
-            kms_vault_token: str = CephService.send_command('mon', 'config get',
-                                                            who=name_to_config_section(full_daemon_name),  # noqa E501 #pylint: disable=line-too-long
-                                                            key='rgw_crypt_vault_token_file')  # noqa E501 #pylint: disable=line-too-long
-            if (kms_vault_auth.strip() != "" and kms_vault_engine.strip() != "" and kms_vault_address.strip() != ""):  # noqa E501 #pylint: disable=line-too-long
-                if(kms_vault_auth == 'token' and kms_vault_token.strip() == ""):
-                    kms_vault_configured = False
-                else:
-                    kms_vault_configured = True
-
-        if sse_s3_backend.strip() == 'vault':
-            s3_vault_auth: str = CephService.send_command('mon', 'config get',
-                                                          who=name_to_config_section(full_daemon_name),  # noqa E501 #pylint: disable=line-too-long
-                                                          key='rgw_crypt_sse_s3_vault_auth')
-            s3_vault_engine: str = CephService.send_command('mon',
-                                                            'config get',
-                                                            who=name_to_config_section(full_daemon_name),  # noqa E501 #pylint: disable=line-too-long
-                                                            key='rgw_crypt_sse_s3_vault_secret_engine')  # noqa E501 #pylint: disable=line-too-long
-            s3_vault_address: str = CephService.send_command('mon', 'config get',
-                                                             who=name_to_config_section(full_daemon_name),  # noqa E501 #pylint: disable=line-too-long
-                                                             key='rgw_crypt_sse_s3_vault_addr')
-            s3_vault_token: str = CephService.send_command('mon', 'config get',
-                                                           who=name_to_config_section(full_daemon_name),  # noqa E501 #pylint: disable=line-too-long
-                                                           key='rgw_crypt_sse_s3_vault_token_file')  # noqa E501 #pylint: disable=line-too-long
-
-            if (s3_vault_auth.strip() != "" and s3_vault_engine.strip() != "" and s3_vault_address.strip() != ""):  # noqa E501 #pylint: disable=line-too-long
-                if(s3_vault_auth == 'token' and s3_vault_token.strip() == ""):
-                    s3_vault_configured = False
-                else:
-                    s3_vault_configured = True
+        # Final configuration values
+        config_values: Dict[str, List[Dict[str, Any]]] = {
+            'SSE_KMS': [],
+            'SSE_S3': []
+        }
+
+        full_daemon_name = 'rgw.' + daemon_name
 
-        vault_stats.append(kms_vault_configured)
-        vault_stats.append(s3_vault_configured)
-        return vault_stats
+        for enc_type, backend_list in backends.items():
+            for backend_name, backend in backend_list.items():
+                config_keys = backend.get_config_keys()
+                required_keys = backend.get_required_keys()
+                key_pattern = backend.get_key_pattern(enc_type)
+
+                # Check if all required configurations are present and not empty
+                all_required_configs_present = True
+                for key in required_keys:
+                    config_key = key_pattern.format(backend=backend_name, key=key)
+                    value = CephService.send_command('mon', 'config get',
+                                                     who=name_to_config_section(full_daemon_name),
+                                                     key=config_key)
+                    if not (isinstance(value, str) and value.strip()):
+                        all_required_configs_present = False
+                        break
+
+                # If all required configurations are present, gather all config values
+                if all_required_configs_present:
+                    config_dict = {}
+                    for key in config_keys:
+                        config_key = key_pattern.format(backend=backend_name, key=key)
+                        value = CephService.send_command('mon', 'config get',
+                                                         who=name_to_config_section(full_daemon_name),  # noqa E501  #pylint: disable=line-too-long
+                                                         key=config_key)
+                        if value:
+                            config_dict[key] = value.strip() if isinstance(value, str) else value
+                    config_dict['backend'] = backend_name
+                    config_dict['encryption_type'] = enc_type
+                    config_dict['unique_id'] = enc_type + '-' + backend_name
+                    config_values[enc_type].append(config_dict)
+
+        return config_values
 
     @classmethod
     def set_encryption_config(cls, encryption_type, kms_provider, auth_method,