Advance Log4j2 Configurations: Masking Sensitive Data with Patterns

Emrullah YILDIRIM
4 min readSep 25, 2023

--

Son kullanıcıların en büyük endişesi kişisel verilerin korunması; acaba verilerim güvenli bir şekilde saklanıyor mu? Kişisel verilerime kimler erişebiliyor? gibi soruları akıllara getirmektedir. Uygulamalarda geliştiriciler zaman içerisinde oluşan hataları çözmek yada yapılan işlemleri yasal kısıtlar gereği kayıt altına almaktadır.

Peki; binlerce kişi tarafından kullanılan bir ödeme sistemi uygulamasında kredi kartı bilgileriniz loglanırsa ve bu loglara erişebilen herhangi bir developer bu bilgileri kötü niyetle kullanmak isterse?

Bu yazı, Log4J2'yi kullanarak JSON nesnelerini bir Spring boot Java uygulamasında loglamaya çalışırken; yaşadığım deneyimlerimin sonucudur. Uygun bir dökümantasyon bulamadığım için bunu başarmanın gerçekten zor olduğunu düşünüyorum. Bu nedenle, eğer böyle bir durumla uğraşmak zorunda kalırsanız, bu yazının size yardımcı olacağını umuyorum.

1. Problem

Genellikle birçok projede common modülü altında OncePerRequestFilter sınıfından kalıtılan bir class oluştururuz. Bu classta contextholderı yada security kısıtlarımızı yapılandırabileceğimiz gibi aynı zamanda gelen giden tüm istekleride loglayabiliriz.

Peki; loglanan request/response lardaki hassas verileri nasıl maskeleyeceğiz?

2. Çözümler

Bunun nasıl yapılacağına dair yolları ararken birkaç blog ve stackoverflow sayfalarında RewritePolicy ve önceden geliştirilmiş bazı pluginler buldum. Ancak bu çözümlerin hiçbiri aslında sorunumu çözmedi.

Apache kendi log4j dökümantasyonlarında “Layouts” kavramı üzerinde durmaktadır. Log4j2 kütüphanesi loglarınız için birden fazla layouts sağlayarak kendi formatınıza esneklik sağlar. Örnek olarak XML, CSV, HTML, YAML, PATTERN, JSON gibi layoutslar bulunmaktadır.

Tüm layoutslar hakkında detaylı bilgi için Apache dökümantasyonunu incelemenizi tavsiye ederim.

Biz ihtiyacımız olan maskeleme için PatternLayouts kullanacağız. org.apache.logging.log4j.core.pattern package altında LogEventPatternConverter sınıfından türeyen aşağıdaki gibi bir class oluşturuyoruz.

Bu classta format methodunu override ederek önce elimizdeki eventi serialiazable bir nesne ile stringe çevirip daha sonra elimizdeki stringi regex ifadesi kullanarak maskeleyeceğiz. Aslında tüm iş loga basılacak olan nesneyi elde edip onu manipüle etmek.

Basit anlamda bu şekilde bir plugin oluşturduk. Şimdi sıra geldi konfigürasyona log4j2.xml dosyasını aşağıdaki gibi yapılandırıyoruz.

Burada anahtar kelime ise mask. Pattern içerisinde, MaskConverter sınıfında @ConverterKeys anatasyonunda belirtildiği gibi burayı eşleştiriyoruz.

Sonuç olarak artık SYSTEM_OUT ‘a yönlendirilen INFO seviyesindeki tüm logları basarken key içeren tüm alanların değerini MaskConverter sınıfındaki regexlere göre maskeliyor olacağız.

Buraya kadar herşey kolay gelmiş olabilir birçok senaryoda bu çözümler sorunumuzu gideriyor. Fakat bu çözümlerin hepsinde bazı sorunlar var.

3. Olası Senaryolar

Projenizin ihtiyaçlarına göre aşağıdaki senaryolarla karşılaşmanız çok olası. Biz merkezi bir sınıfta loglanan request/responseları maskelemek istiyorduk.

Senaryo 1: farklı sınıflardaki tüm key ifadelerini maskelemek istemiyorsak?

Senaryo 2: farklı layout tiplerinde çözümler?

Senaryo 3: class/package seviyesinde bu maskelemeyi yönetebilir miyiz?

Eğer farklı requestlerdeki “key” değerlerini maskelemek istemiyorsak bunu log4j2.xml dosyasında ufak bir trick ile çözebiliriz. Örnek olarak aşağıdaki konfigürasyonu ele alalım.

Burada console için çoklu appender tanımı yaparak com.x.y package altındaki INFO seviyesindeki logları sensitive patterne com.x.z package altındaki INFO seviyesindeki logları ise ConsoleDef patterne yönlendirerek maskelemeyi yönetebiliriz.

Fakat bizim senaryomuzda unutulmaması gereken unsur loglar tek bir merkezi sınıftan türeyen OncePerRequestFilter classtan aktığı için bu yöntem işe yaramayacaktır.

3.1 JsonLayout vs PatternLayout

PatternLayout; bu sınıfın amacı bir log eventi manipule etmektir. Sonucun biçimi yukarıda oluşturduğumuz gibi bir sınıftaki regex yada kendi düzenize bağlı olabilir.

JsonLayout; bu sınıfın amacı ise bir log eventi düz bir json nesnesi olarak basmaktır. complete = “true” olarak yapılandırırsanız, well-formatted bir JSON çıktısını alır.

JSONLayout ile log eventi manipüle etmenin bir yolunu bulamadım fakat bunun için log4j github hesabında bir discussion oluşturdum. Takibini burdan sizde yapabilirsiniz.

3.2 CustomLayout Tanımlama

CustomLayout’u oluşturmak için referans olarak dökümantasyonu alıyoruz.

public class CustomMessage implements Message {
private static final String TYPE = "type";
private static final String BODY = "body";

private final Map<String, Object> requestBody;

public CustomMessage(Map<String, Object> requestBody) {
this.requestBody = requestBody;
}

@Override
public String getFormattedMessage() {
JSONObject jsonBody = new JSONObject(requestBody);
JSONObject jsonToLog = new JSONObject(new HashMap<String, Object>() {{
put(TYPE, "custom");
put(BODY, jsonBody);
}});

return jsonToLog.toString();
}

@Override
public String getFormat() {
return requestBody.toString();
}

@Override
public Object[] getParameters() {
return new Object[0];
}

@Override
public Throwable getThrowable() {
return null;
}
}
@Plugin(name = "CustomLayout", category = "Core", elementType = "layout", printObject = true)
public class CustomLayout extends AbstractStringLayout {

private static final String DEFAULT_EOL = "\r\n";

protected CustomLayout(Charset charset) {
super(charset);
}

@PluginFactory
public static CustomLayout createLayout(@PluginAttribute(value = "charset", defaultString = "UTF-8") Charset charset) {
return new CustomLayout(charset);
}

@Override
public String toSerializable(LogEvent logEvent) {
return logEvent.getMessage().getFormattedMessage() + DEFAULT_EOL;
}
}

Burada önemli olan LogEvent parametresiyle birlikte gelen toSerializable methodur. Buradan mesaj nesnesini alıyoruz ve onu bir Stringe döndürerek manipule ediyoruz. CustomMessage ile sınıfı ile json objemizi belirtiyoruz.

Log4j2.xml dosyasında bu oluşturduğumuz CustomLayout ile konfigürasyon yapmayı unutmuyoruz.

private static Logger customLayout = LogManager.getLogger("LOGGER_WITH_CUSTOM_LAYOUT");

void logWithCustomLayout(Map<String, Object> object) {
customLayout.info(new CustomMessage(object));
}

Custom loglama objemizi kullanmak için ise öncelikle LoggerManager sınıfından kendi oluşturduğumuz log sınıfımızı initialize ediyoruz ve loglamak istediğimiz objemizi kendi yaratığımız obje sınıfı ile kapsüllüyoruz.

Kaynaklar ve Sonuç

Umarım bu gönderiyi faydalı bulmuşsunuzdur ve bu tür logları oluşturmanın daha kolay veya daha uygun bir yolunu bulabilirsiniz. Yorum yapmaktan çekinmeyin.

--

--

Emrullah YILDIRIM

Kendine Blogger. Yazarak öğrenen, yazdıklarini paylaşan Java Software Engineer.