DevSecOps実践ガイド:CI/CD環境の構築編

はじめに

前回までに、GitLabとOpenShift、Gatekeeperを閉域環境で構築する手順を紹介しました。

本記事では、その環境を基盤としてGitLabのプロジェクト作成や設定を追加し、CI/CDパイプラインとポリシー検証を組み合わせたDevSecOpsの実践例に向けた環境構築を行います。

特に、金融や公共分野のように高いセキュリティ要件が求められるシステムでは、インターネット非接続環境での開発・運用が前提となるケースが多くあります。本記事で取り上げる構成は、そうした制約下でも有効な実践例となります。

なお、本記事では構築の全手順を網羅するのではなく、モデルケースの全体像と主要な構成要素を中心に解説します。詳細な設定値やコードについては、こちらのリポジトリをご参照ください。

ゴール

  • GitLabにビルド用・デプロイ用プロジェクトを作成する
  • OpenShiftのNamespaceや権限を設定する
  • Gatekeeperのポリシーを適用し、CI/CDパイプライン実行時にポリシー違反を検知できるようにする
  • 次回のデモ実演に向けた基盤を完成させる

DevSecOpsとは?

DevSecOpsとは、DevOpsの迅速な開発・運用プロセスにセキュリティを統合することで、スピードと安全性を両立する手法です。

従来の「開発の後にセキュリティチェックを行う」アプローチではなく、開発ライフサイクルの各段階にセキュリティを組み込む点が特徴です。

詳細については以下の記事で解説していますので、ご参照ください。

DevSecOpsとは?安全性とスピードを両立する開発手法 | SIOS Tech. Lab

前提条件

サンプル構成の全体像

プロジェクトの分割方針

  • devsecops-build-project(ビルド用)
    • ソースコードとDockerfileを管理し、イメージをビルドしてGitLab Container Registryにpushします。
  • devsecops-deploy-project(デプロイ用)
    • Helmチャートと環境別のvaluesファイルを管理し、Registryのイメージを参照してOpenShiftにデプロイします。

このように ビルドとデプロイを分けることで、開発コードの管理とデプロイ設定の管理を明確に切り分けられるようにしています。

リポジトリ構成(概要)

ビルド用リポジトリ

  • Containerfileとsrc/ : Nginxベースのアプリ
  • .gitlab-ci.yml : イメージビルド&レジストリへpush

デプロイ用リポジトリ

  • devsecops-nginx-chart/ : Helmチャート
  • develop-values.yamlとproduct-values.yaml : 環境別設定
  • .gitlab-ci.yml : Helmを使ったOpenShiftへのアプリのデプロイ

※詳細なディレクトリとファイル内容はこちらのリポジトリをご覧ください。

ブランチ戦略

  • feature/* : 開発作業用ブランチ
  • develop : 開発環境に対応
  • main : 本番環境に対応

feature → develop → main の流れでレビューを経たマージを行い、保護ブランチ設定によりdevelopやmainブランチへの直接pushは禁止しています。

パイプラインの流れ

  1. ビルドパイプライン(build-project)
    1. ソースコード変更を検知
    2. Runnerがイメージをビルドし、レジストリにpush
  2. デプロイパイプライン(deploy-project)
    1. Helm設定変更を検知
    2. Runnerがレジストリのイメージをpullし、OpenShiftにデプロイ
    3. Gatekeeperがポリシーを検証し、違反があればリソース作成を拒否

構築手順(概要)

手順の流れ

今回のサンプルケースでは以下の構成要素を準備します。

  1. GitLabユーザー作成
  2. ソースコード用プロジェクトの作成
  3. デプロイ用プロジェクトの作成
  4. OpenShift側のNamespaceと権限設定
  5. Gatekeeperのポリシー設定

1. GitLabユーザー作成

管理者アカウントでGitLabにログインし、プロジェクト管理用ユーザーを作成します。
今回使用するプロジェクトのネームスペースはここで作成したユーザー名を指定します。

続いて、作成したプロジェクト管理用ユーザー(devsecops-user)にSSHキーを登録し、Gitでのアクセスを確認します。

作成したプロジェクト管理用ユーザーでログインし、サイドメニューからアバターアイコンを選択→[プロファイルを編集]をクリックして、ユーザー設定画面に移動します。


ユーザー設定画面のサイドメニューで[SSHキー]を選択し、このユーザー用に作成した公開鍵を登録します。

例:SSHキー生成とGitLab接続確認

$ ssh-keygen -t rsa -b 4096 -C "devsecops-user@gitlab.local"
$ ssh -T git@gitlab-private.example.local

2. ソースコード用プロジェクト

先ほど作成したプロジェクト管理用ユーザーでログインし、GitLabで新規プロジェクトを作成します。
このプロジェクトはイメージのビルドとレジストリへのイメージ保存を担当します。

プロジェクト作成の詳細な手順は弊社ブログ記事Git & GitLab 入門 (7) ~Git マスターへの道~「GitLabのプロジェクトについて」 | SIOS Tech. Labをご参照ください。

続いて、作成したビルド用プロジェクト内でmain と develop ブランチを用いし、保護ブランチの設定を行います。
ブランチの保護設定を行うことで、mainブランチ及びdevelopブランチへの直接pushを防止し、パイプラインの誤動作を防止します。

ブランチ保護設定手順の詳細につきましても、弊社ブログ記事Git & GitLab 入門 (7) ~Git マスターへの道~「GitLabのプロジェクトについて」 | SIOS Tech. Labに記載しておりますので、ご参考になれば幸いです。

任意の開発者用ユーザーを作成し、プロジェクトにDeveloper権限で追加します。このユーザーはプロジェクト内でコードをコミットしたり、パイプラインを操作するために利用します。

プロジェクトにユーザーを招待する方法や主要なユーザー権限につきましては、Git & GitLab 入門 (7) ~Git マスターへの道~「GitLabのプロジェクトについて」 | SIOS Tech. Labに記載しておりますのでご参照ください。

プロジェクトに追加した開発用ユーザーでログインし、サンプルのアプリコードをリポジトリのfeatureブランチにpushし、管理を開始します。

アプリのベースイメージ(nginx:latestなど)をGitLab Container Registryにpushします。
※この記事では、OpenShiftの環境を使用しているため、コンテナ管理ツールとしてPodmanを利用しています。Dockerを利用している環境ではpodman→dockerに読み替えてください。

$ podman login registry.gitlab.local.example.com
$ podman pull nginx:latest
$ podman tag nginx:latest registry.gitlab.local.example.com/devsecops-user/devsecops-build-project/nginx:latest
$ podman push registry.gitlab.local.example.com/devsecops-user/devsecops-build-project/nginx:latest

参考:Git & GitLab 入門 (7) ~Git マスターへの道~「GitLabのプロジェクトについて」 | SIOS Tech. Lab

3. デプロイ用プロジェクト

プロジェクト管理用ユーザーでGitLabにログインし、今度はデプロイ用のGitLabプロジェクトを新規作成します。
このプロジェクトはレジストリのイメージを参照し、Helmを用いてOpenShiftにデプロイする役割を担います。

ビルド用プロジェクトを作成した時と同様に、mainとdevelopブランチに保護設定を行います。

今回はデプロイ手段としてHelmチャートを利用するため、このプロジェクトのレジストリにHelmが利用可能なコンテナイメージを格納します。
※この記事では、OpenShiftの環境を使用しているため、コンテナ管理ツールとしてPodmanを利用しています。Dockerを利用している環境ではpodman→dockerに読み替えてください。

$ podman login registry.gitlab.local.example.com
$ podman pull docker.io/alpine/helm:3
$ podman tag docker.io/alpine/helm:3 \
  registry.gitlab.local.example.com/root/test-project/helm:3
$ podman push registry.gitlab.local.example.com/root/test-project/helm:3

デプロイ用プロジェクトのレジストリにHelmイメージを格納しておくと、パイプライン内で実行されるジョブPodのイメージを指定できます。この指定は、パイプライン定義ファイル(.gitlab-ci.yml)のimageプロパティで行います。

.default_deploy: &default_deploy
  stage: deploy
  image: registry.gitlab.local.example.com/devsecops-user/devsecops-deploy-project/helm:3
  before_script:
    - set -euo pipefail
    - helm version
    - |
      if [ -n "${K8S_NAMESPACE:-}" ]; then
        HELM_NS_ARG="-n ${K8S_NAMESPACE}"
      else
        echo "K8S_NAMESPACE variable not provided. Helm will use the current context's namespace."
        HELM_NS_ARG=""
      fi
    - helm dependency update "$HELM_CHART_DIR" || true

今回使用するデプロイ用パイプラインは、ブランチに応じてデプロイ先のネームスペースを決定します。
たとえば、developブランチにマージした時はdevsecops-developネームスペースにアプリをデプロイし、mainブランチにマージした時はdevsecops-productionネームスペースにアプリをデプロイするなどの制御を行います。

deploy_develop:
  <<: *default_deploy
  tags:
    - devsecops-runner
  variables:
    <<: *common_vars
    VALUES_ENV: "deploy/Config/develop-values.yaml"
    K8S_NAMESPACE: "$DEPLOY_PROJECT_NAME_DEVELOP"
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop" && $CI_PIPELINE_SOURCE == "push"'
  environment: { name: develop, deployment_tier: development }
  script:
    - |
      set -euo pipefail
      : "${HELM_RELEASE:=devsecops-nginx}"
      : "${HELM_CHART_DIR:=deploy/devsecops-nginx-chart}"
      : "${VALUES_COMMON:=deploy/devsecops-nginx-chart/values.yaml}"
      : "${VALUES_ENV:?VALUES_ENV must be set}"
      test -f "${HELM_CHART_DIR}/Chart.yaml" || { echo "Chart.yaml not found under ${HELM_CHART_DIR}"; exit 1; }
      echo "[RUN] helm upgrade --install ${HELM_RELEASE} ${HELM_CHART_DIR} ${HELM_NS_ARG} -f ${VALUES_COMMON} -f ${VALUES_ENV}"
      helm upgrade --install "${HELM_RELEASE}" "${HELM_CHART_DIR}" ${HELM_NS_ARG} -f "${VALUES_COMMON}" -f "${VALUES_ENV}"

deploy_production:
  <<: *default_deploy
  tags:
    - devsecops-runner
  variables:
    <<: *common_vars
    VALUES_ENV: "deploy/Config/product-values.yaml"
    K8S_NAMESPACE: "$DEPLOY_PROJECT_NAME_PRODUCTION"
  rules:
    - if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"'
      allow_failure: false
  environment: { name: production, deployment_tier: production }
  script:
    - |
      set -euo pipefail
      : "${HELM_RELEASE:=devsecops-nginx}"
      : "${HELM_CHART_DIR:=deploy/devsecops-nginx-chart}"
      : "${VALUES_COMMON:=deploy/devsecops-nginx-chart/values.yaml}"
      : "${VALUES_ENV:?VALUES_ENV must be set}"
      test -f "${HELM_CHART_DIR}/Chart.yaml" || { echo "Chart.yaml not found under ${HELM_CHART_DIR}"; exit 1; }
      echo "[RUN] helm upgrade --install ${HELM_RELEASE} ${HELM_CHART_DIR} ${HELM_NS_ARG} -f ${VALUES_COMMON} -f ${VALUES_ENV}"
      helm upgrade --install "${HELM_RELEASE}" "${HELM_CHART_DIR}" ${HELM_NS_ARG} -f "${VALUES_COMMON}" -f "${VALUES_ENV}"

デプロイ用パイプラインが読み取れるCI/CD変数を登録します。
プロジェクト画面のサイドメニューから[設定] > [CI/CD]を選択し、[変数]のメニューを展開します。
[変数を追加]をクリックすると、CI/CDパイプラインが利用可能な環境変数を定義することができます。今回の例では、環境別のネームスペース名であるDEPLOY_PROJECT_NAME_DEVELOPとDEPLOY_PROJECT_NAME_PRODUCTIONを定義しています。他にも外部サービスのAPIキーなどを登録するなどの利用が可能です。

上記の作業が完了した後、デプロイ用のHelmチャートやマニフェストをfeatureブランチにpushします。

4. OpenShift設定

  • 開発用 (develop) と本番用 (production) のNamespaceを作成し、ラベルを付与します。
    ※tagのenvにはdevやprodなどデプロイ先の環境名を指定しています。
$ oc create namespace <namespace>
$ oc label namespace <namespace> tag=<env>
  • GitLab Runner用のServiceAccountを作成し、必要なRBAC権限とSCCを付与します。
  • 開発用 (develop) と本番用 (production) のNamespaceのGitLab Runner用のServiceAccountに必要なRBAC権限を付与
    • GitLab Container RegistryへのPull SecretをNamespaceに登録します。
    • 自己署名証明書を利用している場合はCA証明書をSecretとして登録します。
  • 開発用 (develop) と本番用 (production) のNamespaceのdefault ServiceAccountにanyuidのSCCを付与します。
  • GitLab Runner用のNamespaceのServiceAccountにデプロイ先namespaceでのedit権限を付与します。

5. Gatekeeper設定

  • 必要なConstraintTemplateを作成します(例:イメージタグに特定文字列を含める、Namespaceとラベルが一致していることなど)。
  • 開発環境、本番環境それぞれを対象としたConstraintsを作成します。
  • oc get コマンドでConstraintTemplateとConstraintsが正しく反映されていることを確認します。

参考:OPA/Gatekeeperで始める安心OpenShift運用:設定編

まとめ

ここまでで、GitLabのビルド・デプロイ用プロジェクト、OpenShiftのNamespace設定、Gatekeeperのポリシー適用といった要素が揃い、セキュリティを組み込んだCI/CD基盤のモデルケースが完成しました。これにより、ソースコードの変更がパイプラインを通じて自動的にビルド・デプロイされるだけでなく、Gatekeeperによるポリシー検証によって不適切なリソースの作成を未然に防ぐことが可能になります。

本記事で紹介した構成は、高いセキュリティ要件が求められるシステムにおいても有効なアプローチです。次回の記事では、この環境を活用して実際のパイプライン実行からポリシー違反検知までの一連の流れをデモンストレーションします。

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です