Add a Secret Manager client for Nomulus (#872)

* Add a Secret Manager client for Nomulus
This commit is contained in:
Weimin Yu 2020-11-12 17:12:52 -05:00 committed by GitHub
parent de20334a66
commit ae6b414b82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
146 changed files with 2666 additions and 1873 deletions

View file

@ -0,0 +1,170 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.privileges.secretmanager;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.cloud.secretmanager.v1.SecretVersion.State;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Optional;
import javax.inject.Inject;
import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
/** Implements {@link SecretManagerClient} for tests. */
public class FakeSecretManagerClient implements SecretManagerClient {
private final HashMap<String, SecretEntry> secrets = new HashMap<>();
@Inject
FakeSecretManagerClient() {}
@Override
public void createSecret(String secretId) {
checkNotNull(secretId, "secretId");
if (secrets.containsKey(secretId)) {
throw new SecretAlreadyExistsException(null);
}
secrets.put(secretId, new SecretEntry(secretId));
}
@Override
public Iterable<String> listSecrets() {
return ImmutableSet.copyOf(secrets.keySet());
}
@Override
public Iterable<SecretVersionState> listSecretVersions(String secretId) {
checkNotNull(secretId, "secretId");
SecretEntry secretEntry = secrets.get(secretId);
if (secretEntry == null) {
throw new NoSuchSecretResourceException(null);
}
return secretEntry.listVersions();
}
@Override
public String addSecretVersion(String secretId, String data) {
checkNotNull(secretId, "secretId");
checkNotNull(data, "data");
SecretEntry secretEntry = secrets.get(secretId);
if (secretEntry == null) {
throw new NoSuchSecretResourceException(null);
}
return secretEntry.addVersion(data);
}
@Override
public String getSecretData(String secretId, Optional<String> version) {
checkNotNull(secretId, "secretId");
checkNotNull(version, "version");
SecretEntry secretEntry = secrets.get(secretId);
if (secretEntry == null) {
throw new NoSuchSecretResourceException(null);
}
return secretEntry.getVersion(version).getData();
}
@Override
public void destroySecretVersion(String secretId, String version) {
checkNotNull(secretId, "secretId");
checkNotNull(version, "version");
SecretEntry secretEntry = secrets.get(secretId);
if (secretEntry == null) {
throw new NoSuchSecretResourceException(null);
}
secretEntry.destroyVersion(version);
}
@Override
public void deleteSecret(String secretId) {
checkNotNull(secretId, "secretId");
if (!secrets.containsKey(secretId)) {
throw new NoSuchSecretResourceException(null);
}
secrets.remove(secretId);
}
private static class VersionEntry {
private String data;
private State state;
VersionEntry(String data) {
this.data = checkNotNull(data, "data");
this.state = State.ENABLED;
}
String getData() {
if (state != State.ENABLED) {
throw new SecretManagerException(null);
}
return data;
}
State getState() {
return state;
}
void destroy() {
data = null;
state = State.DESTROYED;
}
}
private static class SecretEntry {
private final String secretId;
private ArrayList<VersionEntry> versions;
SecretEntry(String secretId) {
this.secretId = secretId;
versions = new ArrayList<>();
}
String addVersion(String data) {
VersionEntry versionEntry = new VersionEntry(data);
versions.add(versionEntry);
return String.valueOf(versions.size() - 1);
}
VersionEntry getVersion(Optional<String> version) {
try {
int index = version.map(Integer::valueOf).orElse(versions.size() - 1);
return versions.get(index);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid version " + version.get());
}
}
Iterable<SecretVersionState> listVersions() {
ImmutableList.Builder<SecretVersionState> builder = new ImmutableList.Builder<>();
for (int i = 0; i < versions.size(); i++) {
builder.add(SecretVersionState.of(secretId, String.valueOf(i), versions.get(i).getState()));
}
return builder.build();
}
void destroyVersion(String version) {
try {
int index = Integer.valueOf(version);
versions.get(index).destroy();
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid version " + version);
} catch (ArrayIndexOutOfBoundsException e) {
throw new NoSuchSecretResourceException(null);
}
}
}
}

View file

@ -0,0 +1,143 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.privileges.secretmanager;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.cloud.secretmanager.v1.SecretVersion.State;
import google.registry.privileges.secretmanager.SecretManagerClient.SecretAlreadyExistsException;
import google.registry.privileges.secretmanager.SecretManagerClient.SecretManagerException;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
/**
* Tests for {@link SecretManagerClient}.
*
* <p>If the 'test.gcp_integration.env' system property is not set, this class serves as unit tests
* for {@link FakeSecretManagerClient}.
*
* <p>If the 'test.gcp_integration.env' environment variable is set, this class serves as
* integration tests with a GCP project whose name is specified by the variable.
*
* <p>See <a href="../../../../../../../../java_common.gradle">java_common.gradle</a> for more
* information.
*/
public class SecretManagerClientTest {
// Common prefix for all secret ids generated in this test.
private static final String SECRET_ID_PREFIX = "TEST_" + UUID.randomUUID() + "_";
// Used for unique secret id generation.
private static int seqno = 0;
private static SecretManagerClient secretManagerClient;
private static boolean isUnitTest = true;
private static String nextSecretId() {
return SECRET_ID_PREFIX + seqno++;
}
@BeforeAll
static void beforeAll() {
String environmentName = System.getProperty("test.gcp_integration.env");
if (environmentName != null) {
secretManagerClient =
DaggerSecretManagerModule_SecretManagerComponent.builder()
.secretManagerModule(
new SecretManagerModule(String.format("domain-registry-%s", environmentName)))
.build()
.secretManagerClient();
isUnitTest = false;
} else {
secretManagerClient = new FakeSecretManagerClient();
}
}
@AfterAll
static void afterAll() {
if (isUnitTest) {
return;
}
for (String secretId : secretManagerClient.listSecrets()) {
if (secretId.startsWith(SECRET_ID_PREFIX)) {
secretManagerClient.deleteSecret(secretId);
}
}
}
@Test
void createSecret_success() {
String secretId = nextSecretId();
secretManagerClient.createSecret(secretId);
assertThat(secretManagerClient.listSecrets()).contains(secretId);
}
@Test
void createSecret_duplicate() {
String secretId = nextSecretId();
secretManagerClient.createSecret(secretId);
assertThrows(
SecretAlreadyExistsException.class, () -> secretManagerClient.createSecret(secretId));
}
@Test
void addSecretVersion() {
String secretId = nextSecretId();
secretManagerClient.createSecret(secretId);
String version = secretManagerClient.addSecretVersion(secretId, "mydata");
assertThat(secretManagerClient.listSecretVersions(secretId, State.ENABLED))
.containsExactly(version);
}
@Test
void getSecretData_byVersion() {
String secretId = nextSecretId();
secretManagerClient.createSecret(secretId);
String version = secretManagerClient.addSecretVersion(secretId, "mydata");
assertThat(secretManagerClient.getSecretData(secretId, Optional.of(version)))
.isEqualTo("mydata");
}
@Test
void getSecretData_latestVersion() {
String secretId = nextSecretId();
secretManagerClient.createSecret(secretId);
secretManagerClient.addSecretVersion(secretId, "mydata");
assertThat(secretManagerClient.getSecretData(secretId, Optional.empty())).isEqualTo("mydata");
}
@Test
void destroySecretVersion() {
String secretId = nextSecretId();
secretManagerClient.createSecret(secretId);
String version = secretManagerClient.addSecretVersion(secretId, "mydata");
secretManagerClient.destroySecretVersion(secretId, version);
assertThat(secretManagerClient.listSecretVersions(secretId, State.DESTROYED)).contains(version);
assertThrows(
SecretManagerException.class,
() -> secretManagerClient.getSecretData(secretId, Optional.of(version)));
}
@Test
void deleteSecret() {
String secretId = nextSecretId();
secretManagerClient.createSecret(secretId);
assertThat(secretManagerClient.listSecrets()).contains(secretId);
secretManagerClient.deleteSecret(secretId);
assertThat(secretManagerClient.listSecrets()).doesNotContain(secretId);
}
}