All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.nifi.properties.ApplicationPropertiesProtector Maven / Gradle / Ivy

There is a newer version: 2.0.0-M3
Show newest version
/*
 * 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.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;

import static java.util.Arrays.asList;

/**
 * Class performing unprotection activities before returning a clean
 * implementation of {@link ApplicationProperties}.
 * This encapsulates the sensitive property access logic from external consumers
 * of {@code ApplicationProperties}.
 *
 * @param  The type of protected application properties
 * @param  The type of standard application properties that backs the protected application properties
 */
public class ApplicationPropertiesProtector, U extends ApplicationProperties>
        implements SensitivePropertyProtector {
    public static final String PROTECTED_KEY_SUFFIX = ".protected";

    private static final Logger logger = LoggerFactory.getLogger(ApplicationPropertiesProtector.class);

    private final T protectedProperties;

    private final Map localProviderCache = new HashMap<>();

    /**
     * Creates an instance containing the provided {@link ProtectedProperties}.
     *
     * @param protectedProperties the ProtectedProperties to contain
     */
    public ApplicationPropertiesProtector(final T protectedProperties) {
        this.protectedProperties = protectedProperties;
        logger.debug("Loaded {} properties (including {} protection schemes) into {}", getPropertyKeysIncludingProtectionSchemes().size(),
                getProtectedPropertyKeys().size(), this.getClass().getName());
    }

    /**
     * Returns the sibling property key which specifies the protection scheme for this key.
     * 

* Example: *

* nifi.sensitive.key=ABCXYZ * nifi.sensitive.key.protected=aes/gcm/256 *

* nifi.sensitive.key -> nifi.sensitive.key.protected * * @param key the key identifying the sensitive property * @return the key identifying the protection scheme for the sensitive property */ public static String getProtectionKey(final String key) { if (key == null || key.isEmpty()) { throw new IllegalArgumentException("Cannot find protection key for null key"); } return key + PROTECTED_KEY_SUFFIX; } /** * Retrieves all known property keys. * * @return all known property keys */ @Override public Set getPropertyKeys() { Set filteredKeys = getPropertyKeysIncludingProtectionSchemes(); filteredKeys.removeIf(p -> p.endsWith(PROTECTED_KEY_SUFFIX)); return filteredKeys; } @Override public int size() { return getPropertyKeys().size(); } @Override public Set getPropertyKeysIncludingProtectionSchemes() { return protectedProperties.getApplicationProperties().getPropertyKeys(); } /** * Splits a single string containing multiple property keys into a List. Delimited by ',' or ';' and ignores leading and trailing whitespace around delimiter. * * @param multipleProperties a single String containing multiple properties, i.e. "nifi.property.1; nifi.property.2, nifi.property.3" * @return a List containing the split and trimmed properties */ private static List splitMultipleProperties(final String multipleProperties) { if (multipleProperties == null || multipleProperties.trim().isEmpty()) { return new ArrayList<>(0); } else { List properties = new ArrayList<>(asList(multipleProperties.split("\\s*[,;]\\s*"))); for (int i = 0; i < properties.size(); i++) { properties.set(i, properties.get(i).trim()); } return properties; } } private String getProperty(final String key) { return protectedProperties.getApplicationProperties().getProperty(key); } private String getAdditionalSensitivePropertiesKeys() { return getProperty(protectedProperties.getAdditionalSensitivePropertiesKeysName()); } private String getAdditionalSensitivePropertiesKeysName() { return protectedProperties.getAdditionalSensitivePropertiesKeysName(); } @Override public List getSensitivePropertyKeys() { final String additionalPropertiesString = getAdditionalSensitivePropertiesKeys(); final String additionalPropertiesKeyName = protectedProperties.getAdditionalSensitivePropertiesKeysName(); if (additionalPropertiesString == null || additionalPropertiesString.trim().isEmpty()) { return protectedProperties.getDefaultSensitiveProperties(); } else { List additionalProperties = splitMultipleProperties(additionalPropertiesString); /* Remove this key if it was accidentally provided as a sensitive key * because we cannot protect it and read from it */ if (additionalProperties.contains(additionalPropertiesKeyName)) { logger.warn("The key '{}' contains itself. This is poor practice and should be removed", additionalPropertiesKeyName); additionalProperties.remove(additionalPropertiesKeyName); } additionalProperties.addAll(protectedProperties.getDefaultSensitiveProperties()); return additionalProperties; } } @Override public List getPopulatedSensitivePropertyKeys() { List allSensitiveKeys = getSensitivePropertyKeys(); return allSensitiveKeys.stream().filter(k -> isNotBlank(getProperty(k))).collect(Collectors.toList()); } @Override public boolean hasProtectedKeys() { final List sensitiveKeys = getSensitivePropertyKeys(); for (String k : sensitiveKeys) { if (isPropertyProtected(k)) { return true; } } return false; } @Override public Map getProtectedPropertyKeys() { final List sensitiveKeys = getSensitivePropertyKeys(); final Map traditionalProtectedProperties = new HashMap<>(); for (final String key : sensitiveKeys) { final String protection = getProperty(getProtectionKey(key)); if (isNotBlank(protection) && isNotBlank(getProperty(key))) { traditionalProtectedProperties.put(key, protection); } } return traditionalProtectedProperties; } @Override public boolean isPropertySensitive(final String key) { // If the explicit check for ADDITIONAL_SENSITIVE_PROPERTIES_KEY is not here, this will loop infinitely return key != null && !key.equals(getAdditionalSensitivePropertiesKeysName()) && getSensitivePropertyKeys().contains(key.trim()); } /** * Returns true if the property identified by this key is considered protected in this instance of {@code NiFiProperties}. * The property value is protected if the key is sensitive and the sibling key of key.protected is present. * * @param key the key * @return true if it is currently marked as protected * @see ApplicationPropertiesProtector#getSensitivePropertyKeys() */ @Override public boolean isPropertyProtected(final String key) { return key != null && isPropertySensitive(key) && isNotBlank(getProperty(getProtectionKey(key))); } @Override public U getUnprotectedProperties() throws SensitivePropertyProtectionException { if (hasProtectedKeys()) { logger.debug("Protected Properties [{}] Sensitive Properties [{}]", getProtectedPropertyKeys().size(), getSensitivePropertyKeys().size()); final Properties rawProperties = new Properties(); final Set failedKeys = new HashSet<>(); for (final String key : getPropertyKeys()) { /* Three kinds of keys * 1. protection schemes -- skip * 2. protected keys -- unprotect and copy * 3. normal keys -- copy over */ if (key.endsWith(PROTECTED_KEY_SUFFIX)) { // Do nothing } else if (isPropertyProtected(key)) { try { rawProperties.setProperty(key, unprotectValue(key, getProperty(key))); } catch (final SensitivePropertyProtectionException e) { logger.warn("Failed to unprotect '{}'", key, e); failedKeys.add(key); } } else { rawProperties.setProperty(key, getProperty(key)); } } if (!failedKeys.isEmpty()) { final String failed = failedKeys.size() == 1 ? failedKeys.iterator().next() : String.join(", ", failedKeys); throw new SensitivePropertyProtectionException(String.format("Failed unprotected properties: %s", failed)); } return protectedProperties.createApplicationProperties(rawProperties); } else { logger.debug("No protected properties"); return protectedProperties.getApplicationProperties(); } } @Override public void addSensitivePropertyProvider(final SensitivePropertyProvider sensitivePropertyProvider) { Objects.requireNonNull(sensitivePropertyProvider, "Provider required"); final String identifierKey = sensitivePropertyProvider.getIdentifierKey(); if (localProviderCache.containsKey(identifierKey)) { throw new UnsupportedOperationException(String.format("Sensitive Property Provider Identifier [%s] override not supported", identifierKey)); } localProviderCache.put(identifierKey, sensitivePropertyProvider); } @Override public String toString() { return String.format("%s Properties [%d]", getClass().getSimpleName(), getPropertyKeys().size()); } /** * If the value is protected, unprotects it and returns it. If not, returns the original value. * * @param key the retrieved property key * @param retrievedValue the retrieved property value * @return the unprotected value */ private String unprotectValue(final String key, final String retrievedValue) { // Checks if the key is sensitive and marked as protected if (isPropertyProtected(key)) { final String protectionSchemePath = getProperty(getProtectionKey(key)); try { final SensitivePropertyProvider provider = findProvider(protectionSchemePath); return provider.unprotect(retrievedValue, ProtectedPropertyContext.defaultContext(key)); } catch (final RuntimeException e) { throw new SensitivePropertyProtectionException(String.format("Property [%s] unprotect failed", key), e); } } return retrievedValue; } private SensitivePropertyProvider findProvider(final String protectionSchemePath) { Objects.requireNonNull(protectionSchemePath, "Protection Scheme Path required"); return localProviderCache.entrySet() .stream() .filter(entry -> protectionSchemePath.startsWith(entry.getKey())) .findFirst() .map(Map.Entry::getValue) .orElseThrow(() -> new UnsupportedOperationException(String.format("Protection Scheme Path [%s] Provider not found", protectionSchemePath))); } private boolean isNotBlank(final String string) { return string != null && string.length() > 0; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy