diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index c56a90c..4cadf85 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -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 diff --git a/compliance-logging-logback-logstash/build.gradle.kts b/compliance-logging-logback-logstash/build.gradle.kts new file mode 100644 index 0000000..6dc48e3 --- /dev/null +++ b/compliance-logging-logback-logstash/build.gradle.kts @@ -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") +} \ No newline at end of file diff --git a/compliance-logging-logback-logstash/src/main/java/com/slalom/logging/compliance/logback/logstash/MaskingMessageJsonProvider.java b/compliance-logging-logback-logstash/src/main/java/com/slalom/logging/compliance/logback/logstash/MaskingMessageJsonProvider.java new file mode 100644 index 0000000..fd8ce2f --- /dev/null +++ b/compliance-logging-logback-logstash/src/main/java/com/slalom/logging/compliance/logback/logstash/MaskingMessageJsonProvider.java @@ -0,0 +1,101 @@ +/** + * MIT License + * + *
Copyright (c) 2020 Slalom LLC + * + *
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: + * + *
The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + *
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 Copyright (c) 2020 Slalom LLC
+ *
+ * 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:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * 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;
+}
diff --git a/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/MaskingMessageJsonProviderTest.java b/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/MaskingMessageJsonProviderTest.java
new file mode 100644
index 0000000..6014b11
--- /dev/null
+++ b/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/MaskingMessageJsonProviderTest.java
@@ -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();
+ }
+}
diff --git a/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/stub/JsonGeneratorStub.java b/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/stub/JsonGeneratorStub.java
new file mode 100644
index 0000000..b1b83b5
--- /dev/null
+++ b/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/stub/JsonGeneratorStub.java
@@ -0,0 +1,179 @@
+package com.slalom.logging.compliance.logback.logstash.stub;
+
+import com.fasterxml.jackson.core.Base64Variant;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonStreamContext;
+import com.fasterxml.jackson.core.ObjectCodec;
+import com.fasterxml.jackson.core.SerializableString;
+import com.fasterxml.jackson.core.TreeNode;
+import com.fasterxml.jackson.core.Version;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import lombok.Getter;
+
+public class JsonGeneratorStub extends JsonGenerator {
+
+ @Getter private String text;
+
+ @Override
+ public JsonGenerator setCodec(ObjectCodec oc) {
+ return null;
+ }
+
+ @Override
+ public ObjectCodec getCodec() {
+ return null;
+ }
+
+ @Override
+ public Version version() {
+ return null;
+ }
+
+ @Override
+ public JsonStreamContext getOutputContext() {
+ return null;
+ }
+
+ @Override
+ public JsonGenerator enable(Feature f) {
+ return null;
+ }
+
+ @Override
+ public JsonGenerator disable(Feature f) {
+ return null;
+ }
+
+ @Override
+ public boolean isEnabled(Feature f) {
+ return false;
+ }
+
+ @Override
+ public int getFeatureMask() {
+ return 0;
+ }
+
+ @Deprecated
+ @Override
+ public JsonGenerator setFeatureMask(int values) {
+ return null;
+ }
+
+ @Override
+ public JsonGenerator useDefaultPrettyPrinter() {
+ return null;
+ }
+
+ @Override
+ public void writeStartArray() throws IOException {}
+
+ @Override
+ public void writeEndArray() throws IOException {}
+
+ @Override
+ public void writeStartObject() throws IOException {}
+
+ @Override
+ public void writeEndObject() throws IOException {}
+
+ @Override
+ public void writeFieldName(String name) throws IOException {}
+
+ @Override
+ public void writeFieldName(SerializableString name) throws IOException {}
+
+ @Override
+ public void writeString(String text) throws IOException {
+ System.out.println("text");
+ this.text = text;
+ }
+
+ @Override
+ public void writeString(char[] buffer, int offset, int len) throws IOException {}
+
+ @Override
+ public void writeString(SerializableString text) throws IOException {}
+
+ @Override
+ public void writeRawUTF8String(byte[] buffer, int offset, int len) throws IOException {}
+
+ @Override
+ public void writeUTF8String(byte[] buffer, int offset, int len) throws IOException {}
+
+ @Override
+ public void writeRaw(String text) throws IOException {}
+
+ @Override
+ public void writeRaw(String text, int offset, int len) throws IOException {}
+
+ @Override
+ public void writeRaw(char[] text, int offset, int len) throws IOException {}
+
+ @Override
+ public void writeRaw(char c) throws IOException {}
+
+ @Override
+ public void writeRawValue(String text) throws IOException {}
+
+ @Override
+ public void writeRawValue(String text, int offset, int len) throws IOException {}
+
+ @Override
+ public void writeRawValue(char[] text, int offset, int len) throws IOException {}
+
+ @Override
+ public void writeBinary(Base64Variant bv, byte[] data, int offset, int len) throws IOException {}
+
+ @Override
+ public int writeBinary(Base64Variant bv, InputStream data, int dataLength) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public void writeNumber(int v) throws IOException {}
+
+ @Override
+ public void writeNumber(long v) throws IOException {}
+
+ @Override
+ public void writeNumber(BigInteger v) throws IOException {}
+
+ @Override
+ public void writeNumber(double v) throws IOException {}
+
+ @Override
+ public void writeNumber(float v) throws IOException {}
+
+ @Override
+ public void writeNumber(BigDecimal v) throws IOException {}
+
+ @Override
+ public void writeNumber(String encodedValue) throws IOException {}
+
+ @Override
+ public void writeBoolean(boolean state) throws IOException {}
+
+ @Override
+ public void writeNull() throws IOException {}
+
+ @Override
+ public void writeObject(Object pojo) throws IOException {}
+
+ @Override
+ public void writeTree(TreeNode rootNode) throws IOException {}
+
+ @Override
+ public void flush() throws IOException {}
+
+ @Override
+ public boolean isClosed() {
+ return false;
+ }
+
+ @Override
+ public void close() throws IOException {}
+}
diff --git a/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/stub/LoggingEventStub.java b/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/stub/LoggingEventStub.java
new file mode 100644
index 0000000..5c63ae4
--- /dev/null
+++ b/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/stub/LoggingEventStub.java
@@ -0,0 +1,120 @@
+/**
+ * MIT License
+ *
+ * Copyright (c) 2020 Slalom LLC
+ *
+ * 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:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * 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.stub;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.IThrowableProxy;
+import ch.qos.logback.classic.spi.LoggerContextVO;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Map;
+import org.slf4j.Marker;
+
+public class LoggingEventStub implements ILoggingEvent {
+
+ private final String message;
+ private final Marker marker;
+
+ public LoggingEventStub(final String message) {
+ this.message = message;
+ this.marker = null;
+ }
+
+ public LoggingEventStub(final String message, final Marker marker) {
+ this.message = message;
+ this.marker = marker;
+ }
+
+ @Override
+ public String getThreadName() {
+ return null;
+ }
+
+ @Override
+ public Level getLevel() {
+ return Level.INFO;
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public Object[] getArgumentArray() {
+ return new Object[0];
+ }
+
+ @Override
+ public String getFormattedMessage() {
+ return message;
+ }
+
+ @Override
+ public String getLoggerName() {
+ return "logger";
+ }
+
+ @Override
+ public LoggerContextVO getLoggerContextVO() {
+ return null;
+ }
+
+ @Override
+ public IThrowableProxy getThrowableProxy() {
+ return null;
+ }
+
+ @Override
+ public StackTraceElement[] getCallerData() {
+ return new StackTraceElement[0];
+ }
+
+ @Override
+ public boolean hasCallerData() {
+ return false;
+ }
+
+ @Override
+ public Marker getMarker() {
+ return marker;
+ }
+
+ @Override
+ public Map Copyright (c) 2020 Slalom LLC
+ *
+ * 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:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * 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.example;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.slalom.logging.compliance.core.MaskType;
+import javax.annotation.PostConstruct;
+import lombok.Builder;
+import lombok.Data;
+import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@Slf4j
+@SpringBootApplication
+public class LogbackLogstashExample {
+
+ public static void main(String[] args) {
+ SpringApplication.run(LogbackLogstashExample.class, args);
+ }
+
+ @PostConstruct
+ public void postConstruct() throws JsonProcessingException {
+ final User user =
+ User.builder().login("mylogin").password("mypassword").ssn("123-123-1234").build();
+ log.info("=================================================================================");
+ log.info("No Marker: {}", user);
+ log.info(MaskType.LOMBOK, "Marker Lombok: {}", user);
+ log.info(MaskType.JSON, "Marker Json: {}", new ObjectMapper().writeValueAsString(user));
+ log.info("=================================================================================");
+ }
+
+ @Data
+ @ToString
+ @Builder
+ public static class User {
+ private final String login;
+ private final String password;
+ private final String ssn;
+ }
+}
diff --git a/examples/spring-boot-logback-logstash-example/src/main/resources/logback.xml b/examples/spring-boot-logback-logstash-example/src/main/resources/logback.xml
new file mode 100644
index 0000000..43b552f
--- /dev/null
+++ b/examples/spring-boot-logback-logstash-example/src/main/resources/logback.xml
@@ -0,0 +1,33 @@
+
+