TeleportでKubernetesクラスタへのユーザーアクセスを管理する

こんにちは、Necoプロジェクトの池添(@zoetro)です。

今回はTeleportというツールを利用して、Kubernetesクラスタへのユーザーアクセスを管理する方法を紹介します。

TL;DR

TeleportとKubernetesを連携させることで、以下のような仕組みを実現することができます。

  • ユーザーが踏み台サーバーを経由してKubernetesクラスタにアクセスできる
  • Kubernetesリソースへのアクセス権を統合的に管理することができる
  • kubectl execの内容はセッションレコードとして保存されリプレイ再生することも可能
  • kubectlの証明書の有効期限を短くすることでリスクを低減

Teleportとは

github.com

Teleport は、簡単に言ってしまうと従来のSSHの踏み台サーバー(Bastion) をクラウドネィティブ 時代に合わせて進化させたものです。TeleportはGravitational社が開発しています。 Teleportによって次のようなことができます。なお、無償版では一部の機能が利用できません。

  • 踏み台サーバー経由でのSSHやSSH-over-HTTPSによるLinuxサーバ群へのアクセス
  • 踏み台サーバー経由でのKubernetes APIによるKubernetesクラスタへのアクセス
  • 様々なSSOとの連携(無償版はGitHub OAuthのみ)
  • SSHのAudit Logの保存
  • SSHやkubectlのセッションレコードの保存
  • RBACによるSSHユーザーの統合的なアクセス権限管理(有償版のみ)
  • RBACによるKubernetesユーザーの統合的なアクセス権限管理

なお、SSHの踏み台サーバーとしての解説は、以下の記事がわかりやすいのでおすすめです。

blog.animereview.jp

Kubernetesクラスタのアクセス権管理

Kubernetesクラスタを利用しているみなさんは、どのようにユーザーのアクセス権限を管理しているでしょうか?

小さなクラスタであれば管理者と開発者の権限を分ける必要はないかもしれません。

しかしクラスタの規模が大きくなり、複数のチームが1つのKubernetesクラスタを利用するような場合、 アクセス権限管理は重要になります。

例えば、開発者がクラスタレベルのリソースや他のチームのリソースにアクセスできないようにする必要があります。 また、NetworkPolicyやPodSecurityPolicyなどのポリシーに関しては、誤って書き換えられないように しておかないとセキュリティインシデントにも繋がってしまいます。

そこで、Kubernetesの認証認可の仕組みを利用することになります。

Kubernetesは様々な認証方式に対応しています(Authenticating | Kubernetes)。 そして認証されたユーザーやサービスアカウントに、必要なアクセス権限を割り当てることができます。(Using RBAC Authorization | Kubernetes)。

しかし、ユーザーのアクセス権に応じて適切な証明書やトークンを発行する仕組みを構築するのは面倒です。 また、証明書やトークンの有効期限を長くしておくとそれらが漏洩した時のリスクも大きくなるため、 有効期限を短くして定期的に再発行する仕組みについても考えなくてはなりません。

Teleportを利用すると、SSO のような仕組みを簡単に構築することができます。

試してみよう

Teleportを利用したKubernetesのユーザー管理の仕組みを試してみましょう。

ここでは、下記のバージョンのソフトウェアを利用します。

  • kind: 0.4.0
  • Kubernetes: 1.15.0
  • Helm: 2.14.2
  • Teleport: 4.0.2 (無償版を利用)
  • cert-manager: 0.10.0

利用しているファイルは下記のリポジトリで公開しています。

github.com

Kubernetesクラスタの作成

kindを利用してKubernetesクラスタを作成します。 kindの使い方に関してはこちらの記事をごらんください。

まず下記のようなファイルを作成しcluster.yamlという名前で保存します。 今回はTeleportのサービスにホストマシンからアクセスできるようにNodePortサービスを利用するので、 extraPortMappingsの設定をしておきます。

kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
nodes:
- role: control-plane
- role: worker
  extraPortMappings:
  - containerPort: 30023
    hostPort: 3023
  - containerPort: 30026
    hostPort: 3026
  - containerPort: 30080
    hostPort: 3080

下記のコマンドを実行してクラスタを作成します。

$ kind create cluster --name teleport-demo --config cluster.yaml 

また、kindで作成したクラスタにアクセスできるようにKUBECONFIG環境変数を設定しておきます。

$ export KUBECONFIG="$(kind get kubeconfig-path --name="teleport-demo")"

Helmのセットアップ

cert-managerとTeleportをセットアップするためにHelmを利用します。 Helmの公式ドキュメントに従って、HelmのCLIをインストールしてください。

次に以下のようなファイルを作ってhelm-account.yamlという名前で保存します。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: helm
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: helm
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: helm
  namespace: kube-system

下記のコマンドを実行して、HelmをKubernetesクラスタにデプロイします。

$ kubectl apply -f helm-account.yaml
$ helm init --service-account helm
$ helm repo update

cert-manager

次にcert-managerをKubernetesクラスタにデプロイします。

cert-managerのドキュメントに従って、下記のようにデプロイします。

$ kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.10/deploy/manifests/00-crds.yaml
$ kubectl create namespace cert-manager
$ kubectl label namespace cert-manager certmanager.k8s.io/disable-validation=true
$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update
$ helm install --name cert-manager --namespace cert-manager --version v0.10.0 jetstack/cert-manager

次にTeleport用の証明書を発行します。

まず下記のyamlをcertificate.yamlという名前で保存します。

apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
  name: selfsigning-issuer
spec:
  selfSigned: {}
---
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: teleport-tls
  namespace: teleport
spec:
  secretName: tls-web
  commonName: teleport.example.com
  isCA: true
  issuerRef:
    name: selfsigning-issuer
    kind: ClusterIssuer

下記のコマンドを実行して証明書を発行します。

$ kubectl create namespace teleport
$ kubectl apply -f certificate.yaml

なお、ここではself signed証明書を発行していますが、実環境で利用する場合はLet's Encryptなどを利用して証明書を発行するのがよいでしょう。

Teleport

さて、いよいよTeleportをデプロイします。 サンプルとして用意されているHelm Chartを利用することにします。

$ git clone https://github.com/gravitational/teleport.git $GOPATH/src/github.com/gravitational/teleport

ただしこのサンプルは有償版のTeleportを利用するようになっているので、無償版を利用するように以下のようにvalues.yamlを書き換えます。 また、サービスをClusterIPからNodePortに変更します。

image:
  repository: quay.io/gravitational/teleport
  tag: 4.0.2
  pullPolicy: IfNotPresent
strategy: RollingUpdate
service:
  type: NodePort
  ports:
    proxyweb:
      port: 3080
      targetPort: 3080
      nodePort: 30080
      protocol: TCP
    authssh:
      port: 3025
      targetPort: 3025
      nodePort: 30025
      protocol: TCP
    proxykube:
      port: 3026
      targetPort: 3026
      nodePort: 30026
      protocol: TCP
    proxyssh:
      port: 3023
      targetPort: 3023
      nodePort: 30023
      protocol: TCP
ingress:
  enabled: false
ports:
  proxyweb:
    containerPort: 3080
  authssh:
    containerPort: 3025
  proxykube:
    containerPort: 3026
  proxyssh:
    containerPort: 3023
  nodessh:
    containerPort: 3022
  proxytunnel:
    containerPort: 3024
proxy:
  tls:
    enabled: true
    secretName: tls-web
license:
  enabled: false
config:
  teleport:
    log:
      output: stderr
      severity: DEBUG
    data_dir: /var/lib/teleport
    storage:
      type: dir
  auth_service:
    enabled: yes
    authentication:
      type: github
    public_addr: teleport.example.com:3025
    cluster_name: teleport.example.com
  ssh_service:
    enabled: yes
    public_addr: teleport.example.com:3022
  proxy_service:
    enabled: yes
    public_addr: teleport.example.com
    web_listen_addr: 0.0.0.0:3080
    listen_addr: 0.0.0.0:3023
    https_key_file: /var/lib/certs/tls.key
    https_cert_file: /var/lib/certs/tls.crt
    kubernetes:
      enabled: yes
      listen_addr: 0.0.0.0:3026
rbac:
  create: true
serviceAccount:
  create: true
persistence:
  enabled: false
automountServiceAccountToken: true

下記のコマンドでTeleportをデプロイします。

$ helm install --name teleport --namespace teleport -f values.yaml $GOPATH/src/github.com/gravitational/teleport/examples/chart/teleport/

GitHub連携

Teleportは有償版は様々な認証方式が利用できますが、無償版ではパスワード認証とGitHub OAuthしか利用できません。 今回はGitHub OAuth認証でKubernetesと連携する方法を紹介します。

TeleportのKubernetes連携機能では、GitHubのTeamとKubernetesのGroupをマッピングすることができます。

まずGitHubにOrganizationとTeamを用意してください。OrganizationのOwner権限が必要となります。

次にCreating an OAuth App の手順に従って、GitHubにOAuth Appの登録をおこないます。

入力項目は下記のようにしてください。callback URLにはブラウザからアクセス可能なTeleportのアドレスを指定する必要があります。

  • Application name: Teleport
  • Homepage URL: https://teleport.example.com
  • Authorization callback URL: https://teleport.example.com:3080/v1/webapi/github/callback

登録後に表示されるClient IDClient Secretを利用して、下記のようなgithub.yamlファイルを作成します。 organizationteamには、GitHubのOrganizationとTeamを指定します。 kubernetes_groupsにはKubernetesクラスタのグループ名を指定します。 system:mastersを指定するとKubernetesクラスタのすべてのリソースにアクセスできます。

kind: github
version: v3
metadata:
  name: github
spec:
  client_id: <client-id>
  client_secret: <client-secret>
  display: Github
  redirect_url: https://teleport.example.com:3080/v1/webapi/github/callback
  teams_to_logins:
    - organization: <your-organization>
      team: <your-team>
      logins:
        - root
      kubernetes_groups: ["system:masters"]

TeleportのPodに入り、github.yamlの登録をおこないます。

$ kubectl -n teleport exec -it teleport-xxx bash
$ tctl create github.yaml
authentication connector "github" has been created

ログイン

ログインの前に、Teleportにアクセスするためのアドレスを、下記のように/etc/hostsに登録しておきます。 本来はDNSで名前解決できるようにすべきですが、今回は/etc/hostsの編集でごまかします。

127.0.0.1 teleport.example.com

次にログインをおこないます。

TeleportのDownloadページからバイナリをダウンロードし、 tshコマンドが利用できるようにインストールをおこなってください。

下記のコマンドを実行するとブラウザが開き、GitHubの認証画面が開きます。 ログインに利用するOrganizationを選択してください。

なお今回はself signedな証明書なので--insecureオプションを付与してますが、Let's Encryptの証明書を使うならこのオプションは不要です。

$ tsh login --insecure --proxy=teleport.example.com:3080 --auth=github

statusを確認してみます。証明書の有効期限はデフォルトで12時間に設定されています。

$ tsh status
> Profile URL:  https://teleport.example.com:3080
  Logged in as: xxxx
  Cluster:      teleport.example.com
  Roles:        admin*
  Logins:       root
  Valid until:  2019-07-30 03:58:06 +0900 JST [valid for 11h49m0s]
  Extensions:   permit-agent-forwarding, permit-port-forwarding, permit-pty


* RBAC is only available in Teleport Enterprise
  https://gravitational.com/teleport/docs/enterprise

ログインに成功すると、kubeconfigにTeleport用のcontextが追加されています。 teleport.example.com という名前のContextがあればOKです。

$ kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://teleport.example.com:3026
  name: teleport.example.com
contexts:
- context:
    cluster: teleport.example.com
    user: teleport.example.com
  name: teleport.example.com
current-context: kubernetes-admin@teleport-demo
kind: Config
preferences: {}
users:
- name: teleport.example.com
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED

現在のContextを確認し、必要があれば切り替えます。

$ kubectl config current-context
teleport.example.com
$ kubectl config use-context teleport.example.com

kubectlを叩いてみましょう。現在はsystem:mastersの権限なので、すべてのリソースにアクセスができるはずです。 なお今回はself signedな証明書なので--insecure-skip-tls-verify=trueオプションを付与しています。

$ kubectl --insecure-skip-tls-verify=true get pods -A

kubectl execを実行して、適当にコマンドを叩いてみてください。 ブラウザでhttps://teleport.example.com:3080/webを開いてログインすると、 kubectl execで実行した内容を動画のようにリプレイ表示することができます。

Recording kubectl session

なお本物の動画ではないので、一時停止して、実行したコマンドやその結果をテキストとしてコピーすることもできます。

権限設定

次にアクセス権の制限されたKubernetesグループを定義してみましょう。 下記のようなファイルを作成し、rbac.yamlという名前で保存します。

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: guest-role
rules:
  - apiGroups:
      - ""
    resources:
      - pods
      - deployments
      - services
    verbs:
      - get
      - list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: guest-role-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: guest-role
subjects:
  - kind: Group
    name: guest
    apiGroup: rbac.authorization.k8s.io

下記のコマンドで適用します。

$ kubectl apply -f rbac.yaml

先ほどのgithub.yamlkubernetes_groupsを書き換えます。

kind: github
version: v3
metadata:
  name: github
spec:
  client_id: <client-id>
  client_secret: <client-secret>
  display: Github
  redirect_url: https://teleport.example.com:3080/v1/webapi/github/callback
  teams_to_logins:
    - organization: <your-organization>
      team: <your-team>
      logins:
        - root
      kubernetes_groups: ["guest"]

TeleportのPodに入り、github.yamlを再登録します。

$ kubectl -n teleport exec -it teleport-xxx bash
$ tctl rm github/github
github connector "github" has been deleted
$ tctl create github.yaml
authentication connector "github" has been created

再度tshでログインし直して、kubectlを実行してみましょう。

podserviceをgetすることはできますが、それ以外のリソースはアクセスできないことが確認できます。

$ kubectl --insecure-skip-tls-verify=true get configmaps
Error from server (Forbidden): configmaps is forbidden: User "xxxx" cannot list resource "configmaps" in API group "" in the namespace "default"

まとめ

今回は、Teleportを利用してKubernetesクラスタへのユーザーアクセスを管理する方法を紹介しました。 この方法を利用することで、GitHubのTeamごとにKubernetesクラスタへのアクセス権限を設定することが できるようになり、非常に簡単にユーザー管理をおこなうことができるようになりました。

本記事がKubernetesのユーザー管理の参考になれば幸いです。