Skip to content

Commit

Permalink
Merge pull request #32450 from vespa-engine/move-secret-impl-to-open-…
Browse files Browse the repository at this point in the history
…source

Move secret model classes to container-disc
  • Loading branch information
lesters authored Sep 23, 2024
2 parents 58ef683 + dc2b241 commit 1f2034f
Show file tree
Hide file tree
Showing 9 changed files with 316 additions and 0 deletions.
40 changes: 40 additions & 0 deletions container-disc/src/main/java/ai/vespa/secret/model/Key.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package ai.vespa.secret.model;

import java.util.Objects;

public record Key(VaultName vaultName, SecretName secretName) {

public Key {
Objects.requireNonNull(vaultName, "vaultName cannot be null");
Objects.requireNonNull(secretName, "secretName cannot be null");
}

@Override
public String toString() {
return vaultName.value() + "/" + secretName.value();
}

public static Key fromString(String key) {
String[] parts = key.split("/");
if (parts.length != 2) {
throw new IllegalArgumentException("Key must be on the form 'vaultName/secretName'");
}
return new Key(VaultName.of(parts[0]), SecretName.of(parts[1]));
}


/* Legacy constructor and methods for backwards compatibility */

public Key(String keyGroup, String keyName) {
this(VaultName.of(keyGroup), SecretName.of(keyName));
}

public String keyGroup() {
return vaultName.value();
}

public String keyName() {
return secretName.value();
}

}
30 changes: 30 additions & 0 deletions container-disc/src/main/java/ai/vespa/secret/model/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ai.vespa.secret.model;

/**
* @author gjoranv
*/
public enum Role {

READER("reader"),
WRITER("writer");

private final String value;

Role(String value) {
this.value = value;
}

public String value() {
return value;
}

public String forVault(VaultName vault) {
return vault.value() + "-" + value;
}

@Override
public String toString() {
return Role.class.getSimpleName() + "." + value;
}

}
94 changes: 94 additions & 0 deletions container-disc/src/main/java/ai/vespa/secret/model/Secret.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package ai.vespa.secret.model;

import com.yahoo.text.Utf8;

import java.util.Arrays;
import java.util.Objects;

public class Secret implements Comparable<Secret> {

private final Key key;
private final byte[] secret;
private final SecretVersionId version;
private final SecretVersionState state;

public Secret(Key key, byte[] secret, SecretVersionId version) {
this(key, secret, version, SecretVersionState.CURRENT);
}

public Secret(Key key, byte[] secret, SecretVersionId version, SecretVersionState state) {
this.key = key;
this.secret = secret;
this.version = version;
this.state = state;
}

public VaultName vaultName() {
return key.vaultName();
}

public SecretName secretName() {
return key.secretName();
}

public byte[] secret() {
return secret;
}

public SecretValue secretValue() {
return SecretValue.of(secretAsString());
}

public String secretAsString() { return Utf8.toString(secret); }

public SecretVersionId version() {
return version;
}

public SecretVersionState state() {
return state;
}

public static Key key(VaultName vaultName, SecretName secretName) {
return new Key(vaultName, secretName);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Secret that = (Secret) o;
if ( ! (that.key.equals(key))) return false;
if ( ! (Arrays.equals(that.secret, secret))) return false;
if (! that.version.equals(version)) return false;
return true;
}

@Override
public int hashCode() {
return Objects.hash(key, version, Arrays.hashCode(secret));
}

@Override
public String toString() {
return "Secret{" +
"key=" + key +
", version=" + version +
", state=" + state +
", secret=<omitted>"+
'}';
}

@Override
public int compareTo(Secret o) {
int v = key.vaultName().compareTo(o.key.vaultName());
if (v != 0) return v;
int n = key.secretName().compareTo(o.key.secretName());
if (n != 0) return n;
int s = state.compareTo(o.state);
if (s != 0) return s;

// Note: reversed for descending order
return o.version.value().compareTo(version.value());
}
}
23 changes: 23 additions & 0 deletions container-disc/src/main/java/ai/vespa/secret/model/SecretName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ai.vespa.secret.model;

import ai.vespa.validation.PatternedStringWrapper;

import java.util.regex.Pattern;

/**
* @author gjoranv
*/
public class SecretName extends PatternedStringWrapper<SecretName> {

// TODO: reset max size to 64 when we have stopped using concatenated vault+secret names
private static final Pattern namePattern = Pattern.compile("[.a-zA-Z0-9_-]{1,128}");

private SecretName(String name) {
super(name, namePattern, "Secret name");
}

public static SecretName of(String name) {
return new SecretName(name);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ai.vespa.secret.model;

/**
* @author gjoranv
*/
public record SecretValue(String value) {

public SecretValue {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("Secret value cannot be null or empty");
}
}

public static SecretValue of(String value) {
return new SecretValue(value);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ai.vespa.secret.model;

/**
* @author gjoranv
*/
public record SecretVersionId(String value) {

public SecretVersionId {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("Version id cannot be null or empty");
}
}

public static SecretVersionId of(String value) {
return new SecretVersionId(value);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package ai.vespa.secret.model;

import java.util.logging.Logger;

/**
* @author gjoranv
*/
public enum SecretVersionState {

// DO NOT CHANGE THE ORDERING OF THESE ENUMS
// They are used to sort lists of secret versions returned by the secret store.
PENDING("PENDING", "Pending"), // Maps to AWSPENDING
CURRENT("CURRENT", "Current"), // Maps to AWSCURRENT
PREVIOUS("PREVIOUS", "Previous"), // Maps to AWSPREVIOUS
DEPRECATED("DEPRECATED", "Deprecated");
// Deprecated versions have no staging labels in ASM, and will be garbage collected automatically

private static final Logger log = Logger.getLogger(SecretVersionState.class.getName());

private final String serializedName;
private final String prettyName;

SecretVersionState(String serializedName, String prettyName) {
this.serializedName =serializedName;
this.prettyName = prettyName;
}

public String serialize() {
return serializedName;
}

public String prettyName() {
return prettyName;
}

// Ensure that toString cannot be directly used for serialization
@Override
public String toString() {
return SecretVersionState.class.getSimpleName() + "." + serializedName;
}

public static SecretVersionState deserialize(String serializedName) {
for (SecretVersionState state : values()) {
if (state.serializedName.equals(serializedName))
return state;
}
throw new IllegalArgumentException("No such secret version state: " + serializedName);
}

public void validateTransition(SecretVersionState newState) {
if (this == newState) {
log.fine("Transition to the same state: " + newState);
}
if (this == PREVIOUS && newState != DEPRECATED) {
throw new IllegalArgumentException("Cannot transition from PREVIOUS state to " + newState);
}
if (this == DEPRECATED) {
throw new IllegalArgumentException("Cannot transition from DEPRECATED state: " + this);
}
if (newState == PENDING) {
throw new IllegalArgumentException("Cannot transition to PENDING state: " + this + " -> " + newState);
}
}

}
22 changes: 22 additions & 0 deletions container-disc/src/main/java/ai/vespa/secret/model/VaultName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ai.vespa.secret.model;

import ai.vespa.validation.PatternedStringWrapper;

import java.util.regex.Pattern;

/**
* @author gjoranv
*/
public class VaultName extends PatternedStringWrapper<VaultName> {

private static final Pattern namePattern = Pattern.compile("[.a-zA-Z0-9_-]{1,64}");

private VaultName(String name) {
super(name, namePattern, "Vault name");
}

public static VaultName of(String name) {
return new VaultName(name);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.

@ExportPackage
package ai.vespa.secret.model;

import com.yahoo.osgi.annotation.ExportPackage;

0 comments on commit 1f2034f

Please sign in to comment.