Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

Implemented getAllCredentials method for etcd credentials store #282

Merged
merged 3 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
"hashed_secret": "1beb7496ebbe82c61151be093956d83dac625c13",
"is_secret": false,
"is_verified": false,
"line_number": 246,
"line_number": 430,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.Properties;
import java.util.Set;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import javax.crypto.spec.SecretKeySpec;

Expand All @@ -38,6 +44,9 @@ public class Etcd3CredentialsStore extends Etcd3Store implements ICredentialsSto
private final SecretKeySpec key;
private final IEncryptionService encryptionService;

private static final String CREDS_NAMESPACE = "secure";
private static final String CREDS_PROPERTY_PREFIX = CREDS_NAMESPACE + ".credentials.";

/**
* This constructor instantiates the Key value client that can retrieve values
* from the etcd store.
Expand All @@ -48,7 +57,7 @@ public class Etcd3CredentialsStore extends Etcd3Store implements ICredentialsSto
public Etcd3CredentialsStore(IFramework framework, URI etcd) throws CredentialsException {
super(etcd);
try {
IConfigurationPropertyStoreService cpsService = framework.getConfigurationPropertyService("secure");
IConfigurationPropertyStoreService cpsService = framework.getConfigurationPropertyService(CREDS_NAMESPACE);
String encryptionKey = cpsService.getProperty("credentials.file", "encryption.key");
if (encryptionKey != null) {
key = createKey(encryptionKey);
Expand Down Expand Up @@ -79,31 +88,16 @@ public Etcd3CredentialsStore(SecretKeySpec key, IEncryptionService encryptionSer
* @throws CredentialsException A failure occurred.
*/
public ICredentials getCredentials(String credentialsId) throws CredentialsException {
ICredentials credentials = null;
try {
ICredentials credentials = null;
String token = get("secure.credentials." + credentialsId + ".token");
String username = get("secure.credentials." + credentialsId + ".username");

// Check if the credentials are UsernameToken or Token
if (token != null && username != null) {
credentials = new CredentialsUsernameToken(key, username, token);
} else if (token != null) {
credentials = new CredentialsToken(key, token);
} else if (username != null) {
// We have a username, so check if the credentials are UsernamePassword or Username
String password = get("secure.credentials." + credentialsId + ".password");
if (password != null) {
credentials = new CredentialsUsernamePassword(key, username, password);
} else {
credentials = new CredentialsUsername(key, username);
}
}

return credentials;
Map<String, String> credentialsProperties = getPrefix(CREDS_PROPERTY_PREFIX + credentialsId);
credentials = convertPropertiesIntoCredentials(credentialsProperties, credentialsId);

} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
throw new CredentialsException("Failed to get credentials", e);
}
return credentials;
}

private static SecretKeySpec createKey(String secret)
Expand All @@ -122,11 +116,14 @@ public void shutdown() throws CredentialsException {
@Override
public void setCredentials(String credentialsId, ICredentials credentials) throws CredentialsException {
Properties credentialProperties = credentials.toProperties(credentialsId);
Properties metadataProperties = credentials.getMetadataProperties(credentialsId);

try {
for (Entry<Object, Object> property : credentialProperties.entrySet()) {
put((String) property.getKey(), encryptionService.encrypt((String) property.getValue()));
}
// Clear any existing properties with the same credentials ID
deleteCredentials(credentialsId);

putAllProperties(credentialProperties, true);
putAllProperties(metadataProperties, false);
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
throw new CredentialsException("Failed to set credentials", e);
Expand All @@ -136,10 +133,103 @@ public void setCredentials(String credentialsId, ICredentials credentials) throw
@Override
public void deleteCredentials(String credentialsId) throws CredentialsException {
try {
deletePrefix("secure.credentials." + credentialsId);
deletePrefix(CREDS_PROPERTY_PREFIX + credentialsId);
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
throw new CredentialsException("Failed to delete credentials", e);
}
}

@Override
public Map<String, ICredentials> getAllCredentials() throws CredentialsException {
eamansour marked this conversation as resolved.
Show resolved Hide resolved
Map<String, ICredentials> credentials = new HashMap<>();
try {
Map<String, String> credentialsKeyValues = getPrefix(CREDS_PROPERTY_PREFIX);

// Build a set of all credential IDs stored in etcd
Set<Entry<String, String>> credentialsEntries = credentialsKeyValues.entrySet();
Set<String> credentialIds = new HashSet<>();
for (Entry<String, String> entry : credentialsEntries) {
String credsId = getCredentialsIdFromKey(entry.getKey());
if (credsId != null) {
credentialIds.add(credsId);
}
}

// For each credential ID, convert its properties into a credentials object for use by the framework
for (String id : credentialIds) {
Map<String, String> idProperties = credentialsEntries.stream()
.filter(entry -> entry.getKey().contains("." + id + "."))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));

ICredentials convertedCredentials = convertPropertiesIntoCredentials(idProperties, id);
if (convertedCredentials != null) {
credentials.put(id, convertedCredentials);
}
}

} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
throw new CredentialsException("Failed to get credentials", e);
}
return credentials;
}

private ICredentials convertPropertiesIntoCredentials(Map<String, String> credProperties, String credentialsId) throws CredentialsException {
String token = credProperties.get(CREDS_PROPERTY_PREFIX + credentialsId + ".token");
String username = credProperties.get(CREDS_PROPERTY_PREFIX + credentialsId + ".username");
String password = credProperties.get(CREDS_PROPERTY_PREFIX + credentialsId + ".password");

ICredentials credentials = null;

// Check if the credentials are UsernameToken or Token
if (token != null && username != null) {
credentials = new CredentialsUsernameToken(key, username, token);
} else if (token != null) {
credentials = new CredentialsToken(key, token);
} else if (username != null) {
// We have a username, so check if the credentials are UsernamePassword or Username
if (password != null) {
credentials = new CredentialsUsernamePassword(key, username, password);
} else {
credentials = new CredentialsUsername(key, username);
}
}

if (credentials != null) {
String description = credProperties.get(CREDS_PROPERTY_PREFIX + credentialsId + ".description");
String lastUpdatedTime = credProperties.get(CREDS_PROPERTY_PREFIX + credentialsId + ".lastUpdated.time");
String lastUpdatedUser = credProperties.get(CREDS_PROPERTY_PREFIX + credentialsId + ".lastUpdated.user");

credentials.setDescription(description);
credentials.setLastUpdatedByUser(lastUpdatedUser);
if (lastUpdatedTime != null) {
credentials.setLastUpdatedTime(Instant.parse(lastUpdatedTime));
}
}
return credentials;
}

private String getCredentialsIdFromKey(String key) {
// Keys for credentials should be in the form:
// secure.credentials.CRED_ID.suffix
// so let's split on "." and grab the third part
String credentialsId = null;
String[] keyParts = key.split("\\.");
if (keyParts.length >= 3) {
credentialsId = keyParts[2];
}
return credentialsId;
}

private void putAllProperties(Properties properties, boolean encryptValues) throws CredentialsException, InterruptedException, ExecutionException {
for (Entry<Object, Object> property : properties.entrySet()) {
String key = (String) property.getKey();
String value = (String) property.getValue();
if (encryptValues) {
value = encryptionService.encrypt(value);
}
put(key, value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

Expand All @@ -21,6 +23,7 @@
import io.etcd.jetcd.KeyValue;
import io.etcd.jetcd.kv.GetResponse;
import io.etcd.jetcd.options.DeleteOption;
import io.etcd.jetcd.options.GetOption;

/**
* Abstract class containing common methods used to interact with etcd, like getting, setting,
Expand Down Expand Up @@ -53,6 +56,28 @@ protected String get(String key) throws InterruptedException, ExecutionException
return retrievedKey;
}

protected Map<String, String> getPrefix(String keyPrefix) throws InterruptedException, ExecutionException {
Map<String, String> keyValues = new HashMap<>();

ByteSequence bsPrefix = ByteSequence.from(keyPrefix, UTF_8);
GetOption options = GetOption.newBuilder().isPrefix(true).build();
CompletableFuture<GetResponse> getFuture = kvClient.get(bsPrefix, options);

GetResponse response = getFuture.get();
List<KeyValue> kvs = response.getKvs();

for (KeyValue kv : kvs) {
// jetcd's getKey() method strips off the given prefix from matching keys, so add them back in
String key = kv.getKey().toString(UTF_8);
if (!key.startsWith(keyPrefix)) {
key = keyPrefix + key;
}
keyValues.put(key, kv.getValue().toString(UTF_8));
}

return keyValues;
}

protected void put(String key, String value) throws InterruptedException, ExecutionException {
ByteSequence bytesKey = ByteSequence.from(key, UTF_8);
ByteSequence bytesValue = ByteSequence.from(value, UTF_8);
Expand Down
Loading