Skip to content

Commit 1a8c74d

Browse files
author
Steve Salas
committed
Allow opt-in for skipping trace acknowledgment
1 parent 9621e1b commit 1a8c74d

File tree

27 files changed

+276
-33
lines changed

27 files changed

+276
-33
lines changed

agent/src/main/java/com/codedx/codepulse/agent/agent/DefaultTraceAgent.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
import com.codedx.codepulse.agent.message.MessageSenderManager;
3838
import com.codedx.codepulse.agent.message.PooledBufferService;
3939
import com.codedx.codepulse.agent.protocol.ProtocolVersion;
40-
import com.codedx.codepulse.agent.protocol.ProtocolVersion3;
40+
import com.codedx.codepulse.agent.protocol.ProtocolVersion4;
4141
import com.codedx.codepulse.agent.trace.TraceDataCollector;
4242
import com.codedx.codepulse.agent.util.ShutdownHook;
4343
import com.codedx.codepulse.agent.util.SocketFactory;
@@ -65,7 +65,7 @@ public class DefaultTraceAgent implements TraceAgent
6565
private RuntimeAgentConfigurationV1 config;
6666

6767
private final Semaphore startMutex = new Semaphore(0);
68-
private final ProtocolVersion protocol = new ProtocolVersion3();
68+
private final ProtocolVersion protocol = new ProtocolVersion4();
6969
private MinlogListener logger = null;
7070
private ClassIdentifier classIdentifier = new ClassIdentifier();
7171
private MethodIdentifier methodIdentifier = new MethodIdentifier();
@@ -197,7 +197,7 @@ public boolean connect(int timeout) throws InterruptedException
197197
SocketConnection controlConnection = new SocketConnection(controlSocket, true, true);
198198

199199
error = "Failed to get configuration from HQ";
200-
config = protocol.getControlConnectionHandshake().performHandshake(controlConnection);
200+
config = protocol.getControlConnectionHandshake().performHandshake(controlConnection, staticConfig.getProjectId());
201201
if (config == null)
202202
{
203203
controlSocket.close();

agent/src/main/java/com/codedx/codepulse/agent/init/ControlConnectionHandshake.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@
2424

2525
public interface ControlConnectionHandshake
2626
{
27-
RuntimeAgentConfigurationV1 performHandshake(Connection connection) throws IOException;
27+
RuntimeAgentConfigurationV1 performHandshake(Connection connection, int projectId) throws IOException;
2828
}

agent/src/main/java/com/codedx/codepulse/agent/init/ControlConnectionHandshakeV1.java

+13-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.DataOutputStream;
2222
import java.io.IOException;
2323

24+
import com.codedx.codepulse.agent.common.message.NotSupportedException;
2425
import com.codedx.codepulse.agent.control.ConfigurationReader;
2526
import com.codedx.codepulse.agent.errors.ErrorHandler;
2627
import com.codedx.codepulse.agent.common.config.RuntimeAgentConfigurationV1;
@@ -29,7 +30,7 @@
2930
import com.codedx.codepulse.agent.common.message.MessageProtocol;
3031

3132
/**
32-
* Implements control connection handshake for protocol version 1.
33+
* Implements control connection handshake for protocol version 4.
3334
* @author RobertF
3435
*/
3536
public class ControlConnectionHandshakeV1 implements ControlConnectionHandshake
@@ -44,13 +45,22 @@ public ControlConnectionHandshakeV1(MessageProtocol protocol, ConfigurationReade
4445
}
4546

4647
@Override
47-
public RuntimeAgentConfigurationV1 performHandshake(Connection connection) throws IOException
48+
public RuntimeAgentConfigurationV1 performHandshake(Connection connection, int projectId) throws IOException
4849
{
4950
DataOutputStream outStream = connection.output();
5051
DataInputStream inStream = connection.input();
5152

5253
// say hello!
53-
protocol.writeHello(outStream);
54+
try {
55+
if (projectId == 0) {
56+
protocol.writeHello(outStream);
57+
} else {
58+
protocol.writeProjectHello(outStream, projectId);
59+
}
60+
} catch (NotSupportedException e) {
61+
ErrorHandler.handleError("protocol error: project-hello message is not supported");
62+
return null;
63+
}
5464
outStream.flush();
5565

5666
byte reply = inStream.readByte();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2018 Secure Decisions, a division of Applied Visions, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* This material is based on research sponsored by the Department of Homeland
17+
* Security (DHS) Science and Technology Directorate, Cyber Security Division
18+
* (DHS S&T/CSD) via contract number HHSP233201600058C.
19+
*/
20+
21+
package com.codedx.codepulse.agent.protocol;
22+
23+
import com.codedx.codepulse.agent.common.message.MessageProtocolV4;
24+
import com.codedx.codepulse.agent.control.ConfigurationReaderV2;
25+
import com.codedx.codepulse.agent.init.ControlConnectionHandshakeV1;
26+
import com.codedx.codepulse.agent.init.DataConnectionHandshakeV1;
27+
28+
/**
29+
* ProtocolVersion implementation for version 4.
30+
* @author ssalas
31+
*/
32+
public class ProtocolVersion4 extends ProtocolVersionBase
33+
{
34+
public ProtocolVersion4()
35+
{
36+
messageProtocol = new MessageProtocolV4();
37+
configurationReader = new ConfigurationReaderV2();
38+
controlConnectionHandshake = new ControlConnectionHandshakeV1(messageProtocol, configurationReader);
39+
dataConnectionHandshake = new DataConnectionHandshakeV1(messageProtocol);
40+
}
41+
}

codepulse/src/main/resources/application.conf

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ cp {
33
# The port used to listen for agent connections
44
tracePort = 8765,
55
tracePort=${?CODE_PULSE_TRACE_PORT}
6+
# Determines whether Code Pulse can skip user acknowledgment of a trace that specifies a project ID
7+
skipUserAcknowledgment=false
8+
skipUserAcknowledgment=${?CODE_PULSE_TRACE_SKIP_ACK}
69
symbolService {
710
port = "49582"
811
port = ${?SYMBOL_SERVICE_PORT}

codepulse/src/main/scala/com/secdec/codepulse/package.scala

+10-4
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ package object codepulse {
8080
private var config =
8181
ConfigFactory
8282
.parseFile(configFile)
83-
.resolve()
83+
.resolve()
8484
.withFallback(ConfigFactory.load())
8585
.withOnlyPath("cp.userSettings") // do not return systemSettings, which could get persisted to disk later on
8686

@@ -93,6 +93,8 @@ package object codepulse {
9393

9494
def symbolServicePort = config.getString("cp.userSettings.symbolService.port")
9595

96+
def skipUserAcknowledgment = config.getBoolean("cp.userSettings.skipUserAcknowledgment")
97+
9698
def secdecLoggingLevel: Option[Level] = {
9799
getLogLevel(config, "cp.userSettings.logging.secdecLoggingLevel")
98100
}
@@ -147,10 +149,14 @@ package object codepulse {
147149

148150
// preserve option to override port for symbol service via an environment variable
149151
val symbolServicePortPattern = """(port="?\d+"?)""".r
150-
val outputWithServicePortPattern = symbolServicePortPattern.replaceFirstIn(outputWithTracePort, "$1\n" + " " * 16 + "port=\\${?SYMBOL_SERVICE_PORT}")
152+
val outputWithServicePort = symbolServicePortPattern.replaceFirstIn(outputWithTracePort, "$1\n" + " " * 16 + "port=\\${?SYMBOL_SERVICE_PORT}")
153+
154+
// preserve option to skip user ack via an environment variable
155+
val allowAgentToAcceptTraceConnectionPattern = """(skipUserAcknowledgment="?(?:false|true|yes|no)"?)""".r
156+
val outputWithSkipAck = allowAgentToAcceptTraceConnectionPattern.replaceFirstIn(outputWithServicePort, "$1\n" + " " * 12 + "skipUserAcknowledgment=\\${?CODE_PULSE_SKIP_ACK}")
151157

152158
val writer = new FileWriter(configFile)
153-
writer.write(outputWithServicePortPattern)
159+
writer.write(outputWithSkipAck)
154160
writer.close()
155161
}
156162
}
@@ -161,4 +167,4 @@ package object codepulse {
161167
def symbolServiceBinary = config.getString("cp.systemSettings.symbolService.binary")
162168
def symbolServiceLocation = config.getString("cp.systemSettings.symbolService.location")
163169
}
164-
}
170+
}

codepulse/src/main/scala/com/secdec/codepulse/tracer/TraceConnectionLooper.scala

+19
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import akka.actor.actorRef2Scala
4444
import akka.pattern.ask
4545
import akka.pattern.pipe
4646
import akka.util.Timeout
47+
import com.secdec.codepulse.userSettings
4748
import net.liftweb.common.Loggable
4849
import reactive.EventSource
4950
import reactive.EventStream
@@ -289,6 +290,24 @@ class TraceConnectionLooper(acknowledger: TraceConnectionAcknowledger) extends A
289290

290291
val acknowledgment = acknowledger.getTraceAcknowledgment(trace)
291292

293+
if (trace.projectId.isDefined) {
294+
val projectId = trace.projectId.get
295+
296+
if (userSettings.skipUserAcknowledgment) {
297+
logger.debug(s"Skipping user acknowledgement for project ID $projectId...")
298+
299+
val project = com.secdec.codepulse.tracer.projectManager().getProject(com.secdec.codepulse.data.model.ProjectId(projectId))
300+
if (project.isDefined) {
301+
com.secdec.codepulse.tracer.traceConnectionAcknowledger().acknowledgeCurrentTrace(project.get)
302+
logger.debug("Skipped user acknowledgement")
303+
} else {
304+
logger.error(s"A trace specified project ID $projectId, but there is no project with that ID.")
305+
}
306+
} else {
307+
logger.error(s"A trace specified project ID $projectId, but the value of cp.userSettings.skipUserAcknowledgment prevents skipping user acknowledgement.")
308+
}
309+
}
310+
292311
// if the trace 'completes' before it has been acknowledged, we need to look for a new one
293312
// (this might happen if the agent gets ctrl+C'd before the ack)
294313
for {

dotnet-tracer/main/CodePulse.Client.Test/MessageProtocolTests.cs

+12
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ public void WhenWriteHelloMessageCreated()
4242
});
4343
}
4444

45+
[TestMethod]
46+
public void WhenWriteProjectHelloMessageCreated()
47+
{
48+
TestMessage((writer, protocol) => protocol.WriteProjectHello(writer, 42),
49+
(reader, protocol) =>
50+
{
51+
Assert.AreEqual(MessageTypes.ProjectHello, reader.ReadByte());
52+
Assert.AreEqual(protocol.ProtocolVersion, reader.ReadByte());
53+
Assert.AreEqual(42, reader.ReadInt32BigEndian());
54+
});
55+
}
56+
4557
[TestMethod]
4658
public void WhenWriteDataHelloMessageCreated()
4759
{

dotnet-tracer/main/CodePulse.Client/Agent/DefaultTraceAgent.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public bool Connect()
139139
{
140140
try
141141
{
142-
RuntimeAgentConfiguration = _protocolVersion.ControlConnectionHandshake.PerformHandshake(socketConnection);
142+
RuntimeAgentConfiguration = _protocolVersion.ControlConnectionHandshake.PerformHandshake(socketConnection, StaticAgentConfiguration.ProjectId);
143143
}
144144
catch (HandshakeException ex)
145145
{

dotnet-tracer/main/CodePulse.Client/Config/StaticAgentConfiguration.cs

+16
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,23 @@ public class StaticAgentConfiguration
3535

3636
public int ConnectTimeout { get; }
3737

38+
public int ProjectId { get; }
39+
3840
public ILog Logger { get; }
3941

4042
public StaticAgentConfiguration(int hqPort,
4143
string hqHost,
4244
int connectTimeout,
4345
ILog logger)
46+
: this(hqPort, hqHost, connectTimeout, 0, logger)
47+
{
48+
}
49+
50+
public StaticAgentConfiguration(int hqPort,
51+
string hqHost,
52+
int connectTimeout,
53+
int projectId,
54+
ILog logger)
4455
{
4556
if (hqHost == null)
4657
{
@@ -54,11 +65,16 @@ public StaticAgentConfiguration(int hqPort,
5465
{
5566
throw new ArgumentOutOfRangeException(nameof(connectTimeout));
5667
}
68+
if (projectId < 0)
69+
{
70+
throw new ArgumentOutOfRangeException(nameof(projectId));
71+
}
5772

5873
HqPort = hqPort;
5974
HqHost = hqHost;
6075
ConnectTimeout = connectTimeout;
6176
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
77+
ProjectId = projectId;
6278
}
6379
}
6480
}

dotnet-tracer/main/CodePulse.Client/Init/ControlConnectionHandshake.cs

+15-2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ public ControlConnectionHandshake(IMessageProtocol messageProtocol, IConfigurati
4141
}
4242

4343
public RuntimeAgentConfiguration PerformHandshake(IConnection connection)
44+
{
45+
return PerformHandshake(connection, 0);
46+
}
47+
48+
public RuntimeAgentConfiguration PerformHandshake(IConnection connection, int projectId)
4449
{
4550
if (connection == null)
4651
{
@@ -49,8 +54,16 @@ public RuntimeAgentConfiguration PerformHandshake(IConnection connection)
4954

5055
var outputWriter = connection.OutputWriter;
5156

52-
_messageProtocol.WriteHello(outputWriter);
53-
outputWriter.FlushAndLog("WriteHello");
57+
if (projectId == 0)
58+
{
59+
_messageProtocol.WriteHello(outputWriter);
60+
outputWriter.FlushAndLog("WriteHello");
61+
}
62+
else
63+
{
64+
_messageProtocol.WriteProjectHello(outputWriter, projectId);
65+
outputWriter.FlushAndLog("WriteProjectHello");
66+
}
5467

5568
var inputReader = connection.InputReader;
5669
var reply = inputReader.ReadByte();

dotnet-tracer/main/CodePulse.Client/Init/IControlConnectionHandshake.cs

+2
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,7 @@ namespace CodePulse.Client.Init
2828
public interface IControlConnectionHandshake
2929
{
3030
RuntimeAgentConfiguration PerformHandshake(IConnection connection);
31+
32+
RuntimeAgentConfiguration PerformHandshake(IConnection connection, int projectId);
3133
}
3234
}

dotnet-tracer/main/CodePulse.Client/Message/IMessageProtocol.cs

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public interface IMessageProtocol
2929
byte ProtocolVersion { get; }
3030

3131
void WriteHello(BinaryWriter writer);
32+
void WriteProjectHello(BinaryWriter writer, int projectId);
3233
void WriteDataHello(BinaryWriter writer, byte runId);
3334
void WriteError(BinaryWriter writer, string error);
3435
void WriteHeartbeat(BinaryWriter writer, AgentOperationMode mode, ushort sendBufferSize);

dotnet-tracer/main/CodePulse.Client/Message/MessageProtocol.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,21 @@ namespace CodePulse.Client.Message
2828
{
2929
public class MessageProtocol : IMessageProtocol
3030
{
31-
public byte ProtocolVersion { get; } = 3;
31+
public byte ProtocolVersion { get; } = 4;
3232

3333
public void WriteHello(BinaryWriter writer)
3434
{
3535
writer.Write(MessageTypes.Hello);
3636
writer.Write(ProtocolVersion);
3737
}
3838

39+
public void WriteProjectHello(BinaryWriter writer, int projectId)
40+
{
41+
writer.Write(MessageTypes.ProjectHello);
42+
writer.Write(ProtocolVersion);
43+
writer.WriteBigEndian(projectId);
44+
}
45+
3946
public void WriteDataHello(BinaryWriter writer, byte runId)
4047
{
4148
writer.Write(MessageTypes.DataHello);

dotnet-tracer/main/CodePulse.Client/Message/MessageTypes.cs

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public abstract class MessageTypes
4141
public const byte MapSourceLocation = 13;
4242
public const byte MethodVisit = 14;
4343

44+
public const byte ProjectHello = 16;
45+
4446
public const byte MethodEntry = 20;
4547
public const byte MethodExit = 21;
4648
public const byte Exception = 22;

dotnet-tracer/main/CodePulse.Console.Test/CommandLineParserTests.cs

+39
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,45 @@ public void WhenAppModeWithLogAllSpecifiedSuccess()
281281
ValidateCommandLineArguments(parameters);
282282
}
283283

284+
[TestMethod]
285+
public void WhenProjectIdInvalidExceptionOccurs()
286+
{
287+
// arrange
288+
var parameters = new[]
289+
{
290+
"-Target:file",
291+
"-SendVisitPointsTimerInterval:45",
292+
"-ProjectId:not-an-id"
293+
};
294+
ValidateCommandLineArguments(parameters, "The Code Pulse project ID must be a valid identifier: not-an-id is not a valid value for Int32.");
295+
}
296+
297+
[TestMethod]
298+
public void WhenProjectIdSpecifiedCorrectly()
299+
{
300+
// arrange
301+
var parameters = new[]
302+
{
303+
"-Target:file",
304+
"-SendVisitPointsTimerInterval:45",
305+
"-ProjectId:1"
306+
};
307+
ValidateCommandLineArguments(parameters);
308+
}
309+
310+
[TestMethod]
311+
public void WhenProjectIdNegativeExceptionOccurs()
312+
{
313+
// arrange
314+
var parameters = new[]
315+
{
316+
"-Target:file",
317+
"-SendVisitPointsTimerInterval:45",
318+
"-ProjectId:-11"
319+
};
320+
ValidateCommandLineArguments(parameters, "The Code Pulse project ID must be a valid identifier: The argument projectid must be between 0 and 2147483647");
321+
}
322+
284323
private static void ValidateCommandLineArguments(string[] parameters, string expectedError = "")
285324
{
286325
// arrange

0 commit comments

Comments
 (0)