在 k8s 中授权外部应用程序的端点

在代码级别实现授权通常很简单。

类似地,公开具有内置身份验证支持的外部工具(例如 Grafana)也可以相对容易地处理。

真正的挑战出现在处理缺乏对身份验证或授权的本机支持的简单工具或服务时,需要外部机制来确保安全访问。

解决此问题的一种方法是限制通过 VPN 的访问或使用 IP 地址范围保护它,这仍然是最可靠的方法之一。

然而,我最近探索了一种替代方案:重用 Keycloak(我们已经在使用的 oauth 提供程序之一)来验证对特定端点的访问。

可能适用于此的工具是**OAuth2 Proxy**,它支持广泛的 OAuth 提供商,例如 Facebook、Google、GitLab 和 Azure。

额外的要求是将这些工具分组到单个 URL 下,例如 **tools.example.com/tool-name**,以简化访问和管理。

**oauth2-proxy 基本设置:**

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s-app: oauth2-proxy
  name: oauth2-proxy
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: oauth2-proxy
  template:
    metadata:
      labels:
        k8s-app: oauth2-proxy
    spec:
      containers:
        - args:
            - --provider=keycloak-oidc
            - --oidc-issuer-url=https://{ISSUER_HOST}/realms/{REALM_NAME}
            - --code-challenge-method=S256
            - --allowed-role={ROLE}
            - --email-domain=*
            - --upstream=file:///dev/null
            - --http-address=0.0.0.0:4180
            - --client-id={CLIENT_ID}
            - --client-secret={CLIENT_SECRET}
            - --session-cookie-minimal
          env:
            - name: OAUTH2_PROXY_COOKIE_SECRET
              value: {COOKIE_SECRET}
          image: quay.io/oauth2-proxy/oauth2-proxy:latest
          imagePullPolicy: Always
          name: oauth2-proxy
          ports:
            - containerPort: 4180
              protocol: TCP

---

apiVersion: v1
kind: Service
metadata:
  labels:
    k8s-app: oauth2-proxy
  name: oauth2-proxy
  namespace: default
spec:
  ports:
    - name: http
      port: 4180
      protocol: TCP
      targetPort: 4180
  selector:
    k8s-app: oauth2-proxy

**入口配置(假设您已经有一个名为 tools-example-com-tls 的 TLS 密钥)**

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tools
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: 15m
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - "tools.example.com
      secretName: tools-example-com-tls
  rules:
      - host: "tools.example.com"
        http:
          paths:
                - path: /oauth2
                  pathType: Prefix
                  backend:
                    service:
                      name: oauth2-proxy
                      port:
                        number: 4180

---

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tools-external-auth-oauth2
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: 15m
    nginx.ingress.kubernetes.io/auth-url: "https://$host/oauth2/auth"
    nginx.ingress.kubernetes.io/auth-signin: "https://$host/oauth2/start?rd=$escaped_request_uri"
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - "tools.example.com"
      secretName: tools-example-com-tls
  rules:
      - host: "tools.example.com"
        http:
          paths:
            - path: /certificates
              pathType: Prefix
              backend:
                service:
                  name: cert-checker
                  port:
                    number: 8081

在我们的案例中,其中一个工具是证书检查器的仪表板。

**额外奖励:**

此外,我们的解决方案包括部署在遵循 s1-app、s2-app 等模式的地址的多个有状态服务,每个相应的服务都通过 ASP.NET 公开自己的工具实例。

例如,我们可能希望在**tools.example.com/s1/quartz**和**tools.example.com/s2/quartz**等地址公开相关的 quartz-ui 实​​例。

Uri routing diagram

**鉴于此需求,Ingress 资源 tools-external-auth-oauth2 由多个路径组成,例如:**

- path: /s[0-9]+/quartz
              pathType: ImplementationSpecific
              backend:
                service:
                  name: tools-nginx-reverse-proxy
                  port:
                    number: 80

**nginx反向代理配置:**

apiVersion: v1
kind: ConfigMap
metadata:
  name: tools-nginx-reverse-proxy-config
  namespace: default
data:
  nginx.conf: |
    events {
      worker_connections 1024; 
    }
    http {
      resolver 10.3.0.10 valid=10s; #KubeDNS or CoreDNS internal ip
      server {
        listen 80;
        location ~ ^/s([0-9]+)/quartz{
          set $service_name "s$1-app.default.svc.cluster.local";
          rewrite ^/s([0-9]+)/quartz$ /quartz break;
          proxy_pass http://$service_name:80;

          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Proto $scheme;
          proxy_http_version 1.1;
          proxy_set_header Connection "";

          proxy_read_timeout 90;
          proxy_send_timeout 90;
        }

        location ~ ^/s([0-9]+)/quartz/(.*\.(css|js|png|jpg|jpeg|gif|svg|woff2|woff|ttf))$ {
            set $service_name "s$1-app.default.svc.cluster.local";
            rewrite ^/s([0-9]+)/quartz/(.*)$ /$2 break;
            proxy_pass http://$service_name:80;
        }

        location / {
            return 404;
        }
      }
    }

---

apiVersion: apps/v1
kind: Deployment
metadata: 
  name: tools-nginx-reverse-proxy
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tools-nginx-reverse-proxy
  template:
    metadata:
      labels:
        app: tools-nginx-reverse-proxy
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
          volumeMounts:
            - name: nginx-config
              mountPath: /etc/nginx/nginx.conf
              subPath: nginx.conf
      volumes:
        - name: nginx-config
          configMap:
            name: tools-nginx-reverse-proxy-config


---

apiVersion: v1
kind: Service
metadata:
  name: tools-nginx-reverse-proxy
  namespace: default
spec:
  selector:
    app: tools-nginx-reverse-proxy
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

**笔记:**

封面图片(显然)是由 bing ai 创建的。

**有用的资源:**

https://oauth2-proxy.github.io/oauth2-proxy/

https://www.keycloak.org

https://github.com/mogensen/cert-checker

https://github.com/guryanovev/CrystalQuartz