Skip to content

Commit

Permalink
[#7706] Platform: Edit backup config credentials. (#8050)
Browse files Browse the repository at this point in the history
Description:
Backup config security credentials can change over time. The platform should support editing the security credentials even if there are existing backups associated with a Backup Config. When security credentials expire, the Platform will fail to take new backups and also fail to delete old backups.

This change should be implemented for all three storage configs:

AWS: Allow editing Access Key/Secret, also toggling between IAM and AccessKey
GCP: Allow editing GCS Credentials [Do not allow editing bucket name]
Azure: Allow editing Azure SAS token [Do not allow editing Container URL]

Test Plan:

1. Hit the config button from the SideNav.
2. Click on Backup Tab and then hit the amazon s3 tab.
3. Create a backup config for s3.
4. Once it got created then you'll see the Edit configuration button next to the Delete configuration button.
5. Click on the Edit Configuration button.
6. AWS access credentials should be editable.

Do the same for the rest of the configs.

Reviewers: @sshev , @jaydeepkumara, @gaurav061, @mjoshi0923, @SergeyPotachev, @Arnav15, @yb-andrew 

Reviewed by: @SergeyPotachev, @sshev, @jaydeepkumara
  • Loading branch information
nishantSharma459 authored May 12, 2021
1 parent 8ef0e71 commit ead3592
Show file tree
Hide file tree
Showing 11 changed files with 439 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

package com.yugabyte.yw.controllers;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import com.google.inject.Inject;
Expand All @@ -10,15 +11,18 @@
import com.yugabyte.yw.forms.YWSuccess;
import com.yugabyte.yw.models.Audit;
import com.yugabyte.yw.models.CustomerConfig;
import com.yugabyte.yw.models.helpers.CommonUtils;
import com.yugabyte.yw.models.helpers.CustomerConfigValidator;

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

import play.libs.Json;
import play.mvc.Result;

import java.util.UUID;


public class CustomerConfigController extends AuthenticatedController {
public static final Logger LOG = LoggerFactory.getLogger(CustomerConfigController.class);

Expand Down Expand Up @@ -62,4 +66,31 @@ public Result delete(UUID customerUUID, UUID configUUID) {
public Result list(UUID customerUUID) {
return ApiResponse.success(CustomerConfig.getAll(customerUUID));
}

public Result edit(UUID customerUUID, UUID configUUID) {
JsonNode formData = request().body().asJson();
ObjectNode errorJson = configValidator.validateFormData(formData);
if (errorJson.size() > 0) {
return ApiResponse.error(BAD_REQUEST, errorJson);
}

errorJson = configValidator.validateDataContent(formData);
if (errorJson.size() > 0) {
return ApiResponse.error(BAD_REQUEST, errorJson);
}
CustomerConfig customerConfig = CustomerConfig.get(customerUUID, configUUID);
if (customerConfig == null) {
return ApiResponse.error(BAD_REQUEST, "Invalid configUUID: " + configUUID);
}
CustomerConfig config = CustomerConfig.get(configUUID);
JsonNode data = Json.toJson(formData.get("data"));
if (data != null && data.get("BACKUP_LOCATION") != null) {
((ObjectNode)data).put("BACKUP_LOCATION", config.data.get("BACKUP_LOCATION"));
}
JsonNode updatedData = CommonUtils.unmaskConfig(config.data, data);
config.data = Json.toJson(updatedData);
config.update();
Audit.createAuditEntry(ctx(), request());
return ApiResponse.success(config);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ public static JsonNode maskConfig(JsonNode config) {
}

private static String getMaskedValue(String key, String value) {
return isStrictlySensitiveField(key) || (value == null) ? MASKED_FIELD_VALUE
: value.replaceAll(maskRegex, "*");
return isStrictlySensitiveField(key) || (value == null)
|| value.length() < 5 ? MASKED_FIELD_VALUE : value.replaceAll(maskRegex, "*");
}

/**
Expand Down
1 change: 1 addition & 0 deletions managed/src/main/resources/v1.routes
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ DELETE /customers/:cUUID/providers/:pUUID/instance_types/:code c
POST /customers/:cUUID/providers/setup_docker com.yugabyte.yw.controllers.CloudProviderController.setupDocker(cUUID: java.util.UUID)
POST /customers/:cUUID/configs com.yugabyte.yw.controllers.CustomerConfigController.create(cUUID: java.util.UUID)
GET /customers/:cUUID/configs com.yugabyte.yw.controllers.CustomerConfigController.list(cUUID: java.util.UUID)
PUT /customers/:cUUID/configs/:configUUID com.yugabyte.yw.controllers.CustomerConfigController.edit(cUUID: java.util.UUID, configUUID: java.util.UUID)
DELETE /customers/:cUUID/configs/:configUUID com.yugabyte.yw.controllers.CustomerConfigController.delete(cUUID: java.util.UUID, configUUID: java.util.UUID)

#---------------------------------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,44 @@ public void testDeleteInUseStorageConfig() {
}

@Test
public void testEditInUseStorageConfig() {
ObjectNode bodyJson = Json.newObject();
JsonNode data = Json.parse("{\"BACKUP_LOCATION\": \"test\", \"ACCESS_KEY\": \"A-KEY\", " +
"\"ACCESS_SECRET\": \"A-SECRET\"}");
bodyJson.put("name", "test1");
bodyJson.set("data", data);
bodyJson.put("type", "STORAGE");
UUID configUUID = ModelFactory.createS3StorageConfig(defaultCustomer).configUUID;
Backup backup = ModelFactory.createBackup(defaultCustomer.uuid, UUID.randomUUID(),
configUUID);
String url = "/api/customers/" + defaultCustomer.uuid + "/configs/" + configUUID;
Result result = FakeApiHelper.doRequestWithAuthTokenAndBody("PUT", url,
defaultUser.createAuthToken(), bodyJson);
assertOk(result);
backup.delete();
result = FakeApiHelper.doRequestWithAuthTokenAndBody("PUT", url,
defaultUser.createAuthToken(), bodyJson);
assertOk(result);
JsonNode json = Json.parse(contentAsString(result));
assertEquals("s3://foo", json.get("data").get("BACKUP_LOCATION").textValue());
}

@Test
public void testEditInvalidCustomerConfig() {
ObjectNode bodyJson = Json.newObject();
JsonNode data = Json.parse("{\"foo\":\"bar\"}");
bodyJson.put("name", "test1");
bodyJson.set("data", data);
bodyJson.put("type", "STORAGE");
Customer customer = ModelFactory.testCustomer("nc", "New Customer");
UUID configUUID = ModelFactory.createS3StorageConfig(customer).configUUID;
String url = "/api/customers/" + defaultCustomer.uuid + "/configs/" + configUUID;
Result result = FakeApiHelper.doRequestWithAuthTokenAndBody("PUT", url,
defaultUser.createAuthToken(), bodyJson);
assertBadRequest(result, "Invalid configUUID: " + configUUID);
assertEquals(1, CustomerConfig.getAll(customer.uuid).size());
}

public void testValidPasswordPolicy() {
Result result = testPasswordPolicy(8, 1, 1, 1, 1);
assertOk(result);
Expand All @@ -188,6 +226,26 @@ public void testNegativePasswordPolicy() {
}

@Test
public void testEditWithBackupLocation() {
ObjectNode bodyJson = Json.newObject();
JsonNode data = Json.parse("{\"BACKUP_LOCATION\": \"test\", \"ACCESS_KEY\": \"A-KEY-NEW\", " +
"\"ACCESS_SECRET\": \"DATA\"}");
bodyJson.put("name", "test1");
bodyJson.set("data", data);
bodyJson.put("type", "STORAGE");
UUID configUUID = ModelFactory.createS3StorageConfig(defaultCustomer).configUUID;
String url = "/api/customers/" + defaultCustomer.uuid + "/configs/" + configUUID;
Result result = FakeApiHelper.doRequestWithAuthTokenAndBody("PUT", url,
defaultUser.createAuthToken(), bodyJson);
assertOk(result);
JsonNode json = Json.parse(contentAsString(result));
// Should not update the field BACKUP_LOCATION to "test".
assertEquals("s3://foo", json.get("data").get("BACKUP_LOCATION").textValue());
// SHould be updated and the API response should give asked data.
assertEquals("A-*****EW", json.get("data").get("ACCESS_KEY").textValue());
assertEquals("********", json.get("data").get("ACCESS_SECRET").textValue());
}

public void testInvalidPasswordPolicy() {
Result result = testPasswordPolicy(8, 3, 3, 2, 1);
assertBadRequest(result, "{\"password policy\":[\"Minimal length should be not less than" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public void maskConfigWithSensitiveData() {
assertValue(maskedData, "MY_SECRET_DATA", "SE**********TA");
assertValue(maskedData, "MY_PASSWORD", "********");
assertValue(maskedData, "DATA", "VALUE");
assertValue(maskedData, "MY_KEY", "********");
}

private ObjectNode prepareConfig() {
Expand All @@ -36,6 +37,7 @@ private ObjectNode prepareConfig() {
config.put("SECRET_DATA", "SENSITIVE_DATA");
config.put("MY_SECRET_DATA", "SENSITIVE_DATA");
config.put("MY_PASSWORD", "SENSITIVE_DATA"); // Strong sensitive, complete masking.
config.put("MY_KEY", "DATA"); // Sensitive, complete masking as the lenght is less than 5.
config.put("DATA", "VALUE");
return config;
}
Expand Down
28 changes: 28 additions & 0 deletions managed/ui/src/actions/customers.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ export const FETCH_YUGAWARE_VERSION_RESPONSE = 'FETCH_YUGAWARE_VERSION_RESPONSE'
export const ADD_CUSTOMER_CONFIG = 'ADD_CUSTOMER_CONFIG';
export const ADD_CUSTOMER_CONFIG_RESPONSE = 'ADD_CUSTOMER_CONFIG_RESPONSE';

export const SET_INITIAL_CONFIG = 'SET_INITIAL_CONFIG';

export const UPDATE_CUSTOMER_CONFIG = 'UPDATE_CUSTOMER_CONFIG';
export const UPDATE_CUSTOMER_CONFIG_RESPONSE = 'UPDATE_CUSTOMER_CONFIG_RESPONSE';

export const DELETE_CUSTOMER_CONFIG = 'DELETE_CUSTOMER_CONFIG';
export const DELETE_CUSTOMER_CONFIG_RESPONSE = 'DELETE_CUSTOMER_CONFIG_RESPONSE';

Expand Down Expand Up @@ -488,6 +493,29 @@ export function addCustomerConfig(config) {
};
}

export function setInitialConfigValues(initialValues) {
return {
type: SET_INITIAL_CONFIG,
payload: initialValues
};
}

export function updateCustomerConfig(config) {
const cUUID = localStorage.getItem('customerId');
const request = axios.put(`${ROOT_URL}/customers/${cUUID}/configs/${config.configUUID}`, config);
return {
type: UPDATE_CUSTOMER_CONFIG,
payload: request
};
}

export function updateCustomerConfigResponse(response) {
return {
type: UPDATE_CUSTOMER_CONFIG_RESPONSE,
payload: response
};
}

export function addCustomerConfigResponse(response) {
return {
type: ADD_CUSTOMER_CONFIG_RESPONSE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,10 +455,10 @@
display: flex;
justify-content: flex-end;
align-items: center;
column-gap: 8px;

.disable-delete {
display: inline-block;
margin: 4px 20px 4px auto;
font-size: inherit;

.fa-ban {
Expand Down
Loading

0 comments on commit ead3592

Please sign in to comment.