mirror of https://github.com/apache/nifi.git
NIFI-4274 Removed EL evaluation logic from custom file validator.
Signed-off-by: Pierre Villard <pierre.villard.fr@gmail.com> This closes #2071.
This commit is contained in:
parent
6ff8321cf7
commit
5150dff70b
|
@ -16,6 +16,18 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi.ssl;
|
package org.apache.nifi.ssl;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
import org.apache.nifi.annotation.documentation.CapabilityDescription;
|
import org.apache.nifi.annotation.documentation.CapabilityDescription;
|
||||||
import org.apache.nifi.annotation.documentation.Tags;
|
import org.apache.nifi.annotation.documentation.Tags;
|
||||||
import org.apache.nifi.annotation.lifecycle.OnEnabled;
|
import org.apache.nifi.annotation.lifecycle.OnEnabled;
|
||||||
|
@ -34,20 +46,7 @@ import org.apache.nifi.security.util.CertificateUtils;
|
||||||
import org.apache.nifi.security.util.KeystoreType;
|
import org.apache.nifi.security.util.KeystoreType;
|
||||||
import org.apache.nifi.security.util.SslContextFactory;
|
import org.apache.nifi.security.util.SslContextFactory;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
@Tags({"ssl", "secure", "certificate", "keystore", "truststore", "jks", "p12", "pkcs12", "pkcs", "tls"})
|
||||||
import java.io.File;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@Tags({"ssl", "secure", "certificate", "keystore", "truststore", "jks", "p12", "pkcs12", "pkcs"})
|
|
||||||
@CapabilityDescription("Standard implementation of the SSLContextService. Provides the ability to configure "
|
@CapabilityDescription("Standard implementation of the SSLContextService. Provides the ability to configure "
|
||||||
+ "keystore and/or truststore properties once and reuse that configuration throughout the application")
|
+ "keystore and/or truststore properties once and reuse that configuration throughout the application")
|
||||||
public class StandardSSLContextService extends AbstractControllerService implements SSLContextService {
|
public class StandardSSLContextService extends AbstractControllerService implements SSLContextService {
|
||||||
|
@ -108,10 +107,11 @@ public class StandardSSLContextService extends AbstractControllerService impleme
|
||||||
.build();
|
.build();
|
||||||
public static final PropertyDescriptor SSL_ALGORITHM = new PropertyDescriptor.Builder()
|
public static final PropertyDescriptor SSL_ALGORITHM = new PropertyDescriptor.Builder()
|
||||||
.name("SSL Protocol")
|
.name("SSL Protocol")
|
||||||
|
.displayName("TLS Protocol")
|
||||||
.defaultValue("TLS")
|
.defaultValue("TLS")
|
||||||
.required(false)
|
.required(false)
|
||||||
.allowableValues(buildAlgorithmAllowableValues())
|
.allowableValues(buildAlgorithmAllowableValues())
|
||||||
.description("The algorithm to use for this SSL context")
|
.description("The algorithm to use for this TLS/SSL context")
|
||||||
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
||||||
.sensitive(false)
|
.sensitive(false)
|
||||||
.build();
|
.build();
|
||||||
|
@ -178,19 +178,7 @@ public class StandardSSLContextService extends AbstractControllerService impleme
|
||||||
// allow expression language
|
// allow expression language
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult validate(String subject, String input, ValidationContext context) {
|
public ValidationResult validate(String subject, String input, ValidationContext context) {
|
||||||
final String substituted;
|
final File file = new File(input);
|
||||||
try {
|
|
||||||
substituted = context.newPropertyValue(input).evaluateAttributeExpressions().getValue();
|
|
||||||
} catch (final Exception e) {
|
|
||||||
return new ValidationResult.Builder()
|
|
||||||
.subject(subject)
|
|
||||||
.input(input)
|
|
||||||
.valid(false)
|
|
||||||
.explanation("Not a valid Expression Language value: " + e.getMessage())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
final File file = new File(substituted);
|
|
||||||
final boolean valid = file.exists() && file.canRead();
|
final boolean valid = file.exists() && file.canRead();
|
||||||
final String explanation = valid ? null : "File " + file + " does not exist or cannot be read";
|
final String explanation = valid ? null : "File " + file + " does not exist or cannot be read";
|
||||||
return new ValidationResult.Builder()
|
return new ValidationResult.Builder()
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
* 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.ssl
|
||||||
|
|
||||||
|
import org.apache.nifi.components.ValidationContext
|
||||||
|
import org.apache.nifi.components.ValidationResult
|
||||||
|
import org.apache.nifi.components.Validator
|
||||||
|
import org.apache.nifi.state.MockStateManager
|
||||||
|
import org.apache.nifi.util.MockProcessContext
|
||||||
|
import org.apache.nifi.util.MockValidationContext
|
||||||
|
import org.apache.nifi.util.MockVariableRegistry
|
||||||
|
import org.apache.nifi.util.TestRunner
|
||||||
|
import org.apache.nifi.util.TestRunners
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.AfterClass
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.BeforeClass
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TemporaryFolder
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.JUnit4
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
import java.security.Security
|
||||||
|
|
||||||
|
import static groovy.test.GroovyAssert.shouldFail
|
||||||
|
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
class StandardSSLContextServiceTest {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(StandardSSLContextServiceTest.class)
|
||||||
|
|
||||||
|
private static final String KEYSTORE_PATH = "src/test/resources/localhost-ks.jks"
|
||||||
|
private static final String TRUSTSTORE_PATH = "src/test/resources/localhost-ts.jks"
|
||||||
|
private static final String TRUSTSTORE_PATH_WITH_EL = "\${someAttribute}/localhost-ts.jks"
|
||||||
|
|
||||||
|
private static final String KEYSTORE_PASSWORD = "localtest"
|
||||||
|
private static final String TRUSTSTORE_PASSWORD = "localtest"
|
||||||
|
|
||||||
|
private static final String KEYSTORE_TYPE = "JKS"
|
||||||
|
private static final String TRUSTSTORE_TYPE = "JKS"
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder tmp = new TemporaryFolder(new File("src/test/resources"))
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
static void setUpOnce() throws Exception {
|
||||||
|
Security.addProvider(new BouncyCastleProvider())
|
||||||
|
|
||||||
|
logger.metaClass.methodMissing = { String name, args ->
|
||||||
|
logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
void setUp() throws Exception {
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
void tearDown() throws Exception {
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
static void tearDownOnce() throws Exception {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testShouldValidateSimpleFileValidatorPath() {
|
||||||
|
// Arrange
|
||||||
|
TestRunner runner = TestRunners.newTestRunner(TestProcessor.class)
|
||||||
|
String controllerServiceId = "ssl-context"
|
||||||
|
final SSLContextService sslContextService = new StandardSSLContextService()
|
||||||
|
runner.addControllerService(controllerServiceId, sslContextService)
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, TRUSTSTORE_PATH)
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, TRUSTSTORE_PASSWORD)
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, TRUSTSTORE_TYPE)
|
||||||
|
runner.enableControllerService(sslContextService)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
runner.assertValid(sslContextService)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
final MockProcessContext processContext = (MockProcessContext) runner.getProcessContext()
|
||||||
|
assert processContext.getControllerServiceProperties(sslContextService).get(StandardSSLContextService.TRUSTSTORE, "") == TRUSTSTORE_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testShouldNotValidateExpressionLanguageInFileValidator() {
|
||||||
|
// Arrange
|
||||||
|
TestRunner runner = TestRunners.newTestRunner(TestProcessor.class)
|
||||||
|
String controllerServiceId = "ssl-context"
|
||||||
|
final SSLContextService sslContextService = new StandardSSLContextService()
|
||||||
|
runner.addControllerService(controllerServiceId, sslContextService)
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, TRUSTSTORE_PATH_WITH_EL)
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, TRUSTSTORE_PASSWORD)
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, TRUSTSTORE_TYPE)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
def msg = shouldFail {
|
||||||
|
runner.enableControllerService(sslContextService)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert msg =~ "invalid because Cannot access file"
|
||||||
|
runner.assertNotValid(sslContextService)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testShouldNotEvaluateExpressionLanguageInFileValidator() {
|
||||||
|
// Arrange
|
||||||
|
final String VALID_TRUSTSTORE_PATH_WITH_EL = "\${literal(''):trim()}${TRUSTSTORE_PATH}"
|
||||||
|
|
||||||
|
TestRunner runner = TestRunners.newTestRunner(TestProcessor.class)
|
||||||
|
String controllerServiceId = "ssl-context"
|
||||||
|
final SSLContextService sslContextService = new StandardSSLContextService()
|
||||||
|
runner.addControllerService(controllerServiceId, sslContextService)
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, VALID_TRUSTSTORE_PATH_WITH_EL)
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, TRUSTSTORE_PASSWORD)
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, TRUSTSTORE_TYPE)
|
||||||
|
|
||||||
|
// The verifySslConfig and customValidate methods correctly do not evaluate EL, but the custom file validator does, so extract it alone and validate
|
||||||
|
Validator fileValidator = StandardSSLContextService.createFileExistsAndReadableValidator()
|
||||||
|
final ValidationContext mockValidationContext = new MockValidationContext(
|
||||||
|
runner.getProcessContext() as MockProcessContext,
|
||||||
|
new MockStateManager(sslContextService),
|
||||||
|
new MockVariableRegistry())
|
||||||
|
|
||||||
|
// Act
|
||||||
|
ValidationResult vr = fileValidator.validate(StandardSSLContextService.TRUSTSTORE.name, VALID_TRUSTSTORE_PATH_WITH_EL, mockValidationContext)
|
||||||
|
logger.info("Custom file validation result: ${vr}")
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
final MockProcessContext processContext = (MockProcessContext) runner.getProcessContext()
|
||||||
|
|
||||||
|
// If the EL was evaluated, the paths would be identical
|
||||||
|
assert processContext.getControllerServiceProperties(sslContextService).get(StandardSSLContextService.TRUSTSTORE, "") != TRUSTSTORE_PATH
|
||||||
|
|
||||||
|
// If the EL was evaluated, the path would be valid
|
||||||
|
assert !vr.isValid()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue