29
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NRI OpenStandiaAdvent Calendar 2021

Day 5

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

Last updated at Posted at 2021-12-05

アドベントカレンダー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 公式サイトより引用](https://www.openpolicyagent.org/)

ユースケース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ですので、ぜひ皆さんご活用ください!

参考サイト

29
10
0

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
  3. You can use dark theme
What you can do with signing up
29
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?