This is an automated email from the ASF dual-hosted git repository.
pvillard 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 8d0eec2 NIFI-9266 Added Azure Key Vault Secret SPP
8d0eec2 is described below
commit 8d0eec2d620d994c77c01388e7df9cf2a227c3c9
Author: exceptionfactory <[email protected]>
AuthorDate: Fri Oct 1 16:34:09 2021 -0500
NIFI-9266 Added Azure Key Vault Secret SPP
Signed-off-by: Pierre Villard <[email protected]>
This closes #5435.
---
.../nifi-sensitive-property-provider/pom.xml | 31 ++++-
...ureKeyVaultSecretSensitivePropertyProvider.java | 125 +++++++++++++++++++++
.../nifi/properties/PropertyProtectionScheme.java | 1 +
.../StandardSensitivePropertyProviderFactory.java | 9 ++
.../configuration/AzureClientProvider.java | 68 +++++++++++
.../AzureCryptographyClientProvider.java | 25 +++--
.../configuration/AzureSecretClientProvider.java | 69 ++++++++++++
...eyVaultSecretSensitivePropertyProviderTest.java | 108 ++++++++++++++++++
.../AzureCryptographyClientProviderTest.java | 45 ++++++++
.../AzureSecretClientProviderTest.java | 45 ++++++++
.../src/main/asciidoc/administration-guide.adoc | 50 ++++++++-
nifi-docs/src/main/asciidoc/toolkit-guide.adoc | 40 ++++++-
.../src/main/resources/conf/bootstrap-azure.conf | 8 +-
.../src/main/resources/conf/bootstrap-azure.conf | 8 +-
pom.xml | 5 +-
15 files changed, 616 insertions(+), 21 deletions(-)
diff --git a/nifi-commons/nifi-sensitive-property-provider/pom.xml
b/nifi-commons/nifi-sensitive-property-provider/pom.xml
index 5ef4a43..0fb83e35 100644
--- a/nifi-commons/nifi-sensitive-property-provider/pom.xml
+++ b/nifi-commons/nifi-sensitive-property-provider/pom.xml
@@ -73,8 +73,31 @@
</dependency>
<dependency>
<groupId>com.azure</groupId>
+ <artifactId>azure-security-keyvault-secrets</artifactId>
+ <version>4.3.3</version>
+ <exclusions>
+ <exclusion>
+ <groupId>com.azure</groupId>
+ <artifactId>azure-core-http-netty</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.fasterxml.woodstox</groupId>
+ <artifactId>woodstox-core</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.codehaus.woodstox</groupId>
+ <artifactId>stax2-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>io.netty</groupId>
+ <artifactId>netty-tcnative-boringssl-static</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>com.azure</groupId>
<artifactId>azure-security-keyvault-keys</artifactId>
- <version>4.3.1</version>
+ <version>4.3.3</version>
<exclusions>
<exclusion>
<groupId>com.azure</groupId>
@@ -144,6 +167,12 @@
<version>2.10.0</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-inline</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<!-- Required to run Groovy tests without any Java tests -->
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java
new file mode 100644
index 0000000..ac9e1da
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProvider.java
@@ -0,0 +1,125 @@
+/*
+ * 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.core.exception.ResourceNotFoundException;
+import com.azure.security.keyvault.secrets.SecretClient;
+import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
+
+import java.util.Objects;
+
+/**
+ * Microsoft Azure Key Vault Secret implementation of Sensitive Property
Provider for externalized storage of properties
+ */
+public class AzureKeyVaultSecretSensitivePropertyProvider implements
SensitivePropertyProvider {
+ private static final String FORWARD_SLASH = "/";
+
+ private static final String PERIOD = "\\.";
+
+ private static final String DASH = "-";
+
+ private SecretClient secretClient;
+
+ AzureKeyVaultSecretSensitivePropertyProvider(final SecretClient
secretClient) {
+ this.secretClient = secretClient;
+ }
+
+ /**
+ * Get Provider name using Protection Scheme
+ *
+ * @return Provider name
+ */
+ @Override
+ public String getName() {
+ return PropertyProtectionScheme.AZURE_KEYVAULT_SECRET.getName();
+ }
+
+ /**
+ * Get Identifier key using Protection Scheme
+ *
+ * @return Identifier key
+ */
+ @Override
+ public String getIdentifierKey() {
+ return PropertyProtectionScheme.AZURE_KEYVAULT_SECRET.getIdentifier();
+ }
+
+ /**
+ * Is Provider supported returns status based on configuration of Secret
Client
+ *
+ * @return Supported status
+ */
+ @Override
+ public boolean isSupported() {
+ return secretClient != null;
+ }
+
+ /**
+ * Protect value stores a Secret in Azure Key Vault using the Property
Context Key as the Secret Name
+ *
+ * @param unprotectedValue Value to be stored
+ * @param context Property Context containing Context Key used to store
Secret
+ * @return Key Vault Secret Identifier
+ * @throws SensitivePropertyProtectionException Thrown when storing Secret
failed
+ */
+ @Override
+ public String protect(final String unprotectedValue, final
ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
+ Objects.requireNonNull(unprotectedValue, "Value required");
+ final String secretName = getSecretName(context);
+ try {
+ final KeyVaultSecret keyVaultSecret =
secretClient.setSecret(secretName, unprotectedValue);
+ return keyVaultSecret.getId();
+ } catch (final RuntimeException e) {
+ throw new SensitivePropertyProtectionException(String.format("Set
Secret [%s] failed", secretName), e);
+ }
+ }
+
+ /**
+ * Unprotect value retrieves a Secret from Azure Key Vault using Property
Context Key
+ *
+ * @param protectedValue Key Vault Secret Identifier is not used
+ * @param context Property Context containing Context Key used to retrieve
Secret
+ * @return Secret Value
+ * @throws SensitivePropertyProtectionException Thrown when Secret not
found or retrieval failed
+ */
+ @Override
+ public String unprotect(final String protectedValue, final
ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
+ final String secretName = getSecretName(context);
+ try {
+ final KeyVaultSecret keyVaultSecret =
secretClient.getSecret(secretName);
+ return keyVaultSecret.getValue();
+ } catch (final ResourceNotFoundException e) {
+ throw new
SensitivePropertyProtectionException(String.format("Secret [%s] not found",
secretName), e);
+ } catch (final RuntimeException e) {
+ throw new
SensitivePropertyProtectionException(String.format("Secret [%s] request
failed", secretName), e);
+ }
+ }
+
+ /**
+ * Clean up not implemented
+ */
+ @Override
+ public void cleanUp() {
+
+ }
+
+ private String getSecretName(final ProtectedPropertyContext context) {
+ final String contextKey = Objects.requireNonNull(context, "Context
required").getContextKey();
+ // Replace forward slash and period with dash since Azure Key Vault
Secret Names do not support certain characters
+ return contextKey.replaceAll(FORWARD_SLASH, DASH).replaceAll(PERIOD,
DASH);
+ }
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/PropertyProtectionScheme.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/PropertyProtectionScheme.java
index 157fc81..7d71004 100644
---
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/PropertyProtectionScheme.java
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/PropertyProtectionScheme.java
@@ -27,6 +27,7 @@ public enum PropertyProtectionScheme {
AES_GCM("aes/gcm/(128|192|256)", "aes/gcm/%s", "AES Sensitive Property
Provider", true),
AWS_KMS("aws/kms", "aws/kms", "AWS KMS Sensitive Property Provider",
false),
AZURE_KEYVAULT_KEY("azure/keyvault/key", "azure/keyvault/key", "Azure Key
Vault Key Sensitive Property Provider", false),
+ AZURE_KEYVAULT_SECRET("azure/keyvault/secret", "azure/keyvault/secret",
"Azure Key Vault Secret Sensitive Property Provider", false),
GCP_KMS("gcp/kms", "gcp/kms", "GCP Cloud KMS Sensitive Property Provider",
false),
HASHICORP_VAULT_KV("hashicorp/vault/kv/[a-zA-Z0-9_-]+",
"hashicorp/vault/kv/%s", "HashiCorp Vault Key/Value Engine Sensitive Property
Provider", false),
HASHICORP_VAULT_TRANSIT("hashicorp/vault/transit/[a-zA-Z0-9_-]+",
"hashicorp/vault/transit/%s", "HashiCorp Vault Transit Engine Sensitive
Property Provider", false);
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 1564e3f..1d7e12f 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
@@ -17,10 +17,12 @@
package org.apache.nifi.properties;
import com.azure.security.keyvault.keys.cryptography.CryptographyClient;
+import com.azure.security.keyvault.secrets.SecretClient;
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.AzureSecretClientProvider;
import org.apache.nifi.properties.configuration.ClientProvider;
import
org.apache.nifi.properties.configuration.GoogleKeyManagementServiceClientProvider;
import org.apache.nifi.util.NiFiBootstrapUtils;
@@ -142,6 +144,13 @@ public class StandardSensitivePropertyProviderFactory
implements SensitiveProper
final Optional<CryptographyClient> cryptographyClient =
clientProvider.getClient(clientProperties);
return new
AzureKeyVaultKeySensitivePropertyProvider(cryptographyClient.orElse(null),
clientProperties);
});
+ case AZURE_KEYVAULT_SECRET:
+ return providerMap.computeIfAbsent(protectionScheme, s -> {
+ final AzureSecretClientProvider clientProvider = new
AzureSecretClientProvider();
+ final Properties clientProperties =
getClientProperties(clientProvider);
+ final Optional<SecretClient> secretClient =
clientProvider.getClient(clientProperties);
+ return new
AzureKeyVaultSecretSensitivePropertyProvider(secretClient.orElse(null));
+ });
case GCP_KMS:
return providerMap.computeIfAbsent(protectionScheme, s -> {
final GoogleKeyManagementServiceClientProvider
clientProvider = new GoogleKeyManagementServiceClientProvider();
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
new file mode 100644
index 0000000..61561e7
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureClientProvider.java
@@ -0,0 +1,68 @@
+/*
+ * 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.core.credential.TokenCredential;
+import com.azure.identity.DefaultAzureCredentialBuilder;
+import org.apache.nifi.properties.BootstrapProperties;
+import org.apache.nifi.util.StringUtils;
+
+import java.util.Optional;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * Abstract Microsoft Azure Client Provider
+ */
+public abstract class AzureClientProvider<T> extends
BootstrapPropertiesClientProvider<T> {
+ public AzureClientProvider() {
+
super(BootstrapProperties.BootstrapPropertyKey.AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF);
+ }
+
+ /**
+ * 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 isMissingProperties(clientProperties) ? Optional.empty() :
Optional.of(getConfiguredClient(clientProperties));
+ }
+
+ /**
+ * Get Default Azure Token Credential using Default Credentials Builder
for environment variables and system properties
+ *
+ * @return Token Credential
+ */
+ protected TokenCredential getDefaultTokenCredential() {
+ return new DefaultAzureCredentialBuilder().build();
+ }
+
+ /**
+ * Get Property Names required for initializing client in order to perform
initial validation
+ *
+ * @return Set of required client property names
+ */
+ protected abstract Set<String> getRequiredPropertyNames();
+
+ private boolean isMissingProperties(final Properties clientProperties) {
+ return clientProperties == null ||
getRequiredPropertyNames().stream().anyMatch(propertyName ->
+ StringUtils.isBlank(clientProperties.getProperty(propertyName))
+ );
+ }
+}
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
index 643d02e..adf974e 100644
---
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
@@ -16,23 +16,22 @@
*/
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.Collections;
+import java.util.HashSet;
import java.util.Properties;
+import java.util.Set;
/**
* Microsoft Azure Cryptography Client Provider
*/
-public class AzureCryptographyClientProvider extends
BootstrapPropertiesClientProvider<CryptographyClient> {
- private static final String KEY_ID_PROPERTY = "azure.keyvault.key.id";
+public class AzureCryptographyClientProvider extends
AzureClientProvider<CryptographyClient> {
+ protected static final String KEY_ID_PROPERTY = "azure.keyvault.key.id";
- public AzureCryptographyClientProvider() {
-
super(BootstrapProperties.BootstrapPropertyKey.AZURE_KEYVAULT_SENSITIVE_PROPERTY_PROVIDER_CONF);
- }
+ private static final Set<String> REQUIRED_PROPERTY_NAMES = new
HashSet<>(Collections.singletonList(KEY_ID_PROPERTY));
/**
* Get Configured Client using Default Azure Credentials Builder and
configured Key Identifier
@@ -47,11 +46,21 @@ public class AzureCryptographyClientProvider extends
BootstrapPropertiesClientPr
try {
return new CryptographyClientBuilder()
- .credential(new DefaultAzureCredentialBuilder().build())
+ .credential(getDefaultTokenCredential())
.keyIdentifier(keyIdentifier)
.buildClient();
} catch (final RuntimeException e) {
throw new SensitivePropertyProtectionException("Azure Cryptography
Builder Client Failed using Default Credentials", e);
}
}
+
+ /**
+ * Get required property names for Azure Cryptography Client
+ *
+ * @return Required client property names
+ */
+ @Override
+ protected Set<String> getRequiredPropertyNames() {
+ return REQUIRED_PROPERTY_NAMES;
+ }
}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureSecretClientProvider.java
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureSecretClientProvider.java
new file mode 100644
index 0000000..65432fa
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/main/java/org/apache/nifi/properties/configuration/AzureSecretClientProvider.java
@@ -0,0 +1,69 @@
+/*
+ * 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.core.credential.TokenCredential;
+import com.azure.identity.DefaultAzureCredentialBuilder;
+import com.azure.security.keyvault.secrets.SecretClient;
+import com.azure.security.keyvault.secrets.SecretClientBuilder;
+import org.apache.nifi.properties.SensitivePropertyProtectionException;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * Microsoft Azure Secret Client Provider
+ */
+public class AzureSecretClientProvider extends
AzureClientProvider<SecretClient> {
+ protected static final String URI_PROPERTY = "azure.keyvault.uri";
+
+ private static final Set<String> REQUIRED_PROPERTY_NAMES = new
HashSet<>(Collections.singletonList(URI_PROPERTY));
+
+ /**
+ * Get Secret Client using Default Azure Credentials Builder and default
configuration from environment variables
+ *
+ * @param clientProperties Client Properties
+ * @return Secret Client
+ */
+ @Override
+ protected SecretClient getConfiguredClient(final Properties
clientProperties) {
+ final String uri = clientProperties.getProperty(URI_PROPERTY);
+ logger.debug("Azure Secret Client with URI [{}]", uri);
+
+ try {
+ final TokenCredential credential = new
DefaultAzureCredentialBuilder().build();
+ return new SecretClientBuilder()
+ .credential(credential)
+ .vaultUrl(uri)
+ .buildClient();
+ } catch (final RuntimeException e) {
+ throw new SensitivePropertyProtectionException("Azure Secret
Builder Client Failed using Default Credentials", e);
+ }
+ }
+
+ /**
+ * Get required property names for Azure Secret Client
+ *
+ * @return Required client property names
+ */
+ @Override
+ protected Set<String> getRequiredPropertyNames() {
+ return REQUIRED_PROPERTY_NAMES;
+ }
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java
new file mode 100644
index 0000000..3ec2d4a
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/AzureKeyVaultSecretSensitivePropertyProviderTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.core.exception.AzureException;
+import com.azure.core.exception.ResourceNotFoundException;
+import com.azure.core.http.HttpResponse;
+import com.azure.security.keyvault.secrets.SecretClient;
+import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
+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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class AzureKeyVaultSecretSensitivePropertyProviderTest {
+ private static final String PROPERTY_NAME = "property.name";
+
+ private static final String SECRET_NAME = "default-property-name";
+
+ private static final String PROPERTY = String.class.getName();
+
+ private static final String PROTECTED_PROPERTY =
KeyVaultSecret.class.getSimpleName();
+
+ private static final String ID = KeyVaultSecret.class.getName();
+
+ @Mock
+ private SecretClient secretClient;
+
+ @Mock
+ private KeyVaultSecret keyVaultSecret;
+
+ @Mock
+ private HttpResponse httpResponse;
+
+ private AzureKeyVaultSecretSensitivePropertyProvider provider;
+
+ @BeforeEach
+ public void setProvider() {
+ provider = new
AzureKeyVaultSecretSensitivePropertyProvider(secretClient);
+ }
+
+ @Test
+ public void testClientNull() {
+ final AzureKeyVaultSecretSensitivePropertyProvider provider = new
AzureKeyVaultSecretSensitivePropertyProvider(null);
+ assertNotNull(provider);
+ assertFalse(provider.isSupported());
+ }
+
+ @Test
+ public void testProtect() {
+ when(secretClient.setSecret(eq(SECRET_NAME),
eq(PROPERTY))).thenReturn(keyVaultSecret);
+ when(keyVaultSecret.getId()).thenReturn(ID);
+
+ final ProtectedPropertyContext context =
ProtectedPropertyContext.defaultContext(PROPERTY_NAME);
+ final String protectedProperty = provider.protect(PROPERTY, context);
+ assertEquals(ID, protectedProperty);
+ }
+
+ @Test
+ public void testProtectException() {
+ final ProtectedPropertyContext context =
ProtectedPropertyContext.defaultContext(PROPERTY_NAME);
+ final String secretName = context.getContextKey();
+ when(secretClient.setSecret(eq(secretName),
eq(PROPERTY))).thenThrow(new AzureException());
+
+ assertThrows(SensitivePropertyProtectionException.class, () ->
provider.protect(PROPERTY, context));
+ }
+
+ @Test
+ public void testUnprotect() {
+
when(secretClient.getSecret(eq(SECRET_NAME))).thenReturn(keyVaultSecret);
+ when(keyVaultSecret.getValue()).thenReturn(PROPERTY);
+
+ final ProtectedPropertyContext context =
ProtectedPropertyContext.defaultContext(PROPERTY_NAME);
+ final String property = provider.unprotect(PROTECTED_PROPERTY,
context);
+ assertEquals(PROPERTY, property);
+ }
+
+ @Test
+ public void testUnprotectResourceNotFoundException() {
+ when(secretClient.getSecret(eq(SECRET_NAME))).thenThrow(new
ResourceNotFoundException(SECRET_NAME, httpResponse));
+
+ final ProtectedPropertyContext context =
ProtectedPropertyContext.defaultContext(PROPERTY_NAME);
+ assertThrows(SensitivePropertyProtectionException.class, () ->
provider.unprotect(PROTECTED_PROPERTY, context));
+ }
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.java
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.java
new file mode 100644
index 0000000..e898ea4
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureCryptographyClientProviderTest.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 com.azure.security.keyvault.keys.cryptography.CryptographyClient;
+import org.apache.nifi.util.StringUtils;
+import org.junit.jupiter.api.Test;
+
+import java.util.Optional;
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+public class AzureCryptographyClientProviderTest {
+
+ @Test
+ public void testClientPropertiesNull() {
+ final AzureCryptographyClientProvider provider = new
AzureCryptographyClientProvider();
+ final Optional<CryptographyClient> optionalClient =
provider.getClient(null);
+ assertFalse(optionalClient.isPresent());
+ }
+
+ @Test
+ public void testClientPropertiesKeyIdBlank() {
+ final AzureCryptographyClientProvider provider = new
AzureCryptographyClientProvider();
+ final Properties clientProperties = new Properties();
+
clientProperties.setProperty(AzureCryptographyClientProvider.KEY_ID_PROPERTY,
StringUtils.EMPTY);
+ final Optional<CryptographyClient> optionalClient =
provider.getClient(clientProperties);
+ assertFalse(optionalClient.isPresent());
+ }
+}
diff --git
a/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.java
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.java
new file mode 100644
index 0000000..6f312df
--- /dev/null
+++
b/nifi-commons/nifi-sensitive-property-provider/src/test/java/org/apache/nifi/properties/configuration/AzureSecretClientProviderTest.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 com.azure.security.keyvault.secrets.SecretClient;
+import org.apache.nifi.util.StringUtils;
+import org.junit.jupiter.api.Test;
+
+import java.util.Optional;
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+public class AzureSecretClientProviderTest {
+
+ @Test
+ public void testClientPropertiesNull() {
+ final AzureSecretClientProvider provider = new
AzureSecretClientProvider();
+ final Optional<SecretClient> optionalClient = provider.getClient(null);
+ assertFalse(optionalClient.isPresent());
+ }
+
+ @Test
+ public void testClientPropertiesUriBlank() {
+ final AzureSecretClientProvider provider = new
AzureSecretClientProvider();
+ final Properties clientProperties = new Properties();
+ clientProperties.setProperty(AzureSecretClientProvider.URI_PROPERTY,
StringUtils.EMPTY);
+ final Optional<SecretClient> optionalClient =
provider.getClient(clientProperties);
+ assertFalse(optionalClient.isPresent());
+ }
+}
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc
b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index 4f64041..8d2c58e 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -1858,8 +1858,21 @@ This provider uses https://aws.amazon.com/kms/[AWS Key
Management Service] for d
|`aws.secret.access.key`|The secret access key used to access AWS KMS.|_none_
|===
-=== Azure Key Vault Key provider
-This protection scheme uses keys managed by
https://docs.microsoft.com/en-us/azure/key-vault/keys/about-keys[Azure Key
Vault Keys] for encryption and decryption. Azure Key Vault configuration
properties can be stored in the `bootstrap-azure.conf` file, as referenced in
the `bootstrap.conf` of NiFi or NiFi Registry. The provider will use the Azure
default credentials provider chain as described in the
https://docs.microsoft.com/en-us/java/api/overview/azure/security-keyvault-keys-readme?v
[...]
+[[azure-key-vault-key-provider]]
+=== Azure Key Vault Key Provider
+This protection scheme uses keys managed by
+https://docs.microsoft.com/en-us/azure/key-vault/keys/about-keys[Azure Key
Vault Keys] for encryption and decryption.
+
+Azure Key Vault configuration properties can be stored in the
`bootstrap-azure.conf` file, as referenced in the
+`bootstrap.conf` of NiFi or NiFi Registry.
+The provider will use the
+https://docs.microsoft.com/en-us/java/api/com.azure.identity.defaultazurecredential[DefaultAzureCredential]
+for authentication.
+The
https://docs.microsoft.com/en-us/java/api/overview/azure/identity-readme#key-concepts[Azure
Identity] client library
+describes the process for credentials resolution, which leverages environment
variables, system properties, and falls
+back to
+https://docs.microsoft.com/en-us/java/api/overview/azure/identity-readme#managed-identity-support[Managed
Identity]
+authentication.
==== Required properties
[options="header,footer"]
@@ -1869,6 +1882,39 @@ This protection scheme uses keys managed by
https://docs.microsoft.com/en-us/azu
|`azure.keyvault.encryption.algorithm`|The encryption algorithm that the Azure
Key Vault client uses for encryption and decryption.|_none_
|===
+[[azure-key-vault-secret-provider]]
+=== Azure Key Vault Secret Provider
+This protection scheme uses secrets managed by
+https://docs.microsoft.com/en-us/azure/key-vault/secrets/about-secrets[Azure
Key Vault Secrets] for storing and
+retrieving protected properties.
+
+Azure Key Vault configuration properties can be stored in the
`bootstrap-azure.conf` file, as referenced in the
+`bootstrap.conf` of NiFi or NiFi Registry.
+The provider will use the
+https://docs.microsoft.com/en-us/java/api/com.azure.identity.defaultazurecredential[DefaultAzureCredential]
+for authentication.
+The
https://docs.microsoft.com/en-us/java/api/overview/azure/identity-readme#key-concepts[Azure
Identity] client library
+describes the process for credentials resolution, which leverages environment
variables, system properties, and falls
+back to
+https://docs.microsoft.com/en-us/java/api/overview/azure/identity-readme#managed-identity-support[Managed
Identity]
+authentication.
+
+Names of secrets stored in Azure Key Vault support alphanumeric and dash
characters, but do not support characters such as `/` or `.`.
+For this reason, NiFi replaces these characters with `-` when storing and
retrieving secrets. The following table provides an example property name
mapping:
+
+[options="header,footer"]
+|===
+|Property Context|Property Name|Secret Name
+|`default`|`nifi.security.keystorePasswd`|`default-nifi-security-keystorePasswd`
+|===
+
+==== Required properties
+[options="header,footer"]
+|===
+|Property Name|Description|Default
+|`azure.keyvault.uri`|URI for the Azure Key Vault service such as
`https://{value-name}.vault.azure.net/` |_none_
+|===
+
=== Google Cloud KMS provider
This protection scheme uses Google Cloud Key Management Service
(https://cloud.google.com/security-key-management[Google Cloud Key Management
Service]) for encryption and decryption. Google Cloud KMS configuration
properties are to be stored in the `bootstrap-gcp.conf` file, as referenced in
the `bootstrap.conf` of NiFi or NiFi Registry. Credentials must be configured
as per the following documentation:
https://cloud.google.com/kms/docs/reference/libraries[Google Cloud KMS
documentation]
diff --git a/nifi-docs/src/main/asciidoc/toolkit-guide.adoc
b/nifi-docs/src/main/asciidoc/toolkit-guide.adoc
index be4eb79..d1c74d9 100644
--- a/nifi-docs/src/main/asciidoc/toolkit-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/toolkit-guide.adoc
@@ -435,7 +435,7 @@ The following are available options when targeting NiFi:
* `-f`,`--flowXml <file>` The _flow.xml.gz_ file
currently protected with old password (will be overwritten unless `-g` is
specified)
* `-g`,`--outputFlowXml <file>` The destination _flow.xml.gz_
file containing protected config values (will not modify input _flow.xml.gz_)
* `-b`,`--bootstrapConf <file>` The bootstrap.conf file to
persist root key and to optionally provide any configuration for the protection
scheme.
-* `-S`,`--protectionScheme <protectionScheme>` Selects the protection scheme
for encrypted properties. Valid values are: [<<AES_GCM>>,
<<HASHICORP_VAULT_TRANSIT>>, <<HASHICORP_VAULT_KV>>, <<AWS_KMS>>,
<<AZURE_KEYVAULT_KEY>>, <<GCP_KMS>>] (default is AES_GCM)
+* `-S`,`--protectionScheme <protectionScheme>` Selects the protection scheme
for encrypted properties. Valid values are: [<<AES_GCM>>,
<<HASHICORP_VAULT_TRANSIT>>, <<HASHICORP_VAULT_KV>>, <<AWS_KMS>>,
<<AZURE_KEYVAULT_KEY>>, <<AZURE_KEYVAULT_SECRET>>, <<GCP_KMS>>] (default is
AES_GCM)
* `-k`,`--key <keyhex>` The raw hexadecimal key to use
to encrypt the sensitive properties
* `-e`,`--oldKey <keyhex>` The old raw hexadecimal key to
use during key migration
* `-H`,`--oldProtectionScheme <protectionScheme>` The old protection scheme to
use during encryption migration (see --protectionScheme for possible values).
Default is AES_GCM
@@ -456,7 +456,7 @@ The following are available options when targeting NiFi
Registry using the `--ni
* `-v`,`--verbose` Sets verbose mode (default
false)
* `-p`,`--password <password>` Protect the files using a
password-derived key. If an argument is not provided to this flag, interactive
mode will be triggered to prompt the user to enter the password.
* `-k`,`--key <keyhex>` Protect the files using a raw
hexadecimal key. If an argument is not provided to this flag, interactive mode
will be triggered to prompt the user to enter the key.
-* `-S`,`--protectionScheme <protectionScheme>` Selects the protection scheme
for encrypted properties. Valid values are: [<<AES_GCM>>,
<<HASHICORP_VAULT_TRANSIT>>, <<HASHICORP_VAULT_KV>>, <<AWS_KMS>>,
<<AZURE_KEYVAULT_KEY>>, <<GCP_KMS>>] (default is AES_GCM)
+* `-S`,`--protectionScheme <protectionScheme>` Selects the protection scheme
for encrypted properties. Valid values are: [<<AES_GCM>>,
<<HASHICORP_VAULT_TRANSIT>>, <<HASHICORP_VAULT_KV>>, <<AWS_KMS>>,
<<AZURE_KEYVAULT_KEY>>, <<AZURE_KEYVAULT_SECRET>>, <<GCP_KMS>>] (default is
AES_GCM)
* `--oldPassword <password>` If the input files are already
protected using a password-derived key, this specifies the old password so that
the files can be unprotected before re-protecting.
* `--oldKey <keyhex>` If the input files are already
protected using a key, this specifies the raw hexadecimal key so that the files
can be unprotected before re-protecting.
* `-H`,`--oldProtectionScheme <protectionScheme>`The old protection scheme to
use during encryption migration (see --protectionScheme for possible values).
Default is AES_GCM.
@@ -485,8 +485,40 @@ This protection scheme uses
https://www.vaultproject.io/docs/secrets/kv/kv-v1[Ha
==== AWS_KMS [[AWS_KMS]]
This protection scheme uses https://aws.amazon.com/kms/[AWS Key Management]
Service for encryption and decryption. AWS KMS configuration properties can be
stored in the `bootstrap-aws.conf` file, as referenced in the `bootstrap.conf`
of NiFi or NiFi Registry. If the configuration properties are not specified in
`bootstrap-aws.conf`, then the provider will attempt to use the AWS default
credentials provider, which checks standard environment variables and system
properties. Therefore, wh [...]
-==== AZURE_KEYVAULT_KEY [[AZURE_KEYVAULT_KEY]]
-This protection scheme uses keys managed by
https://docs.microsoft.com/en-us/azure/key-vault/keys/about-keys[Azure Key
Vault Keys] for encryption and decryption. Azure Key Vault configuration
properties can be stored in the `bootstrap-azure.conf` file, as referenced in
the `bootstrap.conf` of NiFi or NiFi Registry. The provider will utilize the
Azure default credentials provider chain as described in the
https://docs.microsoft.com/en-us/java/api/overview/azure/security-keyvault-keys-read
[...]
+==== Microsoft Azure Key Vault Sensitive Property Providers
+
+Azure Key Vault configuration properties can be stored in the
`bootstrap-azure.conf` file, as referenced in the
+`bootstrap.conf` of NiFi or NiFi Registry.
+
+Azure Key Vault providers will use the
+https://docs.microsoft.com/en-us/java/api/com.azure.identity.defaultazurecredential[DefaultAzureCredential]
+for authentication.
+The
https://docs.microsoft.com/en-us/java/api/overview/azure/identity-readme#key-concepts[Azure
Identity] client library
+describes the process for credentials resolution, which leverages environment
variables, system properties, and falls
+back to
+https://docs.microsoft.com/en-us/java/api/overview/azure/identity-readme#managed-identity-support[Managed
Identity]
+authentication.
+
+When using Azure Key Vault providers, `bootstrap.conf` must contain the
+`nifi.bootstrap.protection.azure.keyvault.conf` property. The `bootstrap.conf`
file location must be specified using the
+`-b` argument when running the Encrypt Config Tool.
+
+===== AZURE_KEYVAULT_KEY [[AZURE_KEYVAULT_KEY]]
+
+This protection scheme uses keys managed by
+https://docs.microsoft.com/en-us/azure/key-vault/keys/about-keys[Azure Key
Vault Keys] for encryption and decryption.
+
+See <<administration-guide.adoc#azure-key-vault-key-provider,Azure Key Vault
Key Provider>> in the NiFi System
+Administrator's Guide for required properties.
+
+===== AZURE_KEYVAULT_SECRET [[AZURE_KEYVAULT_SECRET]]
+
+This protection scheme uses secrets managed by
+https://docs.microsoft.com/en-us/azure/key-vault/secrets/about-secrets[Azure
Key Vault Secrets] for storing and
+retrieving sensitive property values.
+
+See <<administration-guide.adoc#azure-key-vault-secret-provider,Azure Key
Vault Secret Provider>> in the NiFi System
+Administrator's Guide for required properties.
==== GCP_KMS [[GCP_KMS]]
This protection scheme uses Google Cloud Key Management Service
(https://cloud.google.com/security-key-management[Google Cloud Key Management
Service]) for encryption and decryption. Google Cloud KMS configuration
properties are to be stored in the `bootstrap-gcp.conf` file, as referenced in
the `bootstrap.conf` of NiFi or NiFi Registry. Credentials must be configured
as per the following documentation:
https://cloud.google.com/kms/docs/reference/libraries[Google Cloud KMS
documentation] [...]
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/bootstrap-azure.conf
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/bootstrap-azure.conf
index f7cc47e..49e318e 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/bootstrap-azure.conf
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/bootstrap-azure.conf
@@ -15,6 +15,10 @@
# limitations under the License.
#
-# Azure KeyVault Key ID and Encryption Algorithm is required to be configured
for Azure KeyVault Sensitive Property Provider
+# Key Identifier for Azure Key Vault Key Sensitive Property Provider
azure.keyvault.key.id=
-azure.keyvault.encryption.algorithm=
\ No newline at end of file
+# Encryption Algorithm for Azure Key Vault Key Sensitive Property Provider
+azure.keyvault.encryption.algorithm=
+
+# Vault URI for Azure Key Vault Secret Sensitive Property Provider
+azure.keyvault.uri=
diff --git
a/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap-azure.conf
b/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap-azure.conf
index f7cc47e..49e318e 100644
---
a/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap-azure.conf
+++
b/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap-azure.conf
@@ -15,6 +15,10 @@
# limitations under the License.
#
-# Azure KeyVault Key ID and Encryption Algorithm is required to be configured
for Azure KeyVault Sensitive Property Provider
+# Key Identifier for Azure Key Vault Key Sensitive Property Provider
azure.keyvault.key.id=
-azure.keyvault.encryption.algorithm=
\ No newline at end of file
+# Encryption Algorithm for Azure Key Vault Key Sensitive Property Provider
+azure.keyvault.encryption.algorithm=
+
+# Vault URI for Azure Key Vault Secret Sensitive Property Provider
+azure.keyvault.uri=
diff --git a/pom.xml b/pom.xml
index 54ebe3c..2fc31dd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -111,6 +111,7 @@
<aspectj.version>1.9.6</aspectj.version>
<jersey.version>2.33</jersey.version>
<logback.version>1.2.6</logback.version>
+ <mockito.version>2.28.2</mockito.version>
</properties>
<repositories>
@@ -286,12 +287,12 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
- <version>2.28.2</version>
+ <version>${mockito.version}</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
- <version>2.28.2</version>
+ <version>${mockito.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>