Skip to content

Commit

Permalink
ANCHOR-403 Obfuscate Exception in Sep1Helper instead of NetUtil (#1052)
Browse files Browse the repository at this point in the history
* ANCHOR-403 Obfuscate Exception in Sep1Helper instead of NetUtil

* bump minor version

* bump version for docs

* addressing pr comments
  • Loading branch information
reecexlm authored Aug 11, 2023
1 parent 574e8c7 commit 973f0de
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 37 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ subprojects {

allprojects {
group = "org.stellar.anchor-sdk"
version = "2.1.2"
version = "2.1.3"

tasks.jar {
manifest {
Expand Down
4 changes: 1 addition & 3 deletions core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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:
Expand Down
29 changes: 24 additions & 5 deletions core/src/main/java/org/stellar/anchor/util/NetUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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")
Expand Down
29 changes: 18 additions & 11 deletions core/src/main/java/org/stellar/anchor/util/Sep1Helper.java
Original file line number Diff line number Diff line change
@@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion docs/00 - Stellar Anchor Platform.md
Original file line number Diff line number Diff line change
@@ -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)

<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down
34 changes: 21 additions & 13 deletions platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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()
Expand All @@ -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(
Expand All @@ -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()
}

Expand All @@ -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()
}
}

0 comments on commit 973f0de

Please sign in to comment.