[レポート] SEC332:DevOpsパイプラインにセキュリティを追加する #reinvent

re:Invent 2018でのワークショップ「Adding the Sec to Your DevOps Pipelines(SEC332)」のレポートです。このワークショップには参加していませんが「DevSecOpsって具体的に何するの?」という疑問を解消するために執筆してみました。
2018.12.11

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、佐伯です。

re:Invent 2018にてDevSecOpsに関するワークショップがありました。タイトルは 「Adding the Sec to Your DevOps Pipelines(SEC332)」です。このワークショップには参加していませんが、スライドとワークショップ用のページを見ながら「DevSecOpsって具体的に何するの?」という疑問を解消するために執筆してみました。

スライド

[slideshare id=124641412&doc=adding-the-sec-to-your-devops-c8bd9a46-3cbd-46d8-8798-bff4a18eb45d-73645761-181202071801]

概要

DevSecOpsは、ソフトウェア開発ライフサイクルの全員がセキュリティを担当することを前提としています。 DevSecOpsは開発プロセスのあらゆる部分にセキュリティを組み込むことを目指しています。 このワークショップでは標準的なCI/CDパイプラインにセキュリティステージを追加してセキュリティを改善する方法を検討します。 AWS CodeCommitとAWS CodePipelineを使用して、ゴールデンAMIイメージを作成する方法を学んでください。 また、パイプラインフローを修正してセキュリティテストケースを追加する方法を学びます。 Amazon Inspectorを使用してCVE解析とコード解析を実行し、Amazon GuardDutyを使用してコンテナ分析を実行する機会も必要です。

スピーカー

  • Aravind Kodandaramaiah - Solutions Developer
  • Armando Leite - Senior Manager, AWS
  • Adam McLean - Solution Developer, Amazon Web Services

レポート

ワークショップ内でやること

  • CI/CDパイプラインの構築
  • パイプラインにセキュリティを実装

Pre-requisite CloudFormation (事前準備)

ワークショップの資料は上記リンクより参照ください。

CloudFormationテンプレートをデプロイしてワークショップで必要となるリソースを作成します。私はバージニアリージョンで作成しました。ざっくりとですが、作成されるリソースは以下の通りです。

  • IAM
    • 各サービス用IAMロール
  • EC2
    • EC2インスタンス
    • VPC
    • インターネットゲートウェイ
    • サブネット
    • ルートテーブル
    • セキュリティグループ
  • CodeCommit
    • CodeCommitリポジトリ
  • CodeBuild
    • CodeBuildプロジェクト
  • S3
    • CodePipeline用S3バケット
  • AWS Inspector
    • リソースグループ
    • アセスメントテンプレート
    • アセスメントターゲット
  • AWS Systems Manager
    • Automationドキュメント
  • AWS Lambda
    • AWS Systems Manager Automation実行Lambdaファンクション
    • Amazon Inspector実行Lambdaファンクション

Creating the base Pipeline (パイプラインの作成)

ワークショップの資料は上記リンクより参照ください。

手順に従い、CodePipelineを作成します。

パイプラインの設定:

ソースステージの追加:

ビルドステージの追加:

デプロイステージの追加(スキップ):

CodeCommitのソースをビルドするパイプラインが作成できました。ビルド結果を含めたAMIを作成するため、パイプラインにふたつのステージを追加します。ひとつめに追加するステージはEC2インスタンスを起動し、AWS Systems Manager オートメーションを実行するLambdaファンクションを起動するステージを追加します。

パイプラインの編集:

ステージの追加:

アクショングループの追加:

アクション設定:

ふたつめのステージはEC2インスタンスの停止、AMIの作成、EC2インスタンスの削除を行うLambdaファンクションを起動するステージを追加します。ちなみに先程と同じLambdaファンクションを追加する形になっています。

ステージの追加:

アクショングループの追加:

アクション設定:

パイプラインの保存:

パイプライン更新後に「変更のリリース」から一度パイプラインが正常に動作するか確認してみます。

ちゃんと動きました。ちなみにEC2インスタンスが作成され、AWS Systems Managerでオートメーションが実行されていました。オートメーションのAMI作成のアクションなどは「保留中」というステータスになっており、この時点ではAMIは作成されませんでした。

パイプラインの概要と目的

作成したパイプラインの概要は以下の通りです。

本ワークショップではパイプラインに静的なコード解析と脆弱性チェックのステージを追加することが目的になります。

Static code analysis in the Pipeline (静的コード解析の追加)

ワークショップの資料は上記リンクより参照ください。

CloudFormationテンプレートのデプロイで既にCodeCommitリポジトリが作成されており、その中に find-aws-secret.sh というシェルスクリプトが存在しています。このシェルスクリプトはAWSのアクセスキー、シークレットアクセスキー、AWSアカウントIDがソースに含まれているかを検索し、もしそれらの情報がソースに含まれている場合はファイル名を出力し exit 1 でシェルスクリプトを異常終了させるものでした。

参考までに find-aws-secret.sh とシェルスクリプトが参照している credential-patterns ファイルは以下の通りです。

find-aws-secret.sh

#!/usr/bin/env bash

grep_result=$(grep -d recurse -nwHEI "$(cat credential-patterns)" $1)

if [ ${#grep_result} -eq 0 ] ; then
    echo "No text that matches credentials"
    exit 0
else
    echo "#####################################################################################################"
    echo "#####################################################################################################"
    echo "############################### CREDENTIAL(S) FOUND IN YOUR SOURCE CODE #############################"
    echo "#####################################################################################################"
    echo "#####################################################################################################"
    echo "########################### Found credentials at the following locations: ###########################"
    echo "#####################################################################################################"
    echo "$grep_result"
    echo "#####################################################################################################"
    echo "Failing the build..."
    exit 1
fi

credential-patterns

[A-Z0-9]{20}
| (" | ')?(AWS | aws | Aws)?_?(SECRET  | secret  | Secret)?_?(ACCESS | access | Access)?_?(KEY | key      | Key)(" | ')?\s*(: | =>                                  | =)\s*(" | ')?[A-Za-z0-9/\+=]{40}(" | ')? |
| (" | ')?(AWS | aws | Aws)?_?(ACCOUNT | account | Account)_?(ID     | id     | Id)?("         | ')?\s*(: | =>     | =)\s*("  | ')?[0-9]{4}\-?[0-9]{4}\-?[0-9]{4}(" | ')?     |                          |     |

上記のスクリプトをCodeBuildのpre_buildフェーズで実行するようにリポジトリの buildspec.yml を編集し、コミットします。

コミット後、ソースの変更をCodePipelineが変更を検知してパイプラインが実行され、Buildステージが失敗するようになりました。

先程失敗したBuildステージの詳細を確認するため、CodeBuildのビルド履歴からログを確認します。

app/src/config.js 内にアクセスキーとシークレットアクセスキーが設定されているのが確認できました。

CodeCommitリポジトリの app/src/config.js からアクセスキーとシークレットアクセスキーを削除します。

アクセスキー、シークレットアクセスキーを削除したことで、find-aws-secret.sh が正常終了するようになり、パイプラインも最後のステージまで正常終了するようになりました。

Amazon Inspectorを利用した脆弱性アセスメント

Amazon Inspectorとは?
脆弱性アセスメントサービス

  • アプリケーション/EC2セキュリティアセスメント
  • 選択可能なビルトインルール
    • ランタイム行動分析
    • CVE(Common Vulnerabilities and Exposures)
    • AWSセキュリティのベストプラクティス
    • セキュリティ構成(CISセキュリティベンチマーク)
    • ネットワークの到達可能性
  • セキュリティの発見 - ガイダンスと管理
  • APIによる自動化

Vulnerability assessment in the Pipeline (脆弱性アセスメント)

ワークショップの資料は上記リンクより参照ください。

パイプラインにステージを追加します。追加する箇所は Launch-Build-Instance-and-Install-SoftwareStop-Instance-and-Build-AMI の間です。以下のパラメーターで Vulnerability-Scan-Run-Amazon-Inspector ステージを追加します。

項目 設定内容
アクション名 Vulnerability-Scan-Run-Amazon-Inspector
アクションプロバイダ AWS Lambda
ファンクション名 <CloudFormationスタック名>-LaunchRunInspector
インプットアーティファクト LaunchArtifact
アウトプットアーティファクト ScanArtifact

ステージの追加:

アクショングループの追加:

アクション設定:

Amazon Inspectorエージェントをインストール

CodeCommitリポジトリの config.json に設定されている InstallInspectorAgentfalse から true へ変更し、コミットします。この config.json は先程追加したステージで実行されるLambdaファンクションで読み込まれ、AWS Systems Managerオートメーションを実行しているようです。

コミット後は同様にパイプラインが実行されます。追加した Vulnerability-Scan-Run-Amazon-Inspector ステージが実行され失敗していると思います。詳細を確認するとふたつの重要な脆弱性が見つかった旨のメッセージが表示されています。

Addressing Vulnerabilities (脆弱性への対処)

Amazon Inspectorのコンソールからどういった脆弱性が見つかったのか確認してみます。

CVE-2018-17182CVE-2018-14633の脆弱性が見つかったことがわかると思います。脆弱性の細かい内容は省略しますが、カーネルのアップデートが推奨事項として記載されています。

パイプラインではソフトウェアのインストールなどもCodeCommitリポジトリに保存されているシェルスクリプトを実行しています。具体的には install_software.sh がそのシェルスクリプトです。このシェルスクリプトにyumコマンドでのカーネルをアップデートを追加します。

コミット後、パイプラインが実行されるので先程失敗したステージが成功していることを確認します。そしてこのパイプラインの最終的な目的であるAMIが作成されていることも確認できました!

Launching our Golden AMI (ゴールデンAMIの動作確認)

ワークショップの資料は上記リンクより参照ください。

ワークショップではパイプラインで作成されたAMIを起動して動作確認まで行っているようでした。AMIからEC2インスタンスを作成してブラウザでアクセスしてみるといった内容です。アプリ自体が動くかどうかは正直別の話なので本エントリでは省略させていただきます。

Exploration (パイプラインの仕組みとチャレンジ)

ワークショップの資料は上記リンクより参照ください。

パイプラインはどのように動作しているのか

パイプラインのステージの説明とAmazon Inspector、Lambdaファンクションについて説明があります。しかし、この記述だけではなにがどうやって動いているのかわからない!と思ったので、LambdaファンクションやAWS Systems Managerの設定を確認しながら、ちょっと絵を書いてみました。

たぶん、こんな感じです。コードをすべて追えてないので間違ってたらごめんなさい。

チャンレジ

チャレンジとして、Amazon GuardDutyステージを追加するといったお題が出されていました。Amazon GuardDutyは悪意のある操作や不正な動作を継続的にモニタリングする脅威検出サービスです。チャレンジしてみようかと思いましたが、別エントリとして後日書きたいと思います。

ワークショップでの発見

時間がかかるカスタムアクションステージ

ワークショップの最後リンクに記述がありますが、このワークショップで追加したいくつかのLambdaファンクションのステージはAWS Systems Managerのオートメーションの実行にしろ、Amazon Inspectorでの脆弱性チェックは完了までに時間がかかり、AWS Lambdaのタイムアウトである15分を超える可能性もあります。

こういった完了までに時間が必要なカスタムアクションでは PutJobSuccessResult APIリクエストに継続トークンと呼ばれる continuationToken を指定することで、CodePipelineへ時間が必要であることを伝えることができ、CodePipelineは約30秒後にLambdaファンクションを再呼び出しするとのことです。このような機能がある事自体を知りませんでした。

ワークショップで作成したLambdaファンクションだと以下の部分になります。

        if "continuationToken" in event['CodePipeline.job']['data']:
            continuationToken = json.loads(
                event['CodePipeline.job']['data']['continuationToken']
            )
            AutomationExecutionId = continuationToken['AutomationExecutionId']
        else:
            # Not in a continuation.  Launch our respective automation document.

参考リンク:

最後に

DevSecOpsについてなんとなく雰囲気をつかめました。Amazon Inspectorでの脆弱性チェックは実際に業務でも取り入れられそうです。脆弱性への対処についてはこのままだと脆弱性あろうがなかろうがカーネルアップデートされそうですし、対処方法がカーネルではなくミドルウェアパッケージのアップデートの方が多そうなので、脆弱性への対処までをパイプラインに組み込む場合にはもっと工夫しなければいけなさそうだなと思いました。