diff --git a/README.md b/README.md index 3f0a365..a590cf4 100644 --- a/README.md +++ b/README.md @@ -764,4 +764,76 @@ Soap Adapter Config } ``` +## Configuring SSL with PEM-encoded Certificates + +To configure SSL in our integration adapters using PEM-encoded certificates, add the following properties to your +`settings.properties` file: + +### Example Configuration + +For embedding an SSL certificate in a keystore: + +```properties +config.adapter.mirket.v1={\ + ...,\ + "http": {\ + ...,\ + "skipSsl": false,\ + "ssl": {\ + "pem": {\ + "truststore": {\ + "certificate": "file:/Users/johndoe/mirket-certs/*"\ + },\ + "keystore": {\ + "certificate": "file:/Users/johndoe/mirket-certs/*",\ + "privateKey": "file:/Users/johndoe/mirket-certs/private/application.key",\ + "privateKeyPassword": "your_private_key_password_here"\ + }\ + }\ + },\ + ...\ + },\ + ...\ +} +``` + +### Configuration Details + +- `truststore.certificate`: This property accepts a glob pattern to match all certificate files. The certificates + matched by the pattern will be combined into the truststore. +- `keystore.certificate`: Path to the PEM-encoded certificate file. +- `keystore.privateKey`: Path to the PEM-encoded private key file. +- `keystore.privateKeyPassword`: Password used to decrypt the private key, if it is encrypted. +- `skipSsl`: Set to `false` to enable SSL. + +### Using PEM Content Directly + +PEM content can also be used directly for both the `certificate` and `privateKey` properties. If the property values contain `BEGIN` and `END` markers, they will be treated as PEM content rather than a resource location. + +```properties +config.adapter.mirket.v1={\ + ...,\ + "http": {\ + ...,\ + "skipSsl": false,\ + "ssl": {\ + "pem": {\ + "truststore": {\ + "certificate": "-----BEGIN CERTIFICATE-----\nMIID1zCCAr+gAwIBAgIUNM5QQv8IzVQsgSmmdPQNaqyzWs4wDQYJKoZIhvcNAQEL\nBQAwezELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwI\n... (rest of certificate content) ...\n-----END CERTIFICATE-----"\ + },\ + "keystore": {\ + "certificate": "-----BEGIN CERTIFICATE-----\nMIID1zCCAr+gAwIBAgIUNM5QQv8IzVQsgSmmdPQNaqyzWs4wDQYJKoZIhvcNAQEL\nBQAwezELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwI\n... (rest of certificate content) ...\n-----END CERTIFICATE-----",\ + "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD... (rest of private key content) ...\n-----END PRIVATE KEY-----",\ + "privateKeyPassword": "your_private_key_password_here"\ + }\ + }\ + },\ + ...\ + },\ + ...\ +} +``` + +This configuration ensures that SSL is set up using PEM-encoded certificates and private keys, and the settings are read from a JSON value in the `settings.properties` file. + diff --git a/build.gradle b/build.gradle index 982bb65..ac3dda7 100644 --- a/build.gradle +++ b/build.gradle @@ -15,10 +15,10 @@ subprojects { ext { versions = [ //main libs - integration : '1.1.0', - middleware : '1.1.0', + integration : '1.1.1', + middleware : '1.1.1', //adapter ext - dac_bridge : '1.1.0', + dac_bridge : '1.1.1', //ext libs spring_boot_ws : '3.1.5', spring_ws_core : '4.0.5', diff --git a/dynamic-adapter-config-bridge/VERSION b/dynamic-adapter-config-bridge/VERSION index 1cc5f65..8cfbc90 100644 --- a/dynamic-adapter-config-bridge/VERSION +++ b/dynamic-adapter-config-bridge/VERSION @@ -1 +1 @@ -1.1.0 \ No newline at end of file +1.1.1 \ No newline at end of file diff --git a/dynamic-adapter-config-bridge/src/main/java/com/inomera/adapter/config/bridge/DynamicAdapterConfigDataBridgeSupplierHandler.java b/dynamic-adapter-config-bridge/src/main/java/com/inomera/adapter/config/bridge/DynamicAdapterConfigDataBridgeSupplierHandler.java index c962ae9..8699ed0 100644 --- a/dynamic-adapter-config-bridge/src/main/java/com/inomera/adapter/config/bridge/DynamicAdapterConfigDataBridgeSupplierHandler.java +++ b/dynamic-adapter-config-bridge/src/main/java/com/inomera/adapter/config/bridge/DynamicAdapterConfigDataBridgeSupplierHandler.java @@ -48,7 +48,8 @@ public AdapterConfig getConfigV1(String key) { // Merge with common configuration AdapterConfig adapterConfig = new AdapterConfig(key, adapterProperties); - AdapterConfig mergedAdapterConfig = mergeWithCommonConfigIfNotSetInAdapterConfig(adapterConfig); + AdapterConfig mergedAdapterConfig = mergeWithCommonConfigIfNotSetInAdapterConfig( + adapterConfig); // Handle configuration reload logic handleConfigReload(key, mergedAdapterConfig); @@ -112,8 +113,9 @@ public List getConfigs(String key, Class classToDeserialize, List d private Auth extractAuthCredentials(Map adapterPropertiesMap) { Map authMap = (Map) adapterPropertiesMap.get("auth"); - if (authMap == null) { - throw new AdapterConfigException("'auth' section is missing in adapter properties"); + if (authMap == null || authMap.isEmpty()) { + LOG.info("'auth' section is missing in adapter properties so that return null for using common config value"); + return null; } return getAuthCredentials(authMap); } diff --git a/dynamic-adapter-config-bridge/src/test/java/com/inomera/adapter/config/bridge/DynamicAdapterConfigDataBridgeSupplierHandlerTest.java b/dynamic-adapter-config-bridge/src/test/java/com/inomera/adapter/config/bridge/DynamicAdapterConfigDataBridgeSupplierHandlerTest.java index 242bba4..fa84202 100644 --- a/dynamic-adapter-config-bridge/src/test/java/com/inomera/adapter/config/bridge/DynamicAdapterConfigDataBridgeSupplierHandlerTest.java +++ b/dynamic-adapter-config-bridge/src/test/java/com/inomera/adapter/config/bridge/DynamicAdapterConfigDataBridgeSupplierHandlerTest.java @@ -9,8 +9,10 @@ import static org.mockito.Mockito.when; import com.inomera.adapter.config.bridge.exception.AdapterConfigException; +import com.inomera.integration.auth.AuthType; import com.inomera.integration.config.model.AdapterConfig; import com.inomera.integration.config.model.AdapterProperties; +import com.inomera.integration.config.model.Auth; import com.inomera.telco.commons.config.ConfigurationHolder; import java.util.List; import java.util.Map; @@ -60,6 +62,52 @@ void testGetConfigV1_Success() { verify(configurationHolder, times(1)).getJsonObjectProperty(eq(key), eq(Map.class)); } + @Test + void testGetConfigV1WithoutAdapterAuthConfig_Success() { + String key = "testKey"; + AdapterProperties mockAdapterProperties = new AdapterProperties(); + mockAdapterProperties.setUrl("https://api.mirket.com"); + AdapterProperties mockCommonAdapterProperties = new AdapterProperties(); + mockCommonAdapterProperties.setAuth(new Auth.NoneAuth()); + + Map mockAdapterPropertiesMap = Map.of("url", "https://api.mirket.com"); + + when(configurationHolder.getJsonObjectProperty(eq(key), eq(Map.class))).thenReturn(mockAdapterPropertiesMap); + when(configurationHolder.getJsonObjectProperty(eq(key), eq(AdapterProperties.class))).thenReturn(mockAdapterProperties); + when(configurationHolder.getJsonObjectProperty(eq("config.adapter.common.v1"), eq(AdapterProperties.class))).thenReturn(mockCommonAdapterProperties); + + AdapterConfig result = handler.getConfigV1(key); + + assertNotNull(result); + assertEquals(key, result.getKey()); + assertEquals("https://api.mirket.com", result.getAdapterProperties().getUrl()); + assertNotNull(result.getAdapterProperties()); + verify(configurationHolder, times(1)).getJsonObjectProperty(eq(key), eq(Map.class)); + } + + @Test + void testGetConfigV1WithoutAdapterAuthConfigCommonAndAdapter_Success_Default() { + String key = "testKey"; + AdapterProperties mockAdapterProperties = new AdapterProperties(); + mockAdapterProperties.setUrl("https://api.mirket.com"); + AdapterProperties mockCommonAdapterProperties = new AdapterProperties(); + + Map mockAdapterPropertiesMap = Map.of("url", "https://api.mirket.com"); + + when(configurationHolder.getJsonObjectProperty(eq(key), eq(Map.class))).thenReturn(mockAdapterPropertiesMap); + when(configurationHolder.getJsonObjectProperty(eq(key), eq(AdapterProperties.class))).thenReturn(mockAdapterProperties); + when(configurationHolder.getJsonObjectProperty(eq("config.adapter.common.v1"), eq(AdapterProperties.class))).thenReturn(mockCommonAdapterProperties); + + AdapterConfig result = handler.getConfigV1(key); + + assertNotNull(result); + assertEquals(key, result.getKey()); + assertEquals("https://api.mirket.com", result.getAdapterProperties().getUrl()); + assertEquals(AuthType.NONE, result.getAdapterProperties().getAuth().getType()); + assertNotNull(result.getAdapterProperties()); + verify(configurationHolder, times(1)).getJsonObjectProperty(eq(key), eq(Map.class)); + } + @Test void testGetConfigV1_KeyNotFound_ThrowsException() { String key = "testKey"; @@ -116,17 +164,4 @@ void testGetConfigs_Success() { verify(configurationHolder, times(1)).getJsonListProperty(eq(key), eq(AdapterProperties.class)); } - @Test - void testGetConfigV1_AuthExtraction_ThrowsException() { - String key = "testKey"; - Map mockAdapterPropertiesMap = Map.of(); - - when(configurationHolder.getJsonObjectProperty(eq(key), eq(Map.class))).thenReturn(mockAdapterPropertiesMap); - - AdapterConfigException exception = assertThrows(AdapterConfigException.class, () -> handler.getConfigV1(key)); - assertEquals("'auth' section is missing in adapter properties", exception.getCause().getMessage()); - - verify(configurationHolder, times(1)).getJsonObjectProperty(eq(key), eq(Map.class)); - } - } diff --git a/example/ms-example/src/main/resources/certs/smallstep.pem b/example/ms-example/src/main/resources/certs/smallstep.pem new file mode 100644 index 0000000..51edd9e --- /dev/null +++ b/example/ms-example/src/main/resources/certs/smallstep.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBojCCAUmgAwIBAgIQMoaotBxdaZjJTXSWvU/gxzAKBggqhkjOPQQDAjAwMRIw +EAYDVQQKEwlTbWFsbHN0ZXAxGjAYBgNVBAMTEVNtYWxsc3RlcCBSb290IENBMB4X +DTI1MDEyMzA4MDIzMloXDTM1MDEyMTA4MDIzMlowMDESMBAGA1UEChMJU21hbGxz +dGVwMRowGAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABLhdxZL9pjSVyzn78VeSbdLWzQ1hH4VLMcQ561PrVIwVftQw6Z+F +8Sr9oHXNQYXFeP29/4JPN8y321sevJHq75ejRTBDMA4GA1UdDwEB/wQEAwIBBjAS +BgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBSlf+os8AlKA4crK00fqfsPWdIK +yTAKBggqhkjOPQQDAgNHADBEAiAQfhrkGqtBDpr4D5F63m0S6oERJ23qSl6LwpiI +8LE6SwIgXD96CgCYhotT8I1s88RnyXlNGlzwntFzaiiRKWrLXHY= +-----END CERTIFICATE----- diff --git a/example/ms-example/src/main/resources/settings.properties b/example/ms-example/src/main/resources/settings.properties index b04c8dd..9bdcade 100644 --- a/example/ms-example/src/main/resources/settings.properties +++ b/example/ms-example/src/main/resources/settings.properties @@ -1,3 +1,29 @@ config.adapter.common.v1={"logging":{"strategy":"REQ_RES","sensitiveFields":[],"nonLoggingFields":[]},"headers":{},"http":{"requestTimeout":30000,"connectTimeout":10000,"idleConnectionsTimeout":180000,"maxConnections":10,"maxConnPerRoute":10,"poolConcurrencyPolicy":"LAX","timeToLive":60000,"skipSsl":true,"redirectsEnable":true},"auth":{"type":"NONE"}} -config.adapter.mirket.v1={"runtime": true,"auth":{"type":"NONE"},"headers":{"X-GW-TOKEN":""},"http":{"requestTimeout":30000,"connectTimeout":10000,"idleConnectionsTimeout":60000,"maxConnections":50,"maxConnPerRoute":50,"poolConcurrencyPolicy":"LAX","timeToLive":60000,"skipSsl":true,"redirectsEnable":true},"logging":{"strategy":"REQ_RES","sensitiveFields":["Authorization"],"nonLoggingFields":["file","content"]},"url":"https://api.mirket.inomera.com/"} +config.adapter.mirket.v1={\ +"runtime": true,\ +"auth": { "type": "NONE" },\ +"headers": { "X-GW-TOKEN": "" },\ +"http": {\ + "requestTimeout": 30000,\ + "connectTimeout": 10000,\ + "idleConnectionsTimeout": 60000,\ + "maxConnections": 50,\ + "maxConnPerRoute": 50,\ + "poolConcurrencyPolicy": "LAX",\ + "timeToLive": 60000,\ + "skipSsl": true,\ + "ssl": {\ + "pem": {\ + "truststore": { "certificate": "classpath:certs/smallstep.pem" }\ + }\ + },\ + "redirectsEnable": true\ +},\ +"logging": {\ + "strategy": "REQ_RES",\ + "sensitiveFields": ["Authorization"],\ + "nonLoggingFields": ["file","content"]\ +},\ +"url": "https://api.mirket.inomera.com"\ +} config.adapter.country.v1={"runtime": true,"logging":{"strategy":"ALL","sensitiveFields":[],"nonLoggingFields":[]},"headers":{},"http":{"requestTimeout":30000,"connectTimeout":10000,"idleConnectionsTimeout":180000,"maxConnections":10,"maxConnPerRoute":10,"poolConcurrencyPolicy":"LAX","timeToLive":60000,"skipSsl":true,"redirectsEnable":true},"auth":{"type":"NONE"},"url": "http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso"} diff --git a/micro-integration/VERSION b/micro-integration/VERSION index 1cc5f65..8cfbc90 100644 --- a/micro-integration/VERSION +++ b/micro-integration/VERSION @@ -1 +1 @@ -1.1.0 \ No newline at end of file +1.1.1 \ No newline at end of file diff --git a/micro-integration/src/main/java/com/inomera/integration/config/model/AdapterProperties.java b/micro-integration/src/main/java/com/inomera/integration/config/model/AdapterProperties.java index 74c6a25..aa23ece 100644 --- a/micro-integration/src/main/java/com/inomera/integration/config/model/AdapterProperties.java +++ b/micro-integration/src/main/java/com/inomera/integration/config/model/AdapterProperties.java @@ -8,8 +8,8 @@ public class AdapterProperties implements Serializable { private String url; private Map headers; private HttpClientProperties http; - private Auth auth; - private Boolean runtime; + private Auth auth = new Auth.NoneAuth(); + private Boolean runtime = false; public AdapterProperties() { } @@ -91,20 +91,20 @@ public void patch(AdapterProperties commonConfigAdapterProperties) { if (commonConfigAdapterProperties == null) { return; } - if (this.logging == null && commonConfigAdapterProperties.getLogging() == null) { + if (this.logging == null) { this.logging = new AdapterLogging(); + this.logging.patch(commonConfigAdapterProperties.getLogging()); } - this.logging.patch(commonConfigAdapterProperties.getLogging()); - if (this.http == null && commonConfigAdapterProperties.getHttp() == null) { + if (this.http == null) { this.http = new HttpClientProperties(); + this.http.patch(commonConfigAdapterProperties.getHttp()); } - this.http.patch(commonConfigAdapterProperties.getHttp()); - if (this.auth == null && commonConfigAdapterProperties.getAuth() == null) { + if (this.auth == null) { this.auth = new Auth.NoneAuth(); + this.auth.patch(commonConfigAdapterProperties.getAuth()); } - this.auth.patch(commonConfigAdapterProperties.getAuth()); if (this.getHeaders() == null) { this.setHeaders(commonConfigAdapterProperties.getHeaders()); @@ -113,6 +113,7 @@ public void patch(AdapterProperties commonConfigAdapterProperties) { this.getHeaders().putIfAbsent(key, value) ); } + this.runtime = this.runtime != null ? this.runtime : commonConfigAdapterProperties.isRuntime(); } diff --git a/micro-middleware/VERSION b/micro-middleware/VERSION index 1cc5f65..8cfbc90 100644 --- a/micro-middleware/VERSION +++ b/micro-middleware/VERSION @@ -1 +1 @@ -1.1.0 \ No newline at end of file +1.1.1 \ No newline at end of file