Skip to content

Commit ba9d82e

Browse files
authored
Prevent message collection from being updated after message count has been received (#2180)
Also adds mechanism to detect if messages were missed so tests can be updated to appropriate counts. Signed-off-by: Peter Nied <petern@amazon.com>
1 parent 1d7ab5f commit ba9d82e

File tree

5 files changed

+108
-45
lines changed

5 files changed

+108
-45
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
run: ./gradlew clean build -Dbuild.snapshot=false -x test -x integrationTest
4040

4141
- name: Test
42-
run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew test integrationTest -i
42+
run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew test integrationTest
4343

4444
- name: Coverage
4545
uses: codecov/codecov-action@v1

src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ public final class AuditMessage {
8484
public static final String REMOTE_ADDRESS = "audit_request_remote_address";
8585

8686
public static final String REST_REQUEST_PATH = "audit_rest_request_path";
87-
//public static final String REST_REQUEST_BODY = "audit_rest_request_body";
8887
public static final String REST_REQUEST_PARAMS = "audit_rest_request_params";
8988
public static final String REST_REQUEST_HEADERS = "audit_rest_request_headers";
9089
public static final String REST_REQUEST_METHOD = "audit_rest_request_method";
@@ -449,6 +448,18 @@ public String getExceptionStackTrace() {
449448
return (String) this.auditInfo.get(EXCEPTION);
450449
}
451450

451+
public String getRequestBody() {
452+
return (String) this.auditInfo.get(REQUEST_BODY);
453+
}
454+
455+
public String getNodeId() {
456+
return (String) this.auditInfo.get(NODE_ID);
457+
}
458+
459+
public String getDocId() {
460+
return (String) this.auditInfo.get(ID);
461+
}
462+
452463
@Override
453464
public String toString() {
454465
try {

src/test/java/org/opensearch/security/auditlog/compliance/ComplianceAuditlogTest.java

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse;
4444

4545
import static org.hamcrest.MatcherAssert.assertThat;
46+
import static org.hamcrest.Matchers.containsString;
47+
import static org.hamcrest.Matchers.not;
4648
import static org.hamcrest.core.AnyOf.anyOf;
4749
import static org.hamcrest.core.IsEqual.equalTo;
4850
import static org.junit.Assert.assertThrows;
@@ -90,10 +92,11 @@ public void testSourceFilter() throws Exception {
9092
Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
9193
});
9294

93-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_DOC_READ"));
94-
Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("Designation"));
95-
Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("Salary"));
96-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("Gender"));
95+
assertThat(message.getCategory(), equalTo(AuditCategory.COMPLIANCE_DOC_READ));
96+
assertThat(message.getRequestBody(), not(containsString("Designation")));
97+
assertThat(message.getRequestBody(), not(containsString("Salary")));
98+
assertThat(message.getRequestBody(), containsString("Gender"));
99+
97100
Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages));
98101
}
99102

@@ -200,17 +203,24 @@ public void testSourceFilterMsearch() throws Exception {
200203
" }" +
201204
"}"+System.lineSeparator();
202205

203-
TestAuditlogImpl.doThenWaitForMessages(() -> {
206+
final List<AuditMessage> messages = TestAuditlogImpl.doThenWaitForMessages(() -> {
204207
HttpResponse response = rh.executePostRequest("_msearch?pretty", search, encodeBasicHeader("admin", "admin"));
205208
assertNotContains(response, "*exception*");
206209
Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
207210
}, 2);
208-
System.out.println(TestAuditlogImpl.sb.toString());
209-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_DOC_READ"));
210-
Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("Salary"));
211-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("Gender"));
212-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("Designation"));
213-
Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages));
211+
212+
213+
final AuditMessage desginationMsg = messages.stream().filter(msg -> msg.getRequestBody().contains("Designation")).findFirst().orElseThrow();
214+
assertThat(desginationMsg.getCategory(), equalTo(AuditCategory.COMPLIANCE_DOC_READ));
215+
assertThat(desginationMsg.getRequestBody(), containsString("Designation"));
216+
assertThat(desginationMsg.getRequestBody(), not(containsString("Salary")));
217+
218+
final AuditMessage genderMsg = messages.stream().filter(msg -> msg.getRequestBody().contains("Gender")).findFirst().orElseThrow();
219+
assertThat(genderMsg.getCategory(), equalTo(AuditCategory.COMPLIANCE_DOC_READ));
220+
assertThat(genderMsg.getRequestBody(), containsString("Gender"));
221+
assertThat(genderMsg.getRequestBody(), not(containsString("Salary")));
222+
223+
Assert.assertTrue(validateMsgs(messages));
214224
}
215225

216226
@Test
@@ -230,6 +240,7 @@ public void testInternalConfig() throws Exception {
230240

231241
setup(additionalSettings);
232242

243+
final List<String> expectedDocumentsTypes = List.of("config", "actiongroups", "internalusers", "roles", "rolesmapping", "tenants", "audit");
233244
final List<AuditMessage> messages = TestAuditlogImpl.doThenWaitForMessages(() -> {
234245
try (RestHighLevelClient restHighLevelClient = getRestClient(clusterInfo, "kirk-keystore.jks", "truststore.jks")) {
235246
for (IndexRequest ir: new DynamicSecurityConfig().setSecurityRoles("roles_2.yml").getDynamicConfig(getResourceFolder())) {
@@ -245,21 +256,19 @@ public void testInternalConfig() throws Exception {
245256
assertThat(response.getStatusCode(), equalTo(HttpStatus.SC_OK));
246257
}, 14);
247258

248-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_INTERNAL_CONFIG_READ"));
249-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_INTERNAL_CONFIG_WRITE"));
250-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("anonymous_auth_enabled"));
251-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("indices:data/read/suggest"));
252-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("internalusers"));
253-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("opendistro_security_all_access"));
254-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("indices:data/read/suggest"));
255-
Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("eyJzZWFyY2hndWFy"));
256-
Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("eyJBTEwiOlsiaW"));
257-
Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("eyJhZG1pbiI6e"));
258-
Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("eyJzZ19hb"));
259-
Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("eyJzZ19hbGx"));
260-
Assert.assertFalse(TestAuditlogImpl.sb.toString().contains("dvcmYiOnsiY2x"));
261-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("\\\"op\\\":\\\"remove\\\",\\\"path\\\":\\\"/opendistro_security_worf\\\""));
262-
Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages));
259+
final List<String> documentIds = messages.stream().map(AuditMessage::getDocId).distinct().collect(Collectors.toList());
260+
assertThat(documentIds, equalTo(expectedDocumentsTypes));
261+
262+
messages.stream().collect(Collectors.groupingBy(AuditMessage::getDocId)).entrySet().forEach((e) -> {
263+
final String docId = e.getKey();
264+
final List<AuditMessage> messagesByDocId = e.getValue();
265+
assertThat("Doc " + docId + " should have a read/write config message",
266+
messagesByDocId.stream().map(AuditMessage::getCategory).collect(Collectors.toList()),
267+
equalTo(List.of(AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE, AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ))
268+
);
269+
});
270+
271+
Assert.assertTrue(validateMsgs(messages));
263272
}
264273

265274
@Test
@@ -276,7 +285,7 @@ public void testExternalConfig() throws Exception {
276285
.put(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, "authenticated,GRANTED_PRIVILEGES")
277286
.build();
278287

279-
TestAuditlogImpl.doThenWaitForMessages(() -> {
288+
final List<AuditMessage> messages = TestAuditlogImpl.doThenWaitForMessages(() -> {
280289
try {
281290
setup(additionalSettings);
282291
} catch (final Exception ex) {
@@ -293,10 +302,17 @@ public void testExternalConfig() throws Exception {
293302
Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
294303
}, 4);
295304

296-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("external_configuration"));
297-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("COMPLIANCE_EXTERNAL_CONFIG"));
298-
Assert.assertTrue(TestAuditlogImpl.sb.toString().contains("opensearch_yml"));
299-
Assert.assertTrue(validateMsgs(TestAuditlogImpl.messages));
305+
// Record the updated config, and then for each node record that the config was updated
306+
assertThat(messages.get(0).getCategory(), equalTo(AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE));
307+
assertThat(messages.get(1).getCategory(), equalTo(AuditCategory.COMPLIANCE_EXTERNAL_CONFIG));
308+
assertThat(messages.get(2).getCategory(), equalTo(AuditCategory.COMPLIANCE_EXTERNAL_CONFIG));
309+
assertThat(messages.get(3).getCategory(), equalTo(AuditCategory.COMPLIANCE_EXTERNAL_CONFIG));
310+
311+
// Make sure that the config update messsages are for each node in the cluster
312+
assertThat(messages.get(1).getNodeId(), not(equalTo(messages.get(2).getNodeId())));
313+
assertThat(messages.get(2).getNodeId(), not(equalTo(messages.get(3).getNodeId())));
314+
315+
Assert.assertTrue(validateMsgs(messages));
300316
}
301317

302318
@Test

src/test/java/org/opensearch/security/auditlog/integration/BasicAuditlogTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public void testSSLPlainText() throws Exception {
133133
final RuntimeException ex = Assert.assertThrows(RuntimeException.class,
134134
() -> nonSslRestHelper().executeGetRequest("_search", encodeBasicHeader("admin", "admin")));
135135
Assert.assertEquals("org.apache.hc.core5.http.NoHttpResponseException", ex.getCause().getClass().getName());
136-
}, 1);
136+
}, 2);
137137

138138
// All of the messages should be the same as the http client is attempting multiple times.
139139
messages.stream().forEach((message) -> {

src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,30 +58,66 @@ public static synchronized void clear() {
5858
* Perform an action and then wait until the expected number of messages have been found.
5959
*/
6060
public static List<AuditMessage> doThenWaitForMessages(final Runnable action, final int expectedCount) {
61-
final CountDownLatch latch = new CountDownLatch(expectedCount);
61+
final List<AuditMessage> missedMessages = new ArrayList<>();
6262
final List<AuditMessage> messages = new ArrayList<>();
63-
countDownRef.set(latch);
64-
messagesRef.set(messages);
65-
66-
TestAuditlogImpl.sb = new StringBuffer();
67-
TestAuditlogImpl.messages = messages;
63+
final CountDownLatch latch = resetAuditStorage(expectedCount, messages);
6864

6965
try {
7066
action.run();
7167
final int maxSecondsToWaitForMessages = 1;
72-
final boolean foundAll = latch.await(maxSecondsToWaitForMessages, TimeUnit.SECONDS);
73-
if (!foundAll) {
68+
boolean foundAll = false;
69+
foundAll = latch.await(maxSecondsToWaitForMessages, TimeUnit.SECONDS);
70+
// After the wait has prevent any new messages from being recieved
71+
resetAuditStorage(0, missedMessages);
72+
if (!foundAll || messages.size() != expectedCount) {
7473
throw new MessagesNotFoundException(expectedCount, messages);
7574
}
76-
if (messages.size() != expectedCount) {
77-
throw new RuntimeException("Unexpected number of messages, was expecting " + expectedCount + ", received " + messages.size());
78-
}
7975
} catch (final InterruptedException e) {
8076
throw new RuntimeException("Unexpected exception", e);
8177
}
78+
79+
// Do not check for missed messages if no messages were expected
80+
if (expectedCount != 0) {
81+
try {
82+
Thread.sleep(100);
83+
if (missedMessages.size() != 0) {
84+
final String missedMessagesErrorMessage = new StringBuilder()
85+
.append("Audit messages were missed! ")
86+
.append("Found " + (missedMessages.size()) + " messages.")
87+
.append("Messages found during this time: \n\n")
88+
.append(missedMessages.stream()
89+
.map(AuditMessage::toString)
90+
.collect(Collectors.joining("\n")))
91+
.toString();
92+
93+
throw new RuntimeException(missedMessagesErrorMessage);
94+
}
95+
} catch (final Exception e) {
96+
throw new RuntimeException("Unexpected exception", e);
97+
}
98+
}
99+
100+
// Next usage of this class might be using raw stringbuilder / list so reset before that test might run
101+
resetAuditStorage(0, new ArrayList<>());
82102
return new ArrayList<>(messages);
83103
}
84104

105+
/**
106+
* Resets all of the mechanics for fresh messages to be captured
107+
*
108+
* @param expectedMessageCount The number of messages before the latch is signalled, indicating all messages have been recieved
109+
* @param message Where messages will be stored after being recieved
110+
*/
111+
private static CountDownLatch resetAuditStorage(int expectedMessageCount, List<AuditMessage> messages) {
112+
final CountDownLatch latch = new CountDownLatch(expectedMessageCount);
113+
countDownRef.set(latch);
114+
messagesRef.set(messages);
115+
116+
TestAuditlogImpl.sb = new StringBuffer();
117+
TestAuditlogImpl.messages = messages;
118+
return latch;
119+
}
120+
85121
/**
86122
* Perform an action and then wait until a single message has been found.
87123
*/

0 commit comments

Comments
 (0)