rsyslog TLS 상호 인증 연동 설정

리눅스 서버에서 인터넷을 거쳐 클라우드 SIEM에 로그를 전송하려면 TLS 설정이 필수적입니다. 하지만 TLS를 통해 안전하게 시스로그(Syslog)를 전송하도록 설정하려면 인증서를 설치하는 과정이 포함되기 때문에 좀 더 복잡하고 어렵게 느껴질 수 있습니다. 이 글에서는 rsyslog를 로그프레소에 연동하는 방법과 문제 발생 시 진단 방법을 소개합니다.

CA 인증서 준비

아래는 유효 기간 100년으로 CA 인증서를 생성하고, syslog-ca.pem과 syslog-ca-key.pem 파일을 합쳐서 PKCS#12 인증서 파일을 생성합니다. 아래는 RSA 키를 생성하는 과정의 예시입니다.

$ openssl req -new -x509 -keyout syslog-ca-key.pem -out syslog-ca.pem -days 36500
Generating a RSA private key
.......................+++++
..........+++++
writing new private key to 'syslog-ca-key.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:KR
State or Province Name (full name) []:Seoul
Locality Name (eg, city) [Default City]:Mapo
Organization Name (eg, company) [Default Company Ltd]:Logpresso
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:syslog-ca

아래는 PKCS#12 인증서 파일을 생성하는 과정의 예시입니다. PEM 파일에서 비밀 키를 읽어들이기 위해 암호를 물어보고, 새로 생성하는 PKCS#12 파일에 설정할 암호를 다시 요구합니다.

$ openssl pkcs12 -export -out syslog-ca.pfx -in syslog-ca.pem -inkey syslog-ca-key.pem
Enter pass phrase for syslog-ca-key.pem:
Enter Export Password:
Verifying - Enter Export Password:

syslog-ca.pem 파일은 자바 애플리케이션에서 신뢰할 수 있는 루트 인증 기관을 인식할 수 있도록 JKS (Java KeyStore) 파일 형식으로 변환해야 합니다. 먼저 비밀 키를 포함하지 않은 PKCS#12 형식의 CA 인증서 syslog-ca-nokey.pfx 파일을 생성합니다.

$ keytool -importcert -keystore syslog-ca-nokey.pfx -file syslog-ca.pem -alias ca
Enter keystore password:
Re-enter new password:
Owner: CN=syslog-ca, O=Logpresso, L=Mapo, ST=Seoul, C=KR
Issuer: CN=syslog-ca, O=Logpresso, L=Mapo, ST=Seoul, C=KR
Serial number: 1b7cada9fedd02d1980ea28619719d7647ab037e
Valid from: Sat May 27 18:31:05 KST 2023 until: Mon May 03 18:31:05 KST 2123
Certificate fingerprints:
         SHA1: 8D:72:78:0D:75:85:A2:68:1E:31:69:37:E8:57:5C:1D:79:90:FA:C5
         SHA256: 4B:6E:E6:BE:17:7D:FC:3E:BE:E1:3B:06:3C:8B:84:43:97:84:23:32:72:5D:72:1D:03:AE:A1:30:EA:72:64:E2
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3

Extensions:

#1: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: F3 1B 54 63 91 2A DF 6D   1D FD 17 77 51 30 6D 63  ..Tc.*.m...wQ0mc
0010: 5F 40 35 EB                                        _@5.
]
]

#2: ObjectId: 2.5.29.19 Criticality=true
BasicConstraints:[
  CA:true
  PathLen:2147483647
]

#3: ObjectId: 2.5.29.15 Criticality=false
KeyUsage [
  Key_CertSign
]

#4: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: F3 1B 54 63 91 2A DF 6D   1D FD 17 77 51 30 6D 63  ..Tc.*.m...wQ0mc
0010: 5F 40 35 EB                                        _@5.
]
]

Trust this certificate? [no]:  yes
Certificate was added to keystore

이 PKCS#12 파일을 JKS 형식의 파일로 변환하면 공개적으로 배포 가능한 키스토어 파일이 됩니다.

$ keytool -importkeystore -srckeystore syslog-ca-nokey.pfx -srcstoretype pkcs12 -destkeystore syslog-ca.jks -deststoretype jks
Importing keystore syslog-ca-nokey.pfx to syslog-ca.jks...
Enter destination keystore password:
Enter source keystore password:
Entry for alias ca successfully imported.
Import command completed:  1 entries successfully imported, 0 entries failed or cancelled

서버 인증서 준비

$ openssl genpkey -algorithm RSA -out logpresso-key.pem -pkeyopt rsa_keygen_bits:4096
$ openssl req -new -key logpresso-key.pem -out logpresso-csr.pem -subj "/CN=logpresso"
$ openssl x509 -req -in logpresso-csr.pem -CA syslog-ca.pem -CAkey syslog-ca-key.pem -CAcreateserial -out logpresso-cert.pem -days 36500
$ openssl pkcs12 -export -out logpresso.pfx -inkey logpresso-key.pem -in logpresso-cert.pem -certfile syslog-ca.pem

첫번째 명령은 4096비트의 RSA 비밀 키를 생성합니다. 두번째 명령은 이 RSA 키를 이용해서 CSR (Certificate Signing Request) 파일을 생성합니다. 세번째 명령은 사설 CA 인증서를 이용하여 서명된 logpresso-cert.pem 파일을 생성합니다. 네번째 명령은 비밀 키가 포함된 logpresso-key.pem 파일, 공개 키가 포함된 logpresso-cert.pem과 syslog-ca.pem 파일을 결합하여 PKCS#12 형식의 logpresso.pfx 파일을 생성합니다.

클라이언트 인증서 준비

로그프레소 서버가 클라이언트를 인증하려면 rsyslog 데몬이 접속할 때 클라이언트 인증서를 제출하도록 해야 합니다. 임의의 클라이언트가 서버에 접속하는 것을 허용한다면 이 단계는 생략할 수 있습니다. 아래의 명령들은 클라이언트 인증서 파일 이름을 제외하면 서버 인증서 준비 단계의 명령과 동일합니다.

$ openssl genpkey -algorithm RSA -out rsyslog-key.pem -pkeyopt rsa_keygen_bits:4096
$ openssl req -new -key rsyslog-key.pem -out rsyslog-csr.pem -subj "/CN=rsyslog"
$ openssl x509 -req -in rsyslog-csr.pem -CA syslog-ca.pem -CAkey syslog-ca-key.pem -CAcreateserial -out rsyslog-cert.pem -days 36500
$ openssl pkcs12 -export -out rsyslog.pfx -inkey rsyslog-key.pem -in rsyslog-cert.pem -certfile syslog-ca.pem

로그프레소 TLS 포트 설정

/opt/logpresso/cert 디렉터리를 생성하고, 이 디렉터리에 이전 단계에서 만든 인증서 파일들을 복사합니다.

  • syslog-ca.jks: 신뢰할 수 있는 CA 인증서를 포함한 파일
  • logpresso.pfx: 로그프레소가 TLS 서버를 여는데 필요한 비밀 키를 포함한 PKCS#12 파일

로그프레소 쉘에 접속하여 각 인증서를 등록합니다.

logpresso> keystore.register syslog-key PKCS12 /opt/logpresso/cert/logpresso.pfx
Password?
[syslog-key] key store registered

logpresso> keystore.register syslog-ca JKS /opt/logpresso/cert/syslog-ca.jks
Password?
[syslog-ca] key store registered

이제 등록한 인증서를 이용하여 TLS 시스로그 서버 포트를 개방합니다.

logpresso> syslog.openTls mtls syslog-key 6514 0.0.0.0 utf-8 syslog-ca
opened TLS syslog server [/0.0.0.0:6514]

로그프레소 서버에서 클라이언트를 인증하지 않고 임의의 TLS 접속을 허용하려면 마지막 syslog-ca 인자를 제외합니다.

rsyslog 설정

rsyslog에서 TLS 전송을 사용하려면 rsyslog-gnutls 패키지를 설치해야 합니다. 아래는 Rocky 8.7 서버에서 rsyslog-gnutls 패키지를 설치하는 방법입니다.

$ sudo yum install rsyslog-gnutls

/etc/rsyslog.d/cert 디렉터리를 생성하고, 이 디렉터리에 이전 단계에서 생성한 파일을 복사합니다. 복사 후에는 rsyslog 데몬 구동 계정으로 소유자를 변경하고, chmod 400 /etc/rsyslog.d/cert/* 명령으로 소유자만 읽기 권한을 가지도록 조정합니다.

  • syslog-ca.pem: rsyslog에서 신뢰할 수 있는 CA를 지정하는데 필요한 인증서 파일
  • rsyslog-cert.pem: 클라이언트 인증서 (공개 키)
  • rsyslog-key.pem: 클라이언트 비밀 키

이제 /etc/rsyslog.d/logpresso.conf 파일을 아래의 내용으로 생성합니다. LOGPRESSO_IP 부분을 로그프레소 서버의 IP 주소로 변경합니다.

$WorkDirectory /var/spool/rsyslog # 네트워크 단절 시 데이터를 임시 보관할 디렉터리
$ActionQueueFileName logpresso    # 데이터 임시 보관 시 생성되는 파일의 접두어
$ActionQueueMaxDiskSpace 1g       # 임시 보관 최대 용량
$ActionQueueSaveOnShutdown on     # rsyslog 데몬 중지 시 디스크에 저장
$ActionQueueType LinkedList       # 제한 없이 데이터 유지
$ActionResumeRetryCount -1        # TLS 서버에 접속 될 때까지 지속적으로 재시도

# rsyslog-gnutls 설정
$DefaultNetstreamDriverCAFile /etc/rsyslog.d/cert/syslog-ca.pem
$DefaultNetstreamDriverCertFile /etc/rsyslog.d/cert/rsyslog-cert.pem
$DefaultNetstreamDriverKeyFile /etc/rsyslog.d/cert/rsyslog-key.pem

template(name="Logpresso" type="string" string="<%pri%>%timestamp:::date-rfc3339% %HOSTNAME% %app-name% %msg%\n")

# 6514 포트로 TLS 접속한 후 Logpresso 템플릿으로 포맷팅된 로그를 전송
action(type="omfwd" protocol="tcp" target="LOGPRESSO_IP" port="6514" template="Logpresso" StreamDriver="gtls" StreamDriverMode="1" StreamDriverAuthMode="x509/name" StreamDriverPermittedPeers="*logpresso")

로그프레소 서버에서 클라이언트를 인증하지 않는다면 $DefaultNetstreamDriverCertFile 와 $DefaultNetstreamDriverKeyFile 항목은 설정할 필요가 없습니다. StreamDriverPermittedPeers 설정은 rsyslog 측에서 로그프레소 TLS 서버 인증서의 CN을 검증하는 용도로 사용합니다.

/var/spool/rsyslog 디렉터리를 생성합니다. 이 디렉터리를 생성하지 않고 rsyslog 서비스를 시작하면 아래와 같은 에러 로그가 발생하면서 정상적으로 동작하지 않습니다.

May 27 00:32:36 demo rsyslogd: $WorkDirectory: /var/spool/rsyslog can not be accessed, probably does not exist - directive ignored [v8.24.0-57.el7_9.3 try http://www.rsyslog.com/e/2181 ]

이제 모든 준비가 끝났습니다. rsyslog 서비스를 시작합니다.

$ sudo systemctl start rsyslog

시스로그 수신 검증

로그프레소 쉘에서 syslog.trace 명령을 실행하면 실시간으로 아래 예시와 유사한 출력을 확인할 수 있습니다.


logpresso> syslog.trace mtls
press ctrl-c to stop
------------------------
[2023-05-27 18:25:26.221+0900] (/LOGPRESSO_IP:50436) => [fc:3, sv:6] 2023-05-27T20:20:01.127517+09:00 demo systemd Started Session 36661 of user logpresso.

만약 rsyslog에서 로그프레소 TLS 서버에 설치된 인증서에 대한 StreamDriverPermittedPeers 설정이 잘못되면 rsyslog 로그에서 아래와 같은 에러를 보게 됩니다.

May 27 20:21:42 wood rsyslogd: error: peer name not authorized -  not permitted to talk to it. Names: CN: logpresso;  [v8.24.0-57.el7_9.3 try http://www.rsyslog.com/e/2088 ]

만약 rsyslog에 설치된 CA 인증서가 로그프레소 서버의 CA 인증서와 일치하지 않으면, 로그프레소 서버에서는 아래와 같은 에러 로그를 보게 됩니다.

[2023-05-27 19:40:47.764] ERROR (TlsSyslogReceiver) - logpresso syslog: TLS client [/LOGPRESSO_IP:51558] error
javax.net.ssl.SSLHandshakeException: No trusted certificate found
        at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:352)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:295)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:290)
        at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkClientCerts(CertificateMessage.java:700)
        at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:411)
        at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:375)
        at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
        at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443)
        at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:421)
        at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:182)
        at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
        at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1501)
        at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1411)
        at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:451)
        at java.base/sun.security.ssl.SSLSocketImpl.ensureNegotiated(SSLSocketImpl.java:916)
        at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:1007)
        at java.base/java.io.InputStream.read(InputStream.java:205)
        at org.araqne.syslog.TlsSyslogReceiver$TlsClientReceiver.run(TlsSyslogReceiver.java:303)
Caused by: sun.security.validator.ValidatorException: No trusted certificate found
        at java.base/sun.security.validator.SimpleValidator.buildTrustedChain(SimpleValidator.java:411)
        at java.base/sun.security.validator.SimpleValidator.engineValidate(SimpleValidator.java:135)
        at java.base/sun.security.validator.Validator.validate(Validator.java:264)
        at java.base/sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:313)
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:222)
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkClientTrusted(X509TrustManagerImpl.java:123)
        at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkClientCerts(CertificateMessage.java:688)
        ... 14 more

이 때 rsyslog 호스트에서는 아래와 같은 로그를 확인할 수 있습니다.

May 27 19:44:50 demo systemd: Started System Logging Service.
May 27 19:44:50 demo rsyslogd: unexpected GnuTLS error -12 in nsd_gtls.c:1840: A TLS fatal alert has been received.  [v8.24.0-57.el7_9.3 try http://www.rsyslog.com/e/2078 ]
May 27 19:44:50 demo rsyslogd: action 'action 1' suspended, next retry is Sat May 27 19:45:20 2023 [v8.24.0-57.el7_9.3 try http://www.rsyslog.com/e/2007 ]

둘러보기

더보기