diff --git a/build.gradle.kts b/build.gradle.kts index 3c9582f183..e389753fe8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -135,7 +135,7 @@ subprojects { allprojects { group = "org.stellar.anchor-sdk" - version = "2.1.2" + version = "2.1.3" tasks.jar { manifest { diff --git a/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java b/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java index 5a1667b74d..b82c278d94 100644 --- a/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java +++ b/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java @@ -1,6 +1,5 @@ package org.stellar.anchor.sep1; -import static org.stellar.anchor.util.Log.debug; import static org.stellar.anchor.util.Log.debugF; import java.io.IOException; @@ -23,10 +22,9 @@ public class Sep1Service { */ public Sep1Service(Sep1Config sep1Config) throws IOException, InvalidConfigException { if (sep1Config.isEnabled()) { - debug("sep1Config:", sep1Config); switch (sep1Config.getType()) { case STRING: - debug("reading stellar.toml from config[sep1.toml.value]"); + debugF("reading stellar.toml from config[sep1.toml.value]"); tomlValue = sep1Config.getValue(); break; case FILE: diff --git a/core/src/main/java/org/stellar/anchor/util/NetUtil.java b/core/src/main/java/org/stellar/anchor/util/NetUtil.java index 25de3be9cf..58690d5512 100644 --- a/core/src/main/java/org/stellar/anchor/util/NetUtil.java +++ b/core/src/main/java/org/stellar/anchor/util/NetUtil.java @@ -7,25 +7,44 @@ import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; -import java.util.Objects; import okhttp3.Call; import okhttp3.Request; import okhttp3.Response; public class NetUtil { + + /** + * Fetches the content from the specified URL using an HTTP GET request. + * + *

This method expects a response body to be present in the HTTP response. If the response is + * unsuccessful (i.e., not a 2xx status code) or if the response body is null, an IOException will + * be thrown. + * + * @param url The URL to fetch content from. + * @return The content of the response body as a string. + * @throws IOException If the response is unsuccessful, or if the response body is null. + */ public static String fetch(String url) throws IOException { + Request request = OkHttpUtil.buildGetRequest(url); Response response = getCall(request).execute(); - // Check if response was successful (status code 200) + // Check if response was unsuccessful (ie not status code 2xx) and throw IOException if (!response.isSuccessful()) { - throw new IOException(String.format("Unable to fetch data from %s", url)); + throw new IOException( + "Unsuccessful response code: " + response.code() + ", message: " + response.message()); } + // Since fetch expects a response body, we will throw IOException if its null if (response.body() == null) { - throw new IOException("Response body is empty."); + throw new IOException( + "Null response body. Response code: " + + response.code() + + ", message: " + + response.message()); } - return Objects.requireNonNull(response.body()).string(); + + return response.body().string(); } @SuppressWarnings("BooleanMethodIsAlwaysInverted") diff --git a/core/src/main/java/org/stellar/anchor/util/Sep1Helper.java b/core/src/main/java/org/stellar/anchor/util/Sep1Helper.java index f372530ea4..9aca02d869 100644 --- a/core/src/main/java/org/stellar/anchor/util/Sep1Helper.java +++ b/core/src/main/java/org/stellar/anchor/util/Sep1Helper.java @@ -1,32 +1,39 @@ package org.stellar.anchor.util; +import static org.stellar.anchor.util.Log.*; + import com.moandjiezana.toml.Toml; import java.io.IOException; -import java.net.URL; +import org.stellar.anchor.api.exception.InvalidConfigException; public class Sep1Helper { public static TomlContent readToml(String url) throws IOException { - return new TomlContent(new URL(url)); + try { + String tomlValue = NetUtil.fetch(url); + return new TomlContent(tomlValue); + } catch (IOException e) { + String obfuscatedMessage = + String.format("An error occurred while fetching the TOML from %s", url); + Log.error(e.toString()); + throw new IOException(obfuscatedMessage); // Preserve the original exception as the cause + } } - public static TomlContent parse(String tomlString) { + public static TomlContent parse(String tomlString) throws InvalidConfigException { try { return new TomlContent(tomlString); } catch (Exception e) { - // obfuscate exception message to prevent metadata leaks - throw new RuntimeException( - "Failed to parse TOML content"); // or return null or a default TomlContent instance + // Obfuscate the message and rethrow + String obfuscatedMessage = "Failed to parse TOML content. Invalid Config."; + Log.error(e.toString()); // Log the parsing exception + throw new InvalidConfigException( + obfuscatedMessage); // Preserve the original exception as the cause } } public static class TomlContent { private final Toml toml; - TomlContent(URL url) throws IOException { - String tomlValue = NetUtil.fetch(url.toString()); - toml = new Toml().read(tomlValue); - } - TomlContent(String tomlString) { toml = new Toml().read(tomlString); } diff --git a/docs/00 - Stellar Anchor Platform.md b/docs/00 - Stellar Anchor Platform.md index e74fa57a05..c1fc3b0c7e 100644 --- a/docs/00 - Stellar Anchor Platform.md +++ b/docs/00 - Stellar Anchor Platform.md @@ -1,6 +1,6 @@ [![License](https://badgen.net/badge/license/Apache%202/blue?icon=github&label=License)](https://github.com/stellar/java-stellar-anchor-sdk/blob/develop/LICENSE) [![GitHub Version](https://badgen.net/github/release/stellar/java-stellar-anchor-sdk?icon=github&label=Latest%20release)](https://github.com/stellar/java-stellar-anchor-sdk/releases) -[![Docker](https://badgen.net/badge/Latest%20Release/v2.1.2/blue?icon=docker)](https://hub.docker.com/r/stellar/anchor-platform/tags?page=1&name=release-2.1.2) +[![Docker](https://badgen.net/badge/Latest%20Release/v2.1.2/blue?icon=docker)](https://hub.docker.com/r/stellar/anchor-platform/tags?page=1&name=release-2.1.3) ![Develop Branch](https://github.com/stellar/java-stellar-anchor-sdk/actions/workflows/wk_push_to_develop.yml/badge.svg?branch=develop)

diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep1Config.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep1Config.java index 3d40b342bb..70381da1d7 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep1Config.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep1Config.java @@ -6,6 +6,7 @@ import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; +import org.stellar.anchor.api.exception.InvalidConfigException; import org.stellar.anchor.config.Sep1Config; import org.stellar.anchor.util.NetUtil; import org.stellar.anchor.util.Sep1Helper; @@ -59,12 +60,11 @@ void validateTomlTypeAndValue(PropertySep1Config config, Errors errors) { case STRING: try { Sep1Helper.parse(config.getToml().getValue()); - } catch (IllegalStateException isex) { + } catch (InvalidConfigException e) { errors.rejectValue( "value", "sep1-toml-value-string-invalid", - String.format( - "sep1.toml.value does not contain a valid TOML. %s", isex.getMessage())); + String.format("sep1.toml.value does not contain a valid TOML. %s", e.getMessage())); } break; case URL: diff --git a/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt index 6cce8c63c4..7e8d129830 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt @@ -7,6 +7,7 @@ import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.stellar.anchor.config.Sep1Config.TomlType.* import org.stellar.anchor.platform.config.PropertySep1Config @@ -17,7 +18,7 @@ import org.stellar.anchor.sep1.Sep1Service class Sep1ServiceTest { lateinit var sep1: Sep1Service - val stellarToml = + private val stellarToml = """ ACCOUNTS = [ "GCSGSR6KQQ5BP2FXVPWRL6SWPUSFWLVONLIBJZUKTVQB5FYJFVL6XOXE" ] VERSION = "0.1.0" @@ -63,7 +64,7 @@ class Sep1ServiceTest { } @Test - fun `test Sep1Service reading toml from url`() { + fun `getStellarToml fetches data during re-direct`() { val mockServer = MockWebServer() mockServer.start() val mockAnchorUrl = mockServer.url("").toString() @@ -73,11 +74,21 @@ class Sep1ServiceTest { assertEquals(sep1.stellarToml, stellarToml) } + // this test is not expected to raise an exception. given the re-direct to a malicious + // endpoint still returns a 200 the exception will be raised/obfuscated + // when the toml is parsed. @Test - fun `getStellarToml throws exception when redirect is encountered`() { + fun `getStellarTomlthrows exception when redirect is encountered`() { val mockServer = MockWebServer() mockServer.start() val mockAnchorUrl = mockServer.url("").toString() + val metadata = + "{\n" + + " \"ami-id\": \"ami-12345678\",\n" + + " \"instance-id\": \"i-1234567890abcdef\",\n" + + " \"instance-type\": \"t2.micro\"\n" + + " // ... other metadata ...\n" + + "}" // Enqueue a response with a 302 status and a Location header to simulate a redirect. mockServer.enqueue( @@ -86,16 +97,12 @@ class Sep1ServiceTest { .setHeader("Location", mockServer.url("/new_location").toString()) ) - // Enqueue a response at the redirect location that provides a server error. - mockServer.enqueue(MockResponse().setResponseCode(500)) - - // Enqueue an empty response to prevent a timeout. - mockServer.enqueue(MockResponse()) + // Enqueue a response at the redirect location that simulates AWS metadata leak. + mockServer.enqueue(MockResponse().setResponseCode(200).setBody(metadata)) val config = PropertySep1Config(true, TomlConfig(URL, mockAnchorUrl)) - - val exception = assertThrows(IOException::class.java) { Sep1Service(config) } - assertEquals("Unable to fetch data from $mockAnchorUrl", exception.message) + val sep1 = Sep1Service(config) + assertEquals(sep1.getStellarToml(), metadata) mockServer.shutdown() } @@ -109,9 +116,10 @@ class Sep1ServiceTest { mockServer.enqueue(MockResponse().setResponseCode(500)) val config = PropertySep1Config(true, TomlConfig(URL, mockAnchorUrl)) - val exception = assertThrows(IOException::class.java) { sep1 = Sep1Service(config) } - assertEquals("Unable to fetch data from $mockAnchorUrl", exception.message) + assertTrue( + exception.message?.contains("Unsuccessful response code: 500, message: Server Error") == true + ) mockServer.shutdown() } }