Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Objects;
import java.util.Optional;
Expand All @@ -33,13 +34,22 @@ public class BootstrapProperties extends StandardReadableProperties {
public enum BootstrapPropertyKey {
SENSITIVE_KEY("bootstrap.sensitive.key"),
HASHICORP_VAULT_SENSITIVE_PROPERTY_PROVIDER_CONF("bootstrap.protection.hashicorp.vault.conf"),
AWS_KMS_SENSITIVE_PROPERTY_PROVIDER_CONF("bootstrap.protection.aws.kms.conf");
AWS_KMS_SENSITIVE_PROPERTY_PROVIDER_CONF("bootstrap.protection.aws.kms.conf"),
CONTEXT_MAPPING_PREFIX("bootstrap.protection.context.mapping.");

private final String key;

BootstrapPropertyKey(final String key) {
this.key = key;
}

/**
* Returns the property key.
* @return The property key
*/
public String getKey() {
return key;
}
}

private final String propertyPrefix;
Expand Down Expand Up @@ -127,7 +137,7 @@ public String toString() {
public static final BootstrapProperties EMPTY = new BootstrapProperties("", new Properties(), Paths.get("conf/bootstrap.conf")) {
@Override
public Set<String> getPropertyKeys() {
return null;
return Collections.EMPTY_SET;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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.util.Objects;

/**
* A context for protected properties, encapsulating the context name and property name.
*/
public class ProtectedPropertyContext {
private static final String DEFAULT_CONTEXT = "default";

private final String propertyName;
private final String contextName;

/**
* Creates a ProtectedPropertyContext for the given property name, with a specific context name, acting as
* a namespace for the property.
* @param propertyName The property name in this location
* @param contextName A custom context name. If null, the default context will be assigned.
* @return A property context representing a property within a specific context
*/
public static ProtectedPropertyContext contextFor(final String propertyName, final String contextName) {
return new ProtectedPropertyContext(propertyName, contextName);
}

/**
* Creates a ProtectedPropertyContext for the given property name, using the default context.
* @param propertyName The property name in this location
* @return A property context representing a property with the given name in the default context
*/
public static ProtectedPropertyContext defaultContext(final String propertyName) {
return new ProtectedPropertyContext(propertyName, DEFAULT_CONTEXT);
}

/**
* Creates a property context with a property name and custom location.
* @param propertyName The property name
* @param contextName The context name. If null, the default context will be assigned.
*/
private ProtectedPropertyContext(final String propertyName, final String contextName) {
this.propertyName = Objects.requireNonNull(propertyName);
this.contextName = contextName == null ? DEFAULT_CONTEXT : contextName;
}

/**
* Returns the context key, in the format [contextName]/[propertyName]
* @return The context key
*/
public String getContextKey() {
return String.format("%s/%s", contextName, propertyName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,12 @@ private static int getKeySize(final String key) {
* Returns the encrypted cipher text.
*
* @param unprotectedValue the sensitive value
* @param context The property context, unused in this provider
* @return the value to persist in the {@code nifi.properties} file
* @throws SensitivePropertyProtectionException if there is an exception encrypting the value
*/
@Override
public String protect(final String unprotectedValue) throws SensitivePropertyProtectionException {
public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
if (StringUtils.isBlank(unprotectedValue)) {
throw new IllegalArgumentException("Cannot encrypt an empty value");
}
Expand Down Expand Up @@ -204,11 +205,12 @@ private byte[] generateIV() {
* Returns the decrypted plaintext.
*
* @param protectedValue the cipher text read from the {@code nifi.properties} file
* @param context The property context, unused in this provider
* @return the raw value to be used by the application
* @throws SensitivePropertyProtectionException if there is an error decrypting the cipher text
*/
@Override
public String unprotect(final String protectedValue) throws SensitivePropertyProtectionException {
public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
if (protectedValue == null || protectedValue.trim().length() < MIN_CIPHER_TEXT_LENGTH) {
throw new IllegalArgumentException("Cannot decrypt a cipher text shorter than " + MIN_CIPHER_TEXT_LENGTH + " chars");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,11 @@ private void checkAndInitializeClient() throws SensitivePropertyProtectionExcept
* An encryption-based provider would return a cipher text, while a remote-lookup provider could return a unique ID to retrieve the secured value.
*
* @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) throws SensitivePropertyProtectionException {
public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
if (StringUtils.isBlank(unprotectedValue)) {
throw new IllegalArgumentException("Cannot encrypt a blank value");
}
Expand All @@ -304,10 +305,11 @@ public String protect(final String unprotectedValue) throws SensitivePropertyPro
* An encryption-based provider would decrypt a cipher text and return the plaintext, while a remote-lookup provider could retrieve the secured value.
*
* @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) throws SensitivePropertyProtectionException {
public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
if (StringUtils.isBlank(protectedValue)) {
throw new IllegalArgumentException("Cannot decrypt a blank value");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ private String unprotectValue(final String key, final String retrievedValue) {

try {
final SensitivePropertyProvider sensitivePropertyProvider = getSensitivePropertyProvider(protectionScheme);
return sensitivePropertyProvider.unprotect(retrievedValue);
return sensitivePropertyProvider.unprotect(retrievedValue, ProtectedPropertyContext.defaultContext(key));
} catch (SensitivePropertyProtectionException e) {
logger.error("Error unprotecting value for " + key, e);
throw e;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,12 @@ protected boolean hasRequiredSecretsEngineProperties(final BootstrapProperties v
* Returns the encrypted cipher text.
*
* @param unprotectedValue the sensitive value
* @param context The property context, unused in this provider
* @return the value to persist in the {@code nifi.properties} file
* @throws SensitivePropertyProtectionException if there is an exception encrypting the value
*/
@Override
public String protect(final String unprotectedValue) throws SensitivePropertyProtectionException {
public String protect(final String unprotectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
if (StringUtils.isBlank(unprotectedValue)) {
throw new IllegalArgumentException("Cannot encrypt an empty value");
}
Expand All @@ -78,11 +79,12 @@ public String protect(final String unprotectedValue) throws SensitivePropertyPro
* Returns the decrypted plaintext.
*
* @param protectedValue the cipher text read from the {@code nifi.properties} file
* @param context The property context, unused in this provider
* @return the raw value to be used by the application
* @throws SensitivePropertyProtectionException if there is an error decrypting the cipher text
*/
@Override
public String unprotect(final String protectedValue) throws SensitivePropertyProtectionException {
public String unprotect(final String protectedValue, final ProtectedPropertyContext context) throws SensitivePropertyProtectionException {
if (StringUtils.isBlank(protectedValue)) {
throw new IllegalArgumentException("Cannot decrypt an empty value");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,20 @@ public interface SensitivePropertyProvider {
* An encryption-based provider would return a cipher text, while a remote-lookup provider could return a unique ID to retrieve the secured value.
*
* @param unprotectedValue the sensitive value
* @param context The context of the value
* @return the value to persist in the {@code nifi.properties} file
*/
String protect(String unprotectedValue) throws SensitivePropertyProtectionException;
String protect(String unprotectedValue, ProtectedPropertyContext context) throws SensitivePropertyProtectionException;

/**
* Returns the "unprotected" form of this value. This is the raw sensitive value which is used by the application logic.
* An encryption-based provider would decrypt a cipher text and return the plaintext, while a remote-lookup provider could retrieve the secured value.
*
* @param protectedValue the protected value read from the {@code nifi.properties} file
* @param context The context of the value
* @return the raw value to be used by the application
*/
String unprotect(String protectedValue) throws SensitivePropertyProtectionException;
String unprotect(String protectedValue, ProtectedPropertyContext context) throws SensitivePropertyProtectionException;

/**
* Cleans up resources that may have been allocated/used by an SPP implementation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,15 @@ public interface SensitivePropertyProviderFactory {
*/
Collection<SensitivePropertyProvider> getSupportedSensitivePropertyProviders();

/**
* Returns a ProtectedPropertyContext with the given property name. The ProtectedPropertyContext's
* contextName will be the name found in a matching context mapping from bootstrap.conf, or 'default' if
* no matching mapping was found.
* @param groupIdentifier The identifier of a group that contains the configuration property. The definition
* of a group depends on the type of configuration file.
* @param propertyName A property name
* @return The property context, using any mappings configured in bootstrap.conf to match against the
* provided group identifier (or the default context if none match).
*/
ProtectedPropertyContext getPropertyContext(String groupIdentifier, String propertyName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ protected SensitivePropertyProviderFactory getSensitivePropertyProviderFactory()
return sensitivePropertyProviderFactory;
}

protected String decryptValue(final String cipherText, final String protectionScheme, final String propertyName, final String groupIdentifier) throws SensitivePropertyProtectionException {
final SensitivePropertyProviderFactory sensitivePropertyProviderFactory = getSensitivePropertyProviderFactory();
final ProtectedPropertyContext protectedPropertyContext = sensitivePropertyProviderFactory.getPropertyContext(groupIdentifier, propertyName);
return sensitivePropertyProviderFactory.getProvider(PropertyProtectionScheme.fromIdentifier(protectionScheme))
.unprotect(cipherText, protectedPropertyContext);
}

/**
* Configures and sets the SensitivePropertyProviderFactory.
* @param keyHex An key in hex format, which some providers may use for encryption
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import org.apache.nifi.properties.BootstrapProperties.BootstrapPropertyKey;
import org.apache.nifi.util.NiFiBootstrapUtils;
import org.apache.nifi.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -29,6 +30,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class StandardSensitivePropertyProviderFactory implements SensitivePropertyProviderFactory {
Expand All @@ -37,6 +39,7 @@ public class StandardSensitivePropertyProviderFactory implements SensitiveProper
private final Optional<String> keyHex;
private final Supplier<BootstrapProperties> bootstrapPropertiesSupplier;
private final Map<PropertyProtectionScheme, SensitivePropertyProvider> providerMap;
private Map<String, Pattern> customPropertyContextMap;

/**
* Creates a StandardSensitivePropertyProviderFactory using the default bootstrap.conf location and
Expand Down Expand Up @@ -74,6 +77,18 @@ private StandardSensitivePropertyProviderFactory(final String keyHex, final Supp
this.keyHex = Optional.ofNullable(keyHex);
this.bootstrapPropertiesSupplier = bootstrapPropertiesSupplier == null ? () -> null : bootstrapPropertiesSupplier;
this.providerMap = new HashMap<>();
this.customPropertyContextMap = null;
}

private void populateCustomPropertyContextMap() {
final BootstrapProperties bootstrapProperties = getBootstrapProperties();
customPropertyContextMap = new HashMap<>();
final String contextMappingKeyPrefix = BootstrapPropertyKey.CONTEXT_MAPPING_PREFIX.getKey();
bootstrapProperties.getPropertyKeys().stream()
.filter(k -> k.contains(contextMappingKeyPrefix))
.forEach(k -> {
customPropertyContextMap.put(StringUtils.substringAfter(k, contextMappingKeyPrefix), Pattern.compile(bootstrapProperties.getProperty(k)));
});
}

private String getKeyHex() {
Expand Down Expand Up @@ -122,4 +137,17 @@ public Collection<SensitivePropertyProvider> getSupportedSensitivePropertyProvid
.collect(Collectors.toList());
}

@Override
public ProtectedPropertyContext getPropertyContext(final String groupIdentifier, final String propertyName) {
if (customPropertyContextMap == null) {
populateCustomPropertyContextMap();
}
final String contextName = customPropertyContextMap.entrySet().stream()
.filter(entry -> entry.getValue().matcher(groupIdentifier).find())
.map(Map.Entry::getKey)
.findFirst()
.orElse(null);
return ProtectedPropertyContext.contextFor(propertyName, contextName);
}

}
Loading