Skip to content

Commit ce52167

Browse files
committed
HTTP-119 use default certs when no security configured for https
Signed-off-by: davidradl <[email protected]>
1 parent 99ffa43 commit ce52167

File tree

7 files changed

+116
-23
lines changed

7 files changed

+116
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Added
66

7+
- Added support for built in JVM certificates if no security is configured.
78
- Added support for OIDC Bearer tokens.
89

910
## [0.15.0] - 2024-07-30

README.md

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -392,18 +392,19 @@ An example of such a mask would be `3XX, 4XX, 5XX`. In this case, all 300s, 400s
392392
Many status codes can be defined in one value, where each code should be separated with comma, for example:
393393
`401, 402, 403`. In this example, codes 401, 402 and 403 would not be interpreted as error codes.
394394

395-
## TLS and mTLS support
396-
Both Http Sink and Lookup Source connectors supports Https communication using TLS 1.2 and mTLS.
395+
## TLS (more secure replacement for SSL) and mTLS support
396+
Both Http Sink and Lookup Source connectors support Https communication using TLS 1.2 and mTLS.
397397
To enable Https communication simply use `https` protocol in endpoint's URL.
398-
If certificate used by HTTP server is self-signed, or it is signed byt not globally recognize CA
399-
you would have to add this certificate to connector's keystore as trusted certificate.
400-
In order to do so, use `gid.connector.http.security.cert.server` connector property,
401-
which value is a path to the certificate. You can also use your organization's CA Root certificate.
402-
You can specify many certificate, separating each path with `,`.
403-
404-
You can also configure connector to use mTLS. For this simply use `gid.connector.http.security.cert.client`
405-
and `gid.connector.http.security.key.client` connector properties to specify path to certificate and
406-
private key that should be used by connector. Key MUST be in `PCKS8` format. Both PEM and DER keys are
398+
To specify certificate(s) to be used by the server, use `gid.connector.http.security.cert.server` connector property;
399+
the value is a comma separated list of paths to certificate(s), for example you can use your organization's CA
400+
Root certificate, or a self-signed certificate.
401+
402+
Note that if there are no security properties for a `https` url then, the JVMs default certificates are
403+
used - allowing use of globally recognized CAs without the need for configuration.
404+
405+
You can also configure the connector to use mTLS. For this simply use `gid.connector.http.security.cert.client`
406+
and `gid.connector.http.security.key.client` connector properties to specify paths to the certificate and
407+
private key. The key MUST be in `PCKS8` format. Both PEM and DER keys are
407408
allowed.
408409

409410
All properties can be set via Sink's builder `.setProperty(...)` method or through Sink and Source table DDL.
@@ -415,7 +416,7 @@ To enable this option use `gid.connector.http.security.cert.server.allowSelfSign
415416
## Basic Authentication
416417
The connector supports Basic Authentication using a HTTP `Authorization` header.
417418
The header value can be set via properties, similarly as for other headers. The connector converts the passed value to Base64 and uses it for the request.
418-
If the used value starts with the prefix `Basic `, or `gid.connector.http.source.lookup.use-raw-authorization-header`
419+
If the used value starts with the prefix `Basic`, or `gid.connector.http.source.lookup.use-raw-authorization-header`
419420
is set to `'true'`, it will be used as header value as is, without any extra modification.
420421

421422
## OIDC Bearer Authentication
@@ -452,13 +453,13 @@ be requested if the current time is later than the cached token expiry time minu
452453
| lookup.max-retries | optional | The max retry times if the lookup failed; default is 3. See the following <a href="#lookup-cache">Lookup Cache</a> section for more details. |
453454
| gid.connector.http.lookup.error.code | optional | List of HTTP status codes that should be treated as errors by HTTP Source, separated with comma. |
454455
| gid.connector.http.lookup.error.code.exclude | optional | List of HTTP status codes that should be excluded from the `gid.connector.http.lookup.error.code` list, separated with comma. |
455-
| gid.connector.http.security.cert.server | optional | Path to trusted HTTP server certificate that should be add to connectors key store. More than one path can be specified using `,` as path delimiter. |
456+
| gid.connector.http.security.cert.server | optional | Comma separated paths to trusted HTTP server certificates that should be add to connectors trust store. |
456457
| gid.connector.http.security.cert.client | optional | Path to trusted certificate that should be used by connector's HTTP client for mTLS communication. |
457458
| gid.connector.http.security.key.client | optional | Path to trusted private key that should be used by connector's HTTP client for mTLS communication. |
458459
| gid.connector.http.security.cert.server.allowSelfSigned | optional | Accept untrusted certificates for TLS communication. |
459-
| gid.connector.http.security.oidc.token.request | optional | OIDC `Token Request` body in `application/x-www-form-urlencoded` encoding |
460-
| gid.connector.http.security.oidc.token.endpoint.url | optional | OIDC `Token Endpoint` url, to which the token request will be issued |
461-
| gid.connector.http.security.oidc.token.expiry.reduction | optional | OIDC tokens will be requested if the current time is later than the cached token expiry time minus this value. |
460+
| gid.connector.http.security.oidc.token.request | optional | OIDC `Token Request` body in `application/x-www-form-urlencoded` encoding |
461+
| gid.connector.http.security.oidc.token.endpoint.url | optional | OIDC `Token Endpoint` url, to which the token request will be issued |
462+
| gid.connector.http.security.oidc.token.expiry.reduction | optional | OIDC tokens will be requested if the current time is later than the cached token expiry time minus this value. |
462463
| gid.connector.http.source.lookup.request.timeout | optional | Sets HTTP request timeout in seconds. If not specified, the default value of 30 seconds will be used. |
463464
| gid.connector.http.source.lookup.request.thread-pool.size | optional | Sets the size of pool thread for HTTP lookup request processing. Increasing this value would mean that more concurrent requests can be processed in the same time. If not specified, the default value of 8 threads will be used. |
464465
| gid.connector.http.source.lookup.response.thread-pool.size | optional | Sets the size of pool thread for HTTP lookup response processing. Increasing this value would mean that more concurrent requests can be processed in the same time. If not specified, the default value of 4 threads will be used. |

src/main/java/com/getindata/connectors/http/internal/security/SecurityContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public void addCertToTrustStore(String certPath) {
168168
}
169169

170170
/**
171-
* Add certificate and private key that should be used by anny Http Connector instance that uses
171+
* Add certificate and private key that should be used by any Http Connector instance that uses
172172
* this {@link SSLContext} instance. Certificate and key are added only to this Context's
173173
* {@link KeyStore} and not for entire JVM.
174174
* @param publicKeyPath path to public key/certificate used for mTLS.

src/main/java/com/getindata/connectors/http/internal/utils/JavaNetHttpClientFactory.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.net.http.HttpClient;
44
import java.net.http.HttpClient.Redirect;
5+
import java.security.NoSuchAlgorithmException;
56
import java.util.ArrayList;
67
import java.util.List;
78
import java.util.Properties;
@@ -24,8 +25,8 @@
2425
public class JavaNetHttpClientFactory {
2526

2627
/**
27-
* Creates Java's {@link HttpClient} instance that will be using default, JVM shared {@link
28-
* java.util.concurrent.ForkJoinPool} for async calls.
28+
* Creates Java's {@link HttpClient} instance that will be using default, JVM shared
29+
* {@link java.util.concurrent.ForkJoinPool} for async calls.
2930
*
3031
* @param properties properties used to build {@link SSLContext}
3132
* @return new {@link HttpClient} instance.
@@ -73,21 +74,39 @@ public static HttpClient createClient(Properties properties, Executor executor)
7374
* @return new {@link SSLContext} instance.
7475
*/
7576
private static SSLContext getSslContext(Properties properties) {
76-
SecurityContext securityContext = createSecurityContext(properties);
77+
78+
String keyStorePath =
79+
properties.getProperty(HttpConnectorConfigConstants.KEY_STORE_PATH, "");
7780

7881
boolean selfSignedCert = Boolean.parseBoolean(
7982
properties.getProperty(HttpConnectorConfigConstants.ALLOW_SELF_SIGNED, "false"));
8083

8184
String[] serverTrustedCerts = properties
82-
.getProperty(HttpConnectorConfigConstants.SERVER_TRUSTED_CERT, "")
83-
.split(HttpConnectorConfigConstants.PROP_DELIM);
85+
.getProperty(HttpConnectorConfigConstants.SERVER_TRUSTED_CERT, "")
86+
.split(HttpConnectorConfigConstants.PROP_DELIM);
8487

8588
String clientCert = properties
8689
.getProperty(HttpConnectorConfigConstants.CLIENT_CERT, "");
8790

8891
String clientPrivateKey = properties
8992
.getProperty(HttpConnectorConfigConstants.CLIENT_PRIVATE_KEY, "");
9093

94+
if (StringUtils.isNullOrWhitespaceOnly(keyStorePath)
95+
&& !selfSignedCert
96+
// checking the property in this way so that serverTrustedCerts is not left and null
97+
// or empty, which causes the http client to error.
98+
&& (properties.getProperty(HttpConnectorConfigConstants.SERVER_TRUSTED_CERT) == null)
99+
&& StringUtils.isNullOrWhitespaceOnly(clientCert)
100+
&& StringUtils.isNullOrWhitespaceOnly(clientPrivateKey)) {
101+
try {
102+
return SSLContext.getDefault();
103+
} catch (NoSuchAlgorithmException e) {
104+
throw new RuntimeException(e);
105+
}
106+
}
107+
108+
SecurityContext securityContext = createSecurityContext(properties);
109+
91110
for (String cert : serverTrustedCerts) {
92111
if (!StringUtils.isNullOrWhitespaceOnly(cert)) {
93112
securityContext.addCertToTrustStore(cert);

src/test/java/com/getindata/connectors/http/internal/table/lookup/querycreators/ElasticSearchLiteQueryCreatorTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,24 @@
1818

1919
public class ElasticSearchLiteQueryCreatorTest {
2020

21+
@Test
22+
public void testWithEmptyLookup() {
23+
24+
// GIVEN
25+
LookupRow lookupRow = new LookupRow();
26+
lookupRow.setLookupPhysicalRowDataType(DataTypes.STRING());
27+
28+
GenericRowData lookupDataRow = GenericRowData.of(StringData.fromString("val1"));
29+
30+
// WHEN
31+
var queryCreator = new ElasticSearchLiteQueryCreator(lookupRow);
32+
var createdQuery = queryCreator.createLookupQuery(lookupDataRow);
33+
34+
// THEN
35+
assertThat(createdQuery.getLookupQuery()).isEqualTo("");
36+
assertThat(createdQuery.getBodyBasedUrlQueryParameters()).isEmpty();
37+
}
38+
2139
@Test
2240
public void testQueryCreationForSingleQueryStringParam() {
2341

src/test/java/com/getindata/connectors/http/internal/table/lookup/querycreators/GenericJsonQueryCreatorTest.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ class GenericJsonQueryCreatorTest {
3131

3232
@BeforeEach
3333
public void setUp() {
34-
3534
DataType lookupPhysicalDataType = row(List.of(
3635
DataTypes.FIELD("id", DataTypes.INT()),
3736
DataTypes.FIELD("uuid", DataTypes.STRING())
@@ -52,6 +51,23 @@ public void shouldSerializeToJson() {
5251
row.setField(0, 11);
5352
row.setField(1, StringData.fromString("myUuid"));
5453

54+
this.jsonQueryCreator.createLookupQuery(row);
55+
LookupQueryInfo lookupQuery = this.jsonQueryCreator.createLookupQuery(row);
56+
assertThat(lookupQuery.getBodyBasedUrlQueryParameters().isEmpty());
57+
assertThat(lookupQuery.getLookupQuery()).isEqualTo("{\"id\":11,\"uuid\":\"myUuid\"}");
58+
}
59+
60+
@Test
61+
public void shouldSerializeToJsonTwice() {
62+
GenericRowData row = new GenericRowData(2);
63+
row.setField(0, 11);
64+
row.setField(1, StringData.fromString("myUuid"));
65+
66+
this.jsonQueryCreator.createLookupQuery(row);
67+
68+
// Call createLookupQuery two times
69+
// to check that serialization schema is not opened Two times.
70+
this.jsonQueryCreator.createLookupQuery(row);
5571
LookupQueryInfo lookupQuery = this.jsonQueryCreator.createLookupQuery(row);
5672
assertThat(lookupQuery.getBodyBasedUrlQueryParameters().isEmpty());
5773
assertThat(lookupQuery.getLookupQuery()).isEqualTo("{\"id\":11,\"uuid\":\"myUuid\"}");
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.getindata.connectors.http.internal.table.lookup.querycreators;
2+
3+
import java.util.Collections;
4+
import java.util.Optional;
5+
6+
import org.apache.flink.configuration.ConfigOption;
7+
import org.apache.flink.configuration.ConfigOptions;
8+
import org.apache.flink.configuration.Configuration;
9+
import org.junit.jupiter.api.Test;
10+
import static org.assertj.core.api.Assertions.assertThat;
11+
12+
class QueryFormatAwareConfigurationTest {
13+
14+
private static final ConfigOption<String> configOption = ConfigOptions.key("key")
15+
.stringType()
16+
.noDefaultValue();
17+
18+
@Test
19+
public void testWithDot() {
20+
QueryFormatAwareConfiguration queryConfig = new QueryFormatAwareConfiguration(
21+
"prefix.", Configuration.fromMap(Collections.singletonMap("prefix.key", "val"))
22+
);
23+
24+
Optional<String> optional = queryConfig.getOptional(configOption);
25+
assertThat(optional.get()).isEqualTo("val");
26+
}
27+
28+
@Test
29+
public void testWithoutDot() {
30+
QueryFormatAwareConfiguration queryConfig = new QueryFormatAwareConfiguration(
31+
"prefix", Configuration.fromMap(Collections.singletonMap("prefix.key", "val"))
32+
);
33+
34+
Optional<String> optional = queryConfig.getOptional(configOption);
35+
assertThat(optional.get()).isEqualTo("val");
36+
}
37+
38+
}

0 commit comments

Comments
 (0)