< 아래의 글을 참고로 작성했다. >
Custom Log4Net appender
docs.particular.net/samples/logging/log4net-custom/
Pattenlayout 내 커스텀 Converter 추가하기.
devstuffs.wordpress.com/2012/01/12/creating-your-own-pattern-layout-converter-for-log4net/
현재 회사 프로젝트에서 log4net을 많이 이용하고 있다. 어느 정도의 리소스 소모가 발생하는 것은 어쩔 수 없지만, 순간 순간의 현재 상태를 찾아 내려고 로그를 작성할 때, 최소한 내가 만든 것보다 매우 효율적으로 동작하는 것은 확실했다.그래서 잘 사용해 왔는데...
로그 내용이 날 Text로 표시되다 보니, 문제가 내부 로직을 유추할 수 있다는 문제가 발생했다. 그래서 어떻게 해야하나 싶어 고민하는 중, 로그 출력을 담당하는 Appender의 로직 안에 Log를 찍기 직전에 Message 내용을 암호화 하면 된다는 생각이 들었다. 이래 저래 Googling을 통해서 다양한 정보들을 접하면서 하나씩 찾아 적용해보면서 대략적인 방향이 잡혔다. 그 내용을 정리한다.
1. Appender 만들기
log4net에서 사용가능한 Appender를 만들려면, IAppender 인터페이스 혹은 IBulkAppender 인터페이스를 구현해야 한다. 그리고 IOptionHandler 인터페이스도 같이 구현해서 다양한 설정 값을 처리해야 한다. 그래서 이 인터페이스 구현에 대한 최소 버전인 AppenderSkeleton을 상속 받아 구현해도 된다. 하지만, 단순히 파일 기반의 Appender에 대한 수정이라, 나는 RollingFileAppender를 상속 받았다.
이 클래스 안에 있는 void Append(log4net.Core.LoggingEvent loggingEvent) 함수만 override 했다. 여기서 파라미터로 받는 loggingEvent 안에는 로그로 출력할 정보에 대한 대부분의 정보가 담겨 있는데, 그 중 메시지 값만 암호화 하면 된다.
string newMessage = {암호화 처리 함수}(loggingEvent.RenderedMessage);
암호화 처리를 하는 함수를 별도로 구현해서 loggingEvent 안에 있는 메시지 값을 암호화 한 뒤, 문자열로 돌려줄 수 있게만 해주면 된다. 이렇게 암호화된 문자열을 아래의 코드와 같이 loggingEvent 안에 있는 Message 프로퍼티에다 새로 만든 암호화된 문자열을 Set 해주면 된다.
FieldInfo _loggingEventm_dataFieldInfo = typeof(LoggingEvent).GetField("m_data", BindingFlags.Instance | BindingFlags.NonPublic);
LoggingEventData loggingEventData = (LoggingEventData)_loggingEventm_dataFieldInfo.GetValue(loggingEvent);
loggingEventData.Message = newMessage;
_loggingEventm_dataFieldInfo.SetValue(loggingEvent, loggingEventData);
override를 했으니 맨 나중의 값은 base를 불려주도록 해준다.
base.Append(loggingEvent);
2. 암호화 함수 만들기
사실 암호화 방법은 다양하게 있어 자신이 속해있는 조직별, 프로젝트별 암호화 로직은 다양하게 적용될 수 있어 딱 이렇게 하시라라는 부분은 없지만, 중요한 부분은 암호화 결과가 String으로 나와야 한다는 점이다. 보통 암호화 결과물은 바이트 배열이기 때문에, 이 점을 주의해야 할 필요가 있다. 일단 내가 사용한 암호화는 복호화를 하기 쉽게 하고 PC에 부담이 적은 암호화 로직을 사용한 예제로 아래와 같이 사용했다.
private string Encrypt(string str)
{
byte[] arySomeData = new byte[] { 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0 };
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
des.Key = arySomeData;
des.IV = arySomeData;
ICryptoTransform desdecrypt = des.CreateDecryptor();
byte[] src = Encoding.Default.GetBytes(str);
byte[] result = new byte[0];
using (MemoryStream ms = new MemoryStream())
{
CryptoStream cs = new CryptoStream(ms, desdecrypt, CryptoStreamMode.Write);
cs.Write(src, 0, src.Length);
cs.FlushFinalBlock();
aryResult = ms.ToArray();
}
return Convert.ToBase64String(result);
}
문자열을 입력 받으면 salt 값으로 암호화 처리 인스턴스를 받은 뒤, 암호화 하고, 최종적으로 base64 인코딩을 하도록 했다. 이렇게 작성하면 입력된 문자열을 암호화 하게 된다.
3. 설정 적용
원래는 app.config 나, log4net.xml 같은 설정 파일을 통해 적용할 수도 있지만, 외부에 노출된 설정 파일로 맘껏 변경할 수 있게하면, 굳이 암호화를 처리하기 위한 로직을 작성하는 이유가 없다. (그냥 Appender 설정을 log4net에서 제공하는 Appender로 변경하면 끝임) 그래서 프로그램 시작할 때, 이 Appender로 처리될 수 있도록 변경하는 로직을 만들어 적용했다.
Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
PatternLayout patternLayout = new PatternLayout();
patternLayout.ConversionPattern = "[%date %level (%t)[%property{uqid}] %logger - %message]%newline";
patternLayout.ActivateOptions();
EncryptionAppender appender = new EncryptionAppender();
appender.File = @".\EncryptedLog.log";
appender.Layout = patternLayout;
appender.StaticLogFileName = true;
appender.ActivateOptions();
hierarchy.Root.AddAppender(appender);
hierarchy.Root.Level = log4net.Core.Level.All;
hierarchy.Configured = true;
별 특이한 로직은 없다.
로그를 남길 때의 패턴을 구성하고, 위에서 만든 Appender를 생성한 뒤, 앞서 만든 패턴과 함께 설정을 한다.
그리고 난 뒤 앞서 가져온 루트 리파지토리에 Appender를 추가한다.
이제 외부에서 로그 내용의 암호화를 맘대로 끌 수 없게 된다.
4. 결론
굳이 암호화까지 해야 하나.. 라는 생각도 들지만, 내 디버깅 정보를 탈탈 털어서 역공학으로 소스로 만든 내용에 대한 이해/분석을 로그로 한다고 하니, 할 수 없이 암호화를 했다. 이 암호화 한 내용을 복호화 하여 처리하는 로직을 별도로 만들어봤다. 앞서 암호화 한 내용만 추출해서 복호화 할 수 있게만 했다.
워낙 후줄그래한 로직이라, 공개하긴 그렇지만, 일단 잘 암호화되어 저장되었음을 알 수 있었다.