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 @@ -102,6 +102,7 @@ public class NiFiProperties extends ApplicationProperties {
public static final String CONTENT_REPOSITORY_ENCRYPTION_KEY_ID = "nifi.content.repository.encryption.key.id";
public static final String CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS = "nifi.content.repository.encryption.key.provider.implementation";
public static final String CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION = "nifi.content.repository.encryption.key.provider.location";
public static final String CONTENT_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD = "nifi.content.repository.encryption.key.provider.password";

// flowfile repository properties
public static final String FLOWFILE_REPOSITORY_IMPLEMENTATION = "nifi.flowfile.repository.implementation";
Expand All @@ -113,6 +114,7 @@ public class NiFiProperties extends ApplicationProperties {
public static final String FLOWFILE_REPOSITORY_ENCRYPTION_KEY_ID = "nifi.flowfile.repository.encryption.key.id";
public static final String FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS = "nifi.flowfile.repository.encryption.key.provider.implementation";
public static final String FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_LOCATION = "nifi.flowfile.repository.encryption.key.provider.location";
public static final String FLOWFILE_REPOSITORY_ENCRYPTION_KEY_PROVIDER_PASSWORD = "nifi.flowfile.repository.encryption.key.provider.password";
public static final String FLOWFILE_SWAP_MANAGER_IMPLEMENTATION = "nifi.swap.manager.implementation";
public static final String QUEUE_SWAP_THRESHOLD = "nifi.queue.swap.threshold";

Expand All @@ -135,6 +137,7 @@ public class NiFiProperties extends ApplicationProperties {
public static final String PROVENANCE_REPO_ENCRYPTION_KEY_ID = "nifi.provenance.repository.encryption.key.id";
public static final String PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_IMPLEMENTATION_CLASS = "nifi.provenance.repository.encryption.key.provider.implementation";
public static final String PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_LOCATION = "nifi.provenance.repository.encryption.key.provider.location";
public static final String PROVENANCE_REPO_ENCRYPTION_KEY_PROVIDER_PASSWORD = "nifi.provenance.repository.encryption.key.provider.password";
public static final String PROVENANCE_REPO_DEBUG_FREQUENCY = "nifi.provenance.repository.debug.frequency";

// status repository properties
Expand Down Expand Up @@ -1614,8 +1617,7 @@ public String getFlowFileRepoEncryptionKey() {

/**
* Returns a map of keyId -> key in hex loaded from the {@code nifi.properties} file if a
* {@code StaticKeyProvider} is defined. If {@code FileBasedKeyProvider} is defined, use
* {@code CryptoUtils#readKeys()} instead -- this method will return an empty map.
* {@code StaticKeyProvider} is defined. If {@code FileBasedKeyProvider} is defined this method will return an empty map.
*
* @return a Map of the keys identified by key ID
*/
Expand Down Expand Up @@ -1746,8 +1748,7 @@ public String getProvenanceRepoEncryptionKey() {

/**
* Returns a map of keyId -> key in hex loaded from the {@code nifi.properties} file if a
* {@code StaticKeyProvider} is defined. If {@code FileBasedKeyProvider} is defined, use
* {@code CryptoUtils#readKeys()} instead -- this method will return an empty map.
* {@code StaticKeyProvider} is defined. If {@code FileBasedKeyProvider} is defined this method will return an empty map.
*
* @return a Map of the keys identified by key ID
*/
Expand Down Expand Up @@ -1778,8 +1779,7 @@ public String getContentRepositoryEncryptionKey() {

/**
* Returns a map of keyId -> key in hex loaded from the {@code nifi.properties} file if a
* {@code StaticKeyProvider} is defined. If {@code FileBasedKeyProvider} is defined, use
* {@code CryptoUtils#readKeys()} instead -- this method will return an empty map.
* {@code StaticKeyProvider} is defined. If {@code FileBasedKeyProvider} is defined this method will return an empty map.
*
* @return a Map of the keys identified by key ID
*/
Expand Down
31 changes: 31 additions & 0 deletions nifi-commons/nifi-security-kms/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-commons</artifactId>
<version>1.14.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-security-kms</artifactId>
<dependencies>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.security.kms;

import org.apache.nifi.security.kms.reader.StandardFileBasedKeyReader;
import org.apache.nifi.security.kms.reader.FileBasedKeyReader;

import java.nio.file.Path;
import javax.crypto.SecretKey;

/**
* File Based Key Provider reads encrypted Secret Keys from a properties file containing one or more entries
*/
public class FileBasedKeyProvider extends StaticKeyProvider {
private static final FileBasedKeyReader READER = new StandardFileBasedKeyReader();

public FileBasedKeyProvider(final Path location, final SecretKey rootKey) {
super(READER.readSecretKeys(location, rootKey));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@
import java.security.KeyManagementException;
import java.util.List;
import javax.crypto.SecretKey;
import javax.naming.OperationNotSupportedException;

public interface KeyProvider {

/**
* Returns the key identified by this ID or throws an exception if one is not available.
*
Expand All @@ -46,17 +44,4 @@ public interface KeyProvider {
* @return a List of keyIds (empty list if none are available)
*/
List<String> getAvailableKeyIds();

/**
* Adds the key to the provider and associates it with the given ID. Some implementations may not allow this operation.
*
* @param keyId the key identifier
* @param key the key
* @return true if the key was successfully added
* @throws OperationNotSupportedException if this implementation doesn't support adding keys
* @throws KeyManagementException if the key is invalid, the ID conflicts, etc.
*/
boolean addKey(String keyId, SecretKey key) throws OperationNotSupportedException, KeyManagementException;

// TODO: Add #getActiveKeyId() method
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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.security.kms;

import org.apache.commons.codec.DecoderException;
import org.apache.nifi.security.kms.configuration.FileBasedKeyProviderConfiguration;
import org.apache.nifi.security.kms.configuration.KeyProviderConfiguration;
import org.apache.nifi.security.kms.configuration.KeyStoreKeyProviderConfiguration;
import org.apache.nifi.security.kms.configuration.StaticKeyProviderConfiguration;
import org.apache.commons.codec.binary.Hex;
import org.apache.nifi.security.kms.reader.KeyReaderException;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Map;

/**
* Key Provider Factory
*/
public class KeyProviderFactory {
private static final String SECRET_KEY_ALGORITHM = "AES";

/**
* Get Key Provider based on Configuration
*
* @param configuration Key Provider Configuration
* @return Key Provider
*/
public static KeyProvider getKeyProvider(final KeyProviderConfiguration<?> configuration) {
Copy link
Contributor

@Lehel44 Lehel44 Jun 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got an idea to avoid downcasting and branching here. The KeyProviderConfiguration interface could contain a new method:
KeyProvider getKeyProvider(final KeyProviderCreator keyProviderCreator);

A key provider creator class could handle creating the different providers based on different configurations:

public class KeyProviderCreator {

private static final String SECRET_KEY_ALGORITHM = "AES";

public KeyProvider getKeyProvider(final StaticKeyProviderConfiguration configuration) {
    final Map<String, SecretKey> secretKeys;
    try {
        secretKeys = getSecretKeys(configuration.getKeys());
        return new StaticKeyProvider(secretKeys);
    } catch (final DecoderException e) {
        throw new KeyReaderException("Decoding Hexadecimal Secret Keys failed", e);
    }
}

public KeyProvider getKeyProvider(final FileBasedKeyProviderConfiguration configuration) {
    final Path keyProviderPath = Paths.get(configuration.getLocation());
    return new FileBasedKeyProvider(keyProviderPath, configuration.getRootKey());
}

public KeyProvider getKeyProvider(final KeyStoreKeyProviderConfiguration configuration) {
    final KeyStore keyStore = configuration.getKeyStore();
    return new KeyStoreKeyProvider(keyStore, configuration.getKeyPassword());
}

private Map<String, SecretKey> getSecretKeys(final Map<String, String> keys) throws DecoderException {
    final Map<String, SecretKey> secretKeys = new HashMap<>();

    for (final Map.Entry<String, String> keyEntry : keys.entrySet()) {
        final byte[] encodedSecretKey = Hex.decodeHex(keyEntry.getValue());
        final SecretKey secretKey = new SecretKeySpec(encodedSecretKey, SECRET_KEY_ALGORITHM);
        secretKeys.put(keyEntry.getKey(), secretKey);
    }

    return secretKeys;
}

}`

The configuration classes can utilize the creator class to make providers:

@Override public KeyProvider getKeyProvider(KeyProviderCreator keyProviderCreator) { return keyProviderCreator.getKeyProvider(this); }

And then in the factory, there's no branching remaining:

`public class KeyProviderFactory {

private static final KeyProviderCreator creator = new KeyProviderCreator();

public static KeyProvider getKeyProvider(final KeyProviderConfiguration<?> configuration) {
    return configuration.getKeyProvider(creator);
}

}`

What do you think of this approach? It adds a bit of extra complexity to other classes but withdraws some from the factory. Also, this way if anyone adds a new configuration, they will be obliged to implement the provider method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion, that is an interesting idea. The purpose of the KeyProviderFactory is to abstract the details of instantiating a KeyProvider, so going in the direction of KeyProviderCreator seems to create another level of abstraction. The basic purpose of the KeyProviderConfiguration classes is to describe the properties necessary to create the associated KeyProvider. A different approach could be to have one KeyProviderConfiguration with all possible properties, but it would still require branching to determine the required properties. There are probably other options for streamlining and abstracting KeyProvider creation, but the current implementation is a variation on the previous approach.

KeyProvider keyProvider;

if (configuration instanceof StaticKeyProviderConfiguration) {
final StaticKeyProviderConfiguration providerConfiguration = (StaticKeyProviderConfiguration) configuration;
final Map<String, SecretKey> secretKeys;
try {
secretKeys = getSecretKeys(providerConfiguration.getKeys());
keyProvider = new StaticKeyProvider(secretKeys);
} catch (final DecoderException e) {
throw new KeyReaderException("Decoding Hexadecimal Secret Keys failed", e);
}
} else if (configuration instanceof FileBasedKeyProviderConfiguration) {
final FileBasedKeyProviderConfiguration providerConfiguration = (FileBasedKeyProviderConfiguration) configuration;
final Path keyProviderPath = Paths.get(providerConfiguration.getLocation());
keyProvider = new FileBasedKeyProvider(keyProviderPath, providerConfiguration.getRootKey());
} else if (configuration instanceof KeyStoreKeyProviderConfiguration) {
final KeyStoreKeyProviderConfiguration providerConfiguration = (KeyStoreKeyProviderConfiguration) configuration;
final KeyStore keyStore = providerConfiguration.getKeyStore();
keyProvider = new KeyStoreKeyProvider(keyStore, providerConfiguration.getKeyPassword());
} else {
throw new UnsupportedOperationException(String.format("Key Provider [%s] not supported", configuration.getKeyProviderClass().getName()));
}

return keyProvider;
}

private static Map<String, SecretKey> getSecretKeys(final Map<String, String> keys) throws DecoderException {
final Map<String, SecretKey> secretKeys = new HashMap<>();

for (final Map.Entry<String, String> keyEntry : keys.entrySet()) {
final byte[] encodedSecretKey = Hex.decodeHex(keyEntry.getValue());
final SecretKey secretKey = new SecretKeySpec(encodedSecretKey, SECRET_KEY_ALGORITHM);
secretKeys.put(keyEntry.getKey(), secretKey);
}

return secretKeys;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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.security.kms;

import org.apache.nifi.security.kms.reader.KeyReaderException;

import javax.crypto.SecretKey;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyStore;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.requireNonNull;

/**
* KeyStore implementation of Key Provider
*/
public class KeyStoreKeyProvider extends StaticKeyProvider {
/**
* KeyStore Key Provider constructor with KeyStore and password used to read Secret Key entries
*
* @param keyStore KeyStore
* @param keyPassword Password for reading Secret Key entries
*/
public KeyStoreKeyProvider(final KeyStore keyStore, final char[] keyPassword) {
super(readSecretKeys(requireNonNull(keyStore, "KeyStore required"), requireNonNull(keyPassword, "Password required")));
}

private static Map<String, SecretKey> readSecretKeys(final KeyStore keyStore, final char[] keyPassword) throws KeyReaderException {
final Map<String, SecretKey> secretKeys = new HashMap<>();

try {
final Enumeration<String> aliases = keyStore.aliases();
while (aliases.hasMoreElements()) {
final String alias = aliases.nextElement();
final Key key = keyStore.getKey(alias, keyPassword);
if (key instanceof SecretKey) {
final SecretKey secretKey = (SecretKey) key;
secretKeys.put(alias, secretKey);
}
}
} catch (final GeneralSecurityException e) {
throw new KeyReaderException("Reading KeyStore failed", e);
}

return secretKeys;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,45 +18,41 @@

import java.security.KeyManagementException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.crypto.SecretKey;
import javax.naming.OperationNotSupportedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Reference implementation for static key provider (used during tests).
* Static Key Provider stores Secret Keys in memory based on initialized configuration properties
*/
public class StaticKeyProvider implements KeyProvider {
private static final Logger logger = LoggerFactory.getLogger(StaticKeyProvider.class);
private final Map<String, SecretKey> keys;

private Map<String, SecretKey> keys = new HashMap<>();

public StaticKeyProvider(String keyId, String keyHex) throws KeyManagementException {
this.keys.put(keyId, CryptoUtils.formKeyFromHex(keyHex));
}

public StaticKeyProvider(Map<String, SecretKey> keys) throws KeyManagementException {
this.keys.putAll(keys);
/**
* Static Key Provider constructor of Map of Key Identifier to Secret Key
*
* @param keys Map of Key Identifier to Secret Key
*/
public StaticKeyProvider(final Map<String, SecretKey> keys) {
this.keys = Collections.unmodifiableMap(Objects.requireNonNull(keys, "Keys required"));
}

/**
* Returns the key identified by this ID or throws an exception if one is not available.
*
* @param keyId the key identifier
* @return the key
* @throws KeyManagementException if the key cannot be retrieved
* @throws KeyManagementException Thrown when Secret Key not found for Key Identifier
*/
@Override
public SecretKey getKey(String keyId) throws KeyManagementException {
logger.debug("Attempting to get key: " + keyId);
if (keyExists(keyId)) {
return keys.get(keyId);
} else {
throw new KeyManagementException("No key available for " + keyId);
public SecretKey getKey(final String keyId) throws KeyManagementException {
final SecretKey secretKey = keys.get(keyId);
if (secretKey == null) {
throw new KeyManagementException(String.format("Secret Key [%s] not found", keyId));
}
return secretKey;
}

/**
Expand All @@ -66,7 +62,7 @@ public SecretKey getKey(String keyId) throws KeyManagementException {
* @return true if the key can be used
*/
@Override
public boolean keyExists(String keyId) {
public boolean keyExists(final String keyId) {
return keys.containsKey(keyId);
}

Expand All @@ -79,18 +75,4 @@ public boolean keyExists(String keyId) {
public List<String> getAvailableKeyIds() {
return new ArrayList<>(keys.keySet());
}

/**
* Adds the key to the provider and associates it with the given ID. Some implementations may not allow this operation.
*
* @param keyId the key identifier
* @param key the key
* @return true if the key was successfully added
* @throws OperationNotSupportedException if this implementation doesn't support adding keys
* @throws KeyManagementException if the key is invalid, the ID conflicts, etc.
*/
@Override
public boolean addKey(String keyId, SecretKey key) throws OperationNotSupportedException, KeyManagementException {
throw new OperationNotSupportedException("This implementation does not allow adding keys");
}
}
Loading