Do you know if your Osquery client is connecting to the right server? Do you know if your Kolide server is accepting requests from rogue devices? If you have answered “I don’t know” to either of these questions then this blog post is for you. This blog post will be demonstrating how to setup Kolide + Osquery with mutual TLS (mTLS). Mutual TLS is a mechanism that can be implemented to verify the identity of the server and clients.
DISCLAIMER
This blog post is a proof of concept (POC) for a homelab and does implement best practices for an enterprise environment. Please review the Hashicorp Vault, NGINX, and Kolide documetation for best practices.
DISCLAIMER
Background
What is Mutual TLS?
As stated by Cloudflare, “Mutual TLS (mTLS) authentication ensures that traffic is both secure and trusted in both directions between a client and server.” For a more thorough deep dive on mutual TLS please visit this blog post. However, I think for the purpose of this blog post the graphic below provides a good overview of the mutual TLS communication and why it is a powerful mechanism in information security.
What is Osquery?
Osquery exposes an operating system as a high-performance relational database. This allows you to write SQL-based queries to explore operating system data. With osquery, SQL tables represent abstract concepts such as running processes, loaded kernel modules, open network connections, browser plugins, hardware events or file hashes.
What is Hashicorp Vault?
Vault is a tool for securely accessing secrets. A secret is anything that you want to tightly control access to, such as API keys, passwords, or certificates. Vault provides a unified interface to any secret, while providing tight access control and recording a detailed audit log. For this blog post, Vault will act as our certificate authority for our organization.
Explain the magic
Network diagram
Walkthrough
Mutual TLS security
In order to understand how mutal TLS (mTLS) works, I am going to discuss how it works in the context of Osquery (the client) and Kolide + NGINX (the server). When you browse the web you are only performing a one-way authentication which is verifying the server is who they claim to be. For example, any server on the internet could say they are your bank and host a copy of the website. Certificates allow us to verify that claim (identity) to ensure we aren’t entering our username and password for our bank account into a fake website.
With mutual TLS, the client is verifying the server’s identity and the server is verifying the client’s identity. NGINX is performing TLS termination and enforcing mTLS for all communcation for the Kolide service. First, Osquery attempts to connect to NGINX + Kolide (the server) and verifies the certificate being served by NGINX to verify the server’s identity. Once Osquery has verified the server’s identity, it sends its clients certificate and the server checks the client’s identity. Not only is the server checking the client’s identity but it also ensures that the client is authroized to access that service. Once the client (Osquery) has been verified, it completes the TLS handshake and communication between Osquery and Kolide can occur.
You might be asking what is the point of all this work?!?!? A very likely scenario is your organization is deciding whether to expose Kolide to the internet for when an endpoint is off the local network. The risk of exposing a Kolide instance to the internet is the fear of someone compromising it. With mutual TLS you can ensure that only authorized corporate devices are connecting to your NGINX + Kolide setup. Furthermore, mutal TLS provides protection if Kolide has a vulnerability but NGINX is enforcing client certificates. Assuming NGINX is also not vulnerable, an attacker would not be able to exploit this vulnerability without a client certificate. If you remember your networking 101 days, HTTP connections start with a TCP handshake before a client can communicate with a service. With HTTPS there is a TCP handshake and then a TLS handshake. If an attacker does not have a client certificate they can not complete a TLS handshake; therefore, they are unable to exploit the vulnerable Kolide server.
Generate certificates with Hashicorp Vault
This section assumes you have a certificate manager like Vault. If you do not have a certificate manager, in place take a look at my blog post: Install/Setup Vault for PKI + NGINX + Docker – Becoming your own CA.
Connect to Vault via command line
git clone https://github.com/CptOfEvilMinions/BlogProjects.git
cd BlogProjects/kolide-mutual-tls
- Obtain a Vault token
brew install vault
export VAULT_ADDR="http[s]://<Vault IP address>:<port>"
vault login
- Enter token
Obtain ROOT CA public certificate
vault read -format=json pki/cert/ca | jq -r '.data.certificate' > conf/tls/root_ca/root_CA.crt
Generate a leaf cert for Kolide web GUI
vault write -format=json pki_int/issue/<role> common_name="kolide.<domain>" ttl=8760h > conf/tls/kolide/kolide_<domain>.json
cat conf/tls/kolide/kolide_<domain>.json | jq -r '.data.private_key' > conf/tls/kolide/kolide_<domain>.key
- Extract private key from JSON blob
cat conf/tls/kolide/kolide_<domain>.json | jq -r '.data.certificate' > conf/tls/kolide/kolide_<domain>.crt
cat conf/tls/kolide/kolide_<domain>.json | jq -r '.data.ca_chain[]' >> conf/tls/kolide/kolide_<domain>.crt
- Extract public certificate from JSON blob
- Verify the public server certificate and private server key
openssl rsa -noout -modulus -in conf/tls/kolide/*.key | openssl md5
openssl x509 -noout -modulus -in conf/tls/kolide/*.crt | openssl md5
- Both commands should return the same MD5 hash
Generate an intermediate for devices
vault secrets enable -path=devices_<domain>_pki_int pki
- Enable the PKI secrets engine at devices_<domain>_pki_int path
vault secrets tune -max-lease-ttl=43800h devices_<domain>_pki_int
- Tune the devices_<domain>_pki_int secrets engine to issue certificates with a maximum time-to-live (TTL) of 26280 hours
- 26280 hours = 5 years
vault write -format=json devices_<domain>_pki_int/intermediate/generate/internal common_name="devices.<domain> Intermediate Authority" | jq -r '.data.csr' > devices_<domain>_pki_int.csr
- Generate an intermediate certificate and save the certificate signing request (CSR) to disk
vault write -format=json pki_int/root/sign-intermediate csr=@devices_<domain>_pki_int.csr format=pem_bundle ttl="43800h" | jq -r '.data.certificate' > devices_<domain>_pki_int.crt
- Sign the device intermediate certificate (devices.hackinglab.local) with the intermediate certificate (hackinglab.local) and write the generated certificate to disk
rm devices_<domain>_pki_int.csr
- Delete signing
vault write devices_<domain>_pki_int/intermediate/set-signed certificate=@devices_<domain>_pki_int.crt
- Import the signed certificate by the intermediate certificate into Vault
Create a role for device cert
vault write devices_<domain>_pki_int/roles/devices-<domain> allowed_domains="devices.<domain>" allow_subdomains=true max_ttl=26280h
- Create a role to allow the generation of certificates with subdomains and a max TTL of 26280 hours
- 26280 hours = 5 years
Generate a leaf certificate for device/Osquery client
vault write --format=json devices_<domain>_pki_int/issue/devices-<domain> common_name="<hostname>.devices.<domain>" ttl="26279h" > conf/tls/client/<hostname_devices_<domain>.json
- Request a device certificate
cat conf/tls/client/<hostname>_devices_<domain>.json | jq -r '.data.private_key' > conf/tls/client/<hostname>_devices_<domain>.key
- Extract private key from JSON blob
cat conf/tls/client/<hostname>_devices_<domain>.json | jq -r '.data.certificate' > conf/tls/client/<hostname>_devices_<domain>.crt
- Extract public certificate from JSON blob
cat conf/tls/client/device01_devices_hackinglab_local.json | jq -r '.data.ca_chain[]' > conf/tls/client/server_cert_chain.crt
cat conf/tls/root_ca/root_CA.crt >> conf/tls/client/server_cert_chain.crt
rm conf/tls/client/<hostname>_devices_<domain>.json
Convert PEM cert bundle to PKCS12
openssl pkcs12 -export -out conf/tls/client/<hostname>_devices_<domain>.p12 -inkey conf/tls/client/<hostname>_devices_<domain>.key -in conf/tls/client/<hostname>_devices_<domain>.crt -certfile conf/tls/root_ca/root_CA.crt
- Enter a password to protect PKCS12 cert bundle
Spin up Kolide with Docker
Docker-compose up
openssl rand -base64 32
- Copy output
- Paste the key from the previous command in
conf/kolide/kolide.yml
as the value forjwt_key
docker-compose build
docker-compose run --rm kolide fleet prepare db --config /etc/kolide/kolide.yml
docker-compose up -d
docker stats
Verify mutual TLS is working
- Open Firefox
- Browse to https://<Docker IP addr>:8443 which will prompt you with a warning
- Settings > Preferences > Privacy & Security > Certificates
- Select “View certificates”
- Select the “Authorities” tab
- Select “Import”
- Import
.../BlogProjects/kolide-mutual-tls/conf/tls/root_ca/root_CA.crt
- Select “Open”
- Select “Trust this CA to identify websites”
- Select “View certificates”
- Select the “Your certificates” tab
- Select Import
..../BlogProjects/kolide-mutual-tls/conf/tls/client/device01_devices_hackinglab_local.p12
- Select “Open”
- Enter password
- Browse to https://<Docker IP addr>:8443
- Select the new client certificate
Setup Kolide
- Set username and password
- Enter the username for admin
- Enter password
- Enter e-mail
- Select “Submit”
- Set Organization Details
- Enter org name
- Enter org URL
- Select “Submit”
- Set Kolide URL
- Enter https://<FQDN for Kolide>:8443
- Select “Submit”
- Select “Finish”
Install/Setup Osquery 4.4.0 on Ubuntu 20.04 with client certificate
Import client certificate
- Create an Ubuntu 20.04 VM
scp conf/tls/client/<hostname>_devices_<domain>.crt <user>@<ubuntu VM IP addr>:~/<hostname>_devices_<domain>.crt
scp conf/tls/client/<hostname>_devices_<domain>.key <user>@<ubuntu VM IP addr>:~/<hostname>_devices_<domain>.key
scp conf/tls/client/server_cert_chain.crt <user>@<ubuntu VM IP addr>:~/server_cert_chain.crt
- SSH into VM:
ssh <username>@<ubuntu VM IP addr>
sudo mkdir -p /etc/pki/tls/{certs,private}
mv server_cert_chain.crt /etc/pki/tls/certs/osquery_server_cert_chain.crt
- Move Osquery server CA chain to PKI location
mv <hostname>_devices_<domain>.crt /etc/pki/tls/certs/<hostname>_devices_<domain>.crt
- Move device public certificate to PKI location
mv <hostname>_devices_<domain>.key /etc/pki/tls/private/<hostname>_devices_<domain>.key
- Move device private key to PKI location
sudo chown root:root /etc/pki/tls/certs/osquery_server_cert_chain.crt /etc/pki/tls/certs/<hostname>_devices_<domain>.crt /etc/pki/tls/private/<hostname>_devices_<domain>.key
sudo chmod 640 /etc/pki/tls/certs/osquery_server_cert_chain.crt /etc/pki/tls/certs/<hostname>_devices_<domain>.crt /etc/pki/tls/private/<hostname>_devices_<domain>.key
Install Osquery 4.4.0
export OSQUERY_KEY=1484120AC4E9F8A1A577AEEE97A80C63C9D8B80B
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys $OSQUERY_KEY
sudo add-apt-repository 'deb [arch=amd64] https://pkg.osquery.io/deb deb main'
sudo apt-get update -y
sudo apt-get install osquery -y
Configure Osquery
curl https://raw.githubusercontent.com/CptOfEvilMinions/BlogProjects/master/kolide-mutual-tls/conf/osquery/osquery.flags --output /etc/osquery/osquery.flags
sed -i 's#kolide_tls_hostname#kolide.<domain>:8443#g' /etc/osquery/osquery.flags
- Set Kolide hostname and port
sed -i 's#kolide_tls_cert_file_path#/etc/pki/tls/certs/osquery_server_cert_chain.crt#g' /etc/osquery/osquery.flags
- Set Kolide server certificate file path
sed -i 's#device_tls_cert_file_path#/etc/pki/tls/certs/<hostname>_devices_<domain>_local.crt#g' /etc/osquery/osquery.flags
- Set device certificate file path
sed -i 's#device_tls_key_file_path#/etc/pki/tls/private/device01_devices_hackinglab_local.key#g' /etc/osquery/osquery.flags
- Set device private key file path
Register and start Osquery
- Back to Kolide WebGUI
- Select “Add New Host” in the top right
- Select “Reveal Secret” and copy it
- Back to SSH session
echo '<Kolide enroll secret>' > /etc/osquery/enroll.key
systemctl restart osqueryd
systemctl enable osqueryd
- Back to Kolide WebGUI
Future plans
This blog post is a precursor for another project I am working on. My next project is creating a web application that will allow incident responders to use the Osquery file carve feature to upload files. However, this application relies on the implementation of mutual TLS to ensure only authorized machines can upload data to the server.
Lessons learned
I am currently reading a book called “Cracking the Coding Interview” and it is a great book. One interesting part of the book is their matrix to describe projects you worked on and the matrix contains the following sections which are: challenges, mistakes/failures, enjoyed, leadership, conflicts, and what would you do differently. I am going to try and use this model at the end of my blog posts to summarize and reflect on the things I learn. I don’t blog to post things that I know, I blog to learn new things and to share the knowledge of my security research.
New skills/knowledge
- Improved my understanding of Hashicorp Vault
- Improved my understanding of mutal TLS and how to implement it
- Learned how to use the Hashicorp Vault command line tool
- Learned how to use OpenSSL to verify certificate chains and public cert + private key pairs.
Challenges
- Was not able to find best practices documents and tutorials
- Osquery’s
--tls_client_cert
option only accepts a single certificate. If a device certificate is part of a certificate chain with multiple intermediates, all intermediate certs and the root CA must be placed in--tls_server_certs
.
What You’d Do Differently
- Use Traefik to load balance Kolide traffic over multiple instances
- Enforce that only device certificates can talk to Kolide to report results and pull down updates. At the current moment, any user with a device certificate can access the Kolide admin console.
References
- Osquery – TLS client-auth enrollment
- Convert a PEM file and a private key to PKCS#12 (.p12) in OpenSSL
- SSL directory – Where to store SSL certificates on a Linux server?
- The magic of TLS, X509 and mutual authentication explained
- Cloudflare – Mutual TLS authentication
- Error fetching ca certificate #919
- Osquery downloads
- Osquery – Remote authentication
- Github – kolide/fleet – example_osquery.flags
- Github – kolide/fleet – Configuring The Fleet Binary
- Osquery – Command Line Flags
- Importing the root CA certificate to Debian and Ubuntu
- Build Your Own Certificate Authority (CA)
- mTLS image
- Osquery image
- Kolide image