rgw_client.set_bucket_versioning(bucket_name, versioning_state, mfa_delete,
mfa_token_serial, mfa_token_pin)
+ def _set_encryption(self, bid, encryption_type, key_id, daemon_name, owner):
+
+ rgw_client = RgwClient.instance(owner, daemon_name)
+ rgw_client.set_bucket_encryption(bid, key_id, encryption_type)
+
+ # pylint: disable=W0613
+ def _set_encryption_config(self, encryption_type, kms_provider, auth_method, secret_engine,
+ secret_path, namespace, address, token, daemon_name, owner,
+ ssl_cert, client_cert, client_key):
+
+ CephService.set_encryption_config(encryption_type, kms_provider, auth_method,
+ secret_engine, secret_path, namespace, address,
+ token, ssl_cert, client_cert, client_key)
+
+ def _get_encryption(self, bucket_name, daemon_name, owner):
+ rgw_client = RgwClient.instance(owner, daemon_name)
+ return rgw_client.get_bucket_encryption(bucket_name)
+
+ def _delete_encryption(self, bucket_name, daemon_name, owner):
+ rgw_client = RgwClient.instance(owner, daemon_name)
+ return rgw_client.delete_bucket_encryption(bucket_name)
+
def _get_locking(self, owner, daemon_name, bucket_name):
rgw_client = RgwClient.instance(owner, daemon_name)
return rgw_client.get_bucket_locking(bucket_name)
# Append the versioning configuration.
versioning = self._get_versioning(result['owner'], daemon_name, bucket_name)
+ encryption = self._get_encryption(bucket_name, daemon_name, result['owner'])
+ result['encryption'] = encryption['Status']
result['versioning'] = versioning['Status']
result['mfa_delete'] = versioning['MfaDelete']
def create(self, bucket, uid, zonegroup=None, placement_target=None,
lock_enabled='false', lock_mode=None,
lock_retention_period_days=None,
- lock_retention_period_years=None, daemon_name=None):
+ lock_retention_period_years=None, encryption_state='false',
+ encryption_type=None, key_id=None, daemon_name=None):
lock_enabled = str_to_bool(lock_enabled)
+ encryption_state = str_to_bool(encryption_state)
try:
rgw_client = RgwClient.instance(uid, daemon_name)
result = rgw_client.create_bucket(bucket, zonegroup,
self._set_locking(uid, daemon_name, bucket, lock_mode,
lock_retention_period_days,
lock_retention_period_years)
+
+ if encryption_state:
+ self._set_encryption(bucket, encryption_type, key_id, daemon_name, uid)
+
return result
except RequestException as e: # pragma: no cover - handling is too obvious
raise DashboardException(e, http_status_code=500, component='rgw')
@allow_empty_body
def set(self, bucket, bucket_id, uid, versioning_state=None,
+ encryption_state='false', encryption_type=None, key_id=None,
mfa_delete=None, mfa_token_serial=None, mfa_token_pin=None,
lock_mode=None, lock_retention_period_days=None,
lock_retention_period_years=None, daemon_name=None):
+ encryption_state = str_to_bool(encryption_state)
# When linking a non-tenant-user owned bucket to a tenanted user, we
# need to prefix bucket name with '/'. e.g. photos -> /photos
if '$' in uid and '/' not in bucket:
lock_retention_period_days,
lock_retention_period_years)
+ encryption_status = self._get_encryption(bucket_name, daemon_name, uid)
+ if encryption_state and encryption_status['Status'] != 'Enabled':
+ self._set_encryption(bucket_name, encryption_type, key_id, daemon_name, uid)
+ if encryption_status['Status'] == 'Enabled' and (not encryption_state):
+ self._delete_encryption(bucket_name, daemon_name, uid)
return self._append_bid(result)
def delete(self, bucket, purge_objects='true', daemon_name=None):
'purge-objects': purge_objects
}, json_response=False)
+ @RESTController.Collection(method='PUT', path='/setEncryptionConfig')
+ @allow_empty_body
+ def set_encryption_config(self, encryption_type=None, kms_provider=None, auth_method=None,
+ secret_engine=None, secret_path='', namespace='', address=None,
+ token=None, daemon_name=None, owner=None, ssl_cert=None,
+ client_cert=None, client_key=None):
+ return self._set_encryption_config(encryption_type, kms_provider, auth_method,
+ secret_engine, secret_path, namespace,
+ address, token, daemon_name, owner, ssl_cert,
+ client_cert, client_key)
+
+ @RESTController.Collection(method='GET', path='/getEncryption')
+ @allow_empty_body
+ def get_encryption(self, bucket_name, daemon_name=None, owner=None):
+ return self._get_encryption(bucket_name, daemon_name, owner)
+
+ @RESTController.Collection(method='DELETE', path='/deleteEncryption')
+ @allow_empty_body
+ def delete_encryption(self, bucket_name, daemon_name=None, owner=None):
+ return self._delete_encryption(bucket_name, daemon_name, owner)
+
+ @RESTController.Collection(method='GET', path='/getEncryptionConfig')
+ @allow_empty_body
+ def get_encryption_config(self):
+ return CephService.get_encryption_config()
+
@APIRouter('/rgw/user', Scope.RGW)
@APIDoc("RGW User Management API", "RgwUser")
--- /dev/null
+export class RgwBucketEncryptionModel {
+ kmsProviders = ['vault'];
+ authMethods = ['token', 'agent'];
+ secretEngines = ['kv', 'transit'];
+ sse_s3 = 'AES256';
+ sse_kms = 'aws:kms';
+}
class="bold">Versioning</td>
<td>{{ selection.versioning }}</td>
</tr>
+ <tr>
+ <td i18n
+ class="bold">Encryption</td>
+ <td>{{ selection.encryption }}</td>
+ </tr>
<tr>
<td i18n
class="bold">MFA Delete</td>
</div>
</fieldset>
+ <fieldset>
+ <legend class="cd-header"
+ i18n>Security</legend>
+ <div class="form-group row">
+ <div class="cd-col-form-offset">
+ <div class="custom-control custom-checkbox">
+ <input class="custom-control-input"
+ id="encryption_enabled"
+ name="encryption_enabled"
+ formControlName="encryption_enabled"
+ type="checkbox"
+ [attr.disabled]="!kmsVaultConfig && !s3VaultConfig ? true : null">
+ <label class="custom-control-label"
+ for="encryption_enabled"
+ i18n>Encryption</label>
+ <cd-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()">Click here</a></span>
+ </cd-helper>
+ </div>
+ </div>
+ </div>
+
+ <div *ngIf="bucketForm.getValue('encryption_enabled')">
+ <div class="form-group row">
+ <div class="cd-col-form-offset">
+ <div class="custom-control custom-radio custom-control-inline pl-5">
+ <input class="custom-control-input"
+ formControlName="encryption_type"
+ id="sse_S3_enabled"
+ type="radio"
+ name="encryption_type"
+ value="AES256"
+ [attr.disabled]="!s3VaultConfig ? true : null">
+ <label class="custom-control-label"
+ for="sse_S3_enabled"
+ i18n>SSE-S3 Encryption</label>
+ </div>
+ </div>
+ </div>
+
+ <div class="form-group row">
+ <div class="cd-col-form-offset">
+ <div class="custom-control custom-radio custom-control-inline pl-5">
+ <input class="custom-control-input"
+ formControlName="encryption_type"
+ id="kms_enabled"
+ name="encryption_type"
+ value="aws:kms"
+ [attr.disabled]="!kmsVaultConfig ? true : null"
+ type="radio">
+ <label class="custom-control-label"
+ for="kms_enabled"
+ i18n>Connect to an external key management service</label>
+ </div>
+ </div>
+ </div>
+
+ <div *ngIf="bucketForm.getValue('encryption_type') == 'aws:kms'">
+ <div class="form-group row">
+ <label class="cd-col-form-label required"
+ for="kms_provider"
+ i18n>KMS Provider</label>
+ <div class="cd-col-form-input">
+ <select id="kms_provider"
+ name="kms_provider"
+ class="form-control"
+ formControlName="kms_provider"
+ [autofocus]="editing">
+ <option i18n
+ *ngIf="kmsProviders !== null"
+ [ngValue]="null">-- Select a provider --</option>
+ <option *ngFor="let provider of kmsProviders"
+ [value]="provider">{{ provider }}</option>
+ </select>
+ <span class="invalid-feedback"
+ *ngIf="bucketForm.showError('kms_provider', frm, 'required')"
+ i18n>This field is required.</span>
+ </div>
+ </div>
+ </div>
+
+ <div *ngIf="bucketForm.getValue('encryption_type') == 'aws:kms'">
+ <div class="form-group row">
+ <label class="cd-col-form-label required"
+ for="keyId"
+ i18n>Key Id
+ </label>
+ <div class="cd-col-form-input">
+ <input id="keyId"
+ name="keyId"
+ class="form-control"
+ type="text"
+ formControlName="keyId">
+ <span class="invalid-feedback"
+ *ngIf="bucketForm.showError('keyId', frm, 'required')"
+ i18n>This field is required.</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </fieldset>
+
</div>
<div class="card-footer">
<cd-form-button-panel (submitActionEvent)="submit()"
-import { Component, OnInit } from '@angular/core';
-import { Validators } from '@angular/forms';
+import { AfterViewChecked, ChangeDetectorRef, Component, OnInit } from '@angular/core';
+import { AbstractControl, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import _ from 'lodash';
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 { ModalService } from '~/app/shared/services/modal.service';
import { NotificationService } from '~/app/shared/services/notification.service';
+import { RgwBucketEncryptionModel } from '../models/rgw-bucket-encryption';
import { RgwBucketMfaDelete } from '../models/rgw-bucket-mfa-delete';
import { RgwBucketVersioning } from '../models/rgw-bucket-versioning';
+import { RgwConfigModalComponent } from '../rgw-config-modal/rgw-config-modal.component';
@Component({
selector: 'cd-rgw-bucket-form',
templateUrl: './rgw-bucket-form.component.html',
- styleUrls: ['./rgw-bucket-form.component.scss']
+ styleUrls: ['./rgw-bucket-form.component.scss'],
+ providers: [RgwBucketEncryptionModel]
})
-export class RgwBucketFormComponent extends CdForm implements OnInit {
+export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewChecked {
bucketForm: CdFormGroup;
editing = false;
owners: string[] = null;
+ kmsProviders: string[] = null;
action: string;
resource: string;
zonegroup: string;
isVersioningAlreadyEnabled = false;
isMfaDeleteAlreadyEnabled = false;
icons = Icons;
+ kmsVaultConfig = false;
+ s3VaultConfig = false;
get isVersioningEnabled(): boolean {
return this.bucketForm.getValue('versioning');
private formBuilder: CdFormBuilder,
private rgwBucketService: RgwBucketService,
private rgwSiteService: RgwSiteService,
+ private modalService: ModalService,
private rgwUserService: RgwUserService,
private notificationService: NotificationService,
- public actionLabels: ActionLabelsI18n
+ private rgwEncryptionModal: RgwBucketEncryptionModel,
+ public actionLabels: ActionLabelsI18n,
+ private readonly changeDetectorRef: ChangeDetectorRef
) {
super();
this.editing = this.router.url.startsWith(`/rgw/bucket/${URLVerbs.EDIT}`);
this.createForm();
}
+ ngAfterViewChecked(): void {
+ this.changeDetectorRef.detectChanges();
+ }
+
createForm() {
const self = this;
const lockDaysValidator = CdValidators.custom('lockDays', () => {
: [CdValidators.bucketName(), CdValidators.bucketExistence(false, this.rgwBucketService)]
],
owner: [null, [Validators.required]],
+ kms_provider: ['vault'],
'placement-target': [null, this.editing ? [] : [Validators.required]],
versioning: [null],
'mfa-delete': [null],
'mfa-token-serial': [''],
'mfa-token-pin': [''],
lock_enabled: [{ value: false, disabled: this.editing }],
+ encryption_enabled: [null],
+ encryption_type: [
+ null,
+ [
+ CdValidators.requiredIf({
+ encryption_enabled: true
+ })
+ ]
+ ],
+ keyId: [
+ null,
+ [
+ CdValidators.requiredIf({
+ encryption_type: 'aws:kms',
+ encryption_enabled: true
+ })
+ ]
+ ],
lock_mode: ['COMPLIANCE'],
lock_retention_period_days: [0, [CdValidators.number(false), lockDaysValidator]]
});
owners: this.rgwUserService.enumerate()
};
+ this.kmsProviders = this.rgwEncryptionModal.kmsProviders;
+ this.rgwBucketService.getEncryptionConfig().subscribe((data) => {
+ this.kmsVaultConfig = data[0];
+ this.s3VaultConfig = data[1];
+ if (this.kmsVaultConfig && this.s3VaultConfig) {
+ this.bucketForm.get('encryption_type').setValue('');
+ } else if (this.kmsVaultConfig) {
+ this.bucketForm.get('encryption_type').setValue('aws:kms');
+ } else if (this.s3VaultConfig) {
+ this.bucketForm.get('encryption_type').setValue('AES256');
+ } else {
+ this.bucketForm.get('encryption_type').setValue('');
+ }
+ });
+
if (!this.editing) {
promises['getPlacementTargets'] = this.rgwSiteService.get('placement-targets');
}
// the Angular react framework will throw an error if there is no
// field for a given key.
let value: object = _.pick(bidResp, _.keys(defaults));
+
value['lock_retention_period_days'] = this.rgwBucketService.getLockDays(bidResp);
value['placement-target'] = bidResp['placement_rule'];
value['versioning'] = bidResp['versioning'] === RgwBucketVersioning.ENABLED;
value['mfa-delete'] = bidResp['mfa_delete'] === RgwBucketMfaDelete.ENABLED;
-
+ value['encryption_enabled'] = bidResp['encryption'] === 'Enabled';
// Append default values.
value = _.merge(defaults, value);
-
// Update the form.
this.bucketForm.setValue(value);
if (this.editing) {
}
}
}
-
this.loadingReady();
});
});
submit() {
// Exit immediately if the form isn't dirty.
+ if (this.bucketForm.getValue('encryption_enabled') == null) {
+ this.bucketForm.get('encryption_enabled').setValue(false);
+ this.bucketForm.get('encryption_type').setValue(null);
+ }
if (this.bucketForm.pristine) {
this.goToListView();
return;
values['id'],
values['owner'],
versioning,
+ values['encryption_enabled'],
+ values['encryption_type'],
+ values['keyId'],
mfaDelete,
values['mfa-token-serial'],
values['mfa-token-pin'],
values['placement-target'],
values['lock_enabled'],
values['lock_mode'],
- values['lock_retention_period_days']
+ values['lock_retention_period_days'],
+ values['encryption_enabled'],
+ values['encryption_type'],
+ values['keyId']
)
.subscribe(
() => {
getMfaDeleteStatus() {
return this.isMfaDeleteEnabled ? RgwBucketMfaDelete.ENABLED : RgwBucketMfaDelete.DISABLED;
}
+
+ fileUpload(files: FileList, controlName: string) {
+ const file: File = files[0];
+ const reader = new FileReader();
+ reader.addEventListener('load', () => {
+ const control: AbstractControl = this.bucketForm.get(controlName);
+ control.setValue(file);
+ control.markAsDirty();
+ control.markAsTouched();
+ control.updateValueAndValidity();
+ });
+ }
+
+ openConfigModal() {
+ const modalRef = this.modalService.show(RgwConfigModalComponent);
+ modalRef.componentInstance.configForm
+ .get('encryptionType')
+ .setValue(this.bucketForm.getValue('encryption_type'));
+ }
}
--- /dev/null
+<cd-modal [modalRef]="activeModal">
+ <ng-container i18n="form title"
+ class="modal-title">Update RGW Encryption Configurations</ng-container>
+
+ <ng-container class="modal-content">
+ <form name="configForm"
+ #frm="ngForm"
+ [formGroup]="configForm">
+ <div class="modal-body">
+ <label class="cd-col-form-label">Encryption Type:</label>
+ <div class="custom-control custom-radio custom-control-inline">
+ <input class="custom-control-input"
+ formControlName="encryptionType"
+ id="s3Enabled"
+ type="radio"
+ name="encryptionType"
+ value="AES256">
+ <label class="custom-control-label"
+ for="s3Enabled"
+ i18n>SSE-S3 Encryption</label>
+ </div>
+
+ <div class="custom-control custom-radio custom-control-inline">
+ <input class="custom-control-input"
+ formControlName="encryptionType"
+ id="kmsEnabled"
+ name="encryptionType"
+ value="aws:kms"
+ type="radio">
+ <label class="custom-control-label"
+ for="kmsEnabled"
+ i18n>SSE-KMS Encryption</label>
+ </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="kms_provider"
+ i18n>Key management service provider</label>
+ <div class="cd-col-form-input">
+ <select id="kms_provider"
+ name="kms_provider"
+ class="form-control"
+ formControlName="kms_provider">
+ <option i18n
+ *ngIf="kmsProviders !== null"
+ [ngValue]="null">-- Select a provider --</option>
+ <option *ngFor="let provider of kmsProviders"
+ [value]="provider">{{ provider }}</option>
+ </select>
+ <span class="invalid-feedback"
+ *ngIf="configForm.showError('kms_provider', 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 required"
+ for="auth_method"
+ i18n>Authentication Method</label>
+ <div class="cd-col-form-input">
+ <select id="auth_method"
+ name="auth_method"
+ class="form-control"
+ formControlName="auth_method">
+ <option i18n
+ *ngIf="authMethods !== null"
+ [ngValue]="null">-- Select a method --</option>
+ <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 *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-control"
+ formControlName="secret_engine">
+ <option i18n
+ *ngIf="secretEngines !== null"
+ [ngValue]="null">-- Select a method --</option>
+ <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 *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 *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 *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">
+ <span class="invalid-feedback"
+ *ngIf="configForm.showError('address', frm, 'required')"
+ i18n>This field is required.</span>
+ </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>
+ </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('auth_method') == 'agent'">
+ <div class="form-group row">
+ <label class="cd-col-form-label required"
+ for="role"
+ i18n>Role
+ </label>
+ <div class="cd-col-form-input">
+ <input id="role"
+ name="role"
+ class="form-control"
+ formControlName="role">
+ <span class="invalid-feedback"
+ *ngIf="configForm.showError('role', 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="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 *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>
+ <div class="modal-footer">
+ <cd-form-button-panel (submitActionEvent)="onSubmit()"
+ [submitText]="actionLabels.SUBMIT"
+ [form]="configForm"></cd-form-button-panel>
+ </div>
+ </form>
+ </ng-container>
+</cd-modal>
--- /dev/null
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { RouterTestingModule } from '@angular/router/testing';
+
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { ToastrModule } from 'ngx-toastr';
+
+import { SharedModule } from '~/app/shared/shared.module';
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { RgwConfigModalComponent } from './rgw-config-modal.component';
+
+describe('RgwConfigModalComponent', () => {
+ let component: RgwConfigModalComponent;
+ let fixture: ComponentFixture<RgwConfigModalComponent>;
+
+ configureTestBed({
+ declarations: [RgwConfigModalComponent],
+ imports: [
+ SharedModule,
+ ReactiveFormsModule,
+ RouterTestingModule,
+ HttpClientTestingModule,
+ ToastrModule.forRoot()
+ ],
+ providers: [NgbActiveModal]
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(RgwConfigModalComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
--- /dev/null
+import { Component, EventEmitter, OnInit, Output } from '@angular/core';
+import { AbstractControl, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
+
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import _ from 'lodash';
+
+import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { NotificationType } from '~/app/shared/enum/notification-type.enum';
+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';
+
+@Component({
+ selector: 'cd-rgw-config-modal',
+ templateUrl: './rgw-config-modal.component.html',
+ styleUrls: ['./rgw-config-modal.component.scss'],
+ providers: [RgwBucketEncryptionModel]
+})
+export class RgwConfigModalComponent implements OnInit {
+ readonly vaultAddress = /^((https?:\/\/)|(www.))(?:([a-zA-Z]+)|(\d+\.\d+.\d+.\d+)):\d{4}$/;
+
+ kmsProviders: string[];
+
+ configForm: CdFormGroup;
+
+ @Output()
+ submitAction = new EventEmitter();
+ authMethods: string[];
+ secretEngines: 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;
+ }
+
+ createForm() {
+ this.configForm = this.formBuilder.group({
+ address: [
+ null,
+ [
+ Validators.required,
+ CdValidators.custom('vaultPattern', (value: string) => {
+ if (_.isEmpty(value)) {
+ return false;
+ }
+ return !this.vaultAddress.test(value);
+ })
+ ]
+ ],
+ kms_provider: ['vault', Validators.required],
+ encryptionType: [null, Validators.required],
+ auth_method: [null, Validators.required],
+ secret_engine: [null, Validators.required],
+ secret_path: ['/'],
+ namespace: [null],
+ token: [
+ null,
+ [
+ CdValidators.requiredIf({
+ auth_method: 'token'
+ })
+ ]
+ ],
+ ssl_cert: [null, CdValidators.sslCert()],
+ client_cert: [null, CdValidators.pemCert()],
+ client_key: [null, CdValidators.sslPrivKey()],
+ role: [
+ null,
+ [
+ CdValidators.requiredIf({
+ auth_method: 'agent'
+ })
+ ]
+ ],
+ kmsEnabled: [{ value: false }],
+ s3Enabled: [{ value: false }]
+ });
+ }
+
+ fileUpload(files: FileList, controlName: string) {
+ const file: File = files[0];
+ const reader = new FileReader();
+ reader.addEventListener('load', () => {
+ const control: AbstractControl = this.configForm.get(controlName);
+ control.setValue(file);
+ control.markAsDirty();
+ control.markAsTouched();
+ control.updateValueAndValidity();
+ });
+ }
+
+ onSubmit() {
+ const values = this.configForm.value;
+ this.rgwBucketService
+ .setEncryptionConfig(
+ values['encryptionType'],
+ values['kms_provider'],
+ values['auth_method'],
+ values['secret_engine'],
+ values['secret_path'],
+ values['namespace'],
+ values['address'],
+ values['token'],
+ values['owner'],
+ values['ssl_cert'],
+ values['client_cert'],
+ values['client_key']
+ )
+ .subscribe({
+ next: () => {
+ this.notificationService.show(
+ NotificationType.success,
+ $localize`Updated RGW Encryption Configuration values`
+ );
+ },
+ error: (error: any) => {
+ this.notificationService.show(NotificationType.error, error);
+ this.configForm.setErrors({ cdSubmitButton: true });
+ },
+ complete: () => {
+ this.activeModal.close();
+ this.router.routeReuseStrategy.shouldReuseRoute = () => false;
+ this.router.onSameUrlNavigation = 'reload';
+ this.router.navigate([this.router.url]);
+ }
+ });
+ }
+}
import { RgwBucketDetailsComponent } from './rgw-bucket-details/rgw-bucket-details.component';
import { RgwBucketFormComponent } from './rgw-bucket-form/rgw-bucket-form.component';
import { RgwBucketListComponent } from './rgw-bucket-list/rgw-bucket-list.component';
+import { RgwConfigModalComponent } from './rgw-config-modal/rgw-config-modal.component';
import { RgwDaemonDetailsComponent } from './rgw-daemon-details/rgw-daemon-details.component';
import { RgwDaemonListComponent } from './rgw-daemon-list/rgw-daemon-list.component';
import { RgwUserCapabilityModalComponent } from './rgw-user-capability-modal/rgw-user-capability-modal.component';
RgwUserSwiftKeyModalComponent,
RgwUserS3KeyModalComponent,
RgwUserCapabilityModalComponent,
- RgwUserSubuserModalComponent
+ RgwUserSubuserModalComponent,
+ RgwConfigModalComponent
]
})
export class RgwModule {}
it('should call create', () => {
service
- .create('foo', 'bar', 'default', 'default-placement', false, 'COMPLIANCE', '5')
+ .create(
+ 'foo',
+ 'bar',
+ 'default',
+ 'default-placement',
+ false,
+ 'COMPLIANCE',
+ '5',
+ true,
+ 'aws:kms',
+ 'qwerty1'
+ )
.subscribe();
const req = httpTesting.expectOne(
- `api/rgw/bucket?bucket=foo&uid=bar&zonegroup=default&placement_target=default-placement&lock_enabled=false&lock_mode=COMPLIANCE&lock_retention_period_days=5&${RgwHelper.DAEMON_QUERY_PARAM}`
+ `api/rgw/bucket?bucket=foo&uid=bar&zonegroup=default&placement_target=default-placement&lock_enabled=false&lock_mode=COMPLIANCE&lock_retention_period_days=5&encryption_state=true&encryption_type=aws%253Akms&key_id=qwerty1&${RgwHelper.DAEMON_QUERY_PARAM}`
);
expect(req.request.method).toBe('POST');
});
it('should call update', () => {
service
- .update('foo', 'bar', 'baz', 'Enabled', 'Enabled', '1', '223344', 'GOVERNANCE', '10')
+ .update(
+ 'foo',
+ 'bar',
+ 'baz',
+ 'Enabled',
+ true,
+ 'aws:kms',
+ 'qwerty1',
+ 'Enabled',
+ '1',
+ '223344',
+ 'GOVERNANCE',
+ '10'
+ )
.subscribe();
const req = httpTesting.expectOne(
- `api/rgw/bucket/foo?${RgwHelper.DAEMON_QUERY_PARAM}&bucket_id=bar&uid=baz&versioning_state=Enabled&mfa_delete=Enabled&mfa_token_serial=1&mfa_token_pin=223344&lock_mode=GOVERNANCE&lock_retention_period_days=10`
+ `api/rgw/bucket/foo?${RgwHelper.DAEMON_QUERY_PARAM}&bucket_id=bar&uid=baz&versioning_state=Enabled&encryption_state=true&encryption_type=aws%253Akms&key_id=qwerty1&mfa_delete=Enabled&mfa_token_serial=1&mfa_token_pin=223344&lock_mode=GOVERNANCE&lock_retention_period_days=10`
);
expect(req.request.method).toBe('PUT');
});
placementTarget: string,
lockEnabled: boolean,
lock_mode: 'GOVERNANCE' | 'COMPLIANCE',
- lock_retention_period_days: string
+ lock_retention_period_days: string,
+ encryption_state: boolean,
+ encryption_type: string,
+ key_id: string
) {
return this.rgwDaemonService.request((params: HttpParams) => {
return this.http.post(this.url, null, {
lock_enabled: String(lockEnabled),
lock_mode,
lock_retention_period_days,
+ encryption_state: String(encryption_state),
+ encryption_type,
+ key_id,
daemon_name: params.get('daemon_name')
}
})
bucketId: string,
uid: string,
versioningState: string,
+ encryptionState: boolean,
+ encryptionType: string,
+ keyId: string,
mfaDelete: string,
mfaTokenSerial: string,
mfaTokenPin: string,
lockRetentionPeriodDays: string
) {
return this.rgwDaemonService.request((params: HttpParams) => {
- params = params.append('bucket_id', bucketId);
- params = params.append('uid', uid);
- params = params.append('versioning_state', versioningState);
- params = params.append('mfa_delete', mfaDelete);
- params = params.append('mfa_token_serial', mfaTokenSerial);
- params = params.append('mfa_token_pin', mfaTokenPin);
- params = params.append('lock_mode', lockMode);
- params = params.append('lock_retention_period_days', lockRetentionPeriodDays);
+ params = params.appendAll({
+ bucket_id: bucketId,
+ uid: uid,
+ versioning_state: versioningState,
+ encryption_state: String(encryptionState),
+ encryption_type: encryptionType,
+ key_id: keyId,
+ mfa_delete: mfaDelete,
+ mfa_token_serial: mfaTokenSerial,
+ mfa_token_pin: mfaTokenPin,
+ lock_mode: lockMode,
+ lock_retention_period_days: lockRetentionPeriodDays
+ });
return this.http.put(`${this.url}/${bucket}`, null, { params: params });
});
}
return bucketData['lock_retention_period_days'] || 0;
}
+
+ setEncryptionConfig(
+ encryption_type: string,
+ kms_provider: string,
+ auth_method: string,
+ secret_engine: string,
+ secret_path: string,
+ namespace: string,
+ address: string,
+ token: string,
+ owner: string,
+ ssl_cert: string,
+ client_cert: string,
+ client_key: string
+ ) {
+ return this.rgwDaemonService.request((params: HttpParams) => {
+ params = params.appendAll({
+ encryption_type: encryption_type,
+ kms_provider: kms_provider,
+ auth_method: auth_method,
+ secret_engine: secret_engine,
+ secret_path: secret_path,
+ namespace: namespace,
+ address: address,
+ token: token,
+ owner: owner,
+ ssl_cert: ssl_cert,
+ client_cert: client_cert,
+ client_key: client_key
+ });
+ return this.http.put(`${this.url}/setEncryptionConfig`, null, { params: params });
+ });
+ }
+
+ getEncryption(bucket: string) {
+ return this.rgwDaemonService.request((params: HttpParams) => {
+ return this.http.get(`${this.url}/${bucket}/getEncryption`, { params: params });
+ });
+ }
+
+ deleteEncryption(bucket: string) {
+ return this.rgwDaemonService.request((params: HttpParams) => {
+ return this.http.get(`${this.url}/${bucket}/deleteEncryption`, { params: params });
+ });
+ }
+
+ getEncryptionConfig() {
+ return this.rgwDaemonService.request(() => {
+ return this.http.get(`${this.url}/getEncryptionConfig`);
+ });
+ }
}
type: string
daemon_name:
type: string
+ encryption_state:
+ default: 'false'
+ type: string
+ encryption_type:
+ type: string
+ key_id:
+ type: string
lock_enabled:
default: 'false'
type: string
- jwt: []
tags:
- RgwBucket
+ /api/rgw/bucket/deleteEncryption:
+ delete:
+ parameters:
+ - in: query
+ name: bucket_name
+ required: true
+ schema:
+ type: string
+ - allowEmptyValue: true
+ in: query
+ name: daemon_name
+ schema:
+ type: string
+ - allowEmptyValue: true
+ in: query
+ name: owner
+ schema:
+ type: string
+ responses:
+ '202':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Operation is still executing. Please check the task queue.
+ '204':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Resource deleted.
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - RgwBucket
+ /api/rgw/bucket/getEncryption:
+ get:
+ parameters:
+ - in: query
+ name: bucket_name
+ required: true
+ schema:
+ type: string
+ - allowEmptyValue: true
+ in: query
+ name: daemon_name
+ schema:
+ type: string
+ - allowEmptyValue: true
+ in: query
+ name: owner
+ schema:
+ type: string
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: OK
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - RgwBucket
+ /api/rgw/bucket/getEncryptionConfig:
+ get:
+ parameters: []
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: OK
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - RgwBucket
+ /api/rgw/bucket/setEncryptionConfig:
+ put:
+ parameters: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ address:
+ type: string
+ auth_method:
+ type: string
+ client_cert:
+ type: string
+ client_key:
+ type: string
+ daemon_name:
+ type: string
+ encryption_type:
+ type: string
+ kms_provider:
+ type: string
+ namespace:
+ default: ''
+ type: string
+ owner:
+ type: string
+ secret_engine:
+ type: string
+ secret_path:
+ default: ''
+ type: string
+ ssl_cert:
+ type: string
+ token:
+ type: string
+ type: object
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Resource updated.
+ '202':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Operation is still executing. Please check the task queue.
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - RgwBucket
/api/rgw/bucket/{bucket}:
delete:
parameters:
type: string
daemon_name:
type: string
+ encryption_state:
+ default: 'false'
+ type: string
+ encryption_type:
+ type: string
+ key_id:
+ type: string
lock_mode:
type: string
lock_retention_period_days:
import rados
from mgr_module import CommandResult
-from mgr_util import get_most_recent_rate, get_time_series_rates
+from mgr_util import get_most_recent_rate, get_time_series_rates, name_to_config_section
from .. import mgr
return pool
return None
+ @classmethod
+ def get_encryption_config(cls):
+ kms_vault_configured = False
+ s3_vault_configured = False
+ kms_backend: str = ''
+ sse_s3_backend: str = ''
+ vault_stats = []
+
+ kms_backend = CephService.send_command('mon', 'config get',
+ who=name_to_config_section('rgw'),
+ key='rgw_crypt_s3_kms_backend')
+ sse_s3_backend = CephService.send_command('mon', 'config get',
+ who=name_to_config_section('rgw'),
+ 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('rgw'),
+ key='rgw_crypt_vault_auth')
+ kms_vault_engine: str = CephService.send_command('mon', 'config get',
+ who=name_to_config_section('rgw'),
+ key='rgw_crypt_vault_secret_engine')
+ kms_vault_address: str = CephService.send_command('mon', 'config get',
+ who=name_to_config_section('rgw'),
+ key='rgw_crypt_vault_addr')
+ kms_vault_token: str = CephService.send_command('mon', 'config get',
+ who=name_to_config_section('rgw'),
+ key='rgw_crypt_vault_token_file')
+ if (
+ kms_vault_auth.strip() != ""
+ and kms_vault_engine.strip() != ""
+ and kms_vault_address.strip() != ""
+ and kms_vault_token.strip() != ""
+ ):
+ 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('rgw'),
+ key='rgw_crypt_sse_s3_vault_auth')
+ s3_vault_engine: str = CephService.send_command('mon',
+ 'config get',
+ who=name_to_config_section('rgw'),
+ 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('rgw'),
+ key='rgw_crypt_sse_s3_vault_addr')
+ s3_vault_token: str = CephService.send_command('mon', 'config get',
+ who=name_to_config_section('rgw'),
+ key='rgw_crypt_sse_s3_vault_token_file')
+ if (
+ s3_vault_auth.strip() != ""
+ and s3_vault_engine.strip() != ""
+ and s3_vault_address.strip() != ""
+ and s3_vault_token.strip() != ""
+ ):
+ s3_vault_configured = True
+
+ vault_stats.append(kms_vault_configured)
+ vault_stats.append(s3_vault_configured)
+ return vault_stats
+
+ @classmethod
+ def set_encryption_config(cls, encryption_type, kms_provider, auth_method,
+ secret_engine, secret_path, namespace, address,
+ token, ssl_cert, client_cert, client_key):
+
+ if encryption_type == 'aws:kms':
+
+ KMS_CONFIG = [
+ ['rgw_crypt_s3_kms_backend', kms_provider],
+ ['rgw_crypt_vault_auth', auth_method],
+ ['rgw_crypt_vault_prefix', secret_path],
+ ['rgw_crypt_vault_namespace', namespace],
+ ['rgw_crypt_vault_secret_engine', secret_engine],
+ ['rgw_crypt_vault_addr', address],
+ ['rgw_crypt_vault_token_file', token],
+ ['rgw_crypt_vault_ssl_cacert', ssl_cert],
+ ['rgw_crypt_vault_ssl_clientcert', client_cert],
+ ['rgw_crypt_vault_ssl_clientkey', client_key]
+ ]
+
+ for (key, value) in KMS_CONFIG:
+ CephService.send_command('mon', 'config set', who=name_to_config_section('rgw'),
+ name=key, value=value)
+
+ if encryption_type == 'AES256':
+
+ SSE_S3_CONFIG = [
+ ['rgw_crypt_sse_s3_backend', kms_provider],
+ ['rgw_crypt_sse_s3_vault_auth', auth_method],
+ ['rgw_crypt_sse_s3_vault_prefix', secret_path],
+ ['rgw_crypt_sse_s3_vault_namespace', namespace],
+ ['rgw_crypt_sse_s3_vault_secret_engine', secret_engine],
+ ['rgw_crypt_sse_s3_vault_addr', address],
+ ['rgw_crypt_sse_s3_vault_token_file', token],
+ ['rgw_crypt_sse_s3_vault_ssl_cacert', ssl_cert],
+ ['rgw_crypt_sse_s3_vault_ssl_clientcert', client_cert],
+ ['rgw_crypt_sse_s3_vault_ssl_clientkey', client_key]
+ ]
+
+ for (key, value) in SSE_S3_CONFIG:
+ CephService.send_command('mon', 'config set', who=name_to_config_section('rgw'),
+ name=key, value=value)
+
+ return {}
+
@classmethod
def get_pool_pg_status(cls, pool_name):
# type: (str) -> dict
raise NoCredentialsException
+# pylint: disable=R0904
class RgwClient(RestClient):
_host = None
_port = None
http_status_code=error.status_code,
component='rgw')
+ @RestClient.api_get('/{bucket_name}?encryption')
+ def get_bucket_encryption(self, bucket_name, request=None):
+ # pylint: disable=unused-argument
+ try:
+ result = request() # type: ignore
+ result['Status'] = 'Enabled'
+ return result
+ except RequestException as e:
+ if e.content:
+ content = json_str_to_object(e.content)
+ if content.get(
+ 'Code') == 'ServerSideEncryptionConfigurationNotFoundError':
+ return {
+ 'Status': 'Disabled',
+ }
+ raise e
+
+ @RestClient.api_delete('/{bucket_name}?encryption')
+ def delete_bucket_encryption(self, bucket_name, request=None):
+ # pylint: disable=unused-argument
+ result = request() # type: ignore
+ return result
+
+ @RestClient.api_put('/{bucket_name}?encryption')
+ def set_bucket_encryption(self, bucket_name, key_id,
+ sse_algorithm, request: Optional[object] = None):
+ # pylint: disable=unused-argument
+ encryption_configuration = ET.Element('ServerSideEncryptionConfiguration')
+ rule_element = ET.SubElement(encryption_configuration, 'Rule')
+ default_encryption_element = ET.SubElement(rule_element,
+ 'ApplyServerSideEncryptionByDefault')
+ sse_algo_element = ET.SubElement(default_encryption_element,
+ 'SSEAlgorithm')
+ sse_algo_element.text = sse_algorithm
+ if sse_algorithm == 'aws:kms':
+ kms_master_key_element = ET.SubElement(default_encryption_element,
+ 'KMSMasterKeyID')
+ kms_master_key_element.text = key_id
+ data = ET.tostring(encryption_configuration, encoding='unicode')
+ try:
+ _ = request(data=data) # type: ignore
+ except RequestException as e:
+ raise DashboardException(msg=str(e), component='rgw')
+
@RestClient.api_get('/{bucket_name}?object-lock')
def get_bucket_locking(self, bucket_name, request=None):
# type: (str, Optional[object]) -> dict
import logging
import sys
from threading import Lock, Condition, Event
-from typing import no_type_check
+from typing import no_type_check, NewType
import urllib
from functools import wraps
if sys.version_info >= (3, 3):
if TYPE_CHECKING:
from mgr_module import MgrModule
+ConfEntity = NewType('ConfEntity', str)
+
Module_T = TypeVar('Module_T', bound="MgrModule")
(
return [(data2[0], _derivative(data1, data2) if data1 is not None else 0.0) for data1, data2 in
_pairwise(data)]
+def name_to_config_section(name: str) -> ConfEntity:
+ """
+ Map from daemon names to ceph entity names (as seen in config)
+ """
+ daemon_type = name.split('.', 1)[0]
+ if daemon_type in ['rgw', 'rbd-mirror', 'nfs', 'crash', 'iscsi']:
+ return ConfEntity('client.' + name)
+ elif daemon_type in ['mon', 'osd', 'mds', 'mgr', 'client']:
+ return ConfEntity(name)
+ else:
+ return ConfEntity('mon')
+
def _filter_time_series(data: List[Tuple[float, float]]) -> List[Tuple[float, float]]:
""" Filters time series data