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 and external-dns to make it all automatic

🧱 What You’ll Need

🛠️ Step 1: Create Google OAuth Credentials

  1. Go to Google Cloud Console
  2. Create a new project or use an existing one
  3. Open OAuth consent screen → Choose "External" → Fill out app info
  4. Add markcallen.com as an Authorized Domain
  5. Go to CredentialsCreate CredentialsOAuth Client ID
    1. Application type: Web App
    2. Javascript Origins: https://login.markcallen.com
    3. Redirect URI: https://login.markcallen.com/oauth2/callback
  6. Press Create
  7. 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.