How to secure a Kubernetes Nginx Ingress with Basic Auth

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.

Basic Auth Login Screen on Chrome

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.

Wildcard DNS record pointing to my firewall (accessible only on my network)

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!