Skip to content

Commit

Permalink
Add new logtash encoder
Browse files Browse the repository at this point in the history
  • Loading branch information
carlphilipp committed Nov 20, 2022
1 parent b55deff commit 9b00ce5
Show file tree
Hide file tree
Showing 11 changed files with 686 additions and 3 deletions.
3 changes: 2 additions & 1 deletion buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ object Version {
const val junit = "5.6.2"
const val log4j2 = "2.13.1"
const val logback = "1.2.3"
const val spring = "2.2.6.RELEASE"
const val logstash = "7.2"
const val spring = "2.7.0"
const val slf4j = "1.7.30"

// Gradle plugins
Expand Down
8 changes: 8 additions & 0 deletions compliance-logging-logback-logstash/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
description = "Compliance Logstash Logback Encoder library"

dependencies {
api(project(":compliance-logging-logback"))
api(group = "net.logstash.logback", name = "logstash-logback-encoder", version = Version.logstash)

testImplementation(group = "org.junit.jupiter", name = "junit-jupiter")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* MIT License
*
* <p>Copyright (c) 2020 Slalom LLC
*
* <p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* <p>The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* <p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.slalom.logging.compliance.logback.logstash;

import ch.qos.logback.classic.spi.ILoggingEvent;
import com.fasterxml.jackson.core.JsonGenerator;
import com.slalom.logging.compliance.core.MaskService;
import com.slalom.logging.compliance.core.MaskType;
import com.slalom.logging.compliance.core.impl.JsonMaskService;
import com.slalom.logging.compliance.core.impl.LombokMaskService;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.AccessLevel;
import lombok.Getter;
import net.logstash.logback.composite.AbstractFieldJsonProvider;
import net.logstash.logback.composite.FieldNamesAware;
import net.logstash.logback.composite.JsonWritingUtils;
import net.logstash.logback.fieldnames.LogstashFieldNames;
import org.slf4j.Marker;

/** Logtsash encoder in charge of calling the right service to mask sensitive data. */
public class MaskingMessageJsonProvider extends AbstractFieldJsonProvider<ILoggingEvent>
implements FieldNamesAware<LogstashFieldNames> {

@Getter(AccessLevel.PACKAGE)
private boolean enabled = false;

private MaskService jsonMaskService = new JsonMaskService(Collections.emptyList());
private MaskService lombokMaskService = new LombokMaskService(Collections.emptyList());

private static final String FIELD_NAME = "msg";

public MaskingMessageJsonProvider() {
setFieldName(FIELD_NAME);
}

/**
* Method called by logback with fields provided in the configuration file.
*
* @param fields the fields to be masked
*/
public void addFields(final String fields) {
final List<String> regex =
Stream.of(fields.split(",")).map(String::trim).collect(Collectors.toList());
enabled = true;
jsonMaskService = new JsonMaskService(regex);
lombokMaskService = new LombokMaskService(regex);
}

@Override
public void setFieldNames(final LogstashFieldNames fieldNames) {
setFieldName(fieldNames.getMessage());
}

@Override
public void writeTo(final JsonGenerator generator, final ILoggingEvent event) throws IOException {
final String message = event.getFormattedMessage();
if (enabled) {
final Marker marker = event.getMarker();
if (marker == null) {
JsonWritingUtils.writeStringField(generator, getFieldName(), message);
} else {
writeMaskMessageTo(generator, message, marker);
}
} else {
JsonWritingUtils.writeStringField(generator, getFieldName(), message);
}
}

private void writeMaskMessageTo(
final JsonGenerator generator, final String message, final Marker marker) throws IOException {
String maskedMessage = message;
if (MaskType.JSON.getName().equals(marker.getName())) {
maskedMessage = jsonMaskService.maskMessage(message);
} else if (MaskType.LOMBOK.getName().equals(marker.getName())) {
maskedMessage = lombokMaskService.maskMessage(message);
}
JsonWritingUtils.writeStringField(generator, getFieldName(), maskedMessage);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* MIT License
*
* <p>Copyright (c) 2020 Slalom LLC
*
* <p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* <p>The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* <p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.slalom.logging.compliance.logback.logstash;

import lombok.Builder;
import lombok.Getter;
import lombok.ToString;

@Getter
@Builder
@ToString
public class Employee {
private final String firstName;
private final String lastName;
private final String password;
private final String ssn;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.slalom.logging.compliance.logback.logstash;

import static org.assertj.core.api.Assertions.assertThat;

import ch.qos.logback.classic.spi.ILoggingEvent;
import com.slalom.logging.compliance.core.MaskType;
import com.slalom.logging.compliance.logback.logstash.stub.JsonGeneratorStub;
import com.slalom.logging.compliance.logback.logstash.stub.LoggingEventStub;
import java.io.IOException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

public class MaskingMessageJsonProviderTest {

@DisplayName("Check that the masking is disabled when addFields method is not called")
@Test
public void testCheckStateDisabled() {
// when
MaskingMessageJsonProvider provider = new MaskingMessageJsonProvider();

// then
assertThat(provider.isEnabled()).isFalse();
}

@DisplayName("Check that the masking is disabled when addFields method is not called")
@Test
public void testCheckStateDisabled2() {
// given
MaskingMessageJsonProvider provider = new MaskingMessageJsonProvider();

// when
provider.addFields("password,ssn");

// then
assertThat(provider.isEnabled()).isTrue();
}

@DisplayName("Check that the encoder is masking json strings")
@Test
public void testMaskJsonMessage() throws IOException {
// given
String fields = "password,ssn";
String message =
"my message with json {\"login\":\"john\",\"password\":\"mypassword\",\"ssn\":\"123-12-1234\"}";
MaskingMessageJsonProvider provider = buildProvider(fields);
Marker marker = MarkerFactory.getMarker(MaskType.JSON_MARKER_NAME);
ILoggingEvent logEvent = new LoggingEventStub(message, marker);
JsonGeneratorStub jsonGenerator = new JsonGeneratorStub();

// when
provider.writeTo(jsonGenerator, logEvent);

// then
assertThat(jsonGenerator.getText())
.contains(
"my message with json {\"login\":\"john\",\"password\":\"***********\",\"ssn\":\"***********\"}");
}

@DisplayName("Check that the encoder is masking lombok strings")
@Test
public void testMaskLombokMessage() throws IOException {
// given
String fields = "password,ssn";
String message =
"my message with lombok "
+ Employee.builder()
.firstName("john")
.lastName("doe")
.password("mypassword")
.ssn("123-12-1234")
.build();
MaskingMessageJsonProvider provider = buildProvider(fields);
Marker marker = MarkerFactory.getMarker(MaskType.LOMBOK_MARKER_NAME);
ILoggingEvent logEvent = new LoggingEventStub(message, marker);
JsonGeneratorStub jsonGenerator = new JsonGeneratorStub();

// when
provider.writeTo(jsonGenerator, logEvent);

// then
assertThat(jsonGenerator.getText())
.contains(
"my message with lombok Employee(firstName=john, lastName=doe, password=***********, ssn=***********)");
}

@DisplayName("Check that the encoder is not doing anything when no marker is used")
@Test
public void testWithoutMarker() throws IOException {
// given
String fields = "password,ssn";
String message =
"my message with json {\"login\":\"john\",\"password\":\"mypassword\",\"ssn\":\"123-12-1234\"}";
MaskingMessageJsonProvider provider = buildProvider(fields);
ILoggingEvent logEvent = new LoggingEventStub(message);
JsonGeneratorStub jsonGenerator = new JsonGeneratorStub();

// when
provider.writeTo(jsonGenerator, logEvent);

// then
assertThat(jsonGenerator.getText()).contains(message);
}

@DisplayName("Check that the encoder is not doing anything when it is disabled")
@Test
public void testWithPluginDisabled() throws IOException {
// given
String message =
"my message with json {\"login\":\"john\",\"password\":\"mypassword\",\"ssn\":\"123-12-1234\"}";
MaskingMessageJsonProvider provider = buildDisabledProvider();
ILoggingEvent logEvent = new LoggingEventStub(message);
JsonGeneratorStub jsonGenerator = new JsonGeneratorStub();

// when
provider.writeTo(jsonGenerator, logEvent);

// then
assertThat(jsonGenerator.getText()).contains(message);
}

private MaskingMessageJsonProvider buildProvider(String fields) {
MaskingMessageJsonProvider provider = new MaskingMessageJsonProvider();
provider.addFields(fields);
return provider;
}

private MaskingMessageJsonProvider buildDisabledProvider() {
return new MaskingMessageJsonProvider();
}
}
Loading

0 comments on commit 9b00ce5

Please sign in to comment.