はじめに
この記事は、Oracle Cloud Infrastructure Advent Calendar 2021 その1 Day 21の記事として書かれています。
昨今、従来より増して「セキュリティ」に関する意識が高まっているところだと思います。
この記事では、コンテナセキュリティ強化の一環として、OKEやOCIRを利用して比較的容易に実現できる営みをご紹介したいと思います。
この記事でご紹介するのは以下です。
- OCIRに格納したコンテナイメージの脆弱性スキャン
- OCIRでのコンテナイメージに対するイメージ署名
- OCIR、OKEでのイメージ署名に対する検証
それでははじめていきます。
OCIRに格納したコンテナイメージの脆弱性スキャン
まずは、コンテナイメージの脆弱性スキャンから見ていきます。
コンテナイメージの脆弱性をスキャンして、把握することで、コンテナイメージ内のセキュリティホールを埋めることができます。
こちらの機能では、CVEで公開されている脆弱性を対象にスキャンすることができます。
OCIRで脆弱性スキャンを利用するにはAPI/OCI CLIのいずれかを利用します。(現時点でコンソールでの脆弱性スキャンはサポートしておりません)
今回はOCI CLIを利用して行います。
ポリシーの設定
まずは、脆弱性スキャンを行うためのポリシーを設定していきます。
ポリシーはOCIコンソールから、「アイデンティティとセキュリティ」=>「ポリシー」で辿れます。
以下のポリシーを作成します。
以下のポリシーの他に、操作対象となるコンパートメント内のフルアクセス権限があることを前提とします。
ポリシー | 説明 |
---|---|
allow service vulnerability-scanning-service to read repos in compartment <コンパートメント名> | 脆弱性スキャンサービスがOCIRのコンテナイメージを参照可能にするポリシー |
allow service vulnerability-scanning-service to read compartments in compartment <コンパートメント名> | 脆弱性スキャンサービスがコンパートメント情報を参照可能にするポリシー |
これでポリシーの作成は完了です。
## OCIRのレポジトリ作成
今回スキャン対象とするレポジトリをOCIRに作成します。
OCIRへは、「開発者サービス」=>「コンテナ・レジストリ」で遷移します。
「レポジトリの作成」をクリックし、以下の情報を入力します。
入力項目 | 説明 |
---|---|
レポジトリ名 | scan-test |
「レポジトリの作成」をクリックします。
これで、OCIRのレポジトリ作成は完了です。
スキャン・レシピとスキャン・ターゲットの作成
今回は、Cloud Shell環境でOCI CLIを実行していきます。
次にスキャン・レシピとスキャン・ターゲットを作成します。
oci vulnerability-scanning container scan recipe create
--display-name <スキャン・レシピ名>
--compartment-id <コンパートメントのOCID>
--scan-settings '{"scanLevel": "STANDARD"}'
--scan-settings '{"scanLevel": "STANDARD"}'
の部分は、現時点でSTANDARD
しか存在しないので、こちらを利用します。
以下のようなレスポンスが返却されるので、スキャン・レシピのOCID(レスポンスのid
フィールド)だけ記録しておきます。
{
"data": {
"compartment-id": "ocid1.tenancy.oc1..aaaaaaaa6xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxo7prvv7a55asqdzge45nq",
"defined-tags": {
"Oracle-Tags": {
"CreatedBy": "oracleidentitycloudservice/xxxx@gmail.com",
"CreatedOn": "2021-12-21T05:16:43.785Z"
}
},
"display-name": "scan-recipe",
"freeform-tags": {},
"id": "ocid1.vsscontainerscanrecipe.oc1.iad.aaaaaaxxxxxxxxxxxxxxxxxxxxxxxxxxww6yag3b2pe24a",
"lifecycle-state": "ACTIVE",
"scan-settings": {
"scan-level": "STANDARD"
},
"system-tags": {},
"time-created": "2021-12-21T05:16:43.956000+00:00",
"time-updated": "2021-12-21T05:16:43.998000+00:00"
},
"etag": "7591b3eaa0034e78c8dc2e6ad82eb89cbe4e866250d665b56f1021935244bd4f",
"opc-work-request-id": "ocid1.coreservicesworkrequest.oc1..aaaaaxxxxxxxxxxxxxxxxxxxxxxxxxxupdiwfjzlyidoa4ljqjmq"
}
続いてスキャン・ターゲットを作成します。
oci vulnerability-scanning container scan target create --display-name scan-target --compartment-id <コンパートメントのOCID>
--container-scan-recipe-id <上記で作成したスキャン・レシピのOCID>
--target-registry
'{"type": "OCIR", "url": "https://<対象のリージョン・コード>.ocir.io","compartmentId": "<コンパートメントのOCID>", "repositories": ["<OCIRのレポジトリ名>"]}'
ここで"repositories": ["<OCIRのレポジトリ名>"]
を指定しないこともでき、その場合は対象のコンパートメント内の全てのイメージがスキャン対象になります。
コンパートメント内の全てのイメージをスキャン対象にする場合も事前にレポジトリの作成が必要になるので、ご注意ください。
実行するとこのような結果が返ってきます。
{
"data": {
"compartment-id": "ocid1.tenancy.oc1..aaaaaaaa6nvbus6xxxxxxxxxxxxxxxxxxxxvv7a55asqdzge45nq",
"container-scan-recipe-id": "ocid1.vsscontainerscanrecipe.oc1.iad.aaaaaaaxxxxxxxxxxxxxxxxxxxxxxxxxxxxx6yag3b2pe24a",
"defined-tags": {
"Oracle-Tags": {
"CreatedBy": "oracleidentitycloudservice/xxxx@gmail.com",
"CreatedOn": "2021-12-21T05:32:58.119Z"
}
},
"description": null,
"display-name": "scan-target",
"freeform-tags": {},
"id": "ocid1.vsscontainerscanrecipe.oc1.iad.aaaaaaxxxxxxxxxxxxxxxxxxxxxxxxxxww6yag3b2pe24a",
"lifecycle-state": "CREATING",
"system-tags": {},
"target-registry": {
"compartment-id": "ocid1.tenancy.oc1..aaaaaaaa6nvxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx55asqdzge45nq",
"repositories": [
"scan-test"
],
"type": "OCIR",
"url": "https://iad.ocir.io"
},
"time-created": "2021-12-21T05:32:58.389000+00:00",
"time-updated": "2021-12-21T05:32:58.389000+00:00"
},
"etag": "177aaa88f3072f50362c5d9a975fd076349613cbce6ee158299d2130ef79f90e",
"opc-work-request-id": "ocid1.coreservicesworkrequest.oc1..aaaaaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxm24jdzhuafngjg7wea"
}
ここまでできれば、すでに格納済みのイメージもしくは今後プッシュされるイメージに対して脆弱性スキャンが実行されます。
スキャン実行結果はコンソールから確認できます。
OCIRのコンソールから、scan-testレポジトリに格納されたイメージの「スキャン結果」をクリックします.
発見された脆弱性の詳細を見る場合は、右端にあるケバブメニューの「詳細の表示」からアクセスできます。
今回の場合は以下のように表示されました。こちらのリンクをクリックすると、CVEデータベースの詳細な情報を参照することができます。
これをもとに、コンテナイメージのセキュリティホールを事前に対処することができます。
以上がOCIRでのコンテナイメージの脆弱性スキャンです。
OCIRでのコンテナイメージに対するイメージ署名
次に、OCIRでのコンテナイメージに対するイメージ署名を実施してみます。
イメージ署名を利用することで、コンテナイメージに対する改竄を防ぎ、信頼できるコンテナイメージとして管理することができます。
イメージの署名を利用するために、ポリシーについては、対象となるコンパートメント内のフルアクセス権限があることを前提とします。
OCI Vaultでの暗号化キーの作成
イメージ署名に利用するための暗号化キーをOCI Vaultで作成していきます。
OCI Vaultでは、「アイデンティティとセキュリティ」=>「ボールト」をクリックします。
「ボールトの作成」をクリックします。
以下の情報を入力し、「ボールトの作成」をクリックします。
入力項目 | 説明 |
---|---|
名前 | image-vault |
Vaultがプロビジョニングされるまでしばらく待ちます。
プロビジョニングが完了したら、作成した「image-vault」をクリックし、「キーの作成」をクリックします。
以下の情報を入力し、「キーの作成」をクリックします。
入力項目 | 説明 |
---|---|
名前 | image-key |
キーのシェイプ:アルゴリズム | RSA |
イメージの署名で利用する暗号化キーにAESを利用することはできない(利用可能なのはRSAもしくはECDSA非対称キーです)ので、ご注意ください。
キーがプロビジョニングされるまで待ちます。
プロビジョニングされたら、「image-key」をクリックし、「キー情報」のOCIDと「キーバージョン」のOCIDを記録しておきます。
以上で、OCI Vaultでの暗号化キーの作成は完了です。
OCIRでのイメージ署名
続いて、OCIRでのイメージ署名を行います。
コンテナイメージへの署名は、脆弱性スキャンと同様にAPIもしくはOCI CLIを利用します。
今回は、Cloud Shell環境でOCI CLIを実行していきます。
以下のコマンドで署名することができます。
oci artifacts container image-signature sign-upload --compartment-id <コンパートメントのOCID>
--kms-key-id <上記の「キー情報」のOCID>
--kms-key-version-id <上記の「キーバージョン」のOCID>
--signing-algorithm SHA_256_RSA_PKCS_PSS
--image-id <イメージ署名対象のOCID>
--description <署名時の説明>
--metadata '""'
--signing-algorithm
オプションでは、複数のアルゴリズムから選択して利用することができます。
詳細はこちらをご確認ください。
--image-idで指定するコンテナイメージのOCIDはOCIRコンソールから確認できます。
実行すると以下のような結果が返ってきます。
Obtaining container image metadata by the image ID
Generating signature
Signature: d78iommDRruKEPlvfaCFhxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Uploading signature
Uploaded signature: d78iommDRruKEPlvfaCFhTVlixsDLoLdVS2LuCS5cSEnq3IJK/A5NO0ITOL1fUXPD8HjcHtAPK+DODk0VoyCK0rIs9XNkwVIH6x8TQpd9qQz2MbCsvNS2dKGh/AokSu+3ULn6vQ9EnRreumgla70qWQhzNJYTA/F+/AgBn1Ig9gcx8k0C5+KgRs10nKDki5dxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Message: eyJkZXNjcmlwdGlvbiI6ImltYWdlLXNpZ24gZGVtbyIsImltYWdlRGlnZXN0Ijoic2hhMjU2OmJiOWMzZGQwMmYxNDUyOTFjNWQ0ZTIxOGJmMWI1MDVhYzFiMGI0ZjYyYzI1YzEzYTFmZmQyOTNiZDJlZjU1NmEiLCJrbXNLZXlJZCI6Im9jaWQxLmtleS5vYzEuaWFkLmI1cTRjNWxuYWFoaWsuYWJ1d2NsanN2azRzY3B4M3ZrZjU2YTJteTdtaDU3M2pwd2oyamV4ZXBrMnF3am9xcW52M3Q2amNmNjRhIiwia21zS2V5VmVyc2lvbklkIjoib2NpZDEua2V5dmVxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
ID: ocid1.containerimagesignature.oc1.iad.0.idwpqivumvev.aaaaaaaaa5msu6tsr5efanzpzjozh3eex4strnw6wa5a3rdu46grtdobwfza
{
"data": {
"compartment-id": "ocid1.tenancy.oc1..aaaaaaaa6xxxxxxxxxxxxxxxxxxxxxxxxxt6p3o7prvv7a55asqdzge45nq",
"created-by": "ocid1.saml2idp.oc1..aaaaaaaain5ck5a6zeo222ajpq5xxxxxxxxxxxxxxxxxxxxxxxxxq/xxxxx.devdays@gmail.com",
"display-name": "v3t6jcf64a::5slkxvenra::SHA_256_RSA_PKCS_PSS::grtdobwfza",
"id": "ocid1.containerimagesignature.oc1.iad.0.idwpqivumvev.aaaaaaxxxxxxxxxxx4strnw6wa5a3rdu46grtdobwfza",
"image-id": "ocid1.containerimage.oc1.iad.0.idwpqivumvev.aaaaaaxxxxxxxxxxlxjd56yhsmjgkk2d75c6no6oxnq",
"kms-key-id": "ocid1.key.oc1.iad.b5q4c5lnaahik.abuwcljsvk4scpx3vkf56a2my7mh5xxxxxxxxxxv3t6jcf64a",
"kms-key-version-id": "ocid1.keyversion.oc1.iad.b5q4c5lnaahik.aumomhaxn6yaa.abuxxxxxxxxxxuxv5jbvng5eyolkbhm6y5slkxvenra",
"message": "eyJkZXNjcmlwdGlvbiI6ImltYWdlLXNpZxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"signature": "d78iommDRruKEPlvfaCFhTVlixsDLoLdVS2LuCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxqRX8A8Q8LE1Kd4hbZNr8D4eTOJ9xg248PRplWsv7hOQ/ZeZzcpwVSEaa6coaZLHJADdT+F+vVlpP7yT/rMerAOkwBkn9WoIP036B8QOWnJrQYOLQ4d6Qk53tzk+PhFEMsQPqiJOUUC6TT7NAUOMOA==",
"signing-algorithm": "SHA_256_RSA_PKCS_PSS",
"time-created": "2021-12-21T07:02:58.383000+00:00"
}
}
OCIRのコンソールから対象のイメージを確認すると「(署名済)」と表示され、「署名」タブには署名時に--description
オプションで指定した文字列(今回の場合はimage-sign demo
)が表示されていることが確認できます。
これでイメージ署名は完了です。
OCIR、OKEでのイメージ署名に対する検証
最後にOCIR、OKEそれぞれでイメージ署名に対する検証を行います。
OCIRでのイメージ検証
OCIRでのイメージ検証はシンプルかつ一瞬です。
OCIRのコンソールの「署名」タブのケバブメニューから「署名の検証」をクリックします。
イメージ検証が正常にできれば、「検証済」と表示されます。
これで、OCIRでのイメージ検証は完了です。
OKEでのイメージ検証
OKEでのイメージ検証ではOKEクラスタが必要になるので、OCIチュートリアルに沿って作成してください。
OKEでのイメージ検証を有効化すると、指定した暗号化キーで署名されたイメージのみをデプロイすることができます。
(未署名のイメージや検証に失敗したイメージはデプロイされません)
OKEクラスタを作成できたら、イメージの署名を利用可能にするためのポリシーを設定します。
- Allow any-user to use keys in tenancy where request.user.id=<作成したOKEクラスタのOCID>
- Allow any-user to read repos in tenancy where request.user.id=<作成したOKEクラスタのOCID>
OKEクラスタのOCIDはOCIコンソールから確認できます。
次に、OKEクラスタのメニューにある「イメージ検証」をクリックします。
「イメージ検証の編集」をクリックします。
「このクラスタでのイメージ検証の有効化」にチェックを入れ、イメージ署名時に作成したボールト名とキー名を選択し、「イメージ検証設定の保存」をクリックします。
OKEクラスタが「更新中」のステータスになるので、「アクティブ」になるまで、10分~20程度待機します。
ここからは、OKEでのイメージ検証の挙動を確認していきます。
まずは、未署名のイメージがOKEにデプロイできないことを確認します。
今回は通常のnginxイメージをデプロイしてみたいと思います。
以下のようなManifestをデプロイしてみます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: unsigned-nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
このファイルをapplyします。
kubectl apply -f unsigned_nginx.yaml
すると、Deploymentリソースは作成されますが、Podが作成されないことが分かります。
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
unsigned-nginx 0/3 0 0 3m
これは、nginxコンテナイメージが期待された暗号化キーで署名されていないためです。
次に先ほど署名したコンテナイメージをデプロイしてみます。
デプロイする前にOCIRのクレデンシャルを含めたSecretを作成しておきます。
kubectl create secret ocir-cred --docker-server=<リージョン・コード>.ocir.io
--docker-username='オブジェクト・ストレージ・ネームスペース/ユーザ名'
--docker-password='認証トークン'
--docker-email='xxx@example.com'
--docker-emailの値は適当でも問題ありません。
以下のManifestを作成します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: unsigned-nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: iad.ocir.io/idwpqivumvev/scan-test@sha256:bb9c3dd02f145291c5xxxxxxxxxxxxxxxffd293bd2ef556a
ports:
- containerPort: 80
imagePullSecrets:
- name: ocirsecret
OKEでイメージの検証を利用する場合は、必ずイメージをダイジェストで指定する必要があります。
イメージのダイジェストはOCIRのコンソール上で確認できます。
このファイルをapplyします。
kubectl apply -f signed_nginx.yaml
結果を確認してみると、署名済みのイメージはPodが作成されていることが分かります。
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
signed-nginx 3/3 3 3 2m54s
unsigned-nginx 0/3 0 0 36m
このように、OKEでイメージの検証を行うと、指定された暗号化キーで署名したコンテナイメージのみをデプロイすることができます。
まとめ
長くなりましたが、OKEやOCIRではコンテナセキュリティを高めるための様々な機能が実装されており、今回その一部を紹介しました。
コンテナでも、セキュリティには十分に注意する必要がありますので、参考にしてみてください。
なお、今回の記事については、2021/12/17(金)に開催されたOracle Developer Day 2021で私が行った「OCIコンテナ関連サービス最新事情〜OKEと周辺サービスの現在地と未来〜」というセッション内でもデモを実施しておりますので、よければご覧ください。(サイト内にアーカイブがあります)
参考記事