Outshift Logo


8 min read

Blog thumbnail
Published on 04/10/2018
Last updated on 05/03/2024

Secure logging on Kubernetes with Fluentd and Fluent Bit


Update: Logging operator v3 (released March, 2020)

We're constantly improving the logging-operator based on feature requests of our ops team and our customers. The main features of version 3.0 are:
  • Log routing based on namespaces
  • Excluding logs
  • Select (or exclude) logs based on hosts and container names
  • Logging operator documentation is now available on the Banzai Cloud site.
Check The Kubernetes logging operator reloaded post for details.
As we eluded to in the last post in this series, we'll be continuing our discussion of centralized and secure Kubernetes logging/log collection. Log messages can contain sensitive information, so it's important to secure transport between distributed parts of the log flow. This post will describe how we've secured moving log messages on our Kubernetes clusters provisioned by Pipeline.
Logging series: Centralized logging under Kubernetes Secure logging on Kubernetes with Fluentd and Fluent Bit Advanced logging on Kubernetes

Client Certificate Authentication

Most of us are familiar with the TLS protocol that secures connections like HTTPS. We will briefly discuss the basics of TLS handshakes and certificate management, just to make sure we're not glossing over any details about securing the transportation of log messages. In a simple use-case the server owns its private key and a certificate that is signed by a trusted third-party. When the client connects and receives the server certificate, the server verifies the signature. After that, it opens an encrypted channel that is secured by one of the symmetric cipher protocols. In order to provide client authentication, the protocol is extended with mTLS. You can read more about mTLS here. The most common way of enabling mutual authentication is to use client certificates. In this scenario as well, the client has a private key and certificate (signed by the third-party CA), which the server verifies. For a more detailed overview, read this great article by CloudFlare. client-cert-auth

Setting up the Certificates

By following these easy steps you can set-up your own environment to secure your logging infrastructure. If you want a detailed how-to, check this OpenSSL Certificate Authority article. We will follow three easy steps:
  • Generate the Root CA (for self-signed certificates)
  • Generate the Intermediate Server Cert (Singed with CA)
  • Generate the Client certificate (Signed with CA)

Prepare the working directory

mkdir certs csr private touch
index.txt echo "1000" > serial

You need a specific openssl configuration file openssl.cnf

To eliminate problems that arise from different OpenSSL settings, we should make sure to use a custom configuration with explicit settings (and not fall back on defaults).

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir               = .
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

# The root key and root certificate.
private_key       = $dir/private/ca.key.pem
certificate       = $dir/certs/ca.crt.pem

# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 365
preserve          = no
policy            = policy_strict

[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 4096
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name (required)
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = US
stateOrProvinceName_default     = CA
#localityName_default           = Mountain View
0.organizationName_default      = Your company name
#organizationalUnitName_default =
emailAddress_default            = foo@example.com

subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer
basicConstraints = critical,CA:true
keyUsage = critical, cRLSign, digitalSignature, keyCertSign

[ client_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

Generate files for Certification Authority (CA)

Before we start, we need a CA private key file, which we'll use this to sign the certificates: ca.key.pem.
openssl genrsa -aes256 -out
private/ca.key.pem 4096
It's highly recommended that you protect keys with passwords, although you can skip that step by removing the -aes256 option.

Generate cert for Certification Authority (CA)

Using the key, we'll generate the CA certificate: ca.crt.pem.
openssl req -config openssl.cnf \
 -key private/ca.key.pem \
 -new -x509 -days 365 -sha256 -extensions v3_ca \
 -out certs/ca.crt.pem

Generate Server Private Key

We'll need a private key for the server component, as well: server.key.pem
openssl genrsa -aes256 -out
private/server.key.pem 4096

Create Server Certificate Sign Request (csr)

To produce a signed certificate, we first need to generate a request: server.csr
openssl req -config openssl.cnf \
 -key private/server.key.pem \
 -new -sha256 -out csr/server.csr.pem

Self Sign Server CSR with CA

As soon as we get the CSR, we can sign it with our CA: server.crt.pem
openssl ca -config openssl.cnf
-outdir certs \
 -cert certs/ca.crt.pem \
 -keyfile private/ca.key.pem \
 -extensions server_cert -days 365 -notext -md sha256 \
 -in csr/server.csr.pem \
 -out certs/server.crt.pem
Fill out Common Name as hostname or fqdn, ie: server

Create the Client certificates

We'll follow similar steps to create the server certificate.
  • Create a private key: client.key.pem
    openssl genrsa -aes256 -out
    private/client.key.pem 4096
  • Create a certification request: <code>client.csr.pem
    openssl req -config openssl.cnf \
     -key private/client.key.pem \
     -new -sha256 \
     -out csr/client.csr.pem
  • Sign the Client CSR with CA: client.crt.pem
    openssl ca -config openssl.cnf
    -outdir certs \ 
    -cert certs/ca.crt.pem \ 
    -keyfile private/ca.key.pem \ 
    -extensions client_cert -days 365 -notext -md sha256 \ 
    -in csr/client.csr.pem \ 
    -out certs/client.crt.pem
Fill out Common Name as hostname or fqdn, ie: client

Check the certificates

You're ready to use your fresh certificates. However, it's highly advised that you test them to make sure they were created correctly. Please run the following OpenSSL commands in two different shells to check two-way authentication.

Secure logging in action

To use the above, we need to enable TLS auth for Fluent-bit and Fluentd

Enable TLS on Fluent-bit

According to the documentation, "Each output plugin that requires to perform Network I/O can optionally enable TLS...". Great, we've already generated the necessary certificates, so now we just need to modify the configuration. For further information please read the official manual. This is a snippet from our Fluent-bit Helm chart, where the configuration looks like this:
    Name          forward
    Match         *
    Host          {{ .Values.backend.forward.host }}
    Port          {{ .Values.backend.forward.port }}
    Retry_Limit False
    tls               On
    tls.verify        {{ .Values.backend.forward.tls.verify }}
    tls.ca_file       /fluent-bit/ssl/ca.crt.pem
    tls.crt_file      /fluent-bit/ssl/client.crt.pem
    tls.key_file      /fluent-bit/ssl/client.key.pem
    tls.key_passwd    ${TLS_PRIVATE_KEY_PASSPHRASE}
    Shared_Key        ${SHARED_KEY}
The ${VARABLE_NAME} values indicate the values that come from Environment variables.
To use the certificates as Secrets in Kubernetes, we have to upload them to the cluster.
kubectl create secret generic
fluentbit-tls \
 --from-file=ca.crt.pem=./certs/ca.crt.pem \
 --from-file=client.crt.pem=./certs/client.crt.pem \
 --from-file=client.key.pem=./private/client.key.pem \
 --from-literal 'server.key.passphrase=1111' \
 --from-literal 'fluent.shared.key=shared1234'
After this, we only need to attach the secret to the pod with the /fluent-bit/ssl/ path.

Enable TLS on Fluentd

Luckily, with the latest Fluentd we don't need the secure_input plugin. as it's already bundled with the core. For more information, check the official documentation. This is a snippet from our custom Fluentd chart:
  @type   forward
  port    24231
  @log_level debug
    self_hostname fluentd
    shared_key fluentd
  &lt;transport tls&gt;
    version                TLSv1_2
    ca_path                /fluentd/etc/ssl/ca.crt.pem
    cert_path              /fluentd/etc/ssl/server.crt.pem
    private_key_path       /fluentd/etc/ssl/server.key.pem
    private_key_passphrase &quot;#{ENV[&quot;TLS_PRIVATE_KEY_PASSPHRASE&quot;]}&quot;
    client_cert_auth       true
The #{ENV["VARABLE_NAME"]} values indicate that the values come from Environment variables.
We need to upload the server secret to the cluster.
kubectl create secret generic
fluentd-tls \
 --from-file=ca.crt.pem=./certs/ca.crt.pem \
 --from-file=server.crt.pem=./certs/server.crt.pem \
 --from-file=server.key.pem=./private/server.key.pem \
 --from-literal 'server.key.passphrase=1234' \
 --from-literal 'fluent.shared.key=shared1234'

Now put it together

In our first post, we familiarized ourselves with the tools used for collecting, aggregating and storing  logs. In this article we extended those solutions with secure communication channels and authentication. log-architecture In the next post in this series, we'll simplify all of this in a Pipeline spotguide (publishing the Helm charts as well), and demonstrate some advanced log parsing and monitoring techniques, too.
Subscribe card background
Subscribe to
the Shift!

Get emerging insights on emerging technology straight to your inbox.

Unlocking Multi-Cloud Security: Panoptica's Graph-Based Approach

Discover why security teams rely on Panoptica's graph-based technology to navigate and prioritize risks across multi-cloud landscapes, enhancing accuracy and resilience in safeguarding diverse ecosystems.

the Shift
emerging insights
on emerging technology straight to your inbox.

The Shift keeps you at the forefront of cloud native modern applications, application security, generative AI, quantum computing, and other groundbreaking innovations that are shaping the future of technology.

Outshift Background