mirror of
https://github.com/google/nomulus
synced 2026-09-12 11:06:29 +00:00
Remove Cloud KMS from Nomulus Server (#1839)
* Remove Cloud KMS from Nomulus Server Removed Cloud KMS from the Nomulus (:core) since it is no longer used. Renamed remaining classes to reflect their use of the SecretManager. Updated the config instructions to use a new codename for the keyring: KMS to CSM. This PR works with both codenames. Will drop 'KMS' after the internal repo is updated.
This commit is contained in:
@@ -1076,24 +1076,6 @@ public final class RegistryConfig {
|
||||
return config.keyring.activeKeyring;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name to use for the Cloud KMS KeyRing containing encryption keys for Nomulus secrets.
|
||||
*
|
||||
* @see <a
|
||||
* href="https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings#KeyRing">projects.locations.keyRings</a>
|
||||
*/
|
||||
@Provides
|
||||
@Config("cloudKmsKeyRing")
|
||||
public static String provideCloudKmsKeyRing(RegistryConfigSettings config) {
|
||||
return config.keyring.kms.keyringName;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("cloudKmsProjectId")
|
||||
public static String provideCloudKmsProjectId(RegistryConfigSettings config) {
|
||||
return config.keyring.kms.projectId;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("customLogicFactoryClass")
|
||||
public static String provideCustomLogicFactoryClass(RegistryConfigSettings config) {
|
||||
|
||||
@@ -214,6 +214,7 @@ public class RegistryConfigSettings {
|
||||
/** Configuration for keyrings (used to store secrets outside of source). */
|
||||
public static class Keyring {
|
||||
public String activeKeyring;
|
||||
// TODO(b/257276342): Remove after config files in nomulus-internal are updated.
|
||||
public Kms kms;
|
||||
}
|
||||
|
||||
|
||||
@@ -449,7 +449,8 @@ beam:
|
||||
stagingBucketUrl: gcs-bucket-with-staged-templates
|
||||
|
||||
keyring:
|
||||
# The name of the active keyring, either "KMS" or "Dummy".
|
||||
# The name of the active keyring, either "Dummy" or "CSM". The latter stands
|
||||
# for Cloud SecretManager.
|
||||
activeKeyring: Dummy
|
||||
|
||||
# Configuration options specific to Google Cloud KMS.
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright 2017 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.keyring.kms;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* A value type class containing a Cloud KMS encrypted and encoded ciphertext, and the name of the
|
||||
* CryptoKeyVersion used to encrypt it.
|
||||
*/
|
||||
@AutoValue
|
||||
abstract class EncryptResponse {
|
||||
|
||||
static EncryptResponse create(
|
||||
com.google.api.services.cloudkms.v1.model.EncryptResponse cloudKmsEncryptResponse) {
|
||||
return new AutoValue_EncryptResponse(
|
||||
cloudKmsEncryptResponse.getCiphertext(), cloudKmsEncryptResponse.getName());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static EncryptResponse create(String ciphertext, String cryptoKeyVersionName) {
|
||||
return new AutoValue_EncryptResponse(ciphertext, cryptoKeyVersionName);
|
||||
}
|
||||
|
||||
abstract String ciphertext();
|
||||
|
||||
abstract String cryptoKeyVersionName();
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright 2017 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.keyring.kms;
|
||||
|
||||
import google.registry.keyring.api.KeyringException;
|
||||
|
||||
/** An abstraction to simplify Cloud KMS operations. */
|
||||
public interface KmsConnection {
|
||||
|
||||
/**
|
||||
* The maximum allowable secret size, as set by Cloud KMS.
|
||||
*
|
||||
* @see <a
|
||||
* href="https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys/encrypt#request-body">projects.locations.keyRings.cryptoKeys.encrypt</a>
|
||||
*/
|
||||
int MAX_SECRET_SIZE_BYTES = 64 * 1024;
|
||||
|
||||
/**
|
||||
* Encrypts a plaintext with CryptoKey {@code cryptoKeyName} on KeyRing {@code keyRingName}.
|
||||
*
|
||||
* <p>The latest CryptoKeyVersion is used to encrypt the value. The value must not be larger than
|
||||
* {@code MAX_SECRET_SIZE_BYTES}.
|
||||
*
|
||||
* <p>If no applicable CryptoKey or CryptoKeyVersion exist, they will be created.
|
||||
*
|
||||
* @throws KeyringException on encryption failure.
|
||||
*/
|
||||
EncryptResponse encrypt(String cryptoKeyName, byte[] plaintext);
|
||||
|
||||
/**
|
||||
* Decrypts a Cloud KMS encrypted and encoded value with CryptoKey {@code cryptoKeyName}.
|
||||
*
|
||||
* @throws KeyringException on decryption failure.
|
||||
*/
|
||||
byte[] decrypt(String cryptoKeyName, String encodedCiphertext);
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
// Copyright 2017 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.keyring.kms;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
|
||||
import com.google.api.client.http.HttpStatusCodes;
|
||||
import com.google.api.services.cloudkms.v1.CloudKMS;
|
||||
import com.google.api.services.cloudkms.v1.model.CryptoKey;
|
||||
import com.google.api.services.cloudkms.v1.model.CryptoKeyVersion;
|
||||
import com.google.api.services.cloudkms.v1.model.DecryptRequest;
|
||||
import com.google.api.services.cloudkms.v1.model.EncryptRequest;
|
||||
import com.google.api.services.cloudkms.v1.model.KeyRing;
|
||||
import com.google.api.services.cloudkms.v1.model.UpdateCryptoKeyPrimaryVersionRequest;
|
||||
import google.registry.keyring.api.KeyringException;
|
||||
import google.registry.util.Retrier;
|
||||
import java.io.IOException;
|
||||
|
||||
/** The {@link KmsConnection} which talks to Cloud KMS. */
|
||||
class KmsConnectionImpl implements KmsConnection {
|
||||
|
||||
private static final String KMS_LOCATION_FORMAT = "projects/%s/locations/global";
|
||||
private static final String KMS_KEYRING_NAME_FORMAT = "projects/%s/locations/global/keyRings/%s";
|
||||
private static final String KMS_CRYPTO_KEY_NAME_FORMAT =
|
||||
"projects/%s/locations/global/keyRings/%s/cryptoKeys/%s";
|
||||
|
||||
private final CloudKMS kms;
|
||||
private final String kmsKeyRingName;
|
||||
private final String projectId;
|
||||
private final Retrier retrier;
|
||||
|
||||
KmsConnectionImpl(String projectId, String kmsKeyRingName, Retrier retrier, CloudKMS kms) {
|
||||
this.projectId = projectId;
|
||||
this.kmsKeyRingName = kmsKeyRingName;
|
||||
this.retrier = retrier;
|
||||
this.kms = kms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncryptResponse encrypt(String cryptoKeyName, byte[] value) {
|
||||
checkArgument(
|
||||
value.length <= MAX_SECRET_SIZE_BYTES,
|
||||
"Value to encrypt was larger than %s bytes",
|
||||
MAX_SECRET_SIZE_BYTES);
|
||||
try {
|
||||
return attemptEncrypt(cryptoKeyName, value);
|
||||
} catch (IOException e) {
|
||||
throw new KeyringException(
|
||||
String.format("CloudKMS encrypt operation failed for secret %s", cryptoKeyName), e);
|
||||
}
|
||||
}
|
||||
|
||||
private EncryptResponse attemptEncrypt(String cryptoKeyName, byte[] value) throws IOException {
|
||||
String fullKeyRingName = getKeyRingName(projectId, kmsKeyRingName);
|
||||
try {
|
||||
kms.projects().locations().keyRings().get(fullKeyRingName).execute();
|
||||
} catch (GoogleJsonResponseException jsonException) {
|
||||
if (jsonException.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
|
||||
// Create the KeyRing in the "global" namespace. Encryption keys will be accessible from all
|
||||
// GCP regions.
|
||||
kms.projects()
|
||||
.locations()
|
||||
.keyRings()
|
||||
.create(getLocationName(projectId), new KeyRing())
|
||||
.setKeyRingId(kmsKeyRingName)
|
||||
.execute();
|
||||
} else {
|
||||
throw jsonException;
|
||||
}
|
||||
}
|
||||
|
||||
String fullKeyName = getCryptoKeyName(projectId, kmsKeyRingName, cryptoKeyName);
|
||||
|
||||
boolean newCryptoKey = false;
|
||||
try {
|
||||
kms.projects().locations().keyRings().cryptoKeys().get(fullKeyName).execute();
|
||||
} catch (GoogleJsonResponseException jsonException) {
|
||||
if (jsonException.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
|
||||
newCryptoKey = true;
|
||||
kms.projects()
|
||||
.locations()
|
||||
.keyRings()
|
||||
.cryptoKeys()
|
||||
.create(fullKeyRingName, new CryptoKey().setPurpose("ENCRYPT_DECRYPT"))
|
||||
.setCryptoKeyId(cryptoKeyName)
|
||||
.execute();
|
||||
} else {
|
||||
throw jsonException;
|
||||
}
|
||||
}
|
||||
|
||||
// New CryptoKeys start with a CryptoKeyVersion, so we only create a new CryptoKeyVersion and
|
||||
// rotate to it if we're dealing with an existing CryptoKey.
|
||||
if (!newCryptoKey) {
|
||||
CryptoKeyVersion cryptoKeyVersion =
|
||||
kms.projects()
|
||||
.locations()
|
||||
.keyRings()
|
||||
.cryptoKeys()
|
||||
.cryptoKeyVersions()
|
||||
.create(fullKeyName, new CryptoKeyVersion())
|
||||
.execute();
|
||||
|
||||
kms.projects()
|
||||
.locations()
|
||||
.keyRings()
|
||||
.cryptoKeys()
|
||||
.updatePrimaryVersion(
|
||||
fullKeyName,
|
||||
new UpdateCryptoKeyPrimaryVersionRequest()
|
||||
.setCryptoKeyVersionId(getCryptoKeyVersionId(cryptoKeyVersion)))
|
||||
.execute();
|
||||
}
|
||||
|
||||
return EncryptResponse.create(
|
||||
kms.projects()
|
||||
.locations()
|
||||
.keyRings()
|
||||
.cryptoKeys()
|
||||
.encrypt(fullKeyName, new EncryptRequest().encodePlaintext(value))
|
||||
.execute());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] decrypt(final String cryptoKeyName, final String encodedCiphertext) {
|
||||
try {
|
||||
return retrier.callWithRetry(
|
||||
() -> attemptDecrypt(cryptoKeyName, encodedCiphertext), IOException.class);
|
||||
} catch (RuntimeException e) {
|
||||
throw new KeyringException(
|
||||
String.format("CloudKMS decrypt operation failed for secret %s", cryptoKeyName), e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] attemptDecrypt(String cryptoKeyName, String encodedCiphertext) throws IOException {
|
||||
return kms.projects()
|
||||
.locations()
|
||||
.keyRings()
|
||||
.cryptoKeys()
|
||||
.decrypt(
|
||||
getCryptoKeyName(projectId, kmsKeyRingName, cryptoKeyName),
|
||||
new DecryptRequest().setCiphertext(encodedCiphertext))
|
||||
.execute()
|
||||
.decodePlaintext();
|
||||
}
|
||||
|
||||
private static String getLocationName(String projectId) {
|
||||
return String.format(KMS_LOCATION_FORMAT, projectId);
|
||||
}
|
||||
|
||||
private static String getKeyRingName(String projectId, String kmsKeyRingName) {
|
||||
return String.format(KMS_KEYRING_NAME_FORMAT, projectId, kmsKeyRingName);
|
||||
}
|
||||
|
||||
private static String getCryptoKeyName(
|
||||
String projectId, String kmsKeyRingName, String cryptoKeyName) {
|
||||
return String.format(KMS_CRYPTO_KEY_NAME_FORMAT, projectId, kmsKeyRingName, cryptoKeyName);
|
||||
}
|
||||
|
||||
private static String getCryptoKeyVersionId(CryptoKeyVersion cryptoKeyVersion) {
|
||||
String cryptoKeyVersionName = cryptoKeyVersion.getName();
|
||||
return cryptoKeyVersionName.substring(cryptoKeyVersionName.lastIndexOf('/') + 1);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright 2017 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.keyring.kms;
|
||||
|
||||
import com.google.api.services.cloudkms.v1.CloudKMS;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import dagger.multibindings.StringKey;
|
||||
import google.registry.config.CredentialModule.DefaultCredential;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.keyring.api.Keyring;
|
||||
import google.registry.util.GoogleCredentialsBundle;
|
||||
import google.registry.util.Retrier;
|
||||
|
||||
/** Dagger module for Cloud KMS. */
|
||||
@Module
|
||||
public abstract class KmsModule {
|
||||
|
||||
public static final String NAME = "KMS";
|
||||
|
||||
@Provides
|
||||
@Config("defaultKms")
|
||||
static CloudKMS provideKms(
|
||||
@DefaultCredential GoogleCredentialsBundle credentialsBundle,
|
||||
@Config("cloudKmsProjectId") String projectId) {
|
||||
return createKms(credentialsBundle, projectId);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("beamKms")
|
||||
static CloudKMS provideBeamKms(
|
||||
@DefaultCredential GoogleCredentialsBundle credentialsBundle,
|
||||
@Config("beamCloudKmsProjectId") String projectId) {
|
||||
return createKms(credentialsBundle, projectId);
|
||||
}
|
||||
|
||||
private static CloudKMS createKms(GoogleCredentialsBundle credentialsBundle, String projectId) {
|
||||
return new CloudKMS.Builder(
|
||||
credentialsBundle.getHttpTransport(),
|
||||
credentialsBundle.getJsonFactory(),
|
||||
credentialsBundle.getHttpRequestInitializer())
|
||||
.setApplicationName(projectId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("defaultKmsConnection")
|
||||
static KmsConnection provideKmsConnection(
|
||||
@Config("cloudKmsProjectId") String projectId,
|
||||
@Config("cloudKmsKeyRing") String keyringName,
|
||||
Retrier retrier,
|
||||
@Config("defaultKms") CloudKMS defaultKms) {
|
||||
return new KmsConnectionImpl(projectId, keyringName, retrier, defaultKms);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("beamKmsConnection")
|
||||
static KmsConnection provideBeamKmsConnection(
|
||||
@Config("beamCloudKmsProjectId") String projectId,
|
||||
@Config("beamCloudKmsKeyRing") String keyringName,
|
||||
Retrier retrier,
|
||||
@Config("beamKms") CloudKMS defaultKms) {
|
||||
return new KmsConnectionImpl(projectId, keyringName, retrier, defaultKms);
|
||||
}
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@StringKey(NAME)
|
||||
abstract Keyring provideKeyring(KmsKeyring keyring);
|
||||
}
|
||||
+3
-4
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.keyring.kms;
|
||||
package google.registry.keyring.secretmanager;
|
||||
|
||||
import static com.google.common.base.CaseFormat.LOWER_HYPHEN;
|
||||
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
|
||||
@@ -29,8 +29,7 @@ import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
|
||||
/** A {@link Keyring} implementation which stores sensitive data in the Secret Manager. */
|
||||
// TODO(2021-08-01): rename this class to SecretManagerKeyring and update config files.
|
||||
public class KmsKeyring implements Keyring {
|
||||
public class SecretManagerKeyring implements Keyring {
|
||||
|
||||
/** Key labels for private key secrets. */
|
||||
enum PrivateKeyLabel {
|
||||
@@ -75,7 +74,7 @@ public class KmsKeyring implements Keyring {
|
||||
private final KeyringSecretStore secretStore;
|
||||
|
||||
@Inject
|
||||
KmsKeyring(KeyringSecretStore secretStore) {
|
||||
SecretManagerKeyring(KeyringSecretStore secretStore) {
|
||||
this.secretStore = secretStore;
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// Copyright 2017 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.keyring.secretmanager;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import dagger.multibindings.StringKey;
|
||||
import google.registry.keyring.api.Keyring;
|
||||
|
||||
/** Dagger module for {@link Keyring} backed by the Cloud SecretManager. */
|
||||
@Module
|
||||
public abstract class SecretManagerKeyringModule {
|
||||
|
||||
public static final String NAME = "CSM";
|
||||
// TODO(b/257276342): Remove after configs in nomulus-internal are updated.
|
||||
public static final String DEPRECATED_NAME = "KMS";
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@StringKey(DEPRECATED_NAME)
|
||||
abstract Keyring provideDeprecatedKeyring(SecretManagerKeyring keyring);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@StringKey(NAME)
|
||||
abstract Keyring provideKeyring(SecretManagerKeyring keyring);
|
||||
}
|
||||
+46
-42
@@ -12,33 +12,33 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.keyring.kms;
|
||||
package google.registry.keyring.secretmanager;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.keyring.kms.KmsKeyring.PrivateKeyLabel.BRDA_SIGNING_PRIVATE;
|
||||
import static google.registry.keyring.kms.KmsKeyring.PrivateKeyLabel.RDE_SIGNING_PRIVATE;
|
||||
import static google.registry.keyring.kms.KmsKeyring.PrivateKeyLabel.RDE_STAGING_PRIVATE;
|
||||
import static google.registry.keyring.kms.KmsKeyring.PublicKeyLabel.BRDA_RECEIVER_PUBLIC;
|
||||
import static google.registry.keyring.kms.KmsKeyring.PublicKeyLabel.BRDA_SIGNING_PUBLIC;
|
||||
import static google.registry.keyring.kms.KmsKeyring.PublicKeyLabel.RDE_RECEIVER_PUBLIC;
|
||||
import static google.registry.keyring.kms.KmsKeyring.PublicKeyLabel.RDE_SIGNING_PUBLIC;
|
||||
import static google.registry.keyring.kms.KmsKeyring.PublicKeyLabel.RDE_STAGING_PUBLIC;
|
||||
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.ICANN_REPORTING_PASSWORD_STRING;
|
||||
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.JSON_CREDENTIAL_STRING;
|
||||
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.MARKSDB_DNL_LOGIN_STRING;
|
||||
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.MARKSDB_LORDN_PASSWORD_STRING;
|
||||
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.MARKSDB_SMDRL_LOGIN_STRING;
|
||||
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.RDE_SSH_CLIENT_PRIVATE_STRING;
|
||||
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.RDE_SSH_CLIENT_PUBLIC_STRING;
|
||||
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.SAFE_BROWSING_API_KEY;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PrivateKeyLabel.BRDA_SIGNING_PRIVATE;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PrivateKeyLabel.RDE_SIGNING_PRIVATE;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PrivateKeyLabel.RDE_STAGING_PRIVATE;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PublicKeyLabel.BRDA_RECEIVER_PUBLIC;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PublicKeyLabel.BRDA_SIGNING_PUBLIC;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PublicKeyLabel.RDE_RECEIVER_PUBLIC;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PublicKeyLabel.RDE_SIGNING_PUBLIC;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.PublicKeyLabel.RDE_STAGING_PUBLIC;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.ICANN_REPORTING_PASSWORD_STRING;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.JSON_CREDENTIAL_STRING;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.MARKSDB_DNL_LOGIN_STRING;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.MARKSDB_LORDN_PASSWORD_STRING;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.MARKSDB_SMDRL_LOGIN_STRING;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.RDE_SSH_CLIENT_PRIVATE_STRING;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.RDE_SSH_CLIENT_PUBLIC_STRING;
|
||||
import static google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel.SAFE_BROWSING_API_KEY;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.keyring.api.KeySerializer;
|
||||
import google.registry.keyring.kms.KmsKeyring.PrivateKeyLabel;
|
||||
import google.registry.keyring.kms.KmsKeyring.PublicKeyLabel;
|
||||
import google.registry.keyring.kms.KmsKeyring.StringKeyLabel;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyring.PrivateKeyLabel;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyring.PublicKeyLabel;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyring.StringKeyLabel;
|
||||
import google.registry.privileges.secretmanager.KeyringSecretStore;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
@@ -50,73 +50,77 @@ import org.bouncycastle.openpgp.PGPKeyPair;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
|
||||
/**
|
||||
* The {@link KmsUpdater} accumulates updates to a {@link KmsKeyring} and persists them to KMS and
|
||||
* Datastore when closed.
|
||||
* The {@link SecretManagerKeyringUpdater} accumulates updates to a {@link SecretManagerKeyring} and
|
||||
* persists them to KMS and Datastore when closed.
|
||||
*/
|
||||
// TODO(2021-06-01): rename this class to SecretManagerKeyringUpdater
|
||||
public final class KmsUpdater {
|
||||
public final class SecretManagerKeyringUpdater {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final KeyringSecretStore secretStore;
|
||||
private final HashMap<String, byte[]> secretValues;
|
||||
|
||||
@Inject
|
||||
public KmsUpdater(KeyringSecretStore secretStore) {
|
||||
public SecretManagerKeyringUpdater(KeyringSecretStore secretStore) {
|
||||
this.secretStore = secretStore;
|
||||
|
||||
// Use LinkedHashMap to preserve insertion order on update() to simplify testing and debugging
|
||||
this.secretValues = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
public KmsUpdater setRdeSigningKey(PGPKeyPair keyPair) throws IOException, PGPException {
|
||||
public SecretManagerKeyringUpdater setRdeSigningKey(PGPKeyPair keyPair)
|
||||
throws IOException, PGPException {
|
||||
return setKeyPair(keyPair, RDE_SIGNING_PRIVATE, RDE_SIGNING_PUBLIC);
|
||||
}
|
||||
|
||||
public KmsUpdater setRdeStagingKey(PGPKeyPair keyPair) throws IOException, PGPException {
|
||||
public SecretManagerKeyringUpdater setRdeStagingKey(PGPKeyPair keyPair)
|
||||
throws IOException, PGPException {
|
||||
return setKeyPair(keyPair, RDE_STAGING_PRIVATE, RDE_STAGING_PUBLIC);
|
||||
}
|
||||
|
||||
public KmsUpdater setRdeReceiverPublicKey(PGPPublicKey publicKey) throws IOException {
|
||||
public SecretManagerKeyringUpdater setRdeReceiverPublicKey(PGPPublicKey publicKey)
|
||||
throws IOException {
|
||||
return setPublicKey(publicKey, RDE_RECEIVER_PUBLIC);
|
||||
}
|
||||
|
||||
public KmsUpdater setBrdaSigningKey(PGPKeyPair keyPair) throws IOException, PGPException {
|
||||
public SecretManagerKeyringUpdater setBrdaSigningKey(PGPKeyPair keyPair)
|
||||
throws IOException, PGPException {
|
||||
return setKeyPair(keyPair, BRDA_SIGNING_PRIVATE, BRDA_SIGNING_PUBLIC);
|
||||
}
|
||||
|
||||
public KmsUpdater setBrdaReceiverPublicKey(PGPPublicKey publicKey) throws IOException {
|
||||
public SecretManagerKeyringUpdater setBrdaReceiverPublicKey(PGPPublicKey publicKey)
|
||||
throws IOException {
|
||||
return setPublicKey(publicKey, BRDA_RECEIVER_PUBLIC);
|
||||
}
|
||||
|
||||
public KmsUpdater setRdeSshClientPublicKey(String asciiPublicKey) {
|
||||
public SecretManagerKeyringUpdater setRdeSshClientPublicKey(String asciiPublicKey) {
|
||||
return setString(asciiPublicKey, RDE_SSH_CLIENT_PUBLIC_STRING);
|
||||
}
|
||||
|
||||
public KmsUpdater setRdeSshClientPrivateKey(String asciiPrivateKey) {
|
||||
public SecretManagerKeyringUpdater setRdeSshClientPrivateKey(String asciiPrivateKey) {
|
||||
return setString(asciiPrivateKey, RDE_SSH_CLIENT_PRIVATE_STRING);
|
||||
}
|
||||
|
||||
public KmsUpdater setSafeBrowsingAPIKey(String apiKey) {
|
||||
public SecretManagerKeyringUpdater setSafeBrowsingAPIKey(String apiKey) {
|
||||
return setString(apiKey, SAFE_BROWSING_API_KEY);
|
||||
}
|
||||
|
||||
public KmsUpdater setIcannReportingPassword(String password) {
|
||||
public SecretManagerKeyringUpdater setIcannReportingPassword(String password) {
|
||||
return setString(password, ICANN_REPORTING_PASSWORD_STRING);
|
||||
}
|
||||
|
||||
public KmsUpdater setMarksdbDnlLoginAndPassword(String login) {
|
||||
public SecretManagerKeyringUpdater setMarksdbDnlLoginAndPassword(String login) {
|
||||
return setString(login, MARKSDB_DNL_LOGIN_STRING);
|
||||
}
|
||||
|
||||
public KmsUpdater setMarksdbLordnPassword(String password) {
|
||||
public SecretManagerKeyringUpdater setMarksdbLordnPassword(String password) {
|
||||
return setString(password, MARKSDB_LORDN_PASSWORD_STRING);
|
||||
}
|
||||
|
||||
public KmsUpdater setMarksdbSmdrlLoginAndPassword(String login) {
|
||||
public SecretManagerKeyringUpdater setMarksdbSmdrlLoginAndPassword(String login) {
|
||||
return setString(login, MARKSDB_SMDRL_LOGIN_STRING);
|
||||
}
|
||||
|
||||
public KmsUpdater setJsonCredential(String credential) {
|
||||
public SecretManagerKeyringUpdater setJsonCredential(String credential) {
|
||||
return setString(credential, JSON_CREDENTIAL_STRING);
|
||||
}
|
||||
|
||||
@@ -144,22 +148,22 @@ public final class KmsUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
private KmsUpdater setString(String key, StringKeyLabel stringKeyLabel) {
|
||||
private SecretManagerKeyringUpdater setString(String key, StringKeyLabel stringKeyLabel) {
|
||||
checkArgumentNotNull(key);
|
||||
|
||||
setSecret(stringKeyLabel.getLabel(), KeySerializer.serializeString(key));
|
||||
return this;
|
||||
}
|
||||
|
||||
private KmsUpdater setPublicKey(PGPPublicKey publicKey, PublicKeyLabel publicKeyLabel)
|
||||
throws IOException {
|
||||
private SecretManagerKeyringUpdater setPublicKey(
|
||||
PGPPublicKey publicKey, PublicKeyLabel publicKeyLabel) throws IOException {
|
||||
checkArgumentNotNull(publicKey);
|
||||
|
||||
setSecret(publicKeyLabel.getLabel(), KeySerializer.serializePublicKey(publicKey));
|
||||
return this;
|
||||
}
|
||||
|
||||
private KmsUpdater setKeyPair(
|
||||
private SecretManagerKeyringUpdater setKeyPair(
|
||||
PGPKeyPair keyPair, PrivateKeyLabel privateKeyLabel, PublicKeyLabel publicKeyLabel)
|
||||
throws IOException, PGPException {
|
||||
checkArgumentNotNull(keyPair);
|
||||
@@ -33,7 +33,7 @@ import google.registry.groups.GroupssettingsModule;
|
||||
import google.registry.keyring.KeyringModule;
|
||||
import google.registry.keyring.api.DummyKeyringModule;
|
||||
import google.registry.keyring.api.KeyModule;
|
||||
import google.registry.keyring.kms.KmsModule;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
|
||||
import google.registry.module.backend.BackendRequestComponent.BackendRequestComponentModule;
|
||||
import google.registry.monitoring.whitebox.StackdriverModule;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
@@ -70,9 +70,9 @@ import javax.inject.Singleton;
|
||||
GsonModule.class,
|
||||
KeyModule.class,
|
||||
KeyringModule.class,
|
||||
KmsModule.class,
|
||||
NetHttpTransportModule.class,
|
||||
PersistenceModule.class,
|
||||
SecretManagerKeyringModule.class,
|
||||
SecretManagerModule.class,
|
||||
ServerTridProviderModule.class,
|
||||
SheetsServiceModule.class,
|
||||
|
||||
@@ -28,7 +28,7 @@ import google.registry.groups.GroupssettingsModule;
|
||||
import google.registry.keyring.KeyringModule;
|
||||
import google.registry.keyring.api.DummyKeyringModule;
|
||||
import google.registry.keyring.api.KeyModule;
|
||||
import google.registry.keyring.kms.KmsModule;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
|
||||
import google.registry.module.frontend.FrontendRequestComponent.FrontendRequestComponentModule;
|
||||
import google.registry.monitoring.whitebox.StackdriverModule;
|
||||
import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
@@ -59,8 +59,8 @@ import javax.inject.Singleton;
|
||||
GsonModule.class,
|
||||
KeyModule.class,
|
||||
KeyringModule.class,
|
||||
KmsModule.class,
|
||||
NetHttpTransportModule.class,
|
||||
SecretManagerKeyringModule.class,
|
||||
SecretManagerModule.class,
|
||||
ServerTridProviderModule.class,
|
||||
StackdriverModule.class,
|
||||
|
||||
@@ -27,7 +27,7 @@ import google.registry.groups.GroupssettingsModule;
|
||||
import google.registry.keyring.KeyringModule;
|
||||
import google.registry.keyring.api.DummyKeyringModule;
|
||||
import google.registry.keyring.api.KeyModule;
|
||||
import google.registry.keyring.kms.KmsModule;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
|
||||
import google.registry.module.pubapi.PubApiRequestComponent.PubApiRequestComponentModule;
|
||||
import google.registry.monitoring.whitebox.StackdriverModule;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
@@ -54,10 +54,10 @@ import javax.inject.Singleton;
|
||||
GsonModule.class,
|
||||
KeyModule.class,
|
||||
KeyringModule.class,
|
||||
KmsModule.class,
|
||||
NetHttpTransportModule.class,
|
||||
PersistenceModule.class,
|
||||
PubApiRequestComponentModule.class,
|
||||
SecretManagerKeyringModule.class,
|
||||
SecretManagerModule.class,
|
||||
ServerTridProviderModule.class,
|
||||
StackdriverModule.class,
|
||||
|
||||
@@ -29,7 +29,7 @@ import google.registry.groups.GroupssettingsModule;
|
||||
import google.registry.keyring.KeyringModule;
|
||||
import google.registry.keyring.api.DummyKeyringModule;
|
||||
import google.registry.keyring.api.KeyModule;
|
||||
import google.registry.keyring.kms.KmsModule;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
|
||||
import google.registry.module.tools.ToolsRequestComponent.ToolsRequestComponentModule;
|
||||
import google.registry.monitoring.whitebox.StackdriverModule;
|
||||
import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
@@ -57,8 +57,8 @@ import javax.inject.Singleton;
|
||||
GsonModule.class,
|
||||
KeyModule.class,
|
||||
KeyringModule.class,
|
||||
KmsModule.class,
|
||||
NetHttpTransportModule.class,
|
||||
SecretManagerKeyringModule.class,
|
||||
SecretManagerModule.class,
|
||||
ServerTridProviderModule.class,
|
||||
StackdriverModule.class,
|
||||
|
||||
@@ -17,7 +17,7 @@ package google.registry.persistence;
|
||||
import dagger.Component;
|
||||
import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.keyring.kms.KmsModule;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
|
||||
import google.registry.persistence.PersistenceModule.AppEngineJpaTm;
|
||||
import google.registry.persistence.PersistenceModule.ReadOnlyReplicaJpaTm;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
@@ -32,8 +32,8 @@ import javax.persistence.EntityManagerFactory;
|
||||
modules = {
|
||||
ConfigModule.class,
|
||||
CredentialModule.class,
|
||||
KmsModule.class,
|
||||
PersistenceModule.class,
|
||||
SecretManagerKeyringModule.class,
|
||||
SecretManagerModule.class,
|
||||
UtilsModule.class
|
||||
})
|
||||
|
||||
@@ -29,12 +29,11 @@ import google.registry.dns.writer.dnsupdate.DnsUpdateWriterModule;
|
||||
import google.registry.keyring.KeyringModule;
|
||||
import google.registry.keyring.api.DummyKeyringModule;
|
||||
import google.registry.keyring.api.KeyModule;
|
||||
import google.registry.keyring.kms.KmsModule;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyringModule;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.persistence.PersistenceModule.NomulusToolJpaTm;
|
||||
import google.registry.persistence.PersistenceModule.ReadOnlyReplicaJpaTm;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.privileges.secretmanager.SecretManagerModule;
|
||||
import google.registry.rde.RdeModule;
|
||||
import google.registry.request.Modules.GsonModule;
|
||||
import google.registry.request.Modules.UrlConnectionServiceModule;
|
||||
@@ -70,13 +69,13 @@ import javax.inject.Singleton;
|
||||
GsonModule.class,
|
||||
KeyModule.class,
|
||||
KeyringModule.class,
|
||||
KmsModule.class,
|
||||
SecretManagerKeyringModule.class,
|
||||
LocalCredentialModule.class,
|
||||
PersistenceModule.class,
|
||||
RdeModule.class,
|
||||
RegistryToolDataflowModule.class,
|
||||
RequestFactoryModule.class,
|
||||
SecretManagerModule.class,
|
||||
google.registry.privileges.secretmanager.SecretManagerModule.class,
|
||||
UrlConnectionServiceModule.class,
|
||||
UrlFetchServiceModule.class,
|
||||
UserServiceModule.class,
|
||||
|
||||
@@ -20,7 +20,7 @@ import static google.registry.keyring.api.KeySerializer.deserializeString;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import google.registry.keyring.kms.KmsUpdater;
|
||||
import google.registry.keyring.secretmanager.SecretManagerKeyringUpdater;
|
||||
import google.registry.tools.params.KeyringKeyName;
|
||||
import google.registry.tools.params.PathParameter;
|
||||
import java.nio.file.Files;
|
||||
@@ -33,7 +33,7 @@ import javax.inject.Inject;
|
||||
@Parameters(separators = " =", commandDescription = "Update values of secret in the keyring.")
|
||||
final class UpdateKeyringSecretCommand implements CommandWithRemoteApi {
|
||||
|
||||
@Inject KmsUpdater kmsUpdater;
|
||||
@Inject SecretManagerKeyringUpdater secretManagerKeyringUpdater;
|
||||
|
||||
@Inject
|
||||
UpdateKeyringSecretCommand() {}
|
||||
@@ -55,35 +55,35 @@ final class UpdateKeyringSecretCommand implements CommandWithRemoteApi {
|
||||
|
||||
switch (keyringKeyName) {
|
||||
case BRDA_RECEIVER_PUBLIC_KEY:
|
||||
kmsUpdater.setBrdaReceiverPublicKey(deserializePublicKey(input));
|
||||
secretManagerKeyringUpdater.setBrdaReceiverPublicKey(deserializePublicKey(input));
|
||||
break;
|
||||
case BRDA_SIGNING_KEY_PAIR:
|
||||
kmsUpdater.setBrdaSigningKey(deserializeKeyPair(input));
|
||||
secretManagerKeyringUpdater.setBrdaSigningKey(deserializeKeyPair(input));
|
||||
break;
|
||||
case BRDA_SIGNING_PUBLIC_KEY:
|
||||
throw new IllegalArgumentException(
|
||||
"Can't update BRDA_SIGNING_PUBLIC_KEY directly."
|
||||
+ " Must update public and private keys together using BRDA_SIGNING_KEY_PAIR.");
|
||||
case ICANN_REPORTING_PASSWORD:
|
||||
kmsUpdater.setIcannReportingPassword(deserializeString(input));
|
||||
secretManagerKeyringUpdater.setIcannReportingPassword(deserializeString(input));
|
||||
break;
|
||||
case JSON_CREDENTIAL:
|
||||
kmsUpdater.setJsonCredential(deserializeString(input));
|
||||
secretManagerKeyringUpdater.setJsonCredential(deserializeString(input));
|
||||
break;
|
||||
case MARKSDB_DNL_LOGIN_AND_PASSWORD:
|
||||
kmsUpdater.setMarksdbDnlLoginAndPassword(deserializeString(input));
|
||||
secretManagerKeyringUpdater.setMarksdbDnlLoginAndPassword(deserializeString(input));
|
||||
break;
|
||||
case MARKSDB_LORDN_PASSWORD:
|
||||
kmsUpdater.setMarksdbLordnPassword(deserializeString(input));
|
||||
secretManagerKeyringUpdater.setMarksdbLordnPassword(deserializeString(input));
|
||||
break;
|
||||
case MARKSDB_SMDRL_LOGIN_AND_PASSWORD:
|
||||
kmsUpdater.setMarksdbSmdrlLoginAndPassword(deserializeString(input));
|
||||
secretManagerKeyringUpdater.setMarksdbSmdrlLoginAndPassword(deserializeString(input));
|
||||
break;
|
||||
case RDE_RECEIVER_PUBLIC_KEY:
|
||||
kmsUpdater.setRdeReceiverPublicKey(deserializePublicKey(input));
|
||||
secretManagerKeyringUpdater.setRdeReceiverPublicKey(deserializePublicKey(input));
|
||||
break;
|
||||
case RDE_SIGNING_KEY_PAIR:
|
||||
kmsUpdater.setRdeSigningKey(deserializeKeyPair(input));
|
||||
secretManagerKeyringUpdater.setRdeSigningKey(deserializeKeyPair(input));
|
||||
break;
|
||||
case RDE_SIGNING_PUBLIC_KEY:
|
||||
throw new IllegalArgumentException(
|
||||
@@ -95,16 +95,16 @@ final class UpdateKeyringSecretCommand implements CommandWithRemoteApi {
|
||||
//
|
||||
// Hence we can and need to update the private and public keys individually.
|
||||
case RDE_SSH_CLIENT_PRIVATE_KEY:
|
||||
kmsUpdater.setRdeSshClientPrivateKey(deserializeString(input));
|
||||
secretManagerKeyringUpdater.setRdeSshClientPrivateKey(deserializeString(input));
|
||||
break;
|
||||
case RDE_SSH_CLIENT_PUBLIC_KEY:
|
||||
kmsUpdater.setRdeSshClientPublicKey(deserializeString(input));
|
||||
secretManagerKeyringUpdater.setRdeSshClientPublicKey(deserializeString(input));
|
||||
break;
|
||||
case RDE_STAGING_KEY_PAIR:
|
||||
kmsUpdater.setRdeStagingKey(deserializeKeyPair(input));
|
||||
secretManagerKeyringUpdater.setRdeStagingKey(deserializeKeyPair(input));
|
||||
break;
|
||||
case SAFE_BROWSING_API_KEY:
|
||||
kmsUpdater.setSafeBrowsingAPIKey(deserializeString(input));
|
||||
secretManagerKeyringUpdater.setSafeBrowsingAPIKey(deserializeString(input));
|
||||
break;
|
||||
case RDE_STAGING_PUBLIC_KEY:
|
||||
throw new IllegalArgumentException(
|
||||
@@ -112,7 +112,7 @@ final class UpdateKeyringSecretCommand implements CommandWithRemoteApi {
|
||||
+ " Must update public and private keys together using RDE_STAGING_KEY_PAIR.");
|
||||
}
|
||||
|
||||
kmsUpdater.update();
|
||||
secretManagerKeyringUpdater.update();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user