Skip to content

Commit 973f0de

Browse files
authored
ANCHOR-403 Obfuscate Exception in Sep1Helper instead of NetUtil (#1052)
* ANCHOR-403 Obfuscate Exception in Sep1Helper instead of NetUtil * bump minor version * bump version for docs * addressing pr comments
1 parent 574e8c7 commit 973f0de

File tree

7 files changed

+69
-37
lines changed

7 files changed

+69
-37
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ subprojects {
135135

136136
allprojects {
137137
group = "org.stellar.anchor-sdk"
138-
version = "2.1.2"
138+
version = "2.1.3"
139139

140140
tasks.jar {
141141
manifest {

core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.stellar.anchor.sep1;
22

3-
import static org.stellar.anchor.util.Log.debug;
43
import static org.stellar.anchor.util.Log.debugF;
54

65
import java.io.IOException;
@@ -23,10 +22,9 @@ public class Sep1Service {
2322
*/
2423
public Sep1Service(Sep1Config sep1Config) throws IOException, InvalidConfigException {
2524
if (sep1Config.isEnabled()) {
26-
debug("sep1Config:", sep1Config);
2725
switch (sep1Config.getType()) {
2826
case STRING:
29-
debug("reading stellar.toml from config[sep1.toml.value]");
27+
debugF("reading stellar.toml from config[sep1.toml.value]");
3028
tomlValue = sep1Config.getValue();
3129
break;
3230
case FILE:

core/src/main/java/org/stellar/anchor/util/NetUtil.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,44 @@
77
import java.net.InetAddress;
88
import java.net.MalformedURLException;
99
import java.net.URL;
10-
import java.util.Objects;
1110
import okhttp3.Call;
1211
import okhttp3.Request;
1312
import okhttp3.Response;
1413

1514
public class NetUtil {
15+
16+
/**
17+
* Fetches the content from the specified URL using an HTTP GET request.
18+
*
19+
* <p>This method expects a response body to be present in the HTTP response. If the response is
20+
* unsuccessful (i.e., not a 2xx status code) or if the response body is null, an IOException will
21+
* be thrown.
22+
*
23+
* @param url The URL to fetch content from.
24+
* @return The content of the response body as a string.
25+
* @throws IOException If the response is unsuccessful, or if the response body is null.
26+
*/
1627
public static String fetch(String url) throws IOException {
28+
1729
Request request = OkHttpUtil.buildGetRequest(url);
1830
Response response = getCall(request).execute();
1931

20-
// Check if response was successful (status code 200)
32+
// Check if response was unsuccessful (ie not status code 2xx) and throw IOException
2133
if (!response.isSuccessful()) {
22-
throw new IOException(String.format("Unable to fetch data from %s", url));
34+
throw new IOException(
35+
"Unsuccessful response code: " + response.code() + ", message: " + response.message());
2336
}
2437

38+
// Since fetch expects a response body, we will throw IOException if its null
2539
if (response.body() == null) {
26-
throw new IOException("Response body is empty.");
40+
throw new IOException(
41+
"Null response body. Response code: "
42+
+ response.code()
43+
+ ", message: "
44+
+ response.message());
2745
}
28-
return Objects.requireNonNull(response.body()).string();
46+
47+
return response.body().string();
2948
}
3049

3150
@SuppressWarnings("BooleanMethodIsAlwaysInverted")

core/src/main/java/org/stellar/anchor/util/Sep1Helper.java

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,39 @@
11
package org.stellar.anchor.util;
22

3+
import static org.stellar.anchor.util.Log.*;
4+
35
import com.moandjiezana.toml.Toml;
46
import java.io.IOException;
5-
import java.net.URL;
7+
import org.stellar.anchor.api.exception.InvalidConfigException;
68

79
public class Sep1Helper {
810
public static TomlContent readToml(String url) throws IOException {
9-
return new TomlContent(new URL(url));
11+
try {
12+
String tomlValue = NetUtil.fetch(url);
13+
return new TomlContent(tomlValue);
14+
} catch (IOException e) {
15+
String obfuscatedMessage =
16+
String.format("An error occurred while fetching the TOML from %s", url);
17+
Log.error(e.toString());
18+
throw new IOException(obfuscatedMessage); // Preserve the original exception as the cause
19+
}
1020
}
1121

12-
public static TomlContent parse(String tomlString) {
22+
public static TomlContent parse(String tomlString) throws InvalidConfigException {
1323
try {
1424
return new TomlContent(tomlString);
1525
} catch (Exception e) {
16-
// obfuscate exception message to prevent metadata leaks
17-
throw new RuntimeException(
18-
"Failed to parse TOML content"); // or return null or a default TomlContent instance
26+
// Obfuscate the message and rethrow
27+
String obfuscatedMessage = "Failed to parse TOML content. Invalid Config.";
28+
Log.error(e.toString()); // Log the parsing exception
29+
throw new InvalidConfigException(
30+
obfuscatedMessage); // Preserve the original exception as the cause
1931
}
2032
}
2133

2234
public static class TomlContent {
2335
private final Toml toml;
2436

25-
TomlContent(URL url) throws IOException {
26-
String tomlValue = NetUtil.fetch(url.toString());
27-
toml = new Toml().read(tomlValue);
28-
}
29-
3037
TomlContent(String tomlString) {
3138
toml = new Toml().read(tomlString);
3239
}

docs/00 - Stellar Anchor Platform.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[![License](https://badgen.net/badge/license/Apache%202/blue?icon=github&label=License)](https://github.com/stellar/java-stellar-anchor-sdk/blob/develop/LICENSE)
22
[![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)
3-
[![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)
3+
[![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)
44
![Develop Branch](https://github.com/stellar/java-stellar-anchor-sdk/actions/workflows/wk_push_to_develop.yml/badge.svg?branch=develop)
55

66
<div>

platform/src/main/java/org/stellar/anchor/platform/config/PropertySep1Config.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.springframework.validation.Errors;
77
import org.springframework.validation.ValidationUtils;
88
import org.springframework.validation.Validator;
9+
import org.stellar.anchor.api.exception.InvalidConfigException;
910
import org.stellar.anchor.config.Sep1Config;
1011
import org.stellar.anchor.util.NetUtil;
1112
import org.stellar.anchor.util.Sep1Helper;
@@ -59,12 +60,11 @@ void validateTomlTypeAndValue(PropertySep1Config config, Errors errors) {
5960
case STRING:
6061
try {
6162
Sep1Helper.parse(config.getToml().getValue());
62-
} catch (IllegalStateException isex) {
63+
} catch (InvalidConfigException e) {
6364
errors.rejectValue(
6465
"value",
6566
"sep1-toml-value-string-invalid",
66-
String.format(
67-
"sep1.toml.value does not contain a valid TOML. %s", isex.getMessage()));
67+
String.format("sep1.toml.value does not contain a valid TOML. %s", e.getMessage()));
6868
}
6969
break;
7070
case URL:

platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import okhttp3.mockwebserver.MockResponse
77
import okhttp3.mockwebserver.MockWebServer
88
import org.junit.jupiter.api.Assertions.assertEquals
99
import org.junit.jupiter.api.Assertions.assertThrows
10+
import org.junit.jupiter.api.Assertions.assertTrue
1011
import org.junit.jupiter.api.Test
1112
import org.stellar.anchor.config.Sep1Config.TomlType.*
1213
import org.stellar.anchor.platform.config.PropertySep1Config
@@ -17,7 +18,7 @@ import org.stellar.anchor.sep1.Sep1Service
1718
class Sep1ServiceTest {
1819

1920
lateinit var sep1: Sep1Service
20-
val stellarToml =
21+
private val stellarToml =
2122
"""
2223
ACCOUNTS = [ "GCSGSR6KQQ5BP2FXVPWRL6SWPUSFWLVONLIBJZUKTVQB5FYJFVL6XOXE" ]
2324
VERSION = "0.1.0"
@@ -63,7 +64,7 @@ class Sep1ServiceTest {
6364
}
6465

6566
@Test
66-
fun `test Sep1Service reading toml from url`() {
67+
fun `getStellarToml fetches data during re-direct`() {
6768
val mockServer = MockWebServer()
6869
mockServer.start()
6970
val mockAnchorUrl = mockServer.url("").toString()
@@ -73,11 +74,21 @@ class Sep1ServiceTest {
7374
assertEquals(sep1.stellarToml, stellarToml)
7475
}
7576

77+
// this test is not expected to raise an exception. given the re-direct to a malicious
78+
// endpoint still returns a 200 the exception will be raised/obfuscated
79+
// when the toml is parsed.
7680
@Test
77-
fun `getStellarToml throws exception when redirect is encountered`() {
81+
fun `getStellarTomlthrows exception when redirect is encountered`() {
7882
val mockServer = MockWebServer()
7983
mockServer.start()
8084
val mockAnchorUrl = mockServer.url("").toString()
85+
val metadata =
86+
"{\n" +
87+
" \"ami-id\": \"ami-12345678\",\n" +
88+
" \"instance-id\": \"i-1234567890abcdef\",\n" +
89+
" \"instance-type\": \"t2.micro\"\n" +
90+
" // ... other metadata ...\n" +
91+
"}"
8192

8293
// Enqueue a response with a 302 status and a Location header to simulate a redirect.
8394
mockServer.enqueue(
@@ -86,16 +97,12 @@ class Sep1ServiceTest {
8697
.setHeader("Location", mockServer.url("/new_location").toString())
8798
)
8899

89-
// Enqueue a response at the redirect location that provides a server error.
90-
mockServer.enqueue(MockResponse().setResponseCode(500))
91-
92-
// Enqueue an empty response to prevent a timeout.
93-
mockServer.enqueue(MockResponse())
100+
// Enqueue a response at the redirect location that simulates AWS metadata leak.
101+
mockServer.enqueue(MockResponse().setResponseCode(200).setBody(metadata))
94102

95103
val config = PropertySep1Config(true, TomlConfig(URL, mockAnchorUrl))
96-
97-
val exception = assertThrows(IOException::class.java) { Sep1Service(config) }
98-
assertEquals("Unable to fetch data from $mockAnchorUrl", exception.message)
104+
val sep1 = Sep1Service(config)
105+
assertEquals(sep1.getStellarToml(), metadata)
99106
mockServer.shutdown()
100107
}
101108

@@ -109,9 +116,10 @@ class Sep1ServiceTest {
109116
mockServer.enqueue(MockResponse().setResponseCode(500))
110117

111118
val config = PropertySep1Config(true, TomlConfig(URL, mockAnchorUrl))
112-
113119
val exception = assertThrows(IOException::class.java) { sep1 = Sep1Service(config) }
114-
assertEquals("Unable to fetch data from $mockAnchorUrl", exception.message)
120+
assertTrue(
121+
exception.message?.contains("Unsuccessful response code: 500, message: Server Error") == true
122+
)
115123
mockServer.shutdown()
116124
}
117125
}

0 commit comments

Comments
 (0)