Skip to content

Commit

Permalink
Move SecretsImpl to jdisc-cloud-aws
Browse files Browse the repository at this point in the history
  • Loading branch information
gjoranv committed Sep 23, 2024
1 parent 8d9fa15 commit 97a4f54
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
*/
public class CloudSecrets extends SimpleComponent implements SecretsConfig.Producer {

private static final String CLASS = "ai.vespa.secret.cloud.SecretsImpl";
private static final String BUNDLE = "cloud-common";
private static final String CLASS = "ai.vespa.secret.aws.SecretsImpl";
private static final String BUNDLE = "jdisc-cloud-aws";

private final List<SecretConfig> secrets = new ArrayList<>();

Expand Down
53 changes: 53 additions & 0 deletions jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/SecretsImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package ai.vespa.secret.aws;

import ai.vespa.secret.Secret;
import ai.vespa.secret.Secrets;
import ai.vespa.secret.config.SecretsConfig;
import ai.vespa.secret.internal.TypedSecretStore;
import ai.vespa.secret.model.Key;
import ai.vespa.secret.model.SecretName;
import ai.vespa.secret.model.VaultName;

import javax.inject.Inject;

/**
* Implementation of the {@link Secrets} interface for Vespa cloud.
*
* @author lesters
*/
public class SecretsImpl implements Secrets {

private final SecretsConfig secretsConfig;
private final TypedSecretStore secretStore;

@Inject
public SecretsImpl(SecretsConfig config, AsmSecretStore asmSecretStore) {
this.secretStore = asmSecretStore;
this.secretsConfig = config;
}

// For testing
SecretsImpl(SecretsConfig secretsConfig, TypedSecretStore secretStore) {
this.secretsConfig = secretsConfig;
this.secretStore = secretStore;
}

@Override
public Secret get(String key) {
SecretsConfig.Secret secretConfig = secretsConfig.secret(key);
if (secretConfig == null) {
throw new IllegalArgumentException("Secret with key '" + key + "' not found in secrets config");
}

VaultName vaultName = VaultName.of(secretConfig.vault());
SecretName secretName = SecretName.of(secretConfig.name());

var secret = secretStore.getSecret(new Key(vaultName, secretName));
if (secret == null) {
throw new IllegalArgumentException("Secret with key '" + key + "' not found in secret store");
}

return secret::secretAsString;
}

}
120 changes: 120 additions & 0 deletions jdisc-cloud-aws/src/test/java/ai/vespa/secret/aws/SecretsImplTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package ai.vespa.secret.aws;

import ai.vespa.secret.config.SecretsConfig;
import ai.vespa.secret.internal.TypedSecretStore;
import ai.vespa.secret.model.Key;
import ai.vespa.secret.model.Secret;
import ai.vespa.secret.model.SecretVersionId;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

public class SecretsImplTest {

private record SecretConfig(String key, String name, String vault) {}

private final static Map<String, String> secretsInVault = Map.of(
"my-api-key", "0123456789",
"another-api-key", "9876543210"
);

private final static List<SecretConfig> secretsConfig = List.of(
new SecretConfig("myApiKey", "my-api-key", "prod"),
new SecretConfig("anotherApiKey", "another-api-key", "prod"),
new SecretConfig("mySecret", "my-secret", "prod") // not in vault
);

private final static SecretsImpl secrets = createSecrets();

@Test
public void testSecretsCanBeRetrieved() {
assertEquals("0123456789", secrets.get("myApiKey").current());
assertEquals("9876543210", secrets.get("anotherApiKey").current());
}

@Test
public void testThrowOnUnknownSecrets() {
try {
secrets.get("unknown");
fail();
} catch (IllegalArgumentException e) {
assertEquals("Secret with key 'unknown' not found in secrets config", e.getMessage());
}
}

@Test
public void testSecretInConfigButNotInVault() {
try {
secrets.get("mySecret");
fail();
} catch (IllegalArgumentException e) {
assertEquals("Secret with key 'mySecret' not found in secret store", e.getMessage());
}
}

private static SecretsImpl createSecrets() {
var config = createSecretsConfig();
var secretStore = createSecretStore();
return new SecretsImpl(config, secretStore);
}

private static SecretsConfig createSecretsConfig() {
SecretsConfig.Builder builder = new SecretsConfig.Builder();
secretsConfig.forEach(secret ->
builder.secret(secret.key(), new SecretsConfig.Secret.Builder().name(secret.name()).vault(secret.vault()))
);
return builder.build();
}

private static TypedSecretStore createSecretStore() {
var secretStore = new MockSecretStore();
secretsInVault.forEach((k, v) -> {
var key = new Key("prod", k);
var secretValue = v.getBytes();
var version = new SecretVersionId("1");
secretStore.putSecret(key, new Secret(key, secretValue, version));
});
return secretStore;
}

private static class MockSecretStore implements TypedSecretStore {

private Map<Key, Secret> secrets = new HashMap<>();

public void putSecret(Key key, Secret secret) {
secrets.put(key, secret);
}

@Override
public Secret getSecret(Key key, SecretVersionId version) {
return secrets.get(key);
}

@Override
public Secret getSecret(Key key) {
return getSecret(key, null);
}

@Override
public Type type() {
return Type.PUBLIC;
}

@Override
public String getSecret(String key) {
throw new UnsupportedOperationException("Not implemented");
}

@Override
public String getSecret(String key, int version) {
throw new UnsupportedOperationException("Not implemented");
}
}


}

0 comments on commit 97a4f54

Please sign in to comment.