mirror of https://github.com/apache/kafka.git
KAFKA-14346: Remove hard-to-mock javax.crypto calls (#12866)
Reviewers: Chris Egerton <chrise@aiven.io>
This commit is contained in:
parent
fca5bfe13c
commit
31c69ae932
|
@ -134,7 +134,7 @@
|
|||
<suppress checks="ClassFanOutComplexity"
|
||||
files="Worker(|Test).java"/>
|
||||
<suppress checks="MethodLength"
|
||||
files="(DistributedHerder|KafkaConfigBackingStore|Values|IncrementalCooperativeAssignor).java"/>
|
||||
files="(DistributedHerder|DistributedConfig|KafkaConfigBackingStore|Values|IncrementalCooperativeAssignor).java"/>
|
||||
<suppress checks="ParameterNumber"
|
||||
files="Worker(SinkTask|SourceTask|Coordinator).java"/>
|
||||
<suppress checks="ParameterNumber"
|
||||
|
|
|
@ -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.kafka.connect.runtime.distributed;
|
||||
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.Mac;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* An interface to allow the dependency injection of {@link Mac} and {@link KeyGenerator} instances for testing.
|
||||
* Implementations of this class should be thread-safe.
|
||||
*/
|
||||
public interface Crypto {
|
||||
|
||||
/**
|
||||
* Use the system implementation for cryptography calls.
|
||||
*/
|
||||
Crypto SYSTEM = new SystemCrypto();
|
||||
|
||||
/**
|
||||
* Returns a {@code Mac} object that implements the
|
||||
* specified MAC algorithm. See {@link Mac#getInstance(String)}.
|
||||
*
|
||||
* @param algorithm the standard name of the requested MAC algorithm.
|
||||
* @return the new {@code Mac} object
|
||||
* @throws NoSuchAlgorithmException if no {@code Provider} supports a
|
||||
* {@code MacSpi} implementation for the specified algorithm
|
||||
*/
|
||||
Mac mac(String algorithm) throws NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Returns a {@code KeyGenerator} object that generates secret keys
|
||||
* for the specified algorithm. See {@link KeyGenerator#getInstance(String)}.
|
||||
*
|
||||
* @param algorithm the standard name of the requested key algorithm.
|
||||
* @return the new {@code KeyGenerator} object
|
||||
* @throws NoSuchAlgorithmException if no {@code Provider} supports a
|
||||
* {@code KeyGeneratorSpi} implementation for the
|
||||
* specified algorithm
|
||||
*/
|
||||
KeyGenerator keyGenerator(String algorithm) throws NoSuchAlgorithmException;
|
||||
|
||||
class SystemCrypto implements Crypto {
|
||||
@Override
|
||||
public Mac mac(String algorithm) throws NoSuchAlgorithmException {
|
||||
return Mac.getInstance(algorithm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyGenerator keyGenerator(String algorithm) throws NoSuchAlgorithmException {
|
||||
return KeyGenerator.getInstance(algorithm);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -28,7 +28,6 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.Mac;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
|
@ -208,6 +207,7 @@ public class DistributedConfig extends WorkerConfig {
|
|||
+ "which must include the algorithm used for the " + INTER_WORKER_SIGNATURE_ALGORITHM_CONFIG + " property. "
|
||||
+ "The algorithm(s) '" + INTER_WORKER_VERIFICATION_ALGORITHMS_DEFAULT + "' will be used as a default on JVMs that provide them; "
|
||||
+ "on other JVMs, no default is used and a value for this property must be manually specified in the worker config.";
|
||||
private Crypto crypto;
|
||||
|
||||
private enum ExactlyOnceSourceSupport {
|
||||
DISABLED(false),
|
||||
|
@ -237,9 +237,9 @@ public class DistributedConfig extends WorkerConfig {
|
|||
// + "See the exactly-once source support documentation at [add docs link here] for more information on this feature.";
|
||||
public static final String EXACTLY_ONCE_SOURCE_SUPPORT_DEFAULT = ExactlyOnceSourceSupport.DISABLED.toString();
|
||||
|
||||
private static Object defaultKeyGenerationAlgorithm() {
|
||||
private static Object defaultKeyGenerationAlgorithm(Crypto crypto) {
|
||||
try {
|
||||
validateKeyAlgorithm(INTER_WORKER_KEY_GENERATION_ALGORITHM_CONFIG, INTER_WORKER_KEY_GENERATION_ALGORITHM_DEFAULT);
|
||||
validateKeyAlgorithm(crypto, INTER_WORKER_KEY_GENERATION_ALGORITHM_CONFIG, INTER_WORKER_KEY_GENERATION_ALGORITHM_DEFAULT);
|
||||
return INTER_WORKER_KEY_GENERATION_ALGORITHM_DEFAULT;
|
||||
} catch (Throwable t) {
|
||||
log.info(
|
||||
|
@ -252,9 +252,9 @@ public class DistributedConfig extends WorkerConfig {
|
|||
}
|
||||
}
|
||||
|
||||
private static Object defaultSignatureAlgorithm() {
|
||||
private static Object defaultSignatureAlgorithm(Crypto crypto) {
|
||||
try {
|
||||
validateSignatureAlgorithm(INTER_WORKER_SIGNATURE_ALGORITHM_CONFIG, INTER_WORKER_SIGNATURE_ALGORITHM_DEFAULT);
|
||||
validateSignatureAlgorithm(crypto, INTER_WORKER_SIGNATURE_ALGORITHM_CONFIG, INTER_WORKER_SIGNATURE_ALGORITHM_DEFAULT);
|
||||
return INTER_WORKER_SIGNATURE_ALGORITHM_DEFAULT;
|
||||
} catch (Throwable t) {
|
||||
log.info(
|
||||
|
@ -267,11 +267,11 @@ public class DistributedConfig extends WorkerConfig {
|
|||
}
|
||||
}
|
||||
|
||||
private static Object defaultVerificationAlgorithms() {
|
||||
private static Object defaultVerificationAlgorithms(Crypto crypto) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (String verificationAlgorithm : INTER_WORKER_VERIFICATION_ALGORITHMS_DEFAULT) {
|
||||
try {
|
||||
validateSignatureAlgorithm(INTER_WORKER_VERIFICATION_ALGORITHMS_CONFIG, verificationAlgorithm);
|
||||
validateSignatureAlgorithm(crypto, INTER_WORKER_VERIFICATION_ALGORITHMS_CONFIG, verificationAlgorithm);
|
||||
result.add(verificationAlgorithm);
|
||||
} catch (Throwable t) {
|
||||
log.trace("Verification algorithm '{}' not found", verificationAlgorithm);
|
||||
|
@ -290,7 +290,8 @@ public class DistributedConfig extends WorkerConfig {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final ConfigDef CONFIG = baseConfigDef()
|
||||
private static ConfigDef config(Crypto crypto) {
|
||||
return baseConfigDef()
|
||||
.define(GROUP_ID_CONFIG,
|
||||
ConfigDef.Type.STRING,
|
||||
ConfigDef.Importance.HIGH,
|
||||
|
@ -375,7 +376,7 @@ public class DistributedConfig extends WorkerConfig {
|
|||
atLeast(0),
|
||||
ConfigDef.Importance.MEDIUM,
|
||||
CommonClientConfigs.REQUEST_TIMEOUT_MS_DOC)
|
||||
/* default is set to be a bit lower than the server default (10 min), to avoid both client and server closing connection at same time */
|
||||
/* default is set to be a bit lower than the server default (10 min), to avoid both client and server closing connection at same time */
|
||||
.define(CommonClientConfigs.CONNECTIONS_MAX_IDLE_MS_CONFIG,
|
||||
ConfigDef.Type.LONG,
|
||||
TimeUnit.MINUTES.toMillis(9),
|
||||
|
@ -470,9 +471,9 @@ public class DistributedConfig extends WorkerConfig {
|
|||
INTER_WORKER_KEY_TTL_MS_MS_DOC)
|
||||
.define(INTER_WORKER_KEY_GENERATION_ALGORITHM_CONFIG,
|
||||
ConfigDef.Type.STRING,
|
||||
defaultKeyGenerationAlgorithm(),
|
||||
defaultKeyGenerationAlgorithm(crypto),
|
||||
ConfigDef.LambdaValidator.with(
|
||||
(name, value) -> validateKeyAlgorithm(name, (String) value),
|
||||
(name, value) -> validateKeyAlgorithm(crypto, name, (String) value),
|
||||
() -> "Any KeyGenerator algorithm supported by the worker JVM"),
|
||||
ConfigDef.Importance.LOW,
|
||||
INTER_WORKER_KEY_GENERATION_ALGORITHM_DOC)
|
||||
|
@ -483,20 +484,21 @@ public class DistributedConfig extends WorkerConfig {
|
|||
INTER_WORKER_KEY_SIZE_DOC)
|
||||
.define(INTER_WORKER_SIGNATURE_ALGORITHM_CONFIG,
|
||||
ConfigDef.Type.STRING,
|
||||
defaultSignatureAlgorithm(),
|
||||
defaultSignatureAlgorithm(crypto),
|
||||
ConfigDef.LambdaValidator.with(
|
||||
(name, value) -> validateSignatureAlgorithm(name, (String) value),
|
||||
(name, value) -> validateSignatureAlgorithm(crypto, name, (String) value),
|
||||
() -> "Any MAC algorithm supported by the worker JVM"),
|
||||
ConfigDef.Importance.LOW,
|
||||
INTER_WORKER_SIGNATURE_ALGORITHM_DOC)
|
||||
.define(INTER_WORKER_VERIFICATION_ALGORITHMS_CONFIG,
|
||||
ConfigDef.Type.LIST,
|
||||
defaultVerificationAlgorithms(),
|
||||
defaultVerificationAlgorithms(crypto),
|
||||
ConfigDef.LambdaValidator.with(
|
||||
(name, value) -> validateVerificationAlgorithms(name, (List<String>) value),
|
||||
(name, value) -> validateVerificationAlgorithms(crypto, name, (List<String>) value),
|
||||
() -> "A list of one or more MAC algorithms, each supported by the worker JVM"),
|
||||
ConfigDef.Importance.LOW,
|
||||
INTER_WORKER_VERIFICATION_ALGORITHMS_DOC);
|
||||
}
|
||||
|
||||
private final ExactlyOnceSourceSupport exactlyOnceSourceSupport;
|
||||
|
||||
|
@ -547,18 +549,24 @@ public class DistributedConfig extends WorkerConfig {
|
|||
}
|
||||
|
||||
public DistributedConfig(Map<String, String> props) {
|
||||
super(CONFIG, props);
|
||||
this(Crypto.SYSTEM, props);
|
||||
}
|
||||
|
||||
// Visible for testing
|
||||
DistributedConfig(Crypto crypto, Map<String, String> props) {
|
||||
super(config(crypto), props);
|
||||
this.crypto = crypto;
|
||||
exactlyOnceSourceSupport = ExactlyOnceSourceSupport.fromProperty(getString(EXACTLY_ONCE_SOURCE_SUPPORT_CONFIG));
|
||||
validateInterWorkerKeyConfigs();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(CONFIG.toHtml(4, config -> "connectconfigs_" + config));
|
||||
System.out.println(config(Crypto.SYSTEM).toHtml(4, config -> "connectconfigs_" + config));
|
||||
}
|
||||
|
||||
public KeyGenerator getInternalRequestKeyGenerator() {
|
||||
try {
|
||||
KeyGenerator result = KeyGenerator.getInstance(getString(INTER_WORKER_KEY_GENERATION_ALGORITHM_CONFIG));
|
||||
KeyGenerator result = crypto.keyGenerator(getString(INTER_WORKER_KEY_GENERATION_ALGORITHM_CONFIG));
|
||||
Optional.ofNullable(getInt(INTER_WORKER_KEY_SIZE_CONFIG)).ifPresent(result::init);
|
||||
return result;
|
||||
} catch (NoSuchAlgorithmException | InvalidParameterException e) {
|
||||
|
@ -615,7 +623,7 @@ public class DistributedConfig extends WorkerConfig {
|
|||
}
|
||||
}
|
||||
|
||||
private static void validateVerificationAlgorithms(String configName, List<String> algorithms) {
|
||||
private static void validateVerificationAlgorithms(Crypto crypto, String configName, List<String> algorithms) {
|
||||
if (algorithms.isEmpty()) {
|
||||
throw new ConfigException(
|
||||
configName,
|
||||
|
@ -625,24 +633,24 @@ public class DistributedConfig extends WorkerConfig {
|
|||
}
|
||||
for (String algorithm : algorithms) {
|
||||
try {
|
||||
Mac.getInstance(algorithm);
|
||||
crypto.mac(algorithm);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw unsupportedAlgorithmException(configName, algorithm, "Mac");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateSignatureAlgorithm(String configName, String algorithm) {
|
||||
private static void validateSignatureAlgorithm(Crypto crypto, String configName, String algorithm) {
|
||||
try {
|
||||
Mac.getInstance(algorithm);
|
||||
crypto.mac(algorithm);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw unsupportedAlgorithmException(configName, algorithm, "Mac");
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateKeyAlgorithm(String configName, String algorithm) {
|
||||
private static void validateKeyAlgorithm(Crypto crypto, String configName, String algorithm) {
|
||||
try {
|
||||
KeyGenerator.getInstance(algorithm);
|
||||
crypto.keyGenerator(algorithm);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw unsupportedAlgorithmException(configName, algorithm, "KeyGenerator");
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.apache.kafka.connect.runtime.rest;
|
||||
|
||||
import org.apache.kafka.connect.errors.ConnectException;
|
||||
import org.apache.kafka.connect.runtime.distributed.Crypto;
|
||||
import org.apache.kafka.connect.runtime.rest.errors.BadRequestException;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
|
||||
|
@ -41,15 +42,17 @@ public class InternalRequestSignature {
|
|||
|
||||
/**
|
||||
* Add a signature to a request.
|
||||
* @param key the key to sign the request with; may not be null
|
||||
* @param requestBody the body of the request; may not be null
|
||||
*
|
||||
* @param crypto the cryptography library used to generate {@link Mac} instances, may not be null
|
||||
* @param key the key to sign the request with; may not be null
|
||||
* @param requestBody the body of the request; may not be null
|
||||
* @param signatureAlgorithm the algorithm to use to sign the request; may not be null
|
||||
* @param request the request to add the signature to; may not be null
|
||||
* @param request the request to add the signature to; may not be null
|
||||
*/
|
||||
public static void addToRequest(SecretKey key, byte[] requestBody, String signatureAlgorithm, Request request) {
|
||||
public static void addToRequest(Crypto crypto, SecretKey key, byte[] requestBody, String signatureAlgorithm, Request request) {
|
||||
Mac mac;
|
||||
try {
|
||||
mac = mac(signatureAlgorithm);
|
||||
mac = crypto.mac(signatureAlgorithm);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new ConnectException(e);
|
||||
}
|
||||
|
@ -60,12 +63,14 @@ public class InternalRequestSignature {
|
|||
|
||||
/**
|
||||
* Extract a signature from a request.
|
||||
* @param requestBody the body of the request; may not be null
|
||||
* @param headers the headers for the request; may be null
|
||||
*
|
||||
* @param crypto the cryptography library used to generate {@link Mac} instances, may not be null
|
||||
* @param requestBody the body of the request; may not be null
|
||||
* @param headers the headers for the request; may be null
|
||||
* @return the signature extracted from the request, or null if one or more request signature
|
||||
* headers was not present
|
||||
*/
|
||||
public static InternalRequestSignature fromHeaders(byte[] requestBody, HttpHeaders headers) {
|
||||
public static InternalRequestSignature fromHeaders(Crypto crypto, byte[] requestBody, HttpHeaders headers) {
|
||||
if (headers == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -78,7 +83,7 @@ public class InternalRequestSignature {
|
|||
|
||||
Mac mac;
|
||||
try {
|
||||
mac = mac(signatureAlgorithm);
|
||||
mac = crypto.mac(signatureAlgorithm);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new BadRequestException(e.getMessage());
|
||||
}
|
||||
|
@ -112,10 +117,6 @@ public class InternalRequestSignature {
|
|||
return MessageDigest.isEqual(sign(mac, key, requestBody), requestSignature);
|
||||
}
|
||||
|
||||
private static Mac mac(String signatureAlgorithm) throws NoSuchAlgorithmException {
|
||||
return Mac.getInstance(signatureAlgorithm);
|
||||
}
|
||||
|
||||
private static byte[] sign(Mac mac, SecretKey key, byte[] requestBody) {
|
||||
try {
|
||||
mac.init(key);
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.apache.kafka.connect.runtime.rest;
|
|||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.kafka.connect.runtime.WorkerConfig;
|
||||
import org.apache.kafka.connect.runtime.distributed.Crypto;
|
||||
import org.apache.kafka.connect.runtime.rest.entities.ErrorMessage;
|
||||
import org.apache.kafka.connect.runtime.rest.errors.ConnectRestException;
|
||||
import org.apache.kafka.connect.runtime.rest.util.SSLUtils;
|
||||
|
@ -136,6 +137,7 @@ public class RestClient {
|
|||
|
||||
if (sessionKey != null && requestSignatureAlgorithm != null) {
|
||||
InternalRequestSignature.addToRequest(
|
||||
Crypto.SYSTEM,
|
||||
sessionKey,
|
||||
serializedBody != null ? serializedBody.getBytes(StandardCharsets.UTF_8) : null,
|
||||
requestSignatureAlgorithm,
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.apache.kafka.connect.runtime.ConnectorConfig;
|
|||
import org.apache.kafka.connect.runtime.Herder;
|
||||
import org.apache.kafka.connect.runtime.RestartRequest;
|
||||
import org.apache.kafka.connect.runtime.WorkerConfig;
|
||||
import org.apache.kafka.connect.runtime.distributed.Crypto;
|
||||
import org.apache.kafka.connect.runtime.distributed.RebalanceNeededException;
|
||||
import org.apache.kafka.connect.runtime.distributed.RequestTargetException;
|
||||
import org.apache.kafka.connect.runtime.rest.InternalRequestSignature;
|
||||
|
@ -326,7 +327,7 @@ public class ConnectorsResource implements ConnectResource {
|
|||
final byte[] requestBody) throws Throwable {
|
||||
List<Map<String, String>> taskConfigs = new ObjectMapper().readValue(requestBody, TASK_CONFIGS_TYPE);
|
||||
FutureCallback<Void> cb = new FutureCallback<>();
|
||||
herder.putTaskConfigs(connector, taskConfigs, cb, InternalRequestSignature.fromHeaders(requestBody, headers));
|
||||
herder.putTaskConfigs(connector, taskConfigs, cb, InternalRequestSignature.fromHeaders(Crypto.SYSTEM, requestBody, headers));
|
||||
completeOrForwardRequest(cb, "/connectors/" + connector + "/tasks", "POST", headers, taskConfigs, forward);
|
||||
}
|
||||
|
||||
|
@ -338,7 +339,7 @@ public class ConnectorsResource implements ConnectResource {
|
|||
final @QueryParam("forward") Boolean forward,
|
||||
final byte[] requestBody) throws Throwable {
|
||||
FutureCallback<Void> cb = new FutureCallback<>();
|
||||
herder.fenceZombieSourceTasks(connector, cb, InternalRequestSignature.fromHeaders(requestBody, headers));
|
||||
herder.fenceZombieSourceTasks(connector, cb, InternalRequestSignature.fromHeaders(Crypto.SYSTEM, requestBody, headers));
|
||||
completeOrForwardRequest(cb, "/connectors/" + connector + "/fence", "PUT", headers, requestBody, forward);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ package org.apache.kafka.connect.runtime.distributed;
|
|||
import org.apache.kafka.clients.CommonClientConfigs;
|
||||
import org.apache.kafka.common.config.ConfigException;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.Mac;
|
||||
|
@ -45,8 +44,9 @@ import static org.junit.Assert.assertNotEquals;
|
|||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
|
||||
public class DistributedConfigTest {
|
||||
|
||||
|
@ -69,58 +69,53 @@ public class DistributedConfigTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultAlgorithmsNotPresent() {
|
||||
public void testDefaultAlgorithmsNotPresent() throws NoSuchAlgorithmException {
|
||||
final String fakeKeyGenerationAlgorithm = "FakeKeyGenerationAlgorithm";
|
||||
final String fakeMacAlgorithm = "FakeMacAlgorithm";
|
||||
|
||||
final KeyGenerator fakeKeyGenerator = mock(KeyGenerator.class);
|
||||
final Mac fakeMac = mock(Mac.class);
|
||||
final Crypto crypto = mock(Crypto.class);
|
||||
|
||||
Map<String, String> configs = configs();
|
||||
configs.put(DistributedConfig.INTER_WORKER_KEY_GENERATION_ALGORITHM_CONFIG, fakeKeyGenerationAlgorithm);
|
||||
configs.put(DistributedConfig.INTER_WORKER_SIGNATURE_ALGORITHM_CONFIG, fakeMacAlgorithm);
|
||||
configs.put(DistributedConfig.INTER_WORKER_VERIFICATION_ALGORITHMS_CONFIG, fakeMacAlgorithm);
|
||||
|
||||
try (
|
||||
MockedStatic<KeyGenerator> keyGenerator = mockStatic(KeyGenerator.class);
|
||||
MockedStatic<Mac> mac = mockStatic(Mac.class)
|
||||
) {
|
||||
// Make it seem like the default key generation algorithm isn't available on this worker
|
||||
keyGenerator.when(() -> KeyGenerator.getInstance(DistributedConfig.INTER_WORKER_KEY_GENERATION_ALGORITHM_DEFAULT))
|
||||
.thenThrow(new NoSuchAlgorithmException());
|
||||
// But the one specified in the worker config file is
|
||||
keyGenerator.when(() -> KeyGenerator.getInstance(fakeKeyGenerationAlgorithm))
|
||||
.thenReturn(fakeKeyGenerator);
|
||||
// Make it seem like the default key generation algorithm isn't available on this worker
|
||||
doThrow(new NoSuchAlgorithmException())
|
||||
.when(crypto).keyGenerator(DistributedConfig.INTER_WORKER_KEY_GENERATION_ALGORITHM_DEFAULT);
|
||||
// But the one specified in the worker config file is
|
||||
doReturn(fakeKeyGenerator)
|
||||
.when(crypto).keyGenerator(fakeKeyGenerationAlgorithm);
|
||||
|
||||
// And for the signature algorithm
|
||||
mac.when(() -> Mac.getInstance(DistributedConfig.INTER_WORKER_SIGNATURE_ALGORITHM_DEFAULT))
|
||||
.thenThrow(new NoSuchAlgorithmException());
|
||||
// Likewise for key verification algorithms
|
||||
DistributedConfig.INTER_WORKER_VERIFICATION_ALGORITHMS_DEFAULT.forEach(verificationAlgorithm ->
|
||||
keyGenerator.when(() -> Mac.getInstance(verificationAlgorithm))
|
||||
.thenThrow(new NoSuchAlgorithmException())
|
||||
);
|
||||
mac.when(() -> Mac.getInstance(fakeMacAlgorithm))
|
||||
.thenReturn(fakeMac);
|
||||
|
||||
// Should succeed; even though the defaults aren't present, the manually-specified algorithms are valid
|
||||
new DistributedConfig(configs);
|
||||
|
||||
// Should fail; the default key generation algorithm isn't present, and no override is specified
|
||||
String removed = configs.remove(INTER_WORKER_KEY_GENERATION_ALGORITHM_CONFIG);
|
||||
assertThrows(ConfigException.class, () -> new DistributedConfig(configs));
|
||||
configs.put(INTER_WORKER_KEY_GENERATION_ALGORITHM_CONFIG, removed);
|
||||
|
||||
// Should fail; the default key generation algorithm isn't present, and no override is specified
|
||||
removed = configs.remove(INTER_WORKER_SIGNATURE_ALGORITHM_CONFIG);
|
||||
assertThrows(ConfigException.class, () -> new DistributedConfig(configs));
|
||||
configs.put(INTER_WORKER_SIGNATURE_ALGORITHM_CONFIG, removed);
|
||||
|
||||
// Should fail; the default key generation algorithm isn't present, and no override is specified
|
||||
removed = configs.remove(INTER_WORKER_VERIFICATION_ALGORITHMS_CONFIG);
|
||||
assertThrows(ConfigException.class, () -> new DistributedConfig(configs));
|
||||
configs.put(INTER_WORKER_VERIFICATION_ALGORITHMS_CONFIG, removed);
|
||||
// And for the signature algorithm
|
||||
doThrow(new NoSuchAlgorithmException())
|
||||
.when(crypto).mac(DistributedConfig.INTER_WORKER_SIGNATURE_ALGORITHM_DEFAULT);
|
||||
// Likewise for key verification algorithms
|
||||
for (String verificationAlgorithm : DistributedConfig.INTER_WORKER_VERIFICATION_ALGORITHMS_DEFAULT) {
|
||||
doThrow(new NoSuchAlgorithmException())
|
||||
.when(crypto).mac(verificationAlgorithm);
|
||||
}
|
||||
doReturn(fakeMac).when(crypto).mac(fakeMacAlgorithm);
|
||||
|
||||
// Should succeed; even though the defaults aren't present, the manually-specified algorithms are valid
|
||||
new DistributedConfig(crypto, configs);
|
||||
|
||||
// Should fail; the default key generation algorithm isn't present, and no override is specified
|
||||
String removed = configs.remove(INTER_WORKER_KEY_GENERATION_ALGORITHM_CONFIG);
|
||||
assertThrows(ConfigException.class, () -> new DistributedConfig(configs));
|
||||
configs.put(INTER_WORKER_KEY_GENERATION_ALGORITHM_CONFIG, removed);
|
||||
|
||||
// Should fail; the default key generation algorithm isn't present, and no override is specified
|
||||
removed = configs.remove(INTER_WORKER_SIGNATURE_ALGORITHM_CONFIG);
|
||||
assertThrows(ConfigException.class, () -> new DistributedConfig(configs));
|
||||
configs.put(INTER_WORKER_SIGNATURE_ALGORITHM_CONFIG, removed);
|
||||
|
||||
// Should fail; the default key generation algorithm isn't present, and no override is specified
|
||||
removed = configs.remove(INTER_WORKER_VERIFICATION_ALGORITHMS_CONFIG);
|
||||
assertThrows(ConfigException.class, () -> new DistributedConfig(configs));
|
||||
configs.put(INTER_WORKER_VERIFICATION_ALGORITHMS_CONFIG, removed);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.apache.kafka.connect.runtime.rest;
|
||||
|
||||
import org.apache.kafka.connect.errors.ConnectException;
|
||||
import org.apache.kafka.connect.runtime.distributed.Crypto;
|
||||
import org.apache.kafka.connect.runtime.rest.errors.BadRequestException;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.junit.Test;
|
||||
|
@ -28,6 +29,7 @@ import javax.crypto.SecretKey;
|
|||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -36,6 +38,7 @@ import static org.junit.Assert.assertNotNull;
|
|||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
@ -56,46 +59,49 @@ public class InternalRequestSignatureTest {
|
|||
36, 72, 86, -71, -32, 13, -8, 115, 85, 73, -65, -112, 6, 68, 41, -50
|
||||
};
|
||||
private static final String ENCODED_SIGNATURE = Base64.getEncoder().encodeToString(SIGNATURE);
|
||||
private final Crypto crypto = Crypto.SYSTEM;
|
||||
|
||||
@Test
|
||||
public void fromHeadersShouldReturnNullOnNullHeaders() {
|
||||
assertNull(InternalRequestSignature.fromHeaders(REQUEST_BODY, null));
|
||||
assertNull(InternalRequestSignature.fromHeaders(crypto, REQUEST_BODY, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromHeadersShouldReturnNullIfSignatureHeaderMissing() {
|
||||
assertNull(InternalRequestSignature.fromHeaders(REQUEST_BODY, internalRequestHeaders(null, SIGNATURE_ALGORITHM)));
|
||||
assertNull(InternalRequestSignature.fromHeaders(crypto, REQUEST_BODY, internalRequestHeaders(null, SIGNATURE_ALGORITHM)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromHeadersShouldReturnNullIfSignatureAlgorithmHeaderMissing() {
|
||||
assertNull(InternalRequestSignature.fromHeaders(REQUEST_BODY, internalRequestHeaders(ENCODED_SIGNATURE, null)));
|
||||
assertNull(InternalRequestSignature.fromHeaders(crypto, REQUEST_BODY, internalRequestHeaders(ENCODED_SIGNATURE, null)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromHeadersShouldThrowExceptionOnInvalidSignatureAlgorithm() {
|
||||
assertThrows(BadRequestException.class, () -> InternalRequestSignature.fromHeaders(REQUEST_BODY,
|
||||
assertThrows(BadRequestException.class, () -> InternalRequestSignature.fromHeaders(crypto, REQUEST_BODY,
|
||||
internalRequestHeaders(ENCODED_SIGNATURE, "doesn'texist")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromHeadersShouldThrowExceptionOnInvalidBase64Signature() {
|
||||
assertThrows(BadRequestException.class, () -> InternalRequestSignature.fromHeaders(REQUEST_BODY,
|
||||
assertThrows(BadRequestException.class, () -> InternalRequestSignature.fromHeaders(crypto, REQUEST_BODY,
|
||||
internalRequestHeaders("not valid base 64", SIGNATURE_ALGORITHM)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromHeadersShouldReturnNonNullResultOnValidSignatureAndSignatureAlgorithm() {
|
||||
InternalRequestSignature signature =
|
||||
InternalRequestSignature.fromHeaders(REQUEST_BODY, internalRequestHeaders(ENCODED_SIGNATURE, SIGNATURE_ALGORITHM));
|
||||
InternalRequestSignature.fromHeaders(crypto, REQUEST_BODY, internalRequestHeaders(ENCODED_SIGNATURE, SIGNATURE_ALGORITHM));
|
||||
assertNotNull(signature);
|
||||
assertNotNull(signature.keyAlgorithm());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addToRequestShouldThrowExceptionOnInvalidSignatureAlgorithm() {
|
||||
public void addToRequestShouldThrowExceptionOnInvalidSignatureAlgorithm() throws NoSuchAlgorithmException {
|
||||
Request request = mock(Request.class);
|
||||
assertThrows(ConnectException.class, () -> InternalRequestSignature.addToRequest(KEY, REQUEST_BODY, "doesn'texist", request));
|
||||
Crypto crypto = mock(Crypto.class);
|
||||
when(crypto.mac(anyString())).thenThrow(new NoSuchAlgorithmException("doesn'texist"));
|
||||
assertThrows(ConnectException.class, () -> InternalRequestSignature.addToRequest(crypto, KEY, REQUEST_BODY, "doesn'texist", request));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -112,7 +118,7 @@ public class InternalRequestSignatureTest {
|
|||
signatureAlgorithmCapture.capture()
|
||||
)).thenReturn(request);
|
||||
|
||||
InternalRequestSignature.addToRequest(KEY, REQUEST_BODY, SIGNATURE_ALGORITHM, request);
|
||||
InternalRequestSignature.addToRequest(crypto, KEY, REQUEST_BODY, SIGNATURE_ALGORITHM, request);
|
||||
|
||||
assertEquals(
|
||||
"Request should have valid base 64-encoded signature added as header",
|
||||
|
@ -133,7 +139,7 @@ public class InternalRequestSignatureTest {
|
|||
InternalRequestSignature signature = new InternalRequestSignature(REQUEST_BODY, mac, SIGNATURE);
|
||||
assertTrue(signature.isValid(KEY));
|
||||
|
||||
signature = InternalRequestSignature.fromHeaders(REQUEST_BODY, internalRequestHeaders(ENCODED_SIGNATURE, SIGNATURE_ALGORITHM));
|
||||
signature = InternalRequestSignature.fromHeaders(crypto, REQUEST_BODY, internalRequestHeaders(ENCODED_SIGNATURE, SIGNATURE_ALGORITHM));
|
||||
assertTrue(signature.isValid(KEY));
|
||||
|
||||
signature = new InternalRequestSignature("[{\"different_config\":\"different_value\"}]".getBytes(), mac, SIGNATURE);
|
||||
|
|
Loading…
Reference in New Issue