k3d でoidcを試す

@asya_aoi1049 on Sat Jun 19 2021
25.9 min

目次

k3dとは

k3dはk3sをコンテナ化し、Dockerを使用してマルチノードのk3sクラスタを作れるツール
Dockerにのみ依存しローカル環境でkubernetes(k3s)を簡単に作れるので大変便利
README

構成

  • Ubuntu (20.04 LTS) (IP:192.168.1.100)
  • Docker(19.03.11)
  • k3d
  • helm
  • sakura docker registry

手順

k3dでクラスタを作る

# k3d cluster create --k3s-server-arg --no-deploy --k3s-server-arg traefik --volume /root/work/istio/src/registries.yaml:/etc/rancher/k3s/registries.yaml -p 8087:80@loadbalancer -p 8088:443@loadbalancer test-oidc

–k3s-server-arg

k3sはデフォルトでtraefikを使用するが今回はnginx ingressを使うため、traefikをデプロイしないように引数を与える
参考: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

–volume /root/work/istio/src/registries.yaml:/etc/rancher/k3s/registries.yaml

sakura docker registry を使用するため、以下のregistries.yamlを記載する
参考: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

mirrors:
  k3d.sakuracr.jp:
    endpoint:
      - https://k3d.sakuracr.jp

-p 8087:80@loadbalancer -p 8088:443@loadbalancer

k3d が外部からアクセスできるようにPortを指定する
localhost:8087 にアクセスすると、KubernetesのLoadBalancer TypeのService のPort 80にアクセスするように設定している

k3dのkubeconfigを設定

# k3d kubeconfig merge test-oidc --switch-context
# export KUBECONFIG=/root/.k3d/kubeconfig-test-oidc.yaml

podのSTATUSがRunnningになっていることの確認

# kubectl get pods --all-namespaces
NAMESPACE     NAME                                                  READY   STATUS    RESTARTS   AGE
kube-system   metrics-server-7566d596c8-h2fnc                       1/1     Running   0          8d
kube-system   local-path-provisioner-6d59f47c7-npt4h                1/1     Running   0          8d
kube-system   coredns-8655855d6-qk5k4                               1/1     Running   0          8d

認証をかけたいアプリケーションのDeployment, serviceの追加

apiVersion: v1
kind: Service
metadata:
  name: honypot
  labels:
    app: honypot
    service: honypot
spec:
  ports:
  - port: 8080
    name: http
  selector:
    app: honypot
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: bookinfo-honypot
  labels:
    account: honypot
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: honypot-v1
  labels:
    app: honypot
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: honypot
      version: v1
  template:
    metadata:
      labels:
        app: honypot
        version: v1
    spec:
      serviceAccountName: bookinfo-honypot
      containers:
      - name: honypot
        image: k3d.sakuracr.jp/test-oidc
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
# kubectl apply -f app.yml
参考:すべてOKを返すWebアプリケーション
package main

import (
    "context"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func handler(w http.ResponseWriter, r *http.Request) {
    log.Println("------------------------------")
    log.Printf("URL: %v", r.URL)
    log.Printf("Method: %v", r.Method)
    log.Printf("Header: %v", r.Header)

    if body, err := ioutil.ReadAll(r.Body); err != nil {
        log.Printf("ioutil.ReadAll: %v", err)
    } else {
        log.Println(string(body))
        defer r.Body.Close()
    }
    w.WriteHeader(http.StatusOK)
}


func authHandler(w http.ResponseWriter, r *http.Request) {
    log.Println("--------------auth----------------")
    log.Printf("URL: %v", r.URL)
    log.Printf("Method: %v", r.Method)
    log.Printf("Header: %v", r.Header)

    if body, err := ioutil.ReadAll(r.Body); err != nil {
        log.Printf("ioutil.ReadAll: %v", err)
    } else {
        log.Println(string(body))
        defer r.Body.Close()
    }
    w.WriteHeader(http.StatusOK)
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", handler)
    mux.HandleFunc("/auth", authHandler)
    srv := &http.Server{
        Addr:           ":8080",
        Handler:        mux,
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }

    go func() {
        log.Println("Start Server ....")
        if err := srv.ListenAndServe(); err != nil {
            log.Print(err)
        }
    }()

    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGTERM, os.Interrupt)

    <-sigCh
    ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
    if err := srv.Shutdown(ctx); err != nil {
        log.Print(err)
    }
}
参考:Dockerfile
FROM golang:1.14-alpine3.11 AS minimum-base
LABEL MAINTAINER 'mitsuyoshi4'

RUN set -eux && \
    apk add --no-cache \
        curl bash make git
WORKDIR /go/src/honeypot

COPY . /go/src/honeypot
RUN set -eux && \
    go build -ldflags '-w -s'

# ---------- #

FROM alpine:3.8 AS honeypot
LABEL MAINTAINER 'mitsuyoshi4'

COPY --from=minimum-base \
    /go/src/honeypot/honeypot /usr/local/bin/

ENTRYPOINT ["/usr/local/bin/honeypot"]
# docker login k3d.sakuracr.jp
# docker build . --no-cache
# docker tag xxxxxxxxx k3d.sakuracr.jp/test-oidc:latest
# docker push k3d.sakuracr.jp/test-oidc:latest

keycloak設定

起動

# docker run -d -p 18080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin --name keycloak jboss/keycloak

relm追加、ユーザ追加、グループ追加の簡単な流れ

  1. HOSTのIP(192.168.1.100:18080)にアクセスしてKEYCLOAK_USER、KEYCLOAK_PASSWORDで設定したuser/passwordを入力してログインする
  2. relm の追加(name: demo)
  3. client 作成(name: test)
  4. client protocol をopenid-connectに変更
  5. 有効なリダイレクトURLを*に設定
  6. client scopeをapiで作成、protocolはopenid-connect
  7. client のclient scope に6で生成したapiを割り当て
  8. group 作成
  9. user 作成
    ※参考:公式サイト

keycloakの接続先等の情報

公式サイトを参考に、作成したrelmの情報を把握しておく(oauth2-proxyで使用する)
例:http://192.168.1.100:18080/auth/realms/demo/.well-known/uma2-configuration

oauth2-proxy設定

deploy, ingress

GithubのIssueを参考に、マニフェストファイルを作成する
argsの--login-url, --redeem-url, --validate-url、envのOAUTH2_PROXY_CLIENT_ID, OAUTH2_PROXY_CLIENT_SECRET に接続するkeycloakの情報を入力する

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s-app: oauth2-proxy
  name: oauth2-proxy
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: oauth2-proxy
  template:
    metadata:
      labels:
        k8s-app: oauth2-proxy
    spec:
      containers:
        - args:
            - --provider=keycloak
            - --email-domain=*
            - --upstream=file:///dev/null
            - --http-address=0.0.0.0:4180
            - --login-url=http://192.168.1.100:18080/auth/realms/demo/protocol/openid-connect/auth
            - --redeem-url=http://192.168.1.100:18080/auth/realms/demo/protocol/openid-connect/token
            - --validate-url=http://192.168.1.100:18080/auth/realms/demo/protocol/openid-connect/userinfo
            # - --ssl-insecure-skip-verify # Needed for self-signed cert
          env:
            - name: OAUTH2_PROXY_CLIENT_ID
              value: test
            - name: OAUTH2_PROXY_CLIENT_SECRET
              value: 0ea6bf71-9c71-41b5-a5e9-4481439f76d1
            # docker run -ti --rm python:3-alpine python -c 'import secrets,base64; print(base64.b64encode(base64.b64encode(secrets.token_bytes(16))));'
            - name: OAUTH2_PROXY_COOKIE_SECRET
              value: rXPajRXVoIINCXz/xJIC1w==
          # my image - fork of pusher/oauth2_proxy:master + PR containing keycloak provider
          image: quay.io/pusher/oauth2_proxy
          imagePullPolicy: Always
          name: oauth2-proxy
          ports:
            - containerPort: 4180
              protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  labels:
    k8s-app: oauth2-proxy
  name: oauth2-proxy
spec:
  ports:
    - name: http
      port: 4180
      protocol: TCP
      targetPort: 4180
  selector:
    k8s-app: oauth2-proxy

nginx ingress のインストール

公式サイトを参考にhelm を使ってnginx ingress をインストールする
ingress service のexternalIPを設定するためにcontroller.service.externalIPsにHostのIPアドレスを設定する

# helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
# helm install my-release ingress-nginx/ingress-nginx --set 'controller.service.externalIPs={192.168.1.100}'

ingress設定

nginx ingress controllerのoauth設定GithubのIssueを参考に、マニフェストファイルを作成する
nginx.ingress.kubernetes.io/auth-url, nginx.ingress.kubernetes.io/auth-signin

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: gateway-with-oidc-auth
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/auth-url: https://192.168.1.100:8088/oauth2/auth
    nginx.ingress.kubernetes.io/auth-signin: https://192.168.1.100:8088/oauth2/start?rd=$escaped_request_uri
spec:
  rules:
  - http:
      paths:
      - path: /auth
        backend:
          serviceName: honypot
          servicePort: 8080
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: oauth2-proxy
spec:
  rules:
    - http:
        paths:
          - backend:
              serviceName: oauth2-proxy
              servicePort: 4180
            path: /oauth2

確認

ブラウザからHOSTの認証用のPath`https://192.168.1.100:8088/auth’ にアクセスするとkeycloakのログイン画面に遷移するので、
作成したユーザとパスワードを入力して、200 OK が返却されるか確認する
※認証されたセッションはブラウザのCookieとkeycloak側に保存されるので、ログアウトしたい場合はどちらも削除する

書く場所を迷った情報

  • nginx ingress controller はデフォルトのTLSにKubernetes Ingress Controller Fake Certificate を使うため、ローカル環境では自己証明書の作成は不要
  • nginx ingress controller の設定は ingressのannotationsを通して行う 参考: Annotations
  • nginx ingress での認証はlogoutが存在しないため、どのように設定するかは確認中 (おそらくこれ)
  • nginx ingress のannotations nginx.ingress.kubernetes.io/configuration-snippetproxy_set_headerを設定して、認証されたことを設定できるが、認証情報などを設定したい場合はどうやるか確認中
日別に記事を見る