diff --git a/.secrets.baseline b/.secrets.baseline index 39a2d54..9aeed62 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -81,7 +81,7 @@ "hashed_secret": "1beb7496ebbe82c61151be093956d83dac625c13", "is_secret": false, "is_verified": false, - "line_number": 396, + "line_number": 430, "type": "Secret Keyword", "verified_result": null } diff --git a/galasa-extensions-parent/dev.galasa.cps.etcd/src/main/java/dev/galasa/cps/etcd/internal/Etcd3CredentialsStore.java b/galasa-extensions-parent/dev.galasa.cps.etcd/src/main/java/dev/galasa/cps/etcd/internal/Etcd3CredentialsStore.java index cf13ef8..dd5e9e9 100644 --- a/galasa-extensions-parent/dev.galasa.cps.etcd/src/main/java/dev/galasa/cps/etcd/internal/Etcd3CredentialsStore.java +++ b/galasa-extensions-parent/dev.galasa.cps.etcd/src/main/java/dev/galasa/cps/etcd/internal/Etcd3CredentialsStore.java @@ -9,6 +9,7 @@ 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; @@ -44,7 +45,7 @@ public class Etcd3CredentialsStore extends Etcd3Store implements ICredentialsSto private final IEncryptionService encryptionService; private static final String CREDS_NAMESPACE = "secure"; - private static final String CREDS_PROPERTY_PREFIX = CREDS_NAMESPACE + ".credentials"; + private static final String CREDS_PROPERTY_PREFIX = CREDS_NAMESPACE + ".credentials."; /** * This constructor instantiates the Key value client that can retrieve values @@ -89,11 +90,8 @@ public Etcd3CredentialsStore(SecretKeySpec key, IEncryptionService encryptionSer public ICredentials getCredentials(String credentialsId) throws CredentialsException { ICredentials credentials = null; try { - String token = get(CREDS_PROPERTY_PREFIX + "." + credentialsId + ".token"); - String username = get(CREDS_PROPERTY_PREFIX + "." + credentialsId + ".username"); - String password = get(CREDS_PROPERTY_PREFIX + "." + credentialsId + ".password"); - - credentials = convertValuesIntoCredentials(username, password, token); + Map credentialsProperties = getPrefix(CREDS_PROPERTY_PREFIX + credentialsId); + credentials = convertPropertiesIntoCredentials(credentialsProperties, credentialsId); } catch (InterruptedException | ExecutionException e) { Thread.currentThread().interrupt(); @@ -118,14 +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 { // Clear any existing properties with the same credentials ID deleteCredentials(credentialsId); - for (Entry property : credentialProperties.entrySet()) { - put((String) property.getKey(), encryptionService.encrypt((String) property.getValue())); - } + putAllProperties(credentialProperties, true); + putAllProperties(metadataProperties, false); } catch (InterruptedException | ExecutionException e) { Thread.currentThread().interrupt(); throw new CredentialsException("Failed to set credentials", e); @@ -135,7 +133,7 @@ public void setCredentials(String credentialsId, ICredentials credentials) throw @Override public void deleteCredentials(String credentialsId) throws CredentialsException { try { - deletePrefix(CREDS_PROPERTY_PREFIX + "." + credentialsId); + deletePrefix(CREDS_PROPERTY_PREFIX + credentialsId); } catch (InterruptedException | ExecutionException e) { Thread.currentThread().interrupt(); throw new CredentialsException("Failed to delete credentials", e); @@ -146,7 +144,7 @@ public void deleteCredentials(String credentialsId) throws CredentialsException public Map getAllCredentials() throws CredentialsException { Map credentials = new HashMap<>(); try { - Map credentialsKeyValues = getPrefix(CREDS_PROPERTY_PREFIX + "."); + Map credentialsKeyValues = getPrefix(CREDS_PROPERTY_PREFIX); // Build a set of all credential IDs stored in etcd Set> credentialsEntries = credentialsKeyValues.entrySet(); @@ -164,11 +162,7 @@ public Map getAllCredentials() throws CredentialsException .filter(entry -> entry.getKey().contains("." + id + ".")) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); - String username = idProperties.get(CREDS_PROPERTY_PREFIX + "." + id + ".username"); - String password = idProperties.get(CREDS_PROPERTY_PREFIX + "." + id + ".password"); - String token = idProperties.get(CREDS_PROPERTY_PREFIX + "." + id + ".token"); - - ICredentials convertedCredentials = convertValuesIntoCredentials(username, password, token); + ICredentials convertedCredentials = convertPropertiesIntoCredentials(idProperties, id); if (convertedCredentials != null) { credentials.put(id, convertedCredentials); } @@ -181,7 +175,11 @@ public Map getAllCredentials() throws CredentialsException return credentials; } - private ICredentials convertValuesIntoCredentials(String username, String password, String token) throws CredentialsException { + private ICredentials convertPropertiesIntoCredentials(Map 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 @@ -197,6 +195,18 @@ private ICredentials convertValuesIntoCredentials(String username, String passwo 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; } @@ -211,4 +221,15 @@ private String getCredentialsIdFromKey(String key) { } return credentialsId; } + + private void putAllProperties(Properties properties, boolean encryptValues) throws CredentialsException, InterruptedException, ExecutionException { + for (Entry property : properties.entrySet()) { + String key = (String) property.getKey(); + String value = (String) property.getValue(); + if (encryptValues) { + value = encryptionService.encrypt(value); + } + put(key, value); + } + } } diff --git a/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/Etcd3CredentialsStoreTest.java b/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/Etcd3CredentialsStoreTest.java index 960f548..4459113 100644 --- a/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/Etcd3CredentialsStoreTest.java +++ b/galasa-extensions-parent/dev.galasa.cps.etcd/src/test/java/dev/galasa/etcd/internal/Etcd3CredentialsStoreTest.java @@ -5,6 +5,7 @@ */ package dev.galasa.etcd.internal; +import java.time.Instant; import java.util.HashMap; import java.util.Map; @@ -217,6 +218,39 @@ public void testGetUsernamePasswordCredentialsReturnsCredentialsOk() throws Exce assertThat(creds.getPassword()).isEqualTo(password); } + @Test + public void testGetUsernamePasswordCredentialsWithMetadataReturnsCredentialsOk() throws Exception { + // Given... + MockEncryptionService mockEncryptionService = new MockEncryptionService(); + String credsId = "CRED1"; + String username = "my-user"; + String password = "not-a-password"; + String description = "a description of my credentials"; + String lastUpdatedUser = "myUsername"; + Instant lastUpdatedTime = Instant.EPOCH; + + Map mockCreds = new HashMap<>(); + mockCreds.put("secure.credentials." + credsId + ".username", username); + mockCreds.put("secure.credentials." + credsId + ".password", password); + mockCreds.put("secure.credentials." + credsId + ".description", description); + mockCreds.put("secure.credentials." + credsId + ".lastUpdated.time", lastUpdatedTime.toString()); + mockCreds.put("secure.credentials." + credsId + ".lastUpdated.user", lastUpdatedUser); + + MockEtcdClient mockClient = new MockEtcdClient(mockCreds); + Etcd3CredentialsStore store = new Etcd3CredentialsStore(null, mockEncryptionService, mockClient); + + // When... + CredentialsUsernamePassword creds = (CredentialsUsernamePassword) store.getCredentials(credsId); + + // Then... + assertThat(creds).isNotNull(); + assertThat(creds.getUsername()).isEqualTo(username); + assertThat(creds.getPassword()).isEqualTo(password); + assertThat(creds.getDescription()).isEqualTo(description); + assertThat(creds.getLastUpdatedByUser()).isEqualTo(lastUpdatedUser); + assertThat(creds.getLastUpdatedTime()).isEqualTo(lastUpdatedTime); + } + @Test public void testGetUsernameTokenCredentialsReturnsCredentialsOk() throws Exception { // Given... @@ -425,4 +459,39 @@ public void testShutdownClosesEtcdClientsOk() throws Exception { // Then... assertThat(mockClient.isClientShutDown()).isTrue(); } + + @Test + public void testSetCredentialsWithMetadataSetsCredentialsOk() throws Exception { + // Given... + MockEncryptionService mockEncryptionService = new MockEncryptionService(); + String credsId = "CRED1"; + String username = "a-username"; + String lastUpdatedUser = "myuser"; + Instant lastUpdatedTime = Instant.EPOCH; + String description = "this is a description of my username secret"; + + + Map mockCreds = new HashMap<>(); + MockEtcdClient mockClient = new MockEtcdClient(mockCreds); + Etcd3CredentialsStore store = new Etcd3CredentialsStore(null, mockEncryptionService, mockClient); + + CredentialsUsername mockUsernameCreds = new CredentialsUsername(username); + mockUsernameCreds.setDescription(description); + mockUsernameCreds.setLastUpdatedByUser(lastUpdatedUser); + mockUsernameCreds.setLastUpdatedTime(lastUpdatedTime); + + // When... + store.setCredentials(credsId, mockUsernameCreds); + + // Then... + assertThat(mockCreds).hasSize(4); + assertThat(mockCreds.get("secure.credentials." + credsId + ".username")).isEqualTo(username); + assertThat(mockCreds.get("secure.credentials." + credsId + ".description")).isEqualTo(description); + assertThat(mockCreds.get("secure.credentials." + credsId + ".lastUpdated.time")).isEqualTo(lastUpdatedTime.toString()); + assertThat(mockCreds.get("secure.credentials." + credsId + ".lastUpdated.user")).isEqualTo(lastUpdatedUser); + + // The credentials should have been encrypted when being set, but the metadata should not be encrypted + assertThat(mockEncryptionService.getEncryptCount()).isEqualTo(1); + assertThat(mockEncryptionService.getDecryptCount()).isEqualTo(0); + } }