How to secure a Kubernetes Nginx Ingress with Basic Auth
There are scenarios where we want to expose services outside our Kubernetes cluster that do not have proper authentication and authorization baked in. In these particular cases, it is possible to enable Basic Auth at the Ingress with few simple steps.
Note that this tutorial is specific to the Nginx Ingress Controller. Other ingress controllers might support the same functionality, but likely require different configuration steps.
Creating the auth file
The first step is creating the auth
file using htpasswd
.
# Install htpasswd
sudo apt update
sudo apt install apache2-utils
# Create auth file with htpasswd
htpasswd -c auth your-username
Replace your-username
with the username you intend to use. The program will then request you to provide the desired password and confirm it. Check the contents of the auth
file to make sure this step was completed successfully.
cat auth
# Sample output
your-username:$apr1$UwVKS.DO$MMwjWgydmSroyzBw5U8fC1
It is not possible to use another filename for the secret file. It has to be auth
.
Create a Kubernetes Secret
The second step is wrapping the auth file in a Kubernetes secret.
Make sure to create the secret in the same namespace where you intend to create the Ingress by replacing your-namespace
with the appropriate value.
kubectl -n your-namespace create secret generic basic-auth --from-file auth
Create or update your ingress
If you already have an ingress defined, you will just need to add the three annotations I highlight in the following code block. Otherwise, you can copy the entire ingress definition and modify it to fit your particular use case.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size: 10m
#
# Auth annotations required to configure Basic Auth
#
# The prompt message displayed to unauthenticated users
nginx.ingress.kubernetes.io/auth-realm: Authentication Required - Please enter
your credentials
# The name of the Kubernetes secret with the auth file.
nginx.ingress.kubernetes.io/auth-secret: basic-auth
# The authentication scheme
nginx.ingress.kubernetes.io/auth-type: basic
name: your-ingress
namespace: your-namespace
spec:
ingressClassName: nginx
rules:
- host: your-domain
http:
paths:
- backend:
service:
name: your-unsecured-service
port:
number: 80
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- your-domain.whatever
secretName: your-domain-tls
The Ingress example above uses a TLS certificate autogenerated by cert-manager. I have several articles on my blog that explain how to configure it properly with Let's Encrypt and Cloudflare.
Test
We are done! Just open the browser and try to access your service. If all was setup correctly you should be greeted by a popup similar to this.
Security Considerations
As you can see it was extremely simple to enable authentication for an unsecured service using Basic Auth in conjunction with the Nginx Ingress Controller. However, we need to be aware that from a security perspective this setup is susceptible to brute force attacks.
For this reason, we must secure the ingress with additional steps. I will propose few options.
Option 1: Whitelist Source IPs
My favorite option is allow traffic only from trusted IPs. In the context of my home-lab setup, I allow only my private network to access services like Grafana, Prometheus, and Longhorn.
The way I do it is with two simple steps.
First, I define the trusted IP range in the Ingress resource by adding the following annotation.
# Defining whitelisted ranges using CIDR notation
# CIDRs should be replaced with those meaningful in your private network
nginx.ingress.kubernetes.io/whitelist-source-range: "192.168.1.0/24, 192.168.68.0/24"
I then create DNS records for the restricted domains, directing them to internal private IPs. While the safest approach would be to use an internal DNS server to manage these records, I find it more convenient to use Cloudflare. Even though I have an internal DNS server, managing the records on Cloudflare simplifies the setup allowing me to generate valid TLS certificates for my services without an internal trusted authority. And in case you’re wondering, yes—Cloudflare does allow the use of private IPs.
Option 2: Nginx Rate Limiting
Another option is to use nginx "rate limiting". There are several annotations we can add to our Ingress definition documented here. A simple approach is to limit the maximum number of request allowed every minute per source IP address.
# 20 request per minute
# this is multiplied by the limit-burst-multiplier (5 by default)
nginx.ingress.kubernetes.io/limit-rpm: "20"
I personally don't like this option because it does not prevent completely brute force from external actors. It just slows them down.
Option 3: Web Application Firewall
You can also proxy all the traffic through a Web Application Firewall (WAF), e.g. Cloudflare. However, be aware that it might be incompatible with the source IP whitelisting.
Conclusion
That's all!
I hope this article was useful to you. If you have issues, do not hesitate to use the comment section and I will try to help!