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);
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);
--- /dev/null
+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');
+ });
+ });
+});
--- /dev/null
+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);
+ }
+}
-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
+};
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>
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,
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';
@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')
isVersioningAlreadyEnabled = false;
isMfaDeleteAlreadyEnabled = false;
icons = Icons;
- kmsVaultConfig = false;
- s3VaultConfig = false;
+ kmsConfigured = false;
+ s3Configured = false;
tags: Record<string, string>[] = [];
dirtyTags = false;
tagConfig = [
private modalService: ModalService,
private rgwUserService: RgwUserService,
private notificationService: NotificationService,
- private rgwEncryptionModal: RgwBucketEncryptionModel,
private textAreaJsonFormatterService: TextAreaJsonFormatterService,
private textAreaXmlFormatterService: TextAreaXmlFormatterService,
public actionLabels: ActionLabelsI18n,
)
);
- 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('');
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;
--- /dev/null
+<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>
--- /dev/null
+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();
+ });
+});
--- /dev/null
+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;
+ }
+ }
+}
<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>
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}$/;
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() {
}
onSubmit() {
- const values = this.configForm.value;
+ const values = this.configForm.getRawValue();
this.rgwBucketService
.setEncryptionConfig(
values['encryptionType'],
--- /dev/null
+<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>
--- /dev/null
+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();
+ });
+});
--- /dev/null
+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);
+ }
+}
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: [
RgwSyncDataInfoComponent,
BucketTagModalComponent,
RgwMultisiteSyncPolicyComponent,
- RgwMultisiteSyncPolicyFormComponent
+ RgwMultisiteSyncPolicyFormComponent,
+ RgwConfigDetailsComponent,
+ RgwConfigurationPageComponent
],
providers: [TitleCasePipe]
})
data: { breadcrumbs: ActionLabels.EDIT }
}
]
+ },
+ {
+ path: 'configuration',
+ data: { breadcrumbs: 'Configuration' },
+ children: [{ path: '', component: RgwConfigurationPageComponent }]
}
];
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"
--- /dev/null
+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'
+}
import json
import logging
+from abc import ABC, abstractmethod
import rados
from mgr_module import CommandResult
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
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):
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,