search
LoginSignup
8

More than 1 year has passed since last update.

posted at

updated at

Organization

Open Policy Agent (OPA) のユースケース4選

アドベントカレンダー5日目は、Policy as Codeを実現する話題のOSS「Open Policy Agent」の使い道(ユースケース)をいくつか紹介します。

Open Policy Agentとは

opa-horizontal-color.png

クラウドネイティブの台頭により、昨今のシステムの構成は大規模化&複雑化しています。
そのようなシステムをセキュアな状態にするには、システムを構成するサービス1つ1つに対して適切な権限設定をしていく必要があります。

そんなときに便利なのが、Open Policy Agent(OPA:おーぱ)です。
Open Policy Agentは様々なサービスのポリシー設定を同じ書き方(Rego)で表現することができます。
また、システム全体のポリシー管理を、サービス自体のコードから切り離すことになります。
これにより、システム全体のポリシーの管理(例:どのロールのユーザにどのような権限をあたえるのか)が容易になります。

banner-image.png
※ Open Policy Agent 公式サイトより引用

ユースケース1(SSH、sudo)

ここからはユースケースを紹介していきます。
一つ目は、Linux環境にてSSHやsudoが行われた場合の制御です。

LinuxにはPAM (Pluggable Authentication Modules for Linux) と呼ばれる認証モジュールが組み込まれており、これを用いてユーザ認証が行われています。
そして、PAMとOpen Policy Agentを用いることでポリシー管理ができます。

今回は、サービスA開発者とサービスB開発者はそれぞれの開発しているサービスのみ、管理者は両方のサービスにSSHできるケースを考えます。(sudoも同じ要領で記述できます。)

Linuxの場合.png

ユーザから仮想マシンにアクセスがあった際、各サービスはOpen Policy Agentにアクセスし、ポリシーとデータを確認します。
今回のポリシーは下記のソースのようになります。

sshd_authz.rego
package sshd.authz

import input.pull_responses
import input.sysinfo

import data.hosts

# デフォルトでは、すべてのユーザを拒否
default allow = false

# 管理者(admin)の場合にSSHを認可
allow {
    data.roles["admin"][_] == input.sysinfo.pam_username
}

# 該当サービスの開発者であった場合にSSHを認可
allow {
    hosts[pull_responses.files["/etc/host_identity.json"].host_id].contributors[_] == sysinfo.pam_username
}

# 認可されなかった場合に、エラーメッセージを出力
errors["Request denied by administrative policy"] {
    not allow
}

このとき、data.roles["admin"][_]でシステム管理者(admin)を判別できるよう、下記データを読み込みます。

roles.json
{
    "admin": ["admin"]
}

同じように、hosts[pull_responses.files["/etc/host_identity.json"].host_id].contributors[_]でサービス開発者(serviceA-developer、serviceB-developer)を判別できるよう、下記データを読み込みます。

hosts.json
{
  "serviceA": {
    "contributors": [
      "serviceA-developer"
    ]
  },
  "serviceB": {
    "contributors": [
      "serviceB-developer"
    ]
  }
}

※OPAの責任範囲は「認可」のみであり、「認証」は対象外であるためSSH時のauthorized_keysによる認証は、ほかのID管理システムに任せることになります。

ユースケース2(Docker)

【その1: seccomp=unconfinedオプションの無効化】

Dockerの場合.png

Dockerでは、seccomp(secure computing mode)というLinuxカーネルの機能を用いることで、コンテナ内で利用可能なDockerコマンドを限定することができます。

しかし、この機能はユーザがseccomp=unconfinedというオプションをつけることで、seccompプロファイルの内容を無視してDockerコマンドを使うことができてしまいます。
そのため、ユーザがInsecureなコンテナを取り扱わないように、Open Policy Agentを用いて、seccomp=unconfinedオプションを無効にすることが推奨されます。

docker_authz1.rego
package docker.authz

default allow = false

# denyの条件に該当しなければ、認可を許可する
allow {
    not deny
}

# seccomp_unconfinedで定義されているルールに該当する場合はdeny
deny {
    seccomp_unconfined
}

# コンテナを実行する際に`seccomp=unconfined`オプションを用いていた場合は該当
seccomp_unconfined {
    input.Body.HostConfig.SecurityOpt[_] == "seccomp:unconfined"
}

seccompプロファイルは、mobyのGithubリポジトリ上にあるdefault.jsonを編集して作成します。
デフォルトでは、300以上あるシステムコールのうち、下記URLのものが制限されています。(Dockerコマンドで使えないようにされています。)
https://matsuand.github.io/docs.docker.jp.onthefly/engine/security/seccomp/#significant-syscalls-blocked-by-the-default-profile

【その2: 特定ユーザのみ認可】

ユースケース1(SSH、sudo)と同様に、特定ユーザのみがDockerコマンドを使えるように設定することもできます。また、readOnly(write権限無し)ユーザを定義することも可能です。

Dockerの場合3.png

この例では、ユーザAはDockerコンテナに対するすべてのコマンド、ユーザBはreadOnlyなコマンドのみが認可されています。

docker_authz2.rego
package docker.authz

default allow = false

# readOnlyでない(read/write権限のある)ユーザがすべてのメソッドを実施することを認可
allow {
    user_id := input.Headers["Authz-User"]
    user := users[user_id]
    not user.readOnly
}

# readOnlyと定義されたユーザは、readOnlyなコマンドのみ認可
allow {
    user_id := input.Headers["Authz-User"]
    users[user_id].readOnly
    input.Method == "GET"
}

# readOnlyのユーザと、readOnlyでない(read/write権限のある)ユーザを定義
users = {
    "userA": {"readOnly": false},
    "userB": {"readOnly": true},
}

ユースケース3(Kubernetes)

kubernetes-admission-flow.png
※ Open Policy Agent 公式ドキュメントより引用

Open Policy AgentをKubernetesのAdmission Controllersとして導入すると下記のようなことが可能になります。

  • 特定のラベルがついていないリソースへのアクセスのみを認可
  • 取得可能なコンテナイメージを、企業が所持しているイメージレジストリのみに限定
  • すべてのPodに対してリソースのリクエストとリミットを特定できるように定義
  • すでに存在するIngressと競合しないように新規Ingressの設定を限定

ここでは2つのみ、Regoの記述方法を記載します。

【その1:取得可能なコンテナイメージを、企業が所持しているイメージレジストリのみに限定】

kubernetes_admission1.rego
package kubernetes.admission

deny[msg] {
    input.request.kind.kind == "Pod"
    # すでに存在するコンテナのイメージをすべて洗い出す
    some i
    image := input.request.object.spec.containers[i].image
    # コンテナイメージの名前が、"hooli.com/"からはじまっていない場合に、
    #「信用していないレジストリから取得したイメージ」とメッセージを出力
    not startswith(image, "hooli.com/")
    msg := sprintf("image '%v' comes from untrusted registry", [image])
}

【その2:すでに存在するIngressと競合しないように新規Ingressの設定を限定】

kubernetes_admission2.rego
package kubernetes.admission

# Ingressの設定が、すでに存在するIngressと競合した場合に、認可を行わない
deny[msg] {
    input.request.kind.kind == "Ingress"
    # インプット対象のIngressをすべて洗い出す
    some i
    newhost := input.request.object.spec.rules[i].host
    # すでに存在するIngressをすべて洗い出す
    some namespace, name, j
    oldhost := ingresses[namespace][name].spec.rules[j].host
    # インプット対象のIngressのホストと、すでに存在するIngressのホストが一致するかどうかを確認
    newhost == oldhost
    msg := sprintf("ingress host conflicts with ingress %v/%v", [namespace, name])
}

Admission ControllersとしてOpen Policy Agentを利用すると、Kubernetes環境の自動修正も行うこともできます。
例えば、下記のような修正が可能です。

  • 特定のアノテーションを、すべてのリソースにいれる
  • サイドカーコンテナをPodsの中に入れる
  • 企業が所持しているイメージレジストリを使うようにコンテナの設定を変える
  • デプロイメントにNodeとPodのアフィニティ・セレクタを含める

また、KubernetesとOpen Policy Agentの統合に特化したOPA Gatekeeperというモジュールがあります。
OPA Gatekeeperを使うことで、よりOpen Policy AgentがKubernetes上で使いやすくなります。

ユースケース4(Envoy、Istio)

サービスメッシュに対しても、Open Policy Agentは有効です。
今回は簡略化のため、サイドカーとしてEnvoyを利用しているケースについてのせます。

Envoyの場合.png

このケースでは、開発者からのリクエストをEnvoyが受け取った後に、Open Policy Agentがポリシーを確認します。(①)
そして認可がおりた場合、Envoyはサービスにアクセスして情報を取得します。(②)
①②両方を行うことで、Envoyは開発者にレスポンスを返すことができます。

envoy_authz.rego
package envoy.authz

import input.attributes.request.http as http_request

default allow = false

# action_allowedで定義されているルールに該当する場合はallow
allow {
    action_allowed
}

# パスが/pattern1であり、メソッドがGETの場合は該当
action_allowed {
  http_request.method == "GET"
  glob.match("/pattern1", ["/"], http_request.path)
}

# パスが/pattern3であり、メソッドがPOSTの場合は該当
action_allowed {
  http_request.method == "POST"
  glob.match("/pattern3", ["/"], http_request.path)
}

/pattern1はallowされているため求めていたレスポンスが返ってきますが、
/pattern2はallowされていないため、Envoyが②の処理を実施せず、求めていたレスポンスは
返ってこない仕組みとなっています。

さいごに

他にもKong(API Gateway)やTerraform、Kafkaといった様々なものと組み合わせて使うことができます。Open Policy Agentは使い道が多くてとても便利なOSSですので、ぜひ皆さんご活用ください!

参考サイト

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
8