コンテナ・イメージは、業界標準で、アプリケーションとその依存関係をパッケージ化し、移動し、デプロイするために生み出されたものです。多くの場合、コンテナ・イメージには機密情報(独自のアルゴリズムなど)が含まれており、一般には共有されないようになっています。このようなプライベートなイメージを他からのアクセスから保護するために、企業内部のビルド・パイプラインを確立し、権限のないクライアントがアクセスできないプライベートなコンテナ・レジストリにコンテナ・イメージを保管しています。万一攻撃者がプライベート・コンテナ・レジストリに侵入した場合、プライベート・コンテナ・イメージを盗み出して他の場所で実行したり、その内容を調べたり、アプリケーションの弱点を見つけたりすることができます。
コンテナ・イメージの暗号化は、秘密鍵と公開鍵のペアを使用して、コンテナ・イメージ層の暗号化と復号化を行うことで、セキュリティー対策の高度化を支援することができます。暗号化されたコンテナ・イメージは、適切な復号鍵がなければアクセスできないため、情報流出の危険にさらされるリスクを減少させることができます。
これはコンテナ・イメージの暗号化のユースケースの一つに過ぎませんが、他にもいくつかあります。
コンテナ・イメージを暗号化する
コンテナの暗号化を構築するのに、既存のDockerfileを変更する必要はありませんが、 buildah(英語)やskopeo(英語)などの新しいツールが必要になります。また、暗号化に使用する秘密鍵と公開鍵のペアを生成する必要があります。
コンテナ・イメージのビルド、暗号化、プッシュのワークフローは複数あります:
- buildahを使ってDockerfileからコンテナ・イメージをビルドし、それをレジストリにプッシュして暗号化キーを指定することができます。
- skopeoを使って、既存のコンテナをレジストリにプッシュし、暗号化キーを指定することができます。
コンテナ・イメージのビルド、暗号化、プッシュの詳細な手順については、公式文書をご参照いただくことをお勧めします。
主な例
この例では、Dockerfileを使ってコンテナ・イメージを構築し、公開鍵ペアを生成し、その鍵ペアを使ってイメージを暗号化し、イメージをレジストリにプッシュする手順を示しています。
この例のコンテナのようなDockerfileから始めてください:
FROM docker/whalesay
RUN apt update && apt install fortune -y
CMD while true ; do /usr/games/fortune | cowsay; sleep 10; done
|
buildah bud
コマンドでイメージを作成してくださいc:
$ buildah bud -t us.icr.io/attila-fabian/mycontainer:latest .
...
979337e38c8e28491e14ba4d0fabaac5b49ff80380eeb2783ed1b06d14754542
$ buildah images
REPOSITORY TAG IMAGE ID CREATED SIZE
us.icr.io/attila-fabian/mycontainer latest 979337e38c8e 19 seconds ago 266 MB
|
コンテナを暗号化してプッシュする前に、暗号化に使用する秘密鍵と公開鍵のペアを生成します:
$ openssl genrsa --out private.pem
Generating RSA private key, 2048 bit long modulus (2 primes)
...
$ openssl rsa - in private.pem -pubout -out public.pem
writing RSA key
$ ls
private.pem public.pem
|
buildah bud
コマンドを使って、暗号化し、イメージをプッシュしてください:
$ buildah push --encryption-key jwe:public.pem us.icr.io/attila-fabian/mycontainer:latest
Getting image source signatures
...
Writing manifest to image destination
Storing signatures
|
ローカル・イメージを削除し、buildah bud
を使ってイメージをプルしてください :
$ buildah rmi us.icr.io/attila-fabian/mycontainer:latest
untagged: us.icr.io/attila-fabian/mycontainer:latest
979337e38c8e28491e14ba4d0fabaac5b49ff80380eeb2783ed1b06d14754542
$ buildah pull us.icr.io/attila-fabian/mycontainer:latest
Getting image source signatures
...
Error decrypting layer sha256:45a9fb9687e20fec30a55939219706470948ef11a4e4080a6df23e3726000470: missing private key needed for decryption
ERRO exit status 125
|
このエラーはあらかじめ予想されたものです。では、指定した秘密鍵でイメージをプルしてましょう:
$ buildah pull --decryption-key private.pem us.icr.io/attila-fabian/mycontainer:latest
Getting image source signatures
...
Writing manifest to image destination
Storing signatures
979337e38c8e28491e14ba4d0fabaac5b49ff80380eeb2783ed1b06d14754542
|
今度は無事にイメージがプルされました。
暗号化されたコンテナ・イメージからのアプリケーションのをデプロイする
ほとんどのコンテナ・ランタイムは、暗号化されたコンテナ・イメージを扱うことができます。Red Hat OpenShift on IBM Cloudの場合、バージョン1.17から暗号化コンテナ・イメージのサポートを提供しているため、 cri-o (英語)を使用しています。
暗号化されたコンテナ・イメージを使用する場合、適切な復号化キーをコンテナ・ランタイムに提供する必要があります。cri-oを使用する場合、これは秘密鍵を /etc/crio/keys
ディレクトリーの下に配置することを意味します(デフォルトの設定)
OpenShiftクラスタの場合、復号鍵を提供するには2つの課題があります:
- 復号キーはすべてのワーカー・ノード上のcri-oに提供する必要があります。
- キーをワーカー・ノードのファイル・システムに手動でコピーしてはいけません。
これらの課題に対応するには、IBM Cloud Image Key Synchronizer 管理アドオンを使用します。この アドオンを使用すると、復号鍵を Kubernetes の秘密リソースの形で指定し、ワーカー ・ノード上でこれらの鍵の同期化をすることができます。
OpenShiftクラスタのアドオンを有効にした後は、鍵をシークレットに指定するだけで準備は完了です。
IBM Cloud Image Key Synchronizerの使用方法の詳細については、公式文書を参照されることをお勧めします。
標準的な秘密鍵を使用した例
この例では、IBM Cloud Image Key Synchronizer のマネージド・アドオンをテスト用 OpenShift クラスターにインストールし、前の例で構築したイメージを使用してポッドを作成する手順を示します。
まず、使用できるOpenShift 4.4以降のクラスタを特定します:
$ ibmcloud oc cluster ls
OK
Name
ID State Created Workers Location
Version Resource Group Name Provider
encrypted-containers-example
bvbs8g8s0akvcjht7f7g normal 3 weeks ago 2 Sydney
4.5.18_1523_openshift Default classic
|
IBM Cloud Image Key Synchronizer マネージド・アドオンをクラスターにインストールします:
$ ibmcloud oc cluster addon enable image-key-synchronizer --cluster bvbs8g8s0akvcjht7f7g
Enabling add-on image-key-synchronizer for cluster bvbs8g8s0akvcjht7f7g...
The add-on might take several minutes to deploy and become ready for use.
OK
|
アドオンがデプロイされたら、 DaemonSet がimage-key-synchronizer
という新規のプロジェクトに作成されます:
$ oc get daemonset -n image-key-synchronizer
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
addon-image-key-synchronizer 2 2 2 2 2 <none> 14s
|
先ほどの例でビルドしてプッシュしたコンテナをデプロイしてみましょう:
$ oc apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- image: us.icr.io/attila-fabian/mycontainer:latest
name: mycontainer
imagePullPolicy: Always
EOF
pod/mypod created
|
復号鍵がまだ指定されていないため、ポッドは起動しないはずです:
$ oc describe pod mypod
Name: mypod
Namespace: default
...
Containers:
mycontainer:
Image: us.icr.io/attila-fabian/mycontainer:latest
...
State: Waiting
Reason: ImagePullBackOff
Ready: False
...
Conditions:
Type Status
Initialized True
Ready False
ContainersReady False
PodScheduled True
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
...
Warning Failed 45s (x3 over 102s) kubelet, 10.138.107.220 Failed to pull image "us.icr.io/attila-fabian/mycontainer:latest" :
rpc error: code = Unknown desc = Error decrypting layer
sha256:45a9fb9687e20fec30a55939219706470948ef11a4e4080a6df23e3726000470:
no suitable key unwrapper found or none of the private keys could be
used for decryption
Warning Failed 45s (x3 over 102s) kubelet, 10.138.107.220 Error: ErrImagePull
Normal BackOff 18s (x4 over 101s) kubelet, 10.138.107.220 Back-off pulling image "us.icr.io/attila-fabian/mycontainer:latest"
Warning Failed 18s (x4 over 101s) kubelet, 10.138.107.220 Error: ImagePullBackOff
Normal Pulling 5s (x4 over 114s) kubelet, 10.138.107.220 Pulling image "us.icr.io/attila-fabian/mycontainer:latest"
|
よくできました。次に、シークレットで復号鍵を指定し、イメージ鍵同期プロジェクトでシークレットを作成します:
$ oc apply -f - <<EOF
apiVersion: v1
kind: Secret
type: key
metadata:
name: mydecryptionsecret
namespace: image-key-synchronizer
data:
private.pem: $(cat private.pem | base64 | tr -d "\n" )
EOF
secret/mydecryptionsecret created
|
さあ、ポッドを削除して再度作り直しましょう:
$ oc delete pod mypod
pod "mypod" deleted
$ oc apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- image: us.icr.io/attila-fabian/mycontainer:latest
name: mycontainer
imagePullPolicy: Always
EOF
pod/mypod created
|
$ oc get pods
NAME READY STATUS RESTARTS AGE
mypod 1/1 Running 0 35s
$ oc logs mypod
...
______________________________________
/ F.S. Fitzgerald to Hemingway: \
| |
| "Ernest, the rich are different from |
| us." Hemingway: |
| |
\ "Yes. They have more money." /
--------------------------------------
\
\
\
## .
## ## ## ==
## ## ## ## ===
/ "" "" "" "" "" "" "" "" ___/ ===
~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~
\______ o __/
\ \ __/
\____\______/
...
|
これで完了です。今回は暗号化されたコンテナ・イメージがプルされ、無事にポッドが起動しました。
IBM Key Protect for IBM Cloudを使って秘密鍵をラップする例
IBM Cloud Image Key Synchronizer のアドオンは、IBM Key Protect for IBM Cloud (英語)サービスとの統合機能を提供します。
Key Protect を使用すれば、暗号化キーをより安全に利用することができます。例えば、特定のルート鍵を使用して秘密鍵をラップし、プレーンな秘密鍵の代わりにラップされた鍵を指定することができます。
以下の例では、あらかじめ作成した Key Protect インスタンスとルートキーを使用しています。サービスインスタンスとルートキーの作成の詳細については、こちらの公式文書を参照されることをお勧めします。
$ ibmcloud kp keys --instance-id 832fdb1a-c3b3-exmp-847e-31f238f4767c
Retrieving keys...
OK
Key ID Key Name
883cc3d3-d281-44f3-a1d5-372289491c77 test-root-key
|
IBM Cloud Image Key Synchronizerは、Key Protect インスタンスへの接続を解決するために、いくつかの設定が必要です:
oc apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: keyprotect-config
namespace: image-key-synchronizer
type: Opaque
stringData:
config.json: |
{
"keyprotect-url" : "https://us-south.kms.cloud.ibm.com" ,
"instance-id" : "832fdb1a-c3b3-exmp-847e-31f238f4767c" ,
"apikey" : "5ZtsYoNA0zexamleD8Av7lDSuaof-w7examleKc_MGgE"
}
EOF
secret/keyprotect-config created
|
設定を作成したら、Key Protect でラップされた鍵の指定を開始できます。次のスニペットはibmcloud kp key wrap
コマンドを使用して、Key Protect インスタンスの下でルート・キーを使用して private.pem
キー・ファイルの内容をラップしています。Key Protect は、ラップを解除しないと使用できない暗号文を返します。上記で提供された設定により、IBM Cloud Image Key Synchronizer は提供された秘密をアンラップすることができます。:
$ oc apply -f - <<EOF
apiVersion: v1
kind: Secret
type: kp-key
metadata:
name: mywrappeddecryptionsecret
namespace: image-key-synchronizer
data:
private.pem: $(ibmcloud kp key wrap 883cc3d3-d281-44f3-a1d5-372289491c77 --plaintext "$(cat private.pem | base64)" --instance-id 832fdb1a-c3b3-exmp-847e-31f238f4767c -o json | jq -r .Ciphertext)
EOF
secret/mywrappeddecryptionsecret created
|
ここで、テスト・ポッドを削除して、それを再度作成してください:
$ oc delete pod mypod
pod "mypod" deleted
$ oc apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- image: us.icr.io/attila-fabian/mycontainer:latest
name: mycontainer
imagePullPolicy: Always
EOF
pod/mypod created
|
$ oc get pods
NAME READY STATUS RESTARTS AGE
mypod 1/1 Running 0 55s
$ oc logs mypod
...
__________________________________
< Someone is speaking well of you. >
----------------------------------
\
\
\
## .
## ## ## ==
## ## ## ## ===
/ "" "" "" "" "" "" "" "" ___/ ===
~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~
\______ o __/
\ \ __/
\____\______/
...
|
よくできました。今回は、暗号化された画像をKey Protect でラップした秘密鍵を使ってプルすることに成功しました。
さらに詳しい情報は
暗号化されたコンテナ・イメージの詳細については、 Encrypted container images for container image security at rest (英文)と Advancing container image security with encrypted container images (英文)の記事をご参照ください。また、さらに詳しい情報については、こちらの公式文書もあわせてご参照ください。
お問い合わせは
ご質問やお問い合わせがございましたら、こちら(英語)に登録して、IBM Cloud Kubernetes Service の公開Slackの#generalチャンネルでディスカッションに参加してどしどしお寄せください。