add write method with CAS for vault (#59)

* add write method with CAS for vault

* fix review

---------

Co-authored-by: ggmaleva <ggmaleva@yandex.ru>
This commit is contained in:
Gregory 2023-08-18 11:09:48 +03:00 committed by GitHub
parent 68ec296ea6
commit 7f23818021
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 96 additions and 13 deletions

View File

@ -12,7 +12,7 @@
</parent>
<artifactId>adapter-common-lib</artifactId>
<version>1.2.12</version>
<version>1.2.13</version>
<packaging>jar</packaging>
<name>adapter-common-lib</name>

View File

@ -0,0 +1,10 @@
package dev.vality.adapter.common.exception;
public class SecretAlreadyModifyException extends RuntimeException {
public static final String CAS_ERROR_MESSAGE = "check-and-set parameter did not match the current version";
public SecretAlreadyModifyException(Throwable cause) {
super(cause);
}
}

View File

@ -1,9 +1,6 @@
package dev.vality.adapter.common.secret;
import dev.vality.adapter.common.exception.HexDecodeException;
import dev.vality.adapter.common.exception.SecretNotFoundException;
import dev.vality.adapter.common.exception.SecretPathNotFoundException;
import dev.vality.adapter.common.exception.SecretsNotFoundException;
import dev.vality.adapter.common.exception.*;
import java.util.Map;
@ -83,4 +80,16 @@ public interface SecretService {
*/
Integer writeVersionSecret(String serviceName, SecretObj secretObj);
/**
* Сохраняет секреты для терминала, используя CAS (Check-And-Set)
*
* @param serviceName - имя сервиса, для которого сохраняются секреты. Хранится в настройках сервиса.
* @param secretObj - объект с секретами, {@link SecretObj}
* @param version - значение текущей версии секретов, которая требует обновления.
* @return Возвращает обновленную версию хранилища для терминала
* @throws SecretAlreadyModifyException в случае некорректной версии
*/
Integer writeWithCas(String serviceName, SecretObj secretObj, Integer version)
throws SecretAlreadyModifyException;
}

View File

@ -1,18 +1,19 @@
package dev.vality.adapter.common.secret;
import dev.vality.adapter.common.exception.HexDecodeException;
import dev.vality.adapter.common.exception.SecretNotFoundException;
import dev.vality.adapter.common.exception.SecretPathNotFoundException;
import dev.vality.adapter.common.exception.SecretsNotFoundException;
import dev.vality.adapter.common.exception.*;
import lombok.RequiredArgsConstructor;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.vault.VaultException;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.vault.support.Versioned;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static dev.vality.adapter.common.exception.SecretAlreadyModifyException.CAS_ERROR_MESSAGE;
@RequiredArgsConstructor
public class VaultSecretService implements SecretService {
@ -77,6 +78,24 @@ public class VaultSecretService implements SecretService {
return metadata.getVersion().getVersion();
}
@Override
public Integer writeWithCas(String serviceName, SecretObj secretObj, Integer version) {
try {
var versionedBody = Versioned.create(secretObj.getValues(), Versioned.Version.from(version));
var metadata = vaultTemplate.opsForVersionedKeyValue(serviceName).put(secretObj.getPath(), versionedBody);
return metadata.getVersion().getVersion();
} catch (VaultException e) {
if (isCasError(e)) {
throw new SecretAlreadyModifyException(e);
}
throw e;
}
}
private static boolean isCasError(VaultException e) {
return Objects.nonNull(e.getMessage()) && e.getMessage().contains(CAS_ERROR_MESSAGE);
}
private String getSecretString(String serviceName, SecretRef secretRef) throws SecretNotFoundException {
var map = vaultTemplate.opsForVersionedKeyValue(serviceName).get(secretRef.getPath());
if (map == null || map.getData() == null || map.getData().get(secretRef.getKey()) == null) {

View File

@ -1,9 +1,6 @@
package dev.vality.adapter.common.secret;
import dev.vality.adapter.common.exception.HexDecodeException;
import dev.vality.adapter.common.exception.SecretNotFoundException;
import dev.vality.adapter.common.exception.SecretPathNotFoundException;
import dev.vality.adapter.common.exception.SecretsNotFoundException;
import dev.vality.adapter.common.exception.*;
import dev.vality.adapter.common.utils.HmacEncryption;
import org.apache.commons.codec.digest.DigestUtils;
import org.junit.jupiter.api.BeforeAll;
@ -163,4 +160,52 @@ public class VaultSecretServiceTest {
assertEquals(TOKEN_VALUE, versionSecrets.getSecretes().get(TOKEN).getValue());
assertEquals(TOKEN_EXP_DATE_VALUE, versionSecrets.getSecretes().get(TOKEN_EXP_DATE).getValue());
}
@Test
void writeMultipleWithCasError() {
SecretObj secretObj = new SecretObj(
TEST_TOKEN_PATH,
Map.of(
TOKEN, TOKEN_VALUE,
TOKEN_EXP_DATE, TOKEN_EXP_DATE_VALUE
)
);
Integer version = vaultService.writeVersionSecret(SERVICE_NAME, secretObj);
SecretObj updatedSecretObj = new SecretObj(
TEST_TOKEN_PATH,
Map.of(
TOKEN, TOKEN_VALUE + "refresh",
TOKEN_EXP_DATE, TOKEN_EXP_DATE_VALUE
)
);
int wrongVersion = version + 1;
assertThrows(SecretAlreadyModifyException.class,
() -> vaultService.writeWithCas(SERVICE_NAME, updatedSecretObj, wrongVersion));
}
@Test
void writeMultipleWithCasSuccess() {
SecretObj secretObj = new SecretObj(
TEST_TOKEN_PATH,
Map.of(
TOKEN, TOKEN_VALUE,
TOKEN_EXP_DATE, TOKEN_EXP_DATE_VALUE
)
);
Integer version = vaultService.writeVersionSecret(SERVICE_NAME, secretObj);
SecretObj updatedSecretObj = new SecretObj(
TEST_TOKEN_PATH,
Map.of(
TOKEN, TOKEN_VALUE + "refresh",
TOKEN_EXP_DATE, TOKEN_EXP_DATE_VALUE
)
);
Integer newVersion = vaultService.writeWithCas(SERVICE_NAME, updatedSecretObj, version);
assertNotNull(newVersion);
assertEquals(version + 1, newVersion);
}
}