This is an automated email from the ASF dual-hosted git repository.
thenatog pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new e78674e NIFI-9184 Refactored shared methods in Sensitive Property
Providers
e78674e is described below
commit e78674ec596bdd806b06a24a63be7714e1572390
Author: exceptionfactory <[email protected]>
AuthorDate: Tue Aug 31 23:02:37 2021 -0500
NIFI-9184 Refactored shared methods in Sensitive Property Providers
- Added EncodedSensitivePropertiesProvider with Base64 encoding methods
- Added ClientBasedEncodedSensitivePropertiesProvider with validate method
- Abstracted client configuration to ClientProvider interface and
implementations
- Added unit tests for AWS and Azure Property Providers
NIFI-9184 Adjusted abstract provider class names and updated documentation
Signed-off-by: Nathan Gough <[email protected]>
This closes #5363.
---
.../AbstractBootstrapPropertiesLoader.java | 6 +-
.../AWSKMSSensitivePropertyProvider.java | 338 ---------------------
.../AwsKmsSensitivePropertyProvider.java | 121 ++++++++
.../AzureKeyVaultKeySensitivePropertyProvider.java | 298 ++++--------------
...lientBasedEncodedSensitivePropertyProvider.java | 94 ++++++
.../EncodedSensitivePropertyProvider.java | 117 +++++++
.../GCPKMSSensitivePropertyProvider.java | 288 ------------------
.../GcpKmsSensitivePropertyProvider.java | 115 +++++++
.../StandardSensitivePropertyProviderFactory.java | 35 ++-
.../configuration/AwsKmsClientProvider.java | 80 +++++
.../AzureCryptographyClientProvider.java | 57 ++++
.../BootstrapPropertiesClientProvider.java | 99 ++++++
.../properties/configuration/ClientProvider.java | 45 +++
.../GoogleKeyManagementServiceClientProvider.java | 49 +++
...java => AwsKmsSensitivePropertyProviderIT.java} | 13 +-
.../AwsKmsSensitivePropertyProviderTest.java | 119 ++++++++
...zureKeyVaultKeySensitivePropertyProviderIT.java | 9 +-
...reKeyVaultKeySensitivePropertyProviderTest.java | 110 +++++++
...java => GcpKmsSensitivePropertyProviderIT.java} | 23 +-
...andardSensitivePropertyProviderFactoryTest.java | 18 +-
.../src/main/asciidoc/administration-guide.adoc | 2 +-
.../src/main/resources/conf/bootstrap.conf | 8 +-
.../src/main/resources/conf/bootstrap.conf | 8 +-
23 files changed, 1138 insertions(+), 914 deletions(-)
diff --git
a/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/AbstractBootstrapPropertiesLoader.java
b/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/AbstractBootstrapPropertiesLoader.java
index 0bc378e..aa699e7 100644
---
a/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/AbstractBootstrapPropertiesLoader.java
+++
b/nifi-commons/nifi-property-utils/src/main/java/org/apache/nifi/properties/AbstractBootstrapPropertiesLoader.java
@@ -136,7 +136,7 @@ public abstract class AbstractBootstrapPropertiesLoader {
if (confDir.exists() && confDir.canRead()) {
expectedBootstrapFile = new File(confDir, BOOTSTRAP_CONF);
} else {
- throw new IOException(String.format("Cannot read %s directory
for %s", confDir, bootstrapPath));
+ throw new IOException(String.format("Configuration Directory
[%s] not found for Bootstrap Properties", confDir));
}
} else {
expectedBootstrapFile = new File(bootstrapPath);
@@ -164,12 +164,12 @@ public abstract class AbstractBootstrapPropertiesLoader {
String systemPath = System.getProperty(systemPropertyName);
if (systemPath == null || systemPath.trim().isEmpty()) {
- logger.warn("The system property {} is not set, so it is being set
to '{}'", systemPropertyName, defaultRelativePath);
+ logger.warn("System Property [{}] not found: Using Relative Path
[{}]", systemPropertyName, defaultRelativePath);
System.setProperty(systemPropertyName, defaultRelativePath);
systemPath = defaultRelativePath;
}
- logger.info("Determined default application properties path to be
'{}'", systemPath);
+ logger.debug("Default Application Properties Path [{}]", systemPath);
return systemPath;
}
}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AWSKMSSensitivePropertyProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AWSKMSSensitivePropertyProvider.java
deleted file mode 100644
index 35a04af..0000000
---
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AWSKMSSensitivePropertyProvider.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.nifi.properties;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.properties.BootstrapProperties.BootstrapPropertyKey;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
-import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
-import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
-import software.amazon.awssdk.core.SdkBytes;
-import software.amazon.awssdk.core.exception.SdkClientException;
-import software.amazon.awssdk.regions.Region;
-import software.amazon.awssdk.services.kms.KmsClient;
-import software.amazon.awssdk.services.kms.model.DecryptRequest;
-import software.amazon.awssdk.services.kms.model.DecryptResponse;
-import software.amazon.awssdk.services.kms.model.DescribeKeyRequest;
-import software.amazon.awssdk.services.kms.model.DescribeKeyResponse;
-import software.amazon.awssdk.services.kms.model.EncryptRequest;
-import software.amazon.awssdk.services.kms.model.EncryptResponse;
-import software.amazon.awssdk.services.kms.model.KeyMetadata;
-import software.amazon.awssdk.services.kms.model.KmsException;
-
-import java.util.Base64;
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Paths;
-import java.util.Objects;
-
-public class AWSKMSSensitivePropertyProvider extends
AbstractSensitivePropertyProvider {
- private static final Logger logger =
LoggerFactory.getLogger(AWSKMSSensitivePropertyProvider.class);
-
- private static final String AWS_PREFIX = "aws";
- private static final String ACCESS_KEY_PROPS_NAME = "aws.access.key.id";
- private static final String SECRET_KEY_PROPS_NAME =
"aws.secret.access.key";
- private static final String REGION_KEY_PROPS_NAME = "aws.region";
- private static final String KMS_KEY_PROPS_NAME = "aws.kms.key.id";
-
- private static final Charset PROPERTY_CHARSET = StandardCharsets.UTF_8;
-
- private final BootstrapProperties awsBootstrapProperties;
- private KmsClient client;
- private String keyId;
-
-
- AWSKMSSensitivePropertyProvider(final BootstrapProperties
bootstrapProperties) throws SensitivePropertyProtectionException {
- super(bootstrapProperties);
- Objects.requireNonNull(bootstrapProperties, "The file bootstrap.conf
provided to AWS SPP is null");
- awsBootstrapProperties =
getAWSBootstrapProperties(bootstrapProperties);
- loadRequiredAWSProperties(awsBootstrapProperties);
- }
-
- /**
- * Initializes the KMS Client to be used for encrypt, decrypt and other
interactions with AWS KMS.
- * First attempts to use credentials/configuration in bootstrap-aws.conf.
- * If credentials/configuration in bootstrap-aws.conf is not fully
configured,
- * attempt to initialize credentials using default AWS
credentials/configuration chain.
- * Note: This does not verify if credentials are valid.
- */
- private void initializeClient() {
- if (awsBootstrapProperties == null) {
- logger.warn("AWS Bootstrap Properties are required for KMS Client
initialization");
- return;
- }
- final String accessKey =
awsBootstrapProperties.getProperty(ACCESS_KEY_PROPS_NAME);
- final String secretKey =
awsBootstrapProperties.getProperty(SECRET_KEY_PROPS_NAME);
- final String region =
awsBootstrapProperties.getProperty(REGION_KEY_PROPS_NAME);
-
- if (StringUtils.isNoneBlank(accessKey, secretKey, region)) {
- logger.debug("Using AWS credentials from bootstrap properties");
- try {
- final AwsBasicCredentials credentials =
AwsBasicCredentials.create(accessKey, secretKey);
- client = KmsClient.builder()
- .region(Region.of(region))
-
.credentialsProvider(StaticCredentialsProvider.create(credentials))
- .build();
- } catch (final RuntimeException e) {
- final String msg = "Valid configuration/credentials are
required to initialize KMS client";
- throw new SensitivePropertyProtectionException(msg, e);
- }
- } else {
- logger.debug("Using AWS credentials from default credentials
provider");
- try {
- final DefaultCredentialsProvider credentialsProvider =
DefaultCredentialsProvider.builder()
- .build();
- credentialsProvider.resolveCredentials();
- client = KmsClient.builder()
- .credentialsProvider(credentialsProvider)
- .build();
- } catch (final SdkClientException e) {
- final String msg = "Valid configuration/credentials are
required to initialize KMS client";
- throw new SensitivePropertyProtectionException(msg, e);
- }
- }
- }
-
- /**
- * Validates the key ARN, credentials and configuration provided by the
user.
- * Note: This function performs checks on the key and indirectly also
validates the credentials and
- * configurations provided during the initialization of the client.
- */
- private void validate() throws KmsException,
SensitivePropertyProtectionException {
- if (client == null) {
- final String msg = "The AWS KMS Client failed to open, cannot
validate key";
- throw new SensitivePropertyProtectionException(msg);
- }
- if (StringUtils.isBlank(keyId)) {
- final String msg = "The AWS KMS key provided is blank";
- throw new SensitivePropertyProtectionException(msg);
- }
-
- // asking for a Key Description is the best way to check whether a key
is valid
- // because AWS KMS accepts various formats for its keys.
- final DescribeKeyRequest request = DescribeKeyRequest.builder()
- .keyId(keyId)
- .build();
-
- // using the KmsClient in a DescribeKey request indirectly also
verifies if the credentials provided
- // during the initialization of the key are valid
- final DescribeKeyResponse response = client.describeKey(request);
- final KeyMetadata metadata = response.keyMetadata();
-
- if (!metadata.enabled()) {
- final String msg = String.format("AWS KMS key [%s] is not
enabled", keyId);
- throw new SensitivePropertyProtectionException(msg);
- }
- }
-
- /**
- * Checks if we have a key ID from AWS KMS and loads it into {@link
#keyId}. Will load null if key is not present.
- * Note: This function does not verify if the key is correctly
formatted/valid.
- * @param props the properties representing bootstrap-aws.conf
- */
- private void loadRequiredAWSProperties(final BootstrapProperties props) {
- if (props != null) {
- keyId = props.getProperty(KMS_KEY_PROPS_NAME);
- }
- }
-
-
- /**
- * Checks bootstrap.conf to check if
BootstrapPropertyKey.AWS_KMS_SENSITIVE_PROPERTY_PROVIDER_CONF property is
- * configured to the bootstrap-aws.conf file. Also will try to load
bootstrap-aws.conf to {@link #awsBootstrapProperties}.
- * @param bootstrapProperties BootstrapProperties object corresponding to
bootstrap.conf.
- * @return BootstrapProperties object corresponding to bootstrap-aws.conf,
null otherwise.
- */
- private BootstrapProperties getAWSBootstrapProperties(final
BootstrapProperties bootstrapProperties) {
- final BootstrapProperties cloudBootstrapProperties;
-
- // Load the bootstrap-aws.conf file based on path specified in
- // "nifi.bootstrap.protection.aws.kms.conf" property of bootstrap.conf
- final String filePath =
bootstrapProperties.getProperty(BootstrapPropertyKey.AWS_KMS_SENSITIVE_PROPERTY_PROVIDER_CONF).orElse(null);
- if (StringUtils.isBlank(filePath)) {
- logger.warn("AWS KMS properties file path not configured in
bootstrap properties");
- return null;
- }
-
- try {
- cloudBootstrapProperties =
AbstractBootstrapPropertiesLoader.loadBootstrapProperties(
- Paths.get(filePath), AWS_PREFIX);
- } catch (final IOException e) {
- throw new SensitivePropertyProtectionException("Could not load " +
filePath, e);
- }
-
- return cloudBootstrapProperties;
- }
-
- /**
- * Checks bootstrap-aws.conf for the required configurations for AWS KMS
encrypt/decrypt operations.
- * Note: This does not check for credentials/region configurations.
- * Credentials/configuration will be checked during the first
protect/unprotect call during runtime.
- * @return true if bootstrap-aws.conf contains the required properties for
AWS SPP, false otherwise.
- */
- private boolean hasRequiredAWSProperties() {
- return awsBootstrapProperties != null && StringUtils.isNotBlank(keyId);
- }
-
- @Override
- public boolean isSupported() {
- return hasRequiredAWSProperties();
- }
-
- @Override
- protected PropertyProtectionScheme getProtectionScheme() {
- return PropertyProtectionScheme.AWS_KMS;
- }
-
- /**
- * Returns the name of the underlying implementation.
- *
- * @return the name of this sensitive property provider.
- */
- @Override
- public String getName() {
- return PropertyProtectionScheme.AWS_KMS.getName();
- }
-
- /**
- * Returns the key used to identify the provider implementation in {@code
nifi.properties}.
- *
- * @return the key to persist in the sibling property.
- */
- @Override
- public String getIdentifierKey() {
- return PropertyProtectionScheme.AWS_KMS.getIdentifier();
- }
-
-
- /**
- * Returns the ciphertext blob of this value encrypted using an AWS KMS
CMK.
- *
- * @return the ciphertext blob to persist in the {@code nifi.properties}
file.
- */
- private byte[] encrypt(final byte[] input) {
- final SdkBytes plainBytes = SdkBytes.fromByteArray(input);
-
- final EncryptRequest encryptRequest = EncryptRequest.builder()
- .keyId(keyId)
- .plaintext(plainBytes)
- .build();
-
- final EncryptResponse response = client.encrypt(encryptRequest);
- final SdkBytes encryptedData = response.ciphertextBlob();
-
- return encryptedData.asByteArray();
- }
-
- /**
- * Returns the value corresponding to a ciphertext blob decrypted using an
AWS KMS CMK.
- *
- * @return the "unprotected" byte[] of this value, which could be used by
the application.
- */
- private byte[] decrypt(final byte[] input) {
- final SdkBytes cipherBytes = SdkBytes.fromByteArray(input);
-
- final DecryptRequest decryptRequest = DecryptRequest.builder()
- .ciphertextBlob(cipherBytes)
- .keyId(keyId)
- .build();
-
- final DecryptResponse response = client.decrypt(decryptRequest);
- final SdkBytes decryptedData = response.plaintext();
-
- return decryptedData.asByteArray();
- }
-
- /**
- * Checks if the client is open and if not, initializes the client and
validates the key required for AWS KMS.
- */
- private void checkAndInitializeClient() throws
SensitivePropertyProtectionException {
- if (client == null) {
- try {
- initializeClient();
- validate();
- } catch (final SdkClientException | KmsException |
SensitivePropertyProtectionException e) {
- throw new SensitivePropertyProtectionException("Error
initializing the AWS KMS Client", e);
- }
- }
- }
-
- /**
- * Returns the "protected" form of this value. This is a form which can
safely be persisted in the {@code nifi.properties} file without compromising
the value.
- * Encrypts a sensitive value using a key managed by AWS Key Management
Service.
- *
- * @param unprotectedValue the sensitive value.
- * @param context The context of the value (ignored in this implementation)
- * @return the value to persist in the {@code nifi.properties} file.
- */
- @Override
- public String protect(final String unprotectedValue, final
ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
- if (StringUtils.isBlank(unprotectedValue)) {
- throw new IllegalArgumentException("Cannot encrypt a blank value");
- }
-
- checkAndInitializeClient();
-
- try {
- final byte[] plainBytes =
unprotectedValue.getBytes(PROPERTY_CHARSET);
- final byte[] cipherBytes = encrypt(plainBytes);
- return Base64.getEncoder().encodeToString(cipherBytes);
- } catch (final SdkClientException | KmsException e) {
- throw new SensitivePropertyProtectionException("Encrypt failed",
e);
- }
- }
-
- /**
- * Returns the "unprotected" form of this value. This is the raw sensitive
value which is used by the application logic.
- * Decrypts a secured value from a ciphertext using a key managed by AWS
Key Management Service.
- *
- * @param protectedValue the protected value read from the {@code
nifi.properties} file.
- * @param context The context of the value (ignored in this implementation)
- * @return the raw value to be used by the application.
- */
- @Override
- public String unprotect(final String protectedValue, final
ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
- if (StringUtils.isBlank(protectedValue)) {
- throw new IllegalArgumentException("Cannot decrypt a blank value");
- }
-
- checkAndInitializeClient();
-
- try {
- final byte[] cipherBytes =
Base64.getDecoder().decode(protectedValue);
- final byte[] plainBytes = decrypt(cipherBytes);
- return new String(plainBytes, PROPERTY_CHARSET);
- } catch (final SdkClientException | KmsException e) {
- throw new SensitivePropertyProtectionException("Decrypt failed",
e);
- }
- }
-
- /**
- * Closes AWS KMS client that may have been opened.
- */
- @Override
- public void cleanUp() {
- if (client != null) {
- client.close();
- client = null;
- }
- }
-}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java
new file mode 100644
index 0000000..e9cb4ae
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProvider.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.nifi.properties;
+
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.services.kms.KmsClient;
+import software.amazon.awssdk.services.kms.model.DecryptRequest;
+import software.amazon.awssdk.services.kms.model.DecryptResponse;
+import software.amazon.awssdk.services.kms.model.DescribeKeyRequest;
+import software.amazon.awssdk.services.kms.model.DescribeKeyResponse;
+import software.amazon.awssdk.services.kms.model.EncryptRequest;
+import software.amazon.awssdk.services.kms.model.EncryptResponse;
+import software.amazon.awssdk.services.kms.model.KeyMetadata;
+
+import java.util.Properties;
+
+/**
+ * Amazon Web Services Key Management Service Sensitive Property Provider
+ */
+public class AwsKmsSensitivePropertyProvider extends
ClientBasedEncodedSensitivePropertyProvider<KmsClient> {
+ protected static final String KEY_ID_PROPERTY = "aws.kms.key.id";
+
+ AwsKmsSensitivePropertyProvider(final KmsClient kmsClient, final
Properties properties) throws SensitivePropertyProtectionException {
+ super(PropertyProtectionScheme.AWS_KMS, kmsClient, properties);
+ }
+
+ /**
+ * Close KMS Client when configured
+ */
+ @Override
+ public void cleanUp() {
+ final KmsClient kmsClient = getClient();
+ if (kmsClient == null) {
+ logger.debug("AWS KMS Client not configured");
+ } else {
+ kmsClient.close();
+ }
+ }
+
+ /**
+ * Validate Client and Key Identifier status when client is configured
+ *
+ * @param kmsClient KMS Client
+ */
+ @Override
+ protected void validate(final KmsClient kmsClient) {
+ if (kmsClient == null) {
+ logger.debug("AWS KMS Client not configured");
+ } else {
+ final String keyId = getKeyId();
+ try {
+ final DescribeKeyRequest describeKeyRequest =
DescribeKeyRequest.builder()
+ .keyId(keyId)
+ .build();
+ final DescribeKeyResponse describeKeyResponse =
kmsClient.describeKey(describeKeyRequest);
+ final KeyMetadata keyMetadata =
describeKeyResponse.keyMetadata();
+ if (keyMetadata.enabled()) {
+ logger.info("AWS KMS Key [{}] Enabled", keyId);
+ } else {
+ throw new
SensitivePropertyProtectionException(String.format("AWS KMS Key [%s] Disabled",
keyId));
+ }
+ } catch (final RuntimeException e) {
+ throw new
SensitivePropertyProtectionException(String.format("AWS KMS Key [%s] Validation
Failed", keyId), e);
+ }
+ }
+ }
+
+ /**
+ * Get encrypted bytes
+ *
+ * @param bytes Unprotected bytes
+ * @return Encrypted bytes
+ */
+ @Override
+ protected byte[] getEncrypted(final byte[] bytes) {
+ final SdkBytes plainBytes = SdkBytes.fromByteArray(bytes);
+ final EncryptRequest encryptRequest = EncryptRequest.builder()
+ .keyId(getKeyId())
+ .plaintext(plainBytes)
+ .build();
+ final EncryptResponse response = getClient().encrypt(encryptRequest);
+ final SdkBytes encryptedData = response.ciphertextBlob();
+ return encryptedData.asByteArray();
+ }
+
+ /**
+ * Get decrypted bytes
+ *
+ * @param bytes Encrypted bytes
+ * @return Decrypted bytes
+ */
+ @Override
+ protected byte[] getDecrypted(final byte[] bytes) {
+ final SdkBytes cipherBytes = SdkBytes.fromByteArray(bytes);
+ final DecryptRequest decryptRequest = DecryptRequest.builder()
+ .ciphertextBlob(cipherBytes)
+ .keyId(getKeyId())
+ .build();
+ final DecryptResponse response = getClient().decrypt(decryptRequest);
+ final SdkBytes decryptedData = response.plaintext();
+ return decryptedData.asByteArray();
+ }
+
+ private String getKeyId() {
+ return getProperties().getProperty(KEY_ID_PROPERTY);
+ }
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java
index a5adf1a..f8f8fbe 100644
---
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProvider.java
@@ -16,282 +16,90 @@
*/
package org.apache.nifi.properties;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.properties.BootstrapProperties.BootstrapPropertyKey;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.azure.core.exception.ResourceNotFoundException;
-import com.azure.identity.DefaultAzureCredentialBuilder;
+import com.azure.security.keyvault.keys.models.KeyVaultKey;
import com.azure.security.keyvault.keys.cryptography.CryptographyClient;
-import com.azure.security.keyvault.keys.cryptography.CryptographyClientBuilder;
import com.azure.security.keyvault.keys.cryptography.models.DecryptResult;
import com.azure.security.keyvault.keys.cryptography.models.EncryptResult;
import
com.azure.security.keyvault.keys.cryptography.models.EncryptionAlgorithm;
import com.azure.security.keyvault.keys.models.KeyOperation;
import com.azure.security.keyvault.keys.models.KeyProperties;
-import java.util.Base64;
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.Objects;
-
-public class AzureKeyVaultKeySensitivePropertyProvider extends
AbstractSensitivePropertyProvider {
- private static final Logger logger =
LoggerFactory.getLogger(AzureKeyVaultKeySensitivePropertyProvider.class);
-
- private static final String AZURE_PREFIX = "azure";
- private static final String KEYVAULT_KEY_PROPS_NAME =
"azure.keyvault.key.id";
- private static final String ENCRYPTION_ALGORITHM_PROPS_NAME =
"azure.keyvault.encryption.algorithm";
-
- private static final Charset PROPERTY_CHARSET = StandardCharsets.UTF_8;
+import org.apache.nifi.util.StringUtils;
- private final BootstrapProperties azureBootstrapProperties;
- private CryptographyClient client;
- private String keyId;
- private String algorithm;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
- AzureKeyVaultKeySensitivePropertyProvider(final BootstrapProperties
bootstrapProperties) throws SensitivePropertyProtectionException {
- super(bootstrapProperties);
- Objects.requireNonNull(bootstrapProperties, "Bootstrap Properties
required");
- azureBootstrapProperties =
getAzureBootstrapProperties(bootstrapProperties);
- loadRequiredAzureProperties(azureBootstrapProperties);
- }
+/**
+ * Microsoft Azure Key Vault Key Sensitive Property Provider using
Cryptography Client for encryption operations
+ */
+public class AzureKeyVaultKeySensitivePropertyProvider extends
ClientBasedEncodedSensitivePropertyProvider<CryptographyClient> {
+ protected static final String ENCRYPTION_ALGORITHM_PROPERTY =
"azure.keyvault.encryption.algorithm";
- /**
- * Initializes the Azure Key Vault Cryptography Client to be used for
encrypt, decrypt and other interactions with Azure Key Vault.
- * Uses the default Azure credentials provider chain.
- */
- private void initializeClient() {
- if (azureBootstrapProperties == null) {
- logger.warn("Azure Bootstrap Properties are required for Key Vault
Client initialization");
- return;
- }
+ protected static final List<KeyOperation> REQUIRED_OPERATIONS =
Arrays.asList(KeyOperation.DECRYPT, KeyOperation.ENCRYPT);
- if (StringUtils.isBlank(keyId)) {
- logger.warn("Cannot initialize client if Azure Key Vault Key ID is
blank");
- return;
- }
+ private EncryptionAlgorithm encryptionAlgorithm;
- try {
- client = new CryptographyClientBuilder()
- .credential(new DefaultAzureCredentialBuilder().build())
- .keyIdentifier(keyId)
- .buildClient();
- } catch (final RuntimeException e) {
- throw new SensitivePropertyProtectionException("Azure Key Vault
Client initialization failed", e);
- }
+ AzureKeyVaultKeySensitivePropertyProvider(final CryptographyClient
cryptographyClient, final Properties properties) {
+ super(PropertyProtectionScheme.AZURE_KEYVAULT_KEY, cryptographyClient,
properties);
}
/**
- * Validates the key provided by the user.
- * Note: This function performs checks on the key and indirectly also
validates the credentials provided
- * during the initialization of the client.
+ * Validate Client and Key Operations with Encryption Algorithm when
configured
+ *
+ * @param cryptographyClient Cryptography Client
*/
- private void validate() throws SensitivePropertyProtectionException {
- if (client == null) {
- throw new SensitivePropertyProtectionException("Azure Key Vault
validation failed: Client not initialized");
- }
-
- if (StringUtils.isBlank(keyId)) {
- throw new SensitivePropertyProtectionException("Azure Key Vault
validation failed: Key not specified");
- }
-
- try {
- final KeyProperties keyProps = client.getKey().getProperties();
- if (!keyProps.isEnabled()) {
- throw new SensitivePropertyProtectionException("Azure Key
Vault validation failed: Key not enabled");
+ @Override
+ protected void validate(final CryptographyClient cryptographyClient) {
+ if (cryptographyClient == null) {
+ logger.debug("Azure Cryptography Client not configured");
+ } else {
+ try {
+ final KeyVaultKey keyVaultKey = cryptographyClient.getKey();
+ final String id = keyVaultKey.getId();
+ final KeyProperties keyProperties =
keyVaultKey.getProperties();
+ if (keyProperties.isEnabled()) {
+ final List<KeyOperation> keyOperations =
keyVaultKey.getKeyOperations();
+ if (keyOperations.containsAll(REQUIRED_OPERATIONS)) {
+ logger.info("Azure Key Vault Key [{}] Validated", id);
+ } else {
+ throw new
SensitivePropertyProtectionException(String.format("Azure Key Vault Key [%s]
Missing Operations %s", id, REQUIRED_OPERATIONS));
+ }
+ } else {
+ throw new
SensitivePropertyProtectionException(String.format("Azure Key Vault Key [%s]
Disabled", id));
+ }
+ } catch (final RuntimeException e) {
+ throw new SensitivePropertyProtectionException("Azure Key
Vault Key Validation Failed", e);
}
-
- final List<KeyOperation> keyOps =
client.getKey().getKeyOperations();
- if (!(keyOps.contains(KeyOperation.ENCRYPT) &&
keyOps.contains(KeyOperation.DECRYPT))) {
- throw new SensitivePropertyProtectionException("Azure Key
Vault validation failed: Encrypt and Decrypt not supported");
+ final String algorithm =
getProperties().getProperty(ENCRYPTION_ALGORITHM_PROPERTY);
+ if (StringUtils.isBlank(algorithm)) {
+ throw new SensitivePropertyProtectionException("Azure Key
Vault Key Algorithm not configured");
}
- } catch (final ResourceNotFoundException e) {
- throw new SensitivePropertyProtectionException("Azure Key Vault
validation failed: Key not found", e);
- } catch (final RuntimeException e) {
- throw new SensitivePropertyProtectionException("Azure Key Vault
validation failed", e);
+ encryptionAlgorithm = EncryptionAlgorithm.fromString(algorithm);
}
}
/**
- * Checks if we have the required properties {@link #keyId} and {@link
#algorithm} from bootstrap-azure.conf
- * for Azure KeyVault and loads it into the appropriate variables, will
load null if values don't exist.
- * Note: This function does not verify if the properties are valid.
- * @param props the properties representing bootstrap-azure.conf
- */
- private void loadRequiredAzureProperties(final BootstrapProperties props) {
- if (props != null) {
- keyId = props.getProperty(KEYVAULT_KEY_PROPS_NAME);
- algorithm = props.getProperty(ENCRYPTION_ALGORITHM_PROPS_NAME);
- }
- }
-
-
- /**
- * Checks bootstrap.conf to check if
BootstrapPropertyKey.AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF property
is configured to the
- * bootstrap-azure.conf file. Also will load bootstrap-azure.conf to
{@link #azureBootstrapProperties} if possible
- * @param bootstrapProperties BootstrapProperties object corresponding to
bootstrap.conf
- * @return BootstrapProperties object corresponding to
bootstrap-azure.conf, null otherwise
- */
- private BootstrapProperties getAzureBootstrapProperties(final
BootstrapProperties bootstrapProperties) {
- final BootstrapProperties cloudBootstrapProperties;
-
- // Load the bootstrap-azure.conf file based on path specified in
- // "nifi.bootstrap.protection.azure.keyvault.conf" property of
bootstrap.conf
- final String filePath =
bootstrapProperties.getProperty(BootstrapPropertyKey.AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF).orElse(null);
- if (StringUtils.isBlank(filePath)) {
- logger.warn("Azure Key Vault properties file path not configured
in bootstrap properties");
- return null;
- }
-
- try {
- cloudBootstrapProperties =
AbstractBootstrapPropertiesLoader.loadBootstrapProperties(
- Paths.get(filePath), AZURE_PREFIX);
- } catch (final IOException e) {
- throw new SensitivePropertyProtectionException("Could not load " +
filePath, e);
- }
-
- return cloudBootstrapProperties;
- }
-
- /**
- * Checks the BootstrapProperties corresponding to bootstrap-azure.conf
for the required configurations
- * for Azure encrypt/decrypt operations.
- * Note: This does not check for credentials/region configurations.
- * Credentials/configuration will be checked during the first
protect/unprotect call during runtime.
- * @return True if bootstrap-azure.conf contains the required properties
for Azure SPP, False otherwise
- */
- private boolean hasRequiredAzureProperties() {
- return azureBootstrapProperties != null &&
StringUtils.isNoneBlank(keyId, algorithm);
- }
-
- /**
- * Return true if this SensitivePropertyProvider is supported, given the
provided Bootstrap properties.
- * @return True if this SensitivePropertyProvider is supported
- */
- @Override
- public boolean isSupported() {
- return hasRequiredAzureProperties();
- }
-
- /**
- * Return the appropriate PropertyProtectionScheme for this provider.
+ * Get encrypted bytes
*
- * @return The PropertyProtectionScheme
+ * @param bytes Unprotected bytes
+ * @return Encrypted bytes
*/
@Override
- protected PropertyProtectionScheme getProtectionScheme() {
- return PropertyProtectionScheme.AZURE_KEYVAULT_KEY;
- }
-
- /**
- * Returns the name of the underlying implementation.
- *
- * @return the name of this sensitive property provider
- */
- @Override
- public String getName() {
- return PropertyProtectionScheme.AZURE_KEYVAULT_KEY.getName();
- }
-
- /**
- * Returns the key used to identify the provider implementation in {@code
nifi.properties}.
- *
- * @return the key to persist in the sibling property
- */
- @Override
- public String getIdentifierKey() {
- return PropertyProtectionScheme.AZURE_KEYVAULT_KEY.getIdentifier();
- }
-
-
- /**
- * Returns the ciphertext of this value encrypted using a key stored in
Azure Key Vault.
- *
- * @return the ciphertext blob to persist in the {@code nifi.properties}
file
- */
- private byte[] encrypt(final byte[] input) {
- EncryptResult encryptResult =
client.encrypt(EncryptionAlgorithm.fromString(algorithm), input);
+ protected byte[] getEncrypted(final byte[] bytes) {
+ final EncryptResult encryptResult =
getClient().encrypt(encryptionAlgorithm, bytes);
return encryptResult.getCipherText();
}
/**
- * Returns the value corresponding to a ciphertext decrypted using a key
stored in Azure Key Vault
- *
- * @return the "unprotected" byte[] of this value, which could be used by
the application
- */
- private byte[] decrypt(final byte[] input) {
- DecryptResult decryptResult =
client.decrypt(EncryptionAlgorithm.fromString(algorithm), input);
- return decryptResult.getPlainText();
- }
-
- /**
- * Checks if the client is open and if not, initializes the client and
validates the configuration required for Azure Key Vault.
- */
- private void checkAndInitializeClient() {
- if (client == null) {
- initializeClient();
- validate();
- }
- }
-
- /**
- * Returns the "protected" form of this value. This is a form which can
safely be persisted in the {@code nifi.properties} file without compromising
the value.
- * Encrypts a sensitive value using a key managed by Azure Key Vault.
+ * Get decrypted bytes
*
- * @param unprotectedValue the sensitive value
- * @param context The context of the value (ignored in this implementation)
- * @return the value to persist in the {@code nifi.properties} file
+ * @param bytes Encrypted bytes
+ * @return Decrypted bytes
*/
@Override
- public String protect(final String unprotectedValue, final
ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
- if (StringUtils.isBlank(unprotectedValue)) {
- throw new IllegalArgumentException("Cannot encrypt a blank value");
- }
-
- checkAndInitializeClient();
-
- try {
- final byte[] plainBytes =
unprotectedValue.getBytes(PROPERTY_CHARSET);
- final byte[] cipherBytes = encrypt(plainBytes);
- return Base64.getEncoder().encodeToString(cipherBytes);
- } catch (final RuntimeException e) {
- throw new SensitivePropertyProtectionException("Encrypt failed",
e);
- }
- }
-
- /**
- * Returns the "unprotected" form of this value. This is the raw sensitive
value which is used by the application logic.
- * Decrypts a secured value from a ciphertext using a key managed by Azure
Key Vault.
- *
- * @param protectedValue the protected value read from the {@code
nifi.properties} file
- * @param context The context of the value (ignored in this implementation)
- * @return the raw value to be used by the application
- */
- @Override
- public String unprotect(final String protectedValue, final
ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
- if (StringUtils.isBlank(protectedValue)) {
- throw new IllegalArgumentException("Cannot decrypt a blank value");
- }
-
- checkAndInitializeClient();
-
- try {
- final byte[] cipherBytes =
Base64.getDecoder().decode(protectedValue);
- final byte[] plainBytes = decrypt(cipherBytes);
- return new String(plainBytes, PROPERTY_CHARSET);
- } catch (final RuntimeException e) {
- throw new SensitivePropertyProtectionException("Decrypt failed",
e);
- }
+ protected byte[] getDecrypted(final byte[] bytes) {
+ final DecryptResult decryptResult =
getClient().decrypt(encryptionAlgorithm, bytes);
+ return decryptResult.getPlainText();
}
-
- /**
- * Nothing required to be done for Azure Client cleanUp function.
- */
- @Override
- public void cleanUp() {}
}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java
new file mode 100644
index 0000000..6887378
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/ClientBasedEncodedSensitivePropertyProvider.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.nifi.properties;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Properties;
+
+/**
+ * Client-Based extension of Encoded Sensitive Property Provider
+ *
+ * @param <T> Client Type
+ */
+public abstract class ClientBasedEncodedSensitivePropertyProvider<T> extends
EncodedSensitivePropertyProvider {
+ protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+ private final T client;
+
+ private final Properties properties;
+
+ public ClientBasedEncodedSensitivePropertyProvider(final
PropertyProtectionScheme propertyProtectionScheme,
+ final T client,
+ final Properties
properties) {
+ super(propertyProtectionScheme);
+ this.client = client;
+ this.properties = properties;
+ validate(client);
+ }
+
+ /**
+ * Is Provider supported based on client status
+ *
+ * @return Provider supported status
+ */
+ @Override
+ public boolean isSupported() {
+ return client != null;
+ }
+
+ /**
+ * Clean up resources should be overridden when client requires shutdown
+ */
+ @Override
+ public void cleanUp() {
+ logger.debug("Cleanup Started");
+ }
+
+ /**
+ * Get Client Properties
+ *
+ * @return Client Properties
+ */
+ protected Properties getProperties() {
+ return properties;
+ }
+
+ /**
+ * Get Client
+ *
+ * @return Client can be null when not configured
+ */
+ protected T getClient() {
+ if (client == null) {
+ throw new IllegalStateException("Client not configured");
+ }
+ return client;
+ }
+
+ /**
+ * Validate Provider and Client configuration
+ *
+ * @param configuredClient Configured Client
+ */
+ protected void validate(final T configuredClient) {
+ if (configuredClient == null) {
+ logger.debug("Client not configured");
+ }
+ }
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java
new file mode 100644
index 0000000..b288402
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/EncodedSensitivePropertyProvider.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.nifi.properties;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Objects;
+
+/**
+ * Encoded Sensitive Property Provider handles Base64 encoding and decoding of
property values
+ */
+public abstract class EncodedSensitivePropertyProvider implements
SensitivePropertyProvider {
+ private static final Charset VALUE_CHARACTER_SET = StandardCharsets.UTF_8;
+
+ private static final Base64.Encoder ENCODER =
Base64.getEncoder().withoutPadding();
+
+ private static final Base64.Decoder DECODER = Base64.getDecoder();
+
+ private final PropertyProtectionScheme propertyProtectionScheme;
+
+ public EncodedSensitivePropertyProvider(final PropertyProtectionScheme
propertyProtectionScheme) {
+ this.propertyProtectionScheme =
Objects.requireNonNull(propertyProtectionScheme, "Scheme Required");
+ }
+
+ /**
+ * Get Property Protection Scheme Name
+ *
+ * @return Property Protection Scheme Name
+ */
+ @Override
+ public String getName() {
+ return propertyProtectionScheme.getName();
+ }
+
+ /**
+ * Get Identifier Key based on Property Protection Scheme
+ *
+ * @return Property Protection Scheme Identifier
+ */
+ @Override
+ public String getIdentifierKey() {
+ return propertyProtectionScheme.getIdentifier();
+ }
+
+ /**
+ * Protect property value and return Base64-encoded representation of
encrypted bytes
+ *
+ * @param unprotectedValue Unprotected property value to be encrypted
+ * @param context Property Context
+ * @return Base64-encoded representation of encrypted bytes
+ */
+ @Override
+ public String protect(final String unprotectedValue, final
ProtectedPropertyContext context) {
+ Objects.requireNonNull(unprotectedValue, "Value required");
+ Objects.requireNonNull(context, "Context required");
+ try {
+ final byte[] bytes =
unprotectedValue.getBytes(VALUE_CHARACTER_SET);
+ final byte[] encrypted = getEncrypted(bytes);
+ return ENCODER.encodeToString(encrypted);
+ } catch (final RuntimeException e) {
+ final String message = String.format("Property [%s] Encryption
Failed", context.getContextKey());
+ throw new SensitivePropertyProtectionException(message, e);
+ }
+ }
+
+ /**
+ * Unprotect Base64-encoded representation of encrypted property value and
return string
+ *
+ * @param protectedValue Base64-encoded representation of encrypted bytes
+ * @param context Property Context
+ * @return Decrypted property value string
+ */
+ @Override
+ public String unprotect(final String protectedValue, final
ProtectedPropertyContext context) {
+ Objects.requireNonNull(protectedValue, "Value required");
+ Objects.requireNonNull(context, "Context required");
+ try {
+ final byte[] decoded = DECODER.decode(protectedValue);
+ final byte[] decrypted = getDecrypted(decoded);
+ return new String(decrypted, VALUE_CHARACTER_SET);
+ } catch (final RuntimeException e) {
+ final String message = String.format("Property [%s] Decryption
Failed", context.getContextKey());
+ throw new SensitivePropertyProtectionException(message, e);
+ }
+ }
+
+ /**
+ * Get encrypted byte array representation of bytes
+ *
+ * @param bytes Unprotected bytes
+ * @return Encrypted bytes
+ */
+ protected abstract byte[] getEncrypted(byte[] bytes);
+
+ /**
+ * Get decrypted byte array representation of encrypted bytes
+ *
+ * @param bytes Encrypted bytes
+ * @return Decrypted bytes
+ */
+ protected abstract byte[] getDecrypted(byte[] bytes);
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/GCPKMSSensitivePropertyProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/GCPKMSSensitivePropertyProvider.java
deleted file mode 100644
index 0322230..0000000
---
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/GCPKMSSensitivePropertyProvider.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.nifi.properties;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.properties.BootstrapProperties.BootstrapPropertyKey;
-
-import com.google.api.gax.rpc.ApiException;
-import com.google.cloud.kms.v1.CryptoKey;
-import com.google.cloud.kms.v1.CryptoKeyName;
-import com.google.cloud.kms.v1.CryptoKeyVersion;
-import com.google.cloud.kms.v1.DecryptResponse;
-import com.google.cloud.kms.v1.EncryptResponse;
-import com.google.cloud.kms.v1.KeyManagementServiceClient;
-import com.google.protobuf.ByteString;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Base64;
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Paths;
-import java.util.Objects;
-
-public class GCPKMSSensitivePropertyProvider extends
AbstractSensitivePropertyProvider {
- private static final Logger logger =
LoggerFactory.getLogger(GCPKMSSensitivePropertyProvider.class);
-
- private static final String GCP_PREFIX = "gcp";
- private static final String PROJECT_ID_PROPS_NAME = "gcp.kms.project";
- private static final String LOCATION_ID_PROPS_NAME = "gcp.kms.location";
- private static final String KEYRING_ID_PROPS_NAME = "gcp.kms.keyring";
- private static final String KEY_ID_PROPS_NAME = "gcp.kms.key";
-
- private static final Charset PROPERTY_CHARSET = StandardCharsets.UTF_8;
-
- private final BootstrapProperties gcpBootstrapProperties;
- private KeyManagementServiceClient client;
- private CryptoKeyName keyName;
-
- GCPKMSSensitivePropertyProvider(final BootstrapProperties
bootstrapProperties) {
- super(bootstrapProperties);
- Objects.requireNonNull(bootstrapProperties, "The file bootstrap.conf
provided to GCP SPP is null");
- gcpBootstrapProperties =
getGCPBootstrapProperties(bootstrapProperties);
- loadRequiredGCPProperties(gcpBootstrapProperties);
- }
-
- /**
- * Initializes the GCP KMS Client to be used for encrypt, decrypt and
other interactions with GCP Cloud KMS.
- * Note: This does not verify if credentials are valid.
- */
- private void initializeClient() {
- try {
- client = KeyManagementServiceClient.create();
- } catch (final IOException e) {
- final String msg = "Encountered an error initializing GCP Cloud
KMS client";
- throw new SensitivePropertyProtectionException(msg, e);
- }
- }
-
- /**
- * Validates the key details provided by the user.
- */
- private void validate() throws ApiException,
SensitivePropertyProtectionException {
- if (client == null) {
- final String msg = "The GCP KMS client failed to open, cannot
validate key";
- throw new SensitivePropertyProtectionException(msg);
- }
- if (keyName == null) {
- final String msg = "The GCP KMS key provided is not
provided/complete";
- throw new SensitivePropertyProtectionException(msg);
- }
- final CryptoKey key;
- final CryptoKeyVersion keyVersion;
- try {
- key = client.getCryptoKey(keyName);
- keyVersion =
client.getCryptoKeyVersion(key.getPrimary().getName());
- } catch (final ApiException e) {
- throw new SensitivePropertyProtectionException("Encountered an
error while fetching key details", e);
- }
-
- if (keyVersion.getState() !=
CryptoKeyVersion.CryptoKeyVersionState.ENABLED) {
- throw new SensitivePropertyProtectionException("The key is not
enabled");
- }
- }
-
- /**
- * Checks if we have the required key properties for GCP Cloud KMS and
loads it into {@link #keyName}.
- * Will load null if key is not present.
- * Note: This function does not verify if the key is correctly
formatted/valid.
- * @param props the properties representing bootstrap-gcp.conf.
- */
- private void loadRequiredGCPProperties(final BootstrapProperties props) {
- if (props != null) {
- final String projectId = props.getProperty(PROJECT_ID_PROPS_NAME);
- final String locationId =
props.getProperty(LOCATION_ID_PROPS_NAME);
- final String keyRingId = props.getProperty(KEYRING_ID_PROPS_NAME);
- final String keyId = props.getProperty(KEY_ID_PROPS_NAME);
- if (StringUtils.isNoneBlank(projectId, locationId, keyRingId,
keyId)) {
- keyName = CryptoKeyName.of(projectId, locationId, keyRingId,
keyId);
- }
- }
- }
-
- /**
- * Checks bootstrap.conf to check if
BootstrapPropertyKey.GCP_KMS_SENSITIVE_PROPERTY_PROVIDER_CONF property is
- * configured to the bootstrap-gcp.conf file. Also will load
bootstrap-gcp.conf to {@link #gcpBootstrapProperties}.
- * @param bootstrapProperties BootstrapProperties object corresponding to
bootstrap.conf.
- * @return BootstrapProperties object corresponding to bootstrap-gcp.conf,
null otherwise.
- */
- private BootstrapProperties getGCPBootstrapProperties(final
BootstrapProperties bootstrapProperties) {
- final BootstrapProperties cloudBootstrapProperties;
-
- // Load the bootstrap-gcp.conf file based on path specified in
- // "nifi.bootstrap.protection.gcp.kms.conf" property of bootstrap.conf
- final String filePath =
bootstrapProperties.getProperty(BootstrapPropertyKey.GCP_KMS_SENSITIVE_PROPERTY_PROVIDER_CONF).orElse(null);
- if (StringUtils.isBlank(filePath)) {
- logger.warn("GCP KMS properties file path not configured in
bootstrap properties");
- return null;
- }
-
- try {
- cloudBootstrapProperties =
AbstractBootstrapPropertiesLoader.loadBootstrapProperties(
- Paths.get(filePath), GCP_PREFIX);
- } catch (final IOException e) {
- throw new SensitivePropertyProtectionException("Could not load " +
filePath, e);
- }
-
- return cloudBootstrapProperties;
- }
-
- /**
- * Checks bootstrap-gcp.conf for the required configurations for Google
Cloud KMS encrypt/decrypt operations.
- * @return true if bootstrap-gcp.conf contains the required properties for
GCP KMS SPP, false otherwise.
- */
- private boolean hasRequiredGCPProperties() {
- if (gcpBootstrapProperties == null) {
- return false;
- }
-
- final String projectId =
gcpBootstrapProperties.getProperty(PROJECT_ID_PROPS_NAME);
- final String locationId =
gcpBootstrapProperties.getProperty(LOCATION_ID_PROPS_NAME);
- final String keyRingId =
gcpBootstrapProperties.getProperty(KEYRING_ID_PROPS_NAME);
- final String keyId =
gcpBootstrapProperties.getProperty(KEY_ID_PROPS_NAME);
-
- // Note: the following does not verify if the properties are valid
properties, they only verify if
- // the properties are configured in bootstrap-gcp.conf.
- return StringUtils.isNoneBlank(projectId, locationId, keyRingId,
keyId);
- }
-
- @Override
- public boolean isSupported() {
- return hasRequiredGCPProperties();
- }
-
- @Override
- protected PropertyProtectionScheme getProtectionScheme() {
- return PropertyProtectionScheme.GCP_KMS;
- }
-
- /**
- * Returns the name of the underlying implementation.
- *
- * @return the name of this sensitive property provider.
- */
- @Override
- public String getName() {
- return PropertyProtectionScheme.GCP_KMS.getName();
- }
-
- /**
- * Returns the key used to identify the provider implementation in {@code
nifi.properties}.
- *
- * @return the key to persist in the sibling property.
- */
- @Override
- public String getIdentifierKey() {
- return PropertyProtectionScheme.GCP_KMS.getIdentifier();
- }
-
- /**
- * Returns the ciphertext blob of this value encrypted using a key stored
in GCP KMS.
- * @return the ciphertext blob to persist in the {@code nifi.properties}
file.
- */
- private byte[] encrypt(final byte[] input) throws IOException {
- final EncryptResponse response = client.encrypt(keyName,
ByteString.copyFrom(input));
- return response.getCiphertext().toByteArray();
- }
-
- /**
- * Returns the value corresponding to a ciphertext blob decrypted using a
key stored in GCP KMS.
- * @return the "unprotected" byte[] of this value, which could be used by
the application.
- */
- private byte[] decrypt(final byte[] input) throws IOException {
- final DecryptResponse response = client.decrypt(keyName,
ByteString.copyFrom(input));
- return response.getPlaintext().toByteArray();
- }
-
- /**
- * Checks if the client is open and if not, initializes the client and
validates the key required for GCP KMS.
- */
- private void checkAndInitializeClient() throws
SensitivePropertyProtectionException {
- if (client == null) {
- try {
- initializeClient();
- validate();
- } catch (final SensitivePropertyProtectionException e) {
- throw new SensitivePropertyProtectionException("Error
initializing the GCP KMS client", e);
- }
- }
- }
-
- /**
- * Returns the "protected" form of this value. This is a form which can
safely be persisted in the {@code nifi.properties} file without compromising
the value.
- * Encrypts a sensitive value using a key managed by Google Cloud Key
Management Service.
- *
- * @param unprotectedValue the sensitive value.
- * @param context The context of the value (ignored in this implementation)
- * @return the value to persist in the {@code nifi.properties} file.
- */
- @Override
- public String protect(final String unprotectedValue, final
ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
- if (StringUtils.isBlank(unprotectedValue)) {
- throw new IllegalArgumentException("Cannot encrypt a blank value");
- }
-
- checkAndInitializeClient();
-
- try {
- byte[] plainBytes = unprotectedValue.getBytes(PROPERTY_CHARSET);
- byte[] cipherBytes = encrypt(plainBytes);
- return Base64.getEncoder().encodeToString(cipherBytes);
- } catch (final IOException | ApiException e) {
- throw new SensitivePropertyProtectionException("Encrypt failed",
e);
- }
- }
-
- /**
- * Returns the "unprotected" form of this value. This is the raw sensitive
value which is used by the application logic.
- * Decrypts a secured value from a ciphertext using a key managed by
Google Cloud Key Management Service.
- *
- * @param protectedValue the protected value read from the {@code
nifi.properties} file.
- * @param context The context of the value (ignored in this implementation)
- * @return the raw value to be used by the application.
- */
- @Override
- public String unprotect(final String protectedValue, final
ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
- if (StringUtils.isBlank(protectedValue)) {
- throw new IllegalArgumentException("Cannot decrypt a blank value");
- }
-
- checkAndInitializeClient();
-
- try {
- byte[] cipherBytes = Base64.getDecoder().decode(protectedValue);
- byte[] plainBytes = decrypt(cipherBytes);
- return new String(plainBytes, PROPERTY_CHARSET);
- } catch (final IOException | ApiException e) {
- throw new SensitivePropertyProtectionException("Decrypt failed",
e);
- }
- }
-
- /**
- * Closes GCP KMS client that may have been opened.
- */
- @Override
- public void cleanUp() {
- if (client != null) {
- client.close();
- client = null;
- }
- }
-}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java
new file mode 100644
index 0000000..c7b275d
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProvider.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.nifi.properties;
+
+import com.google.api.gax.rpc.ApiException;
+import com.google.cloud.kms.v1.CryptoKey;
+import com.google.cloud.kms.v1.CryptoKeyName;
+import com.google.cloud.kms.v1.CryptoKeyVersion;
+import com.google.cloud.kms.v1.DecryptResponse;
+import com.google.cloud.kms.v1.EncryptResponse;
+import com.google.cloud.kms.v1.KeyManagementServiceClient;
+import com.google.protobuf.ByteString;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Properties;
+
+/**
+ * Google Cloud Platform Key Management Service Sensitive Property Provider
+ */
+public class GcpKmsSensitivePropertyProvider extends
ClientBasedEncodedSensitivePropertyProvider<KeyManagementServiceClient> {
+ protected static final String PROJECT_PROPERTY = "gcp.kms.project";
+ protected static final String LOCATION_PROPERTY = "gcp.kms.location";
+ protected static final String KEYRING_PROPERTY = "gcp.kms.keyring";
+ protected static final String KEY_PROPERTY = "gcp.kms.key";
+
+ private CryptoKeyName cryptoKeyName;
+
+ GcpKmsSensitivePropertyProvider(final KeyManagementServiceClient
keyManagementServiceClient, final Properties properties) {
+ super(PropertyProtectionScheme.GCP_KMS, keyManagementServiceClient,
properties);
+ }
+
+ /**
+ * Close Client when configured
+ */
+ @Override
+ public void cleanUp() {
+ final KeyManagementServiceClient keyManagementServiceClient =
getClient();
+ if (keyManagementServiceClient == null) {
+ logger.debug("GCP KMS Client not configured");
+ } else {
+ keyManagementServiceClient.close();
+ }
+ }
+
+ /**
+ * Validate Client and Key Operations with Encryption Algorithm when
configured
+ *
+ * @param keyManagementServiceClient Key Management Service Client
+ */
+ @Override
+ protected void validate(final KeyManagementServiceClient
keyManagementServiceClient) {
+ if (keyManagementServiceClient == null) {
+ logger.debug("GCP KMS Client not configured");
+ } else {
+ final String project =
getProperties().getProperty(PROJECT_PROPERTY);
+ final String location =
getProperties().getProperty(LOCATION_PROPERTY);
+ final String keyring =
getProperties().getProperty(KEYRING_PROPERTY);
+ final String key = getProperties().getProperty(KEY_PROPERTY);
+ if (StringUtils.isNoneBlank(project, location, keyring, key)) {
+ cryptoKeyName = CryptoKeyName.of(project, location, keyring,
key);
+ try {
+ final CryptoKey cryptoKey =
keyManagementServiceClient.getCryptoKey(cryptoKeyName);
+ final CryptoKeyVersion cryptoKeyVersion =
cryptoKey.getPrimary();
+ if (CryptoKeyVersion.CryptoKeyVersionState.ENABLED ==
cryptoKeyVersion.getState()) {
+ logger.info("GCP KMS Crypto Key [{}] Validated",
cryptoKeyName);
+ } else {
+ throw new
SensitivePropertyProtectionException(String.format("GCP KMS Crypto Key [%s]
Disabled", cryptoKeyName));
+ }
+ } catch (final ApiException e) {
+ throw new
SensitivePropertyProtectionException(String.format("GCP KMS Crypto Key [%s]
Validation Failed", cryptoKeyName), e);
+ }
+ } else {
+ throw new SensitivePropertyProtectionException("GCP KMS
Missing Required Properties");
+ }
+ }
+ }
+
+ /**
+ * Get encrypted bytes
+ *
+ * @param bytes Unprotected bytes
+ * @return Encrypted bytes
+ */
+ @Override
+ protected byte[] getEncrypted(final byte[] bytes) {
+ final EncryptResponse encryptResponse =
getClient().encrypt(cryptoKeyName, ByteString.copyFrom(bytes));
+ return encryptResponse.getCiphertext().toByteArray();
+ }
+
+ /**
+ * Get decrypted bytes
+ *
+ * @param bytes Encrypted bytes
+ * @return Decrypted bytes
+ */
+ @Override
+ protected byte[] getDecrypted(final byte[] bytes) {
+ final DecryptResponse decryptResponse =
getClient().decrypt(cryptoKeyName, ByteString.copyFrom(bytes));
+ return decryptResponse.getPlaintext().toByteArray();
+ }
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java
index a64bd0a..1564e3f 100644
---
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactory.java
@@ -16,11 +16,18 @@
*/
package org.apache.nifi.properties;
+import com.azure.security.keyvault.keys.cryptography.CryptographyClient;
+import com.google.cloud.kms.v1.KeyManagementServiceClient;
import org.apache.nifi.properties.BootstrapProperties.BootstrapPropertyKey;
+import org.apache.nifi.properties.configuration.AwsKmsClientProvider;
+import
org.apache.nifi.properties.configuration.AzureCryptographyClientProvider;
+import org.apache.nifi.properties.configuration.ClientProvider;
+import
org.apache.nifi.properties.configuration.GoogleKeyManagementServiceClientProvider;
import org.apache.nifi.util.NiFiBootstrapUtils;
import org.apache.nifi.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import software.amazon.awssdk.services.kms.KmsClient;
import java.io.IOException;
import java.util.Arrays;
@@ -29,6 +36,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.Properties;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -106,7 +114,7 @@ public class StandardSensitivePropertyProviderFactory
implements SensitiveProper
try {
return NiFiBootstrapUtils.loadBootstrapProperties();
} catch (final IOException e) {
- logger.debug("Could not load bootstrap.conf from disk, so
using empty bootstrap.conf", e);
+ logger.debug("Bootstrap Properties loading failed", e);
return BootstrapProperties.EMPTY;
}
});
@@ -121,11 +129,26 @@ public class StandardSensitivePropertyProviderFactory
implements SensitiveProper
case AES_GCM:
return providerMap.computeIfAbsent(protectionScheme, s -> new
AESSensitivePropertyProvider(keyHex));
case AWS_KMS:
- return providerMap.computeIfAbsent(protectionScheme, s -> new
AWSKMSSensitivePropertyProvider(getBootstrapProperties()));
+ return providerMap.computeIfAbsent(protectionScheme, s -> {
+ final AwsKmsClientProvider clientProvider = new
AwsKmsClientProvider();
+ final Properties clientProperties =
getClientProperties(clientProvider);
+ final Optional<KmsClient> kmsClient =
clientProvider.getClient(clientProperties);
+ return new
AwsKmsSensitivePropertyProvider(kmsClient.orElse(null), clientProperties);
+ });
case AZURE_KEYVAULT_KEY:
- return providerMap.computeIfAbsent(protectionScheme, s -> new
AzureKeyVaultKeySensitivePropertyProvider(getBootstrapProperties()));
+ return providerMap.computeIfAbsent(protectionScheme, s -> {
+ final AzureCryptographyClientProvider clientProvider = new
AzureCryptographyClientProvider();
+ final Properties clientProperties =
getClientProperties(clientProvider);
+ final Optional<CryptographyClient> cryptographyClient =
clientProvider.getClient(clientProperties);
+ return new
AzureKeyVaultKeySensitivePropertyProvider(cryptographyClient.orElse(null),
clientProperties);
+ });
case GCP_KMS:
- return providerMap.computeIfAbsent(protectionScheme, s -> new
GCPKMSSensitivePropertyProvider(getBootstrapProperties()));
+ return providerMap.computeIfAbsent(protectionScheme, s -> {
+ final GoogleKeyManagementServiceClientProvider
clientProvider = new GoogleKeyManagementServiceClientProvider();
+ final Properties clientProperties =
getClientProperties(clientProvider);
+ final Optional<KeyManagementServiceClient>
keyManagementServiceClient = clientProvider.getClient(clientProperties);
+ return new
GcpKmsSensitivePropertyProvider(keyManagementServiceClient.orElse(null),
clientProperties);
+ });
case HASHICORP_VAULT_TRANSIT:
return providerMap.computeIfAbsent(protectionScheme, s -> new
HashiCorpVaultTransitSensitivePropertyProvider(getBootstrapProperties()));
case HASHICORP_VAULT_KV:
@@ -156,4 +179,8 @@ public class StandardSensitivePropertyProviderFactory
implements SensitiveProper
return ProtectedPropertyContext.contextFor(propertyName, contextName);
}
+ private <T> Properties getClientProperties(final ClientProvider<T>
clientProvider) {
+ final Optional<Properties> clientProperties =
clientProvider.getClientProperties(getBootstrapProperties());
+ return clientProperties.orElse(null);
+ }
}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AwsKmsClientProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AwsKmsClientProvider.java
new file mode 100644
index 0000000..5e01fed
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AwsKmsClientProvider.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.nifi.properties.configuration;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.properties.BootstrapProperties;
+import org.apache.nifi.properties.SensitivePropertyProtectionException;
+
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.kms.KmsClient;
+import software.amazon.awssdk.services.kms.KmsClientBuilder;
+
+import java.util.Properties;
+
+/**
+ * Amazon Web Services Key Management Service Client Provider
+ */
+public class AwsKmsClientProvider extends
BootstrapPropertiesClientProvider<KmsClient> {
+ private static final String ACCESS_KEY_PROPS_NAME = "aws.access.key.id";
+
+ private static final String SECRET_KEY_PROPS_NAME =
"aws.secret.access.key";
+
+ private static final String REGION_KEY_PROPS_NAME = "aws.region";
+
+ public AwsKmsClientProvider() {
+
super(BootstrapProperties.BootstrapPropertyKey.AWS_KMS_SENSITIVE_PROPERTY_PROVIDER_CONF);
+ }
+
+ /**
+ * Get Configured Client using either Client Properties or AWS Default
Credentials Provider
+ *
+ * @param clientProperties Client Properties
+ * @return KMS Client
+ */
+ @Override
+ protected KmsClient getConfiguredClient(final Properties clientProperties)
{
+ final String accessKey =
clientProperties.getProperty(ACCESS_KEY_PROPS_NAME);
+ final String secretKey =
clientProperties.getProperty(SECRET_KEY_PROPS_NAME);
+ final String region =
clientProperties.getProperty(REGION_KEY_PROPS_NAME);
+
+ final KmsClientBuilder kmsClientBuilder = KmsClient.builder();
+ if (StringUtils.isNoneBlank(accessKey, secretKey, region)) {
+ logger.debug("AWS Credentials Location: Client Properties");
+ try {
+ final AwsBasicCredentials credentials =
AwsBasicCredentials.create(accessKey, secretKey);
+ return kmsClientBuilder
+ .region(Region.of(region))
+
.credentialsProvider(StaticCredentialsProvider.create(credentials))
+ .build();
+ } catch (final RuntimeException e) {
+ throw new SensitivePropertyProtectionException("AWS KMS Client
Builder Failed using Client Properties", e);
+ }
+ } else {
+ logger.debug("AWS Credentials Location: Default Credentials
Provider");
+ try {
+ final DefaultCredentialsProvider credentialsProvider =
DefaultCredentialsProvider.builder().build();
+ return
kmsClientBuilder.credentialsProvider(credentialsProvider).build();
+ } catch (final RuntimeException e) {
+ throw new SensitivePropertyProtectionException("AWS KMS Client
Builder Failed using Default Credentials Provider", e);
+ }
+ }
+ }
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProvider.java
new file mode 100644
index 0000000..643d02e
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProvider.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.nifi.properties.configuration;
+
+import com.azure.identity.DefaultAzureCredentialBuilder;
+import com.azure.security.keyvault.keys.cryptography.CryptographyClient;
+import com.azure.security.keyvault.keys.cryptography.CryptographyClientBuilder;
+import org.apache.nifi.properties.BootstrapProperties;
+import org.apache.nifi.properties.SensitivePropertyProtectionException;
+
+import java.util.Properties;
+
+/**
+ * Microsoft Azure Cryptography Client Provider
+ */
+public class AzureCryptographyClientProvider extends
BootstrapPropertiesClientProvider<CryptographyClient> {
+ private static final String KEY_ID_PROPERTY = "azure.keyvault.key.id";
+
+ public AzureCryptographyClientProvider() {
+
super(BootstrapProperties.BootstrapPropertyKey.AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF);
+ }
+
+ /**
+ * Get Configured Client using Default Azure Credentials Builder and
configured Key Identifier
+ *
+ * @param clientProperties Client Properties
+ * @return Cryptography Client
+ */
+ @Override
+ protected CryptographyClient getConfiguredClient(final Properties
clientProperties) {
+ final String keyIdentifier =
clientProperties.getProperty(KEY_ID_PROPERTY);
+ logger.debug("Azure Cryptography Client with Key Identifier [{}]",
keyIdentifier);
+
+ try {
+ return new CryptographyClientBuilder()
+ .credential(new DefaultAzureCredentialBuilder().build())
+ .keyIdentifier(keyIdentifier)
+ .buildClient();
+ } catch (final RuntimeException e) {
+ throw new SensitivePropertyProtectionException("Azure Cryptography
Builder Client Failed using Default Credentials", e);
+ }
+ }
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java
new file mode 100644
index 0000000..1bbb021
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/BootstrapPropertiesClientProvider.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.nifi.properties.configuration;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.properties.BootstrapProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Properties;
+
+/**
+ * Shared Client Provider for reading Client Properties from file referenced
in configured Bootstrap Property Key
+ *
+ * @param <T> Client Type
+ */
+public abstract class BootstrapPropertiesClientProvider<T> implements
ClientProvider<T> {
+ protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+ private final BootstrapProperties.BootstrapPropertyKey
bootstrapPropertyKey;
+
+ public BootstrapPropertiesClientProvider(final
BootstrapProperties.BootstrapPropertyKey bootstrapPropertyKey) {
+ this.bootstrapPropertyKey =
Objects.requireNonNull(bootstrapPropertyKey, "Bootstrap Property Key required");
+ }
+
+ /**
+ * Get Client using Client Properties
+ *
+ * @param clientProperties Client Properties can be null
+ * @return Configured Client or empty when Client Properties object is null
+ */
+ @Override
+ public Optional<T> getClient(final Properties clientProperties) {
+ return clientProperties == null ? Optional.empty() :
Optional.of(getConfiguredClient(clientProperties));
+ }
+
+ /**
+ * Get Client Properties from file referenced in Bootstrap Properties
+ *
+ * @param bootstrapProperties Bootstrap Properties
+ * @return Client Properties or empty when not configured
+ */
+ @Override
+ public Optional<Properties> getClientProperties(final BootstrapProperties
bootstrapProperties) {
+ Objects.requireNonNull(bootstrapProperties, "Bootstrap Properties
required");
+ final String clientBootstrapPropertiesPath =
bootstrapProperties.getProperty(bootstrapPropertyKey).orElse(null);
+ if (StringUtils.isBlank(clientBootstrapPropertiesPath)) {
+ logger.debug("Client Properties [{}] not configured",
bootstrapPropertyKey);
+ return Optional.empty();
+ } else {
+ final Path propertiesPath =
Paths.get(clientBootstrapPropertiesPath);
+ if (Files.exists(propertiesPath)) {
+ try {
+ final Properties clientProperties = new Properties();
+ try (final InputStream inputStream =
Files.newInputStream(propertiesPath)) {
+ clientProperties.load(inputStream);
+ }
+ return Optional.of(clientProperties);
+ } catch (final IOException e) {
+ final String message = String.format("Loading Client
Properties Failed [%s]", propertiesPath);
+ throw new UncheckedIOException(message, e);
+ }
+ } else {
+ logger.debug("Client Properties [{}] Path [{}] not found",
bootstrapPropertyKey, propertiesPath);
+ return Optional.empty();
+ }
+ }
+ }
+
+ /**
+ * Get Configured Client using Client Properties
+ *
+ * @param clientProperties Client Properties
+ * @return Configured Client
+ */
+ protected abstract T getConfiguredClient(final Properties
clientProperties);
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java
new file mode 100644
index 0000000..cee6a9c
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/ClientProvider.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.nifi.properties.configuration;
+
+import org.apache.nifi.properties.BootstrapProperties;
+
+import java.util.Optional;
+import java.util.Properties;
+
+/**
+ * Client Provider responsible for reading Client Properties and instantiating
client services
+ *
+ * @param <T> Client Type
+ */
+public interface ClientProvider<T> {
+ /**
+ * Get Client Properties from Bootstrap Properties
+ *
+ * @param bootstrapProperties Bootstrap Properties
+ * @return Client Properties or empty when not configured
+ */
+ Optional<Properties> getClientProperties(BootstrapProperties
bootstrapProperties);
+
+ /**
+ * Get Client using Client Properties
+ *
+ * @param properties Client Properties
+ * @return Client or empty when not configured
+ */
+ Optional<T> getClient(Properties properties);
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/GoogleKeyManagementServiceClientProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/GoogleKeyManagementServiceClientProvider.java
new file mode 100644
index 0000000..8f46edc
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/GoogleKeyManagementServiceClientProvider.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.nifi.properties.configuration;
+
+import com.google.cloud.kms.v1.KeyManagementServiceClient;
+
+import org.apache.nifi.properties.BootstrapProperties;
+import org.apache.nifi.properties.SensitivePropertyProtectionException;
+
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Google Key Management Service Client Provider
+ */
+public class GoogleKeyManagementServiceClientProvider extends
BootstrapPropertiesClientProvider<KeyManagementServiceClient> {
+ public GoogleKeyManagementServiceClientProvider() {
+
super(BootstrapProperties.BootstrapPropertyKey.GCP_KMS_SENSITIVE_PROPERTY_PROVIDER_CONF);
+ }
+
+ /**
+ * Get Configured Client using default Key Management Service Client
settings
+ *
+ * @param clientProperties Client Properties
+ * @return Key Management Service Client
+ */
+ @Override
+ protected KeyManagementServiceClient getConfiguredClient(final Properties
clientProperties) {
+ try {
+ return KeyManagementServiceClient.create();
+ } catch (final IOException e) {
+ throw new SensitivePropertyProtectionException("Google Key
Management Service Create Failed", e);
+ }
+ }
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AWSKMSSensitivePropertyProviderIT.java
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderIT.java
similarity index 90%
rename from
nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AWSKMSSensitivePropertyProviderIT.java
rename to
nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderIT.java
index d29126d..708f54e 100644
---
a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AWSKMSSensitivePropertyProviderIT.java
+++
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderIT.java
@@ -16,6 +16,7 @@
*/
package org.apache.nifi.properties;
+import org.apache.nifi.properties.configuration.AwsKmsClientProvider;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
@@ -23,6 +24,7 @@ import org.junit.Test;
import org.mockito.internal.util.io.IOUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import software.amazon.awssdk.services.kms.KmsClient;
import java.io.IOException;
import java.io.InputStream;
@@ -52,7 +54,7 @@ import java.util.Properties;
*
*/
-public class AWSKMSSensitivePropertyProviderIT {
+public class AwsKmsSensitivePropertyProviderIT {
private static final String SAMPLE_PLAINTEXT =
"AWSKMSSensitivePropertyProviderIT SAMPLE-PLAINTEXT";
private static final String ACCESS_KEY_PROPS_NAME = "aws.access.key.id";
private static final String SECRET_KEY_PROPS_NAME =
"aws.secret.access.key";
@@ -63,13 +65,13 @@ public class AWSKMSSensitivePropertyProviderIT {
private static final String EMPTY_PROPERTY = "";
- private static AWSKMSSensitivePropertyProvider spp;
+ private static AwsKmsSensitivePropertyProvider spp;
private static BootstrapProperties props;
private static Path mockBootstrapConf, mockAWSBootstrapConf;
- private static final Logger logger =
LoggerFactory.getLogger(AWSKMSSensitivePropertyProviderIT.class);
+ private static final Logger logger =
LoggerFactory.getLogger(AwsKmsSensitivePropertyProviderIT.class);
private static void initializeBootstrapProperties() throws IOException{
mockBootstrapConf = Files.createTempFile("bootstrap",
".conf").toAbsolutePath();
@@ -101,7 +103,10 @@ public class AWSKMSSensitivePropertyProviderIT {
public static void initOnce() throws IOException {
initializeBootstrapProperties();
Assert.assertNotNull(props);
- spp = new AWSKMSSensitivePropertyProvider(props);
+ final AwsKmsClientProvider provider = new AwsKmsClientProvider();
+ final Properties properties =
provider.getClientProperties(props).orElse(null);
+ final KmsClient kmsClient =
provider.getClient(properties).orElse(null);
+ spp = new AwsKmsSensitivePropertyProvider(kmsClient, properties);
Assert.assertNotNull(spp);
}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java
new file mode 100644
index 0000000..b21b017
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AwsKmsSensitivePropertyProviderTest.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.nifi.properties;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.services.kms.KmsClient;
+import software.amazon.awssdk.services.kms.model.DecryptRequest;
+import software.amazon.awssdk.services.kms.model.DecryptResponse;
+import software.amazon.awssdk.services.kms.model.DescribeKeyRequest;
+import software.amazon.awssdk.services.kms.model.DescribeKeyResponse;
+import software.amazon.awssdk.services.kms.model.EncryptRequest;
+import software.amazon.awssdk.services.kms.model.EncryptResponse;
+import software.amazon.awssdk.services.kms.model.KeyMetadata;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.ArgumentMatchers.any;
+
+@ExtendWith(MockitoExtension.class)
+public class AwsKmsSensitivePropertyProviderTest {
+ private static final String PROPERTY_NAME = String.class.getSimpleName();
+
+ private static final String PROPERTY = String.class.getName();
+
+ private static final String ENCRYPTED_PROPERTY = Integer.class.getName();
+
+ private static final byte[] ENCRYPTED_BYTES =
ENCRYPTED_PROPERTY.getBytes(StandardCharsets.UTF_8);
+
+ private static final String PROTECTED_PROPERTY =
Base64.getEncoder().withoutPadding().encodeToString(ENCRYPTED_BYTES);
+
+ private static final String KEY_ID =
AwsKmsSensitivePropertyProvider.class.getSimpleName();
+
+ private static final Properties PROPERTIES = new Properties();
+
+ static {
+
PROPERTIES.setProperty(AwsKmsSensitivePropertyProvider.KEY_ID_PROPERTY, KEY_ID);
+ }
+
+ @Mock
+ private KmsClient kmsClient;
+
+ private AwsKmsSensitivePropertyProvider provider;
+
+ @BeforeEach
+ public void setProvider() {
+ final KeyMetadata keyMetadata =
KeyMetadata.builder().enabled(true).build();
+ final DescribeKeyResponse describeKeyResponse =
DescribeKeyResponse.builder().keyMetadata(keyMetadata).build();
+
when(kmsClient.describeKey(any(DescribeKeyRequest.class))).thenReturn(describeKeyResponse);
+
+ provider = new AwsKmsSensitivePropertyProvider(kmsClient, PROPERTIES);
+ }
+
+ @Test
+ public void testValidateClientNull() {
+ final AwsKmsSensitivePropertyProvider provider = new
AwsKmsSensitivePropertyProvider(null, PROPERTIES);
+ assertNotNull(provider);
+ }
+
+ @Test
+ public void testValidateKeyDisabled() {
+ final KeyMetadata keyMetadata =
KeyMetadata.builder().enabled(false).build();
+ final DescribeKeyResponse describeKeyResponse =
DescribeKeyResponse.builder().keyMetadata(keyMetadata).build();
+
when(kmsClient.describeKey(any(DescribeKeyRequest.class))).thenReturn(describeKeyResponse);
+
+ assertThrows(SensitivePropertyProtectionException.class, () -> new
AwsKmsSensitivePropertyProvider(kmsClient, PROPERTIES));
+ }
+
+ @Test
+ public void testCleanUp() {
+ provider.cleanUp();
+ verify(kmsClient).close();
+ }
+
+ @Test
+ public void testProtect() {
+ final SdkBytes blob = SdkBytes.fromUtf8String(ENCRYPTED_PROPERTY);
+ final EncryptResponse encryptResponse =
EncryptResponse.builder().ciphertextBlob(blob).build();
+
when(kmsClient.encrypt(any(EncryptRequest.class))).thenReturn(encryptResponse);
+
+ final String protectedProperty = provider.protect(PROPERTY,
ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
+ assertEquals(PROTECTED_PROPERTY, protectedProperty);
+ }
+
+ @Test
+ public void testUnprotect() {
+ final SdkBytes blob = SdkBytes.fromUtf8String(PROPERTY);
+ final DecryptResponse decryptResponse =
DecryptResponse.builder().plaintext(blob).build();
+
when(kmsClient.decrypt(any(DecryptRequest.class))).thenReturn(decryptResponse);
+
+ final String property = provider.unprotect(PROTECTED_PROPERTY,
ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
+ assertEquals(PROPERTY, property);
+ }
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderIT.java
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderIT.java
index c89f872..4d7bb56 100644
---
a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderIT.java
+++
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderIT.java
@@ -16,6 +16,8 @@
*/
package org.apache.nifi.properties;
+import com.azure.security.keyvault.keys.cryptography.CryptographyClient;
+import
org.apache.nifi.properties.configuration.AzureCryptographyClientProvider;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
@@ -94,7 +96,10 @@ public class AzureKeyVaultKeySensitivePropertyProviderIT {
public static void initOnce() throws IOException {
initializeBootstrapProperties();
Assert.assertNotNull(props);
- spp = new AzureKeyVaultKeySensitivePropertyProvider(props);
+ final AzureCryptographyClientProvider provider = new
AzureCryptographyClientProvider();
+ final Properties properties =
provider.getClientProperties(props).orElse(null);
+ final CryptographyClient cryptographyClient =
provider.getClient(properties).orElse(null);
+ spp = new
AzureKeyVaultKeySensitivePropertyProvider(cryptographyClient, properties);
Assert.assertNotNull(spp);
}
@@ -109,7 +114,7 @@ public class AzureKeyVaultKeySensitivePropertyProviderIT {
@Test
public void testEncryptDecrypt() {
logger.info("Running testEncryptDecrypt of Azure Key Vault Key SPP
integration test");
- this.runEncryptDecryptTest();
+ runEncryptDecryptTest();
logger.info("testEncryptDecrypt of Azure Key Vault Key SPP integration
test completed");
}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java
new file mode 100644
index 0000000..412a9df
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultKeySensitivePropertyProviderTest.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.nifi.properties;
+
+import com.azure.security.keyvault.keys.cryptography.CryptographyClient;
+import com.azure.security.keyvault.keys.cryptography.models.DecryptResult;
+import com.azure.security.keyvault.keys.cryptography.models.EncryptResult;
+import
com.azure.security.keyvault.keys.cryptography.models.EncryptionAlgorithm;
+import com.azure.security.keyvault.keys.models.KeyProperties;
+import com.azure.security.keyvault.keys.models.KeyVaultKey;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class AzureKeyVaultKeySensitivePropertyProviderTest {
+ private static final String PROPERTY_NAME = String.class.getSimpleName();
+
+ private static final String PROPERTY = String.class.getName();
+
+ private static final byte[] PROPERTY_BYTES =
PROPERTY.getBytes(StandardCharsets.UTF_8);
+
+ private static final String ENCRYPTED_PROPERTY = Integer.class.getName();
+
+ private static final byte[] ENCRYPTED_BYTES =
ENCRYPTED_PROPERTY.getBytes(StandardCharsets.UTF_8);
+
+ private static final String PROTECTED_PROPERTY =
Base64.getEncoder().withoutPadding().encodeToString(ENCRYPTED_BYTES);
+
+ private static final String ID = KeyVaultKey.class.getSimpleName();
+
+ private static final Properties PROPERTIES = new Properties();
+
+ private static final EncryptionAlgorithm ALGORITHM =
EncryptionAlgorithm.A256GCM;
+
+ static {
+
PROPERTIES.setProperty(AzureKeyVaultKeySensitivePropertyProvider.ENCRYPTION_ALGORITHM_PROPERTY,
ALGORITHM.toString());
+ }
+
+ @Mock
+ private CryptographyClient cryptographyClient;
+
+ @Mock
+ private KeyVaultKey keyVaultKey;
+
+ @Mock
+ private KeyProperties keyProperties;
+
+ private AzureKeyVaultKeySensitivePropertyProvider provider;
+
+ @BeforeEach
+ public void setProvider() {
+ when(keyProperties.isEnabled()).thenReturn(true);
+ when(keyVaultKey.getId()).thenReturn(ID);
+ when(keyVaultKey.getProperties()).thenReturn(keyProperties);
+
when(keyVaultKey.getKeyOperations()).thenReturn(AzureKeyVaultKeySensitivePropertyProvider.REQUIRED_OPERATIONS);
+ when(cryptographyClient.getKey()).thenReturn(keyVaultKey);
+
+ provider = new
AzureKeyVaultKeySensitivePropertyProvider(cryptographyClient, PROPERTIES);
+ }
+
+ @Test
+ public void testValidateClientNull() {
+ final AzureKeyVaultKeySensitivePropertyProvider provider = new
AzureKeyVaultKeySensitivePropertyProvider(null, PROPERTIES);
+ assertNotNull(provider);
+ }
+
+ @Test
+ public void testProtect() {
+ final EncryptResult encryptResult = new EncryptResult(ENCRYPTED_BYTES,
ALGORITHM, ID);
+ when(cryptographyClient.encrypt(eq(ALGORITHM),
any(byte[].class))).thenReturn(encryptResult);
+
+ final String protectedProperty = provider.protect(PROPERTY,
ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
+ assertEquals(PROTECTED_PROPERTY, protectedProperty);
+ }
+
+ @Test
+ public void testUnprotect() {
+ final DecryptResult decryptResult = new DecryptResult(PROPERTY_BYTES,
ALGORITHM, ID);
+ when(cryptographyClient.decrypt(eq(ALGORITHM),
any(byte[].class))).thenReturn(decryptResult);
+
+ final String property = provider.unprotect(PROTECTED_PROPERTY,
ProtectedPropertyContext.defaultContext(PROPERTY_NAME));
+ assertEquals(PROPERTY, property);
+ }
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/GCPKMSSensitivePropertyProviderIT.java
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProviderIT.java
similarity index 81%
rename from
nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/GCPKMSSensitivePropertyProviderIT.java
rename to
nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProviderIT.java
index 64c58da..60c4d04 100644
---
a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/GCPKMSSensitivePropertyProviderIT.java
+++
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/GcpKmsSensitivePropertyProviderIT.java
@@ -16,6 +16,8 @@
*/
package org.apache.nifi.properties;
+import com.google.cloud.kms.v1.KeyManagementServiceClient;
+import
org.apache.nifi.properties.configuration.GoogleKeyManagementServiceClientProvider;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
@@ -46,7 +48,7 @@ import java.util.Properties;
* when running the integration tests
*/
-public class GCPKMSSensitivePropertyProviderIT {
+public class GcpKmsSensitivePropertyProviderIT {
private static final String SAMPLE_PLAINTEXT =
"GCPKMSSensitivePropertyProviderIT SAMPLE-PLAINTEXT";
private static final String PROJECT_ID_PROPS_NAME = "gcp.kms.project";
private static final String LOCATION_ID_PROPS_NAME = "gcp.kms.location";
@@ -56,13 +58,13 @@ public class GCPKMSSensitivePropertyProviderIT {
private static final String EMPTY_PROPERTY = "";
- private static GCPKMSSensitivePropertyProvider spp;
+ private static GcpKmsSensitivePropertyProvider spp;
private static BootstrapProperties props;
private static Path mockBootstrapConf, mockGCPBootstrapConf;
- private static final Logger logger =
LoggerFactory.getLogger(GCPKMSSensitivePropertyProviderIT.class);
+ private static final Logger logger =
LoggerFactory.getLogger(GcpKmsSensitivePropertyProviderIT.class);
private static void initializeBootstrapProperties() throws IOException{
mockBootstrapConf = Files.createTempFile("bootstrap",
".conf").toAbsolutePath();
@@ -81,11 +83,11 @@ public class GCPKMSSensitivePropertyProviderIT {
String keyId = System.getProperty(KEY_ID_PROPS_NAME, EMPTY_PROPERTY);
StringBuilder bootstrapConfText = new StringBuilder();
- String lineSeparator = System.getProperty("line.separator");
- bootstrapConfText.append(PROJECT_ID_PROPS_NAME + "=" + projectId);
- bootstrapConfText.append(lineSeparator + LOCATION_ID_PROPS_NAME + "="
+ locationId);
- bootstrapConfText.append(lineSeparator + KEYRING_ID_PROPS_NAME + "=" +
keyringId);
- bootstrapConfText.append(lineSeparator + KEY_ID_PROPS_NAME + "=" +
keyId);
+ String lineSeparator = System.lineSeparator();
+
bootstrapConfText.append(PROJECT_ID_PROPS_NAME).append("=").append(projectId).append(lineSeparator);
+
bootstrapConfText.append(LOCATION_ID_PROPS_NAME).append("=").append(locationId).append(lineSeparator);
+
bootstrapConfText.append(KEYRING_ID_PROPS_NAME).append("=").append(keyringId).append(lineSeparator);
+
bootstrapConfText.append(KEY_ID_PROPS_NAME).append("=").append(keyId).append(lineSeparator);
IOUtil.writeText(bootstrapConfText.toString(),
mockGCPBootstrapConf.toFile());
}
@@ -93,7 +95,10 @@ public class GCPKMSSensitivePropertyProviderIT {
public static void initOnce() throws IOException {
initializeBootstrapProperties();
Assert.assertNotNull(props);
- spp = new GCPKMSSensitivePropertyProvider(props);
+ final GoogleKeyManagementServiceClientProvider provider = new
GoogleKeyManagementServiceClientProvider();
+ final Properties clientProperties =
provider.getClientProperties(props).orElse(null);
+ final KeyManagementServiceClient client =
provider.getClient(clientProperties).orElse(null);
+ spp = new GcpKmsSensitivePropertyProvider(client, clientProperties);
Assert.assertNotNull(spp);
}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java
index 0f012b1..f9fa266 100644
---
a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java
+++
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/StandardSensitivePropertyProviderFactoryTest.java
@@ -56,8 +56,6 @@ public class StandardSensitivePropertyProviderFactoryTest {
private static Path nifiProperties;
private static String defaultBootstrapContents;
- private static NiFiProperties niFiProperties;
-
@BeforeClass
public static void initOnce() throws IOException {
Security.addProvider(new BouncyCastleProvider());
@@ -74,8 +72,6 @@ public class StandardSensitivePropertyProviderFactoryTest {
"nifi.bootstrap.protection.hashicorp.vault.conf",
FilenameUtils.separatorsToUnix(hashicorpVaultBootstrapConf.toString()));
bootstrapConf = writeDefaultBootstrapConf();
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH,
FilenameUtils.separatorsToUnix(nifiProperties.toString()));
-
- niFiProperties = new NiFiProperties();
}
private static Path writeDefaultBootstrapConf() throws IOException {
@@ -86,8 +82,7 @@ public class StandardSensitivePropertyProviderFactoryTest {
final Path tempBootstrapConf = Files.createTempFile("bootstrap",
".conf").toAbsolutePath();
final Path bootstrapConf = Files.move(tempBootstrapConf,
tempConfDir.resolve("bootstrap.conf"), StandardCopyOption.REPLACE_EXISTING);
- final String bootstrapConfText = String.format(contents);
- IOUtil.writeText(bootstrapConfText, bootstrapConf.toFile());
+ IOUtil.writeText(contents, bootstrapConf.toFile());
return bootstrapConf;
}
@@ -137,10 +132,9 @@ public class StandardSensitivePropertyProviderFactoryTest {
}
@Test
- public void testGetPropertyContextUnconfigured() {
+ public void testGetPropertyContextNotConfigured() {
configureDefaultFactory();
assertEquals("default/prop",
factory.getPropertyContext("ldap-provider", "prop").getContextKey());
-
}
@Test
@@ -163,11 +157,11 @@ public class StandardSensitivePropertyProviderFactoryTest
{
properties.put("vault.transit.path", "nifi-transit");
configureHashicorpVault(properties);
- final SensitivePropertyProvider spp =
factory.getProvider(PropertyProtectionScheme.HASHICORP_VAULT_TRANSIT);
+ factory.getProvider(PropertyProtectionScheme.HASHICORP_VAULT_TRANSIT);
}
@Test
- public void testHashicorpVaultTransit_isSupported() throws IOException {
+ public void testHashicorpVaultTransitSupported() throws IOException {
configureDefaultFactory();
final Properties properties = new Properties();
properties.put("vault.transit.path", "nifi-transit");
@@ -191,7 +185,7 @@ public class StandardSensitivePropertyProviderFactoryTest {
}
@Test
- public void testHashicorpVaultTransit_invalidCharacters() throws
IOException {
+ public void testHashicorpVaultTransitInvalidCharacters() throws
IOException {
configureDefaultFactory();
final Properties properties = new Properties();
properties.put("vault.transit.path", "invalid/characters");
@@ -201,7 +195,7 @@ public class StandardSensitivePropertyProviderFactoryTest {
}
@Test
- public void testAES_GCM() throws IOException {
+ public void testAesGcm() throws IOException {
configureDefaultFactory();
final ProtectedPropertyContext context =
ProtectedPropertyContext.defaultContext("propertyName");
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc
b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index 103fe65..acf9406 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -1795,7 +1795,7 @@ If no administrator action is taken, the configuration
values remain unencrypted
For more information, see the
<<toolkit-guide.adoc#encrypt_config_tool,Encrypt-Config Tool>> section in the
link:toolkit-guide.html[NiFi Toolkit Guide].
-In addition to the default AES encryption provider, other providers can be
configured in their respective `bootstrap-*.conf` files. Following is a list of
additional encryption providers and their configuration:
+Configuring each Sensitive Property Provider requires including the
appropriate file reference property in `bootstrap.conf`. The default
`bootstrap.conf` includes commented file reference properties for available
providers.
=== HashiCorp Vault providers
Two encryption providers are currently configurable in the
`bootstrap-hashicorp-vault.conf` file:
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/bootstrap.conf
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/bootstrap.conf
index 5390153..33843d7 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/bootstrap.conf
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/bootstrap.conf
@@ -61,16 +61,16 @@ nifi.bootstrap.sensitive.key=
# Sensitive Property Provider configuration
# HashiCorp Vault Sensitive Property Providers
-nifi.bootstrap.protection.hashicorp.vault.conf=./conf/bootstrap-hashicorp-vault.conf
+#nifi.bootstrap.protection.hashicorp.vault.conf=./conf/bootstrap-hashicorp-vault.conf
# AWS KMS Sensitive Property Providers
-nifi.bootstrap.protection.aws.kms.conf=./conf/bootstrap-aws.conf
+#nifi.bootstrap.protection.aws.kms.conf=./conf/bootstrap-aws.conf
# Azure Key Vault Sensitive Property Providers
-nifi.bootstrap.protection.azure.keyvault.conf=./conf/bootstrap-azure.conf
+#nifi.bootstrap.protection.azure.keyvault.conf=./conf/bootstrap-azure.conf
# GCP KMS Sensitive Property Providers
-nifi.bootstrap.protection.gcp.kms.conf=./conf/bootstrap-gcp.conf
+#nifi.bootstrap.protection.gcp.kms.conf=./conf/bootstrap-gcp.conf
# Sets the provider of SecureRandom to /dev/urandom to prevent blocking on VMs
java.arg.15=-Djava.security.egd=file:/dev/urandom
diff --git
a/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap.conf
b/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap.conf
index ee03964..997a05e 100644
---
a/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap.conf
+++
b/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap.conf
@@ -56,13 +56,13 @@ nifi.registry.bootstrap.sensitive.key=
# Sensitive Property Provider configuration
# HashiCorp Vault Sensitive Property Providers
-nifi.registry.bootstrap.protection.hashicorp.vault.conf=./conf/bootstrap-hashicorp-vault.conf
+#nifi.registry.bootstrap.protection.hashicorp.vault.conf=./conf/bootstrap-hashicorp-vault.conf
# AWS KMS Sensitive Property Providers
-nifi.registry.bootstrap.protection.aws.kms.conf=./conf/bootstrap-aws.conf
+#nifi.registry.bootstrap.protection.aws.kms.conf=./conf/bootstrap-aws.conf
# Azure Key Vault Sensitive Property Providers
-nifi.registry.bootstrap.protection.azure.keyvault.conf=./conf/bootstrap-azure.conf
+#nifi.registry.bootstrap.protection.azure.keyvault.conf=./conf/bootstrap-azure.conf
# GCP KMS Sensitive Property Providers
-nifi.registry.bootstrap.protection.gcp.kms.conf=./conf/bootstrap-gcp.conf
+#nifi.registry.bootstrap.protection.gcp.kms.conf=./conf/bootstrap-gcp.conf