Securing Internal Kubernetes Apps with Google Login Using OAuth2 Proxy
Internal tools are handy, but they can be dangerous when exposed. Here’s how to guard them with Google login and Kubernetes.
Why Secure Internal Apps?
Maybe it’s your admin dashboard. Maybe it’s a tool only your dev team uses. Either way, internal apps often start wide open, behind a firewall or IP block. That works, until it doesn’t. Firewalls shift. Ingress controllers expose things. One misconfigured route and boom, your tool is public.
OAuth2 Proxy + Google Login is a simple, modern way to secure your apps using accounts your team already has.
In this guide, you’ll learn how to:
- Deploy an ingress controller that requires a Google login
- Use
oauth2-proxy
to handle authentication - Protect a sample app (
whoami
) with TLS and OAuth2 - Use
cert-manager
andexternal-dns
to make it all automatic
🧱 What You’ll Need
- A Kubernetes cluster with:
external-dns
see Automate Kubernetes DNS with ExternalDNS and Cloudflare- A verified domain (we’ll use
markcallen.com
) - Two subdomains:
login.markcallen.com
→ hostsoauth2-proxy
example.markcallen.com
→ your protected app
- A Google account and OAuth credentials (we’ll create those next)
🛠️ Step 1: Create Google OAuth Credentials
- Go to Google Cloud Console
- Create a new project or use an existing one
- Open OAuth consent screen → Choose "External" → Fill out app info
- Add
markcallen.com
as an Authorized Domain - Go to Credentials → Create Credentials → OAuth Client ID
- Application type: Web App
- Javascript Origins: https://login.markcallen.com
- Redirect URI: https://login.markcallen.com/oauth2/callback
- Press Create
- Copy your Client ID and Client Secret
🔐 Step 2: Deploy oauth2-proxy
This service handles Google login and issues a secure cookie to authenticated users.
Create the oauth2
namespace
kubectl create ns oauth2
Create a certificate for login.markcallen.com
# login-cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: oauth2-proxy-cert
namespace: oauth2
spec:
secretName: login-markcallen-tls
issuerRef:
name: letsencrypt-dns
kind: ClusterIssuer
dnsNames:
- login.markcallen.com
Wait for the cert to be issued
kubectl describe order -n oauth2
Normal Complete 29s cert-manager-orders Order completed successfully
Setup helm
helm repo add oauth2-proxy https://oauth2-proxy.github.io/manifests
helm repo update
Create a cookie secret
openssl rand -base64 32 | tr -d '\n' | tr '+/' '-_'
Create a values.yaml
using the client id and secret and cookie secret created above. This will allow for multiple subdomains to use oauth2 as the authentication provider.
config:
clientID: "your-google-client-id"
clientSecret: "your-google-client-secret"
cookieSecret: "your-generated-cookie-secret"
redirectUrl: "https://login.markcallen.com/oauth2/callback"
configFile: |
email_domains = [ "markcallen.com" ]
upstreams = [ "file:///dev/null" ]
cookie_secure = "true"
cookie_domains = [ ".markcallen.com" ]
whitelist_domains = [ ".markcallen.com" ]
provider = "google"
scope = "openid email profile"
ingress:
enabled: true
className: nginx
hosts:
- login.markcallen.com
annotations:
cert-manager.io/cluster-issuer: letsencrypt-dns
external-dns.alpha.kubernetes.io/hostname: login.markcallen.com
tls:
- hosts:
- login.markcallen.com
secretName: login-markcallen-tls
Install oauth2-proxy
using helm
helm install oauth2-proxy oauth2-proxy/oauth2-proxy -f values.yaml \
--namespace oauth2
Check that the DNS is set up correctly
dig +short login.markcallen.com
and that you can get to the login URL over https without a certificate error
curl https://login.markcallen.com
📦 Step 4: Deploy Your Internal App (Example: whoami
)
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami
spec:
replicas: 1
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: traefik/whoami
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: whoami
spec:
selector:
app: whoami
ports:
- port: 80
targetPort: 80
🔐 Step 5: Protect Your App with OAuth2 Proxy
Create a certificate for whoami.markcallen.com
# whoami-cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-cert
namespace: default
spec:
secretName: whoami-markcallen-tls
issuerRef:
name: letsencrypt-dns
kind: ClusterIssuer
dnsNames:
- whoami.markcallen.com
Now create an ingress for it.
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-dns
nginx.ingress.kubernetes.io/auth-url: "http://oauth2-proxy.oauth2.svc.cluster.local/oauth2/auth"
nginx.ingress.kubernetes.io/auth-signin: "https://login.markcallen.com/oauth2/start?rd=https://$host$escaped_request_uri"
nginx.ingress.kubernetes.io/auth-response-headers: "X-Forwarded-User"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
external-dns.alpha.kubernetes.io/hostname: whoami.markcallen.com
spec:
ingressClassName: nginx
rules:
- host: whoami.markcallen.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami
port:
number: 80
tls:
- hosts:
- whoami.markcallen.com
secretName: whoami-markcallen-tls
Note that the nginx.ingress.kubernetes.io/auth-url
is the internal DNS for the oauth2-proxy service using http.
✅ The Result
- Users visit
whoami.markcallen.com
- If not logged in, they’re redirected to
login.markcallen.com
→ Google - On success, they get a secure cookie and access to your internal app
It’s simple, stateless, and backed by Google’s login infrastructure.
I've created the following Github repo: https://github.com/markcallen/internal-login-google-example
🧠 Final Thoughts
This method works great for dashboards, job runners, internal UIs, or staging environments. You get:
- Zero user management
- Passwordless auth with Google
- A secure, reusable OAuth2 proxy
Once you’ve done it once, it’s copy-paste for every internal app that needs a lock on the door.