Skip to content

Commit

Permalink
[yugabyte#6540] Platform: Remove certificate feature. (yugabyte#6885)
Browse files Browse the repository at this point in the history
* certificate delete api

* added one field to response

* added removable

* [6540] Added ability to delete certificates

* added removable field

* removable added

* removable added to list api

* [6540]Added ability to remove certificate

* added validation for remove certificates.

* review comment fixes

* replaced removable props with inUse as recommended

* fixed disabled scenario of remove cert button

* review comment fixes

* Addressed review comments

* modified test cases

* Code improvements

* modified FE code as recommended

* removed local state declaration

* Review comment fixes

* review comment fixes

* review comment fixes

* review comment fixes

* review comment fixes

* certificate delete api

* added one field to response

* added removable

* [6540] Added ability to delete certificates

* added removable field

* removable added

* [6540]Added ability to remove certificate

* removable added to list api

* added validation for remove certificates.

* review comment fixes

* replaced removable props with inUse as recommended

* fixed disabled scenario of remove cert button

* review comment fixes

* Addressed review comments

* modified test cases

* Code improvements

* modified FE code as recommended

* removed local state declaration

* Review comment fixes

* review comment fixes

* review comment fixes

* review comment fixes

* review comment fixes

* review comment fixes

* Review comment fixes

* Review comment fixes

Co-authored-by: mahendra bhat <[email protected]>
Co-authored-by: gaurav061 <[email protected]>
  • Loading branch information
3 people authored and Alex Ball committed Mar 9, 2021
1 parent 911a90f commit 9987226
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import play.mvc.Result;
import play.data.Form;
import play.data.FormFactory;
Expand Down Expand Up @@ -145,9 +146,30 @@ public Result get(UUID customerUUID, String label) {
}
}

public Result delete(UUID customerUUID, UUID reqCertUUID) {
CertificateInfo certificate = CertificateInfo.get(reqCertUUID);
if (certificate == null) {
return ApiResponse.error(BAD_REQUEST, "Invalid certificate.");
}
if (!certificate.customerUUID.equals(customerUUID)) {
return ApiResponse.error(BAD_REQUEST, "Certificate doesn't belong to customer");
}
if (!certificate.getInUse()) {
if (certificate.delete()) {
Audit.createAuditEntry(ctx(), request());
LOG.info("Successfully deleted the certificate:" + reqCertUUID);
return ApiResponse.success();
} else {
return ApiResponse.error(INTERNAL_SERVER_ERROR, "Unable to delete the Certificate");
}
} else {
return ApiResponse.error(BAD_REQUEST, "The certificate is in use.");
}
}

public Result updateEmptyCustomCert(UUID customerUUID, UUID rootCA) {
Form<CertificateParams> formData = formFactory.form(CertificateParams.class)
.bindFromRequest();
.bindFromRequest();
if (formData.hasErrors()) {
return ApiResponse.error(BAD_REQUEST, formData.errorsAsJson());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,9 @@ public static boolean isCertificateValid(UUID certUUID) {
}
return true;
}

// Returns if there is an in use reference to the object.
public boolean getInUse() {
return Universe.existsCertificate(this.uuid, this.customerUUID);
}
}
9 changes: 9 additions & 0 deletions managed/src/main/java/com/yugabyte/yw/models/Universe.java
Original file line number Diff line number Diff line change
Expand Up @@ -828,4 +828,13 @@ public void run(Universe universe) {}
};
Universe.saveDetails(universeUUID, updater);
}

public static boolean existsCertificate(UUID certUUID, UUID customerUUID) {
Set<Universe> universeList = Customer.get(customerUUID).getUniverses();
universeList = universeList.stream()
.filter(s -> s.getUniverseDetails().rootCA != null
&& s.getUniverseDetails().rootCA.equals(certUUID))
.collect(Collectors.toSet());
return universeList.size() != 0;
}
}
2 changes: 2 additions & 0 deletions managed/src/main/resources/v1.routes
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,13 @@ PUT /customers/:cUUID/releases/:name c
# Certificate API
POST /customers/:cUUID/certificates com.yugabyte.yw.controllers.CertificateController.upload(cUUID: java.util.UUID)
GET /customers/:cUUID/certificates com.yugabyte.yw.controllers.CertificateController.list(cUUID: java.util.UUID)
DELETE /customers/:cUUID/certificates/:rUUID com.yugabyte.yw.controllers.CertificateController.delete(cUUID: java.util.UUID, rUUID: java.util.UUID)
GET /customers/:cUUID/certificates/:name com.yugabyte.yw.controllers.CertificateController.get(cUUID: java.util.UUID, name: String)
GET /customers/:cUUID/certificates/:rUUID/download com.yugabyte.yw.controllers.CertificateController.getRootCert(cUUID: java.util.UUID, rUUID: java.util.UUID)
POST /customers/:cUUID/certificates/:rUUID com.yugabyte.yw.controllers.CertificateController.getClientCert(cUUID: java.util.UUID, rUUID: java.util.UUID)
POST /customers/:cUUID/certificates/:rUUID/update_empty_cert com.yugabyte.yw.controllers.CertificateController.updateEmptyCustomCert(cUUID: java.util.UUID, rUUID: java.util.UUID)


# Alerts API
GET /customers/:cUUID/alerts com.yugabyte.yw.controllers.AlertController.list(cUUID: java.util.UUID)
PUT /customers/:cUUID/alerts com.yugabyte.yw.controllers.AlertController.upsert(cUUID: java.util.UUID)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ private Result getCertificate(UUID customerUUID, String label) {
String uri = "/api/customers/" + customerUUID + "/certificates/" + label;
return FakeApiHelper.doRequestWithAuthToken("GET", uri, user.createAuthToken());
}

private Result deleteCertificate(UUID customerUUID, UUID certUUID) {
String uri = "/api/customers/" + customerUUID + "/certificates/" + certUUID.toString();
return FakeApiHelper.doRequestWithAuthToken("DELETE", uri, user.createAuthToken());
}

private Result createClientCertificate(UUID customerUUID, UUID rootUUID,
ObjectNode bodyJson) {
Expand All @@ -120,12 +125,29 @@ public void testListCertificates() {
result_uuids.add(UUID.fromString(e.get("uuid").toString()));
result_labels.add(e.get("label").toString());
assertEquals(e.get("certType"), "SelfSigned");
assertEquals(e.get("inUse"), false);
}
assertEquals(test_certs, result_labels);
assertEquals(test_certs_uuids, result_uuids);
assertAuditEntry(0, customer.uuid);
}

@Test
public void testDeleteCertificate() {
UUID cert_uuid = test_certs_uuids.get(0);
Result result = deleteCertificate(customer.uuid, cert_uuid);
JsonNode json = Json.parse(contentAsString(result));
assertEquals(OK, result.status());
}

@Test
public void testDeleteInvalidCertificate() {
UUID uuid=UUID.randomUUID();
Result result = deleteCertificate(customer.uuid, uuid);
JsonNode json = Json.parse(contentAsString(result));
assertEquals(BAD_REQUEST, result.status());
}

@Test
public void testGetCertificate() {
UUID cert_uuid = test_certs_uuids.get(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { isNotHidden, isDisabled } from '../../../../utils/LayoutUtils';
import './certificates.scss';
import { AddCertificateFormContainer } from './';
import { CertificateDetails } from './CertificateDetails';
import { api } from '../../../../redesign/helpers/api';
import { YBFormInput } from '../../../common/forms/fields';

const validationSchema = Yup.object().shape({
Expand Down Expand Up @@ -116,8 +117,27 @@ class Certificates extends Component {
.finally(() => this.setState({ showSubmitting: false }));
};

/**
* Delete the root certificate if certificate is safe to remove,
* i.e - Certificate is not attached to any universe for current user.
*
* @param certificateUUID Unique id of certificate.
*/
deleteCertificate = (certificateUUID) => {
const {
customer: { currentCustomer}
} = this.props;

api.deleteCertificate(certificateUUID, currentCustomer?.data?.uuid).then(
() => this.props.fetchCustomerCertificates()
).catch(
err => console.error(`Failed to delete certificate ${certificateUUID}`, err)
)
};

formatActionButtons = (cell, row) => {
const downloadDisabled = row.type !== 'SelfSigned';
const deleteDisabled = row.inUse;
const payload = {
name: row.name,
uuid: row.uuid,
Expand Down Expand Up @@ -155,6 +175,14 @@ class Certificates extends Component {
>
<i className="fa fa-download"></i> Download Root CA Cert
</MenuItem>
<MenuItem
onClick={() => {
this.deleteCertificate(payload?.uuid)
}}
disabled={deleteDisabled}
>
<i className="fa fa-trash"></i> Delete Cert
</MenuItem>
</DropdownButton>
);
};
Expand All @@ -165,22 +193,24 @@ class Certificates extends Component {
modal: { showModal, visibleModal },
showAddCertificateModal
} = this.props;

const { showSubmitting } = this.state;

const certificateArray = getPromiseState(userCertificates).isSuccess()
? userCertificates.data.map((cert) => {
return {
type: cert.certType,
uuid: cert.uuid,
name: cert.label,
expiryDate: cert.expiryDate,
certificate: cert.certificate,
creationTime: cert.startDate,
privateKey: cert.privateKey,
customCertInfo: cert.customCertInfo
};
})
: [];
? userCertificates.data.map((cert) => {
return {
type: cert.certType,
uuid: cert.uuid,
name: cert.label,
expiryDate: cert.expiryDate,
certificate: cert.certificate,
creationTime: cert.startDate,
privateKey: cert.privateKey,
customCertInfo: cert.customCertInfo,
inUse: cert.inUse
};
})
: [];

return (
<div id="page-wrapper">
Expand Down
13 changes: 12 additions & 1 deletion managed/ui/src/redesign/helpers/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export enum QUERY_KEY {
getDBVersions = 'getDBVersions',
getAccessKeys = 'getAccessKeys',
getCertificates = 'getCertificates',
getKMSConfigs = 'getKMSConfigs'
getKMSConfigs = 'getKMSConfigs',
deleteCertificate = 'deleteCertificate'
}

class ApiService {
Expand Down Expand Up @@ -112,6 +113,16 @@ class ApiService {
isRequestCancelError(error: unknown): boolean {
return axios.isCancel(error);
}

/**
* Delete certificate which is not attched to any universe.
*
* @param certUUID - certificate UUID
*/
deleteCertificate = (certUUID: string, customerUUID: string): Promise<any> => {
const requestUrl = `${ROOT_URL}/customers/${customerUUID}/certificates/${certUUID}`
return axios.delete<any>(requestUrl).then((res) => res.data);
}
}

export const api = new ApiService();

0 comments on commit 9987226

Please sign in to comment.