Skip to content

Commit 4d81e2b

Browse files
authored
Merge branch 'master' into ib/misc-security
2 parents c5c7d45 + e9f5baf commit 4d81e2b

File tree

7 files changed

+174
-33
lines changed

7 files changed

+174
-33
lines changed

it/runtime-v2/src/test/java/com/walmartlabs/concord/it/runtime/v2/ProcessIT.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,25 @@ public void testOutVariables() throws Exception {
197197
assertFalse(data.containsKey("z"));
198198
}
199199

200+
@Test
201+
public void testOutVariablesForFailedProcess() throws Exception {
202+
Payload payload = new Payload()
203+
.archive(resource("outForFailed"))
204+
.out("x", "y.some.boolean", "z");
205+
206+
ConcordProcess proc = concord.processes().start(payload);
207+
expectStatus(proc, ProcessEntry.StatusEnum.FAILED);
208+
209+
// ---
210+
211+
Map<String, Object> data = proc.getOutVariables();
212+
assertNotNull(data);
213+
214+
assertEquals(123, data.get("x"));
215+
assertEquals(true, data.get("y.some.boolean"));
216+
assertFalse(data.containsKey("z"));
217+
}
218+
200219
@Test
201220
public void testThrowWithPayload() throws Exception {
202221
Payload payload = new Payload()
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
configuration:
2+
runtime: "concord-v2"
3+
4+
flows:
5+
default:
6+
- set:
7+
x: 123
8+
y:
9+
some:
10+
nested: ["data", "in", "arrays"]
11+
boolean: true
12+
number: 234
13+
14+
- throw: BOOM

runtime/v2/runner/src/main/java/com/walmartlabs/concord/runtime/v2/runner/OutVariablesProcessor.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
* Licensed under the Apache License, Version 2.0 (the "License");
1010
* you may not use this file except in compliance with the License.
1111
* You may obtain a copy of the License at
12-
*
12+
*
1313
* http://www.apache.org/licenses/LICENSE-2.0
14-
*
14+
*
1515
* Unless required by applicable law or agreed to in writing, software
1616
* distributed under the License is distributed on an "AS IS" BASIS,
1717
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,6 +20,7 @@
2020
* =====
2121
*/
2222

23+
import com.fasterxml.jackson.core.type.TypeReference;
2324
import com.fasterxml.jackson.databind.ObjectMapper;
2425
import com.walmartlabs.concord.runtime.v2.sdk.EvalContext;
2526
import com.walmartlabs.concord.runtime.v2.sdk.EvalContextFactory;
@@ -43,6 +44,9 @@
4344
*/
4445
public class OutVariablesProcessor implements ExecutionListener {
4546

47+
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {
48+
};
49+
4650
private final ObjectMapper objectMapper;
4751
private final PersistenceService persistenceService;
4852
private final List<String> outVariables;
@@ -84,7 +88,13 @@ public void afterProcessEnds(Runtime runtime, State state, Frame lastFrame) {
8488
return;
8589
}
8690

91+
Map<String, Object> currentOut = persistenceService.loadPersistedFile(Constants.Files.OUT_VALUES_FILE_NAME,
92+
in -> objectMapper.readValue(in, MAP_TYPE));
93+
94+
Map<String, Object> result = new HashMap<>(currentOut != null ? currentOut : Collections.emptyMap());
95+
result.putAll(outValues);
96+
8797
persistenceService.persistFile(Constants.Files.OUT_VALUES_FILE_NAME,
88-
out -> objectMapper.writeValue(out, outValues));
98+
out -> objectMapper.writeValue(out, result));
8999
}
90100
}

runtime/v2/runner/src/main/java/com/walmartlabs/concord/runtime/v2/runner/Runner.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@
2626
import com.walmartlabs.concord.runtime.common.injector.InstanceId;
2727
import com.walmartlabs.concord.runtime.v2.model.ProcessDefinition;
2828
import com.walmartlabs.concord.runtime.v2.runner.compiler.CompilerUtils;
29-
import com.walmartlabs.concord.runtime.v2.runner.vm.SaveLastErrorCommand;
30-
import com.walmartlabs.concord.runtime.v2.runner.vm.UpdateLocalsCommand;
31-
import com.walmartlabs.concord.runtime.v2.runner.vm.VMUtils;
29+
import com.walmartlabs.concord.runtime.v2.runner.vm.*;
3230
import com.walmartlabs.concord.runtime.v2.sdk.Compiler;
3331
import com.walmartlabs.concord.runtime.v2.sdk.ProcessConfiguration;
3432
import com.walmartlabs.concord.svm.*;
@@ -77,7 +75,7 @@ public ProcessSnapshot start(ProcessConfiguration processConfiguration, ProcessD
7775
// install the exception handler into the root frame
7876
// takes care of all unhandled errors bubbling up
7977
VMUtils.assertNearestRoot(state, state.getRootThreadId())
80-
.setExceptionHandler(new SaveLastErrorCommand());
78+
.setExceptionHandler(new BlockCommand(new SaveOutVariablesOnErrorCommand(), new SaveLastErrorCommand()));
8179

8280
VM vm = createVM(processDefinition);
8381
// update the global variables using the input map by running a special command

runtime/v2/runner/src/main/java/com/walmartlabs/concord/runtime/v2/runner/remote/TaskCallEventRecordingListener.java

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.walmartlabs.concord.runtime.v2.model.Location;
2929
import com.walmartlabs.concord.runtime.v2.model.Step;
3030
import com.walmartlabs.concord.runtime.v2.runner.EventReportingService;
31+
import com.walmartlabs.concord.runtime.v2.runner.SensitiveDataHolder;
3132
import com.walmartlabs.concord.runtime.v2.runner.tasks.TaskCallEvent;
3233
import com.walmartlabs.concord.runtime.v2.runner.tasks.TaskCallListener;
3334
import com.walmartlabs.concord.runtime.v2.sdk.*;
@@ -64,7 +65,9 @@ public void onEvent(TaskCallEvent event) {
6465

6566
List<Object> inVars = event.input();
6667
if (inVars != null && eventConfiguration.recordTaskInVars()) {
67-
Map<String, Object> vars = maskVars(convertInput(hideSensitiveData(inVars, event.inputAnnotations())), eventConfiguration.inVarsBlacklist());
68+
Map<String, Object> input = convertInput(processSensitiveDataAnnotations(inVars, event.inputAnnotations()));
69+
input = processSensitiveData(input);
70+
Map<String, Object> vars = maskVars(input, eventConfiguration.inVarsBlacklist());
6871
if (eventConfiguration.truncateInVars()) {
6972
vars = ObjectTruncater.truncateMap(vars, eventConfiguration.truncateMaxStringLength(), eventConfiguration.truncateMaxArrayLength(), eventConfiguration.truncateMaxDepth());
7073
}
@@ -75,7 +78,9 @@ public void onEvent(TaskCallEvent event) {
7578

7679
Object outVars = event.result();
7780
if (outVars != null && eventConfiguration.recordTaskOutVars()) {
78-
Map<String, Object> vars = maskVars(asMapOrNull(outVars), eventConfiguration.outVarsBlacklist());
81+
Map<String, Object> output = asMapOrNull(outVars);
82+
output = processSensitiveData(output);
83+
Map<String, Object> vars = maskVars(output, eventConfiguration.outVarsBlacklist());
7984
if (eventConfiguration.truncateOutVars()) {
8085
vars = ObjectTruncater.truncateMap(vars, eventConfiguration.truncateMaxStringLength(), eventConfiguration.truncateMaxArrayLength(), eventConfiguration.truncateMaxDepth());
8186
}
@@ -86,7 +91,9 @@ public void onEvent(TaskCallEvent event) {
8691

8792
Object metaVars = event.meta();
8893
if (metaVars != null && eventConfiguration.recordTaskMeta()) {
89-
Map<String, Object> meta = maskVars(asMapOrNull(metaVars), eventConfiguration.metaBlacklist());
94+
Map<String, Object> rawMeta = asMapOrNull(metaVars);
95+
Map<String, Object> meta = processSensitiveData(rawMeta);
96+
meta = maskVars(meta, eventConfiguration.metaBlacklist());
9097
if (eventConfiguration.truncateMeta()) {
9198
meta = ObjectTruncater.truncateMap(meta, eventConfiguration.truncateMaxStringLength(), eventConfiguration.truncateMaxArrayLength(), eventConfiguration.truncateMaxDepth());
9299
}
@@ -170,6 +177,34 @@ static Map<String, Object> maskVars(Map<String, Object> vars, Collection<String>
170177
return result;
171178
}
172179

180+
@SuppressWarnings({"unchecked", "rawtypes"})
181+
static <T> T processSensitiveData(T v) {
182+
Set<String> sensitiveStrings = SensitiveDataHolder.getInstance().get();
183+
if (sensitiveStrings.isEmpty()) {
184+
return v;
185+
}
186+
187+
if (v instanceof String s) {
188+
for (String sensitiveString : sensitiveStrings) {
189+
s = s.replace(sensitiveString, MASK);
190+
}
191+
return (T) s;
192+
} else if (v instanceof List<?> l) {
193+
List<Object> result = new ArrayList<>(l.size());
194+
for (Object vv : l) {
195+
vv = processSensitiveData(vv);
196+
result.add(vv);
197+
}
198+
return (T) result;
199+
} else if (v instanceof Map m) {
200+
Map<String, Object> result = new HashMap<>(m);
201+
result.replaceAll((k, vv) -> processSensitiveData(vv));
202+
return (T) result;
203+
}
204+
205+
return v;
206+
}
207+
173208
@SuppressWarnings("unchecked")
174209
private static Map<String, Object> ensureModifiable(Map<String, Object> m, int depth, String[] path) {
175210
if (depth == 0) {
@@ -217,7 +252,7 @@ private static Map<String, Object> convertInput(List<Object> input) {
217252
return result;
218253
}
219254

220-
private static List<Object> hideSensitiveData(List<Object> input, List<List<Annotation>> annotations) {
255+
private static List<Object> processSensitiveDataAnnotations(List<Object> input, List<List<Annotation>> annotations) {
221256
if (annotations.isEmpty()) {
222257
return input;
223258
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.walmartlabs.concord.runtime.v2.runner.vm;
2+
3+
/*-
4+
* *****
5+
* Concord
6+
* -----
7+
* Copyright (C) 2017 - 2023 Walmart Inc.
8+
* -----
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
* =====
21+
*/
22+
23+
import com.walmartlabs.concord.runtime.v2.runner.OutVariablesProcessor;
24+
import com.walmartlabs.concord.svm.Runtime;
25+
import com.walmartlabs.concord.svm.*;
26+
27+
import java.io.Serial;
28+
29+
public class SaveOutVariablesOnErrorCommand implements Command {
30+
31+
@Serial
32+
private static final long serialVersionUID = 1L;
33+
34+
@Override
35+
public void eval(Runtime runtime, State state, ThreadId threadId) throws Exception {
36+
Frame frame = state.peekFrame(threadId);
37+
frame.pop();
38+
39+
runtime.getService(OutVariablesProcessor.class).afterProcessEnds(runtime, state, frame);
40+
}
41+
}

runtime/v2/runner/src/test/java/com/walmartlabs/concord/runtime/v2/runner/remote/TaskCallEventRecordingListenerTest.java

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import com.fasterxml.jackson.core.JsonProcessingException;
2424
import com.fasterxml.jackson.databind.ObjectMapper;
25+
import com.walmartlabs.concord.runtime.v2.runner.SensitiveDataHolder;
2526
import org.junit.jupiter.api.Test;
2627

2728
import java.util.Arrays;
@@ -38,33 +39,33 @@ public class TaskCallEventRecordingListenerTest {
3839
@Test
3940
public void testMaskVars() throws Exception {
4041
String in = "{" +
41-
" \"a\":1," +
42-
" \"b\":2," +
43-
" \"c\":{" +
44-
" \"c1\":3," +
45-
" \"c2\":4," +
46-
" \"c3\":{" +
47-
" \"c31\":5," +
48-
" \"c32\":6" +
49-
" }" +
50-
" }" +
51-
"}";
42+
" \"a\":1," +
43+
" \"b\":2," +
44+
" \"c\":{" +
45+
" \"c1\":3," +
46+
" \"c2\":4," +
47+
" \"c3\":{" +
48+
" \"c31\":5," +
49+
" \"c32\":6" +
50+
" }" +
51+
" }" +
52+
"}";
5253

5354
List<String> blackList = Arrays.asList("b", "c.c1", "c.c3.c31");
5455
Map<String, Object> result = TaskCallEventRecordingListener.maskVars(vars(in), blackList);
5556

5657
String expected = "{" +
57-
" \"a\":1," +
58-
" \"b\":\"***\"," +
59-
" \"c\":{" +
60-
" \"c1\":\"***\"," +
61-
" \"c2\":4," +
62-
" \"c3\":{" +
63-
" \"c31\":\"***\"," +
64-
" \"c32\":6" +
65-
" }" +
66-
" }" +
67-
"}";
58+
" \"a\":1," +
59+
" \"b\":\"***\"," +
60+
" \"c\":{" +
61+
" \"c1\":\"***\"," +
62+
" \"c2\":4," +
63+
" \"c3\":{" +
64+
" \"c31\":\"***\"," +
65+
" \"c32\":6" +
66+
" }" +
67+
" }" +
68+
"}";
6869
assertEquals(vars(expected), result);
6970
}
7071

@@ -80,6 +81,29 @@ public void testMaskVarsUnmodifiable() {
8081
assertEquals("{x={y={z=***}}}", result.toString());
8182
}
8283

84+
@Test
85+
public void testSensitiveDataMasking() throws JsonProcessingException {
86+
SensitiveDataHolder holder = SensitiveDataHolder.getInstance();
87+
holder.add("foo");
88+
holder.add("bar");
89+
90+
String in = "{" +
91+
"\"a\": \"foo\"," +
92+
"\"b\": \"bar\"," +
93+
"\"c\": \"baz\"," +
94+
"\"d\": { \"e\": \"foo\" }" +
95+
"}";
96+
97+
Map<String, Object> result = TaskCallEventRecordingListener.processSensitiveData(vars(in));
98+
String expected = "{" +
99+
" \"a\": \"***\"," +
100+
" \"b\": \"***\"," +
101+
" \"c\": \"baz\"," +
102+
" \"d\": { \"e\": \"***\" }" +
103+
"}";
104+
assertEquals(vars(expected), result);
105+
}
106+
83107
@SuppressWarnings("unchecked")
84108
private static Map<String, Object> vars(String in) throws JsonProcessingException {
85109
return om.readValue(in, Map.class);

0 commit comments

Comments
 (0)