セキュリティはアプリケーション開発で重視すべき要素ですが、システムの脆弱性が突かれるまで軽視されることも少なくありません。 セキュリティが侵害された場合、アプリケーションの完全性はもとより、会社の評判や収益まで損なわれるおそれがあります。 そのため、ソフトウェア アーキテクトやエンジニアには、担当するシステムのセキュリティに細心の注意を払うことが求められます。

そこで、DevOps をアプリケーション セキュリティの分野にまで拡張する DevSecOps というアイデアが考案されました。CI/CD ワークフローにセキュリティ面のテストを組み込み、セキュリティも自動化するのです。

この記事では、アプリケーションに自動セキュリティ チェックを組み込む方法について解説します。 具体的には、Web フォーム の URL フィールドについて、悪意のあるドメインが挿入されていないかテストする方法をご紹介します。 テストの目標は、Web フォームの処理を中断させることです。 今回使用する Web フォームには、攻撃を阻止するために、悪意のあるドメインが入力されていないかチェックするロジックを組み込んでいます。 また、CircleCI のパイプラインのスケジュール実行機能を使用して、セキュリティ テストを定期的に実行する方法もご紹介します。

前提条件

このチュートリアルを進めるには、以下の準備が必要です。

  • Node.js (バージョン 12 以上) をローカル システムにインストールする
  • Git をローカル システムにインストールする
  • Webshrinker アカウントを作成する (こちらから無料トライアルに登録できます)
  • CircleCI 新規アカウントを作成する

これらのセットアップが完了したら、チュートリアルを始めましょう。

Webshrinker の認証情報を取得する

Webshrinker は、DNS セキュリティ企業の DNSFilter が所有する AI ドメイン分類システムです。 脅威の可能性があるドメインを検出し、脅威のカテゴリに応じてラベル付けする機能を備えています。 Webshrinker で検出可能な脅威は、フィッシング、マルウェア、ボットネットの 3 つです。

このプロジェクトで保護を実装するフォームでは、URL フィールドで完全修飾ドメイン名を受け付けます。 入力されたドメイン名を Webshrinker の API に送信し、脅威ではないかスキャンします。 悪意のあるドメインであった場合、Webshrinker から脅威の識別子が返されます。 Web フォームは脅威の識別子に基づき、入力されたドメインの処理を拒否します。

このチュートリアルで Webshrinker を使用するには、API キーと API シークレットが必要です。 Webshrinker アカウントの作成後、[API Access Keys (API アクセス キー)] ページの [Create API Key (API キーを作成)] ボタンをクリックして、新しい API 認証情報を作成してください。 これで、API シークレットとトークンが作成されます。

これらの認証情報は次の手順で使用します。

デモ アプリケーションをクローンして実行する

最初の手順として、チュートリアル用 Web フォームをローカルで実行し、動作を確認しましょう。 以下のコマンドをシステム上で実行して、こちらのリポジトリ からフォームのコードをクローンしてください。

git clone -b base-project --single-branch https://github.com/coderonfleek/scheduled-security-scan.git

ローカル システムにコードを取得したら、以下のコマンドを実行して依存関係をインストールします。

cd scheduled-security-scan
npm install

次に、プロジェクトのルートにある index.html ファイルを開き、65 行目と 66 行目のプレースホルダーを Webshrinker の API キーと API シークレットにそれぞれ置き換えます。

依存関係のインストールが完了したら、以下のコマンドでアプリケーションを実行します。

npm start

アプリケーションが起動して、http://localhost:5000 に Web フォームが表示されます。

[Email address (メール アドレス)] フィールドにメールアドレスを入力します。 [Your Site (Web サイト URL)] フィールドに安全なドメイン (facebook.com など) を入力して、[Submit (送信)] をクリックします。 フォーム右側に、安全なドメインであることを示す応答メッセージが表示されます。

安全なドメインのテスト

次は、脅威であるとわかっているドメイン名 (selcdn.ru) を使用して、[Your Site (Web サイト URL)] フィールドをテストします。 このドメイン名をブラウザーに直接入力することは、絶対にしないでください。 今度は、脅威であることを示す警告メッセージが表示されます。

悪意のあるドメインのテスト

セキュリティ テストを追加する

次の手順として、上述のセキュリティ チェックを自動で実行するテストを作成しましょう。 Google の Puppeteer を使用して、自動機能テストをいくつか追加します。 Puppeteer は、実際のユーザーのフォーム入力をシミュレーションするツールです。

プロジェクトのルートに login.test.js という名前の新しいファイルを作成して、以下のコードを入力します。

const puppeteer = require("puppeteer");

const user_email = "test@example.com";
const non_threat_site = "facebook.com";
const malicious_site = "selcdn.ru";
const phishing_site = "mail.glesys.se";
const expected_safe_site_message = "Entry clean, process form";
const expected_threat_site_message = "Threat Detected. Do not Process";

test("Check Non-threat Site Entry", async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();

    await page.goto("http://localhost:5000");

    await page.type("#userEmail", user_email);
    await page.type("#userSite", non_threat_site);
    await page.click("#submitButton");

    let messageContainer = await page.$("#infoDisplay");
    await page.waitForTimeout(4000);
    let value = await messageContainer.evaluate((el) => el.textContent);

    console.log(value);

    expect(value).toBe(expected_safe_site_message);
  } finally {
    await browser.close();
  }
}, 120000);

test("Check Malicious Site Entry", async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();

    await page.goto("http://localhost:5000");

    await page.type("#userEmail", user_email);
    await page.type("#userSite", malicious_site);
    await page.click("#submitButton");

    let messageContainer = await page.$("#infoDisplay");
    await page.waitForTimeout(4000);

    let value = await messageContainer.evaluate((el) => el.textContent);

    console.log(value);

    expect(value).toBe(expected_threat_site_message);
  } finally {
    await browser.close();
  }
}, 120000);

このファイルでは、2 つのテスト ケースを定義しています。 最初のテスト ケースでは、脅威ではないドメイン (この例では facebook.com) がシステムでブロックされないことを確認します。 これにより、セキュリティ実装によって安全なドメインまでブロックされてしまう事態を防ぐことができます。

2 番目のテスト ケースでは、悪意のあるサンプル ドメインを使用して、悪意のある入力がなされたときの処理を確認します。 フォームでサンプル ドメインがブロックされれば、テストは合格です。 ブロックされなければ、テストは失敗です。

上記のファイルを保存して、ターミナルでプロジェクトのルートに移動します。 先に、他のシェルでアプリケーションを実行しておいてください。テストを行うにはアプリケーションが正常に実行されている必要があります。 以下のコマンドを実行します。

npm run test

テストが完了すると、CLI に以下のような結果が表示されます。

MACs-MBP-2:scheduled-security-scan mac$ npm run test

> xss-attack@1.0.0 test /Users/mac/Documents/CircleCiProjects/2022/scheduled-security-scan
> jest

  console.log
    Entry clean, process form

      at Object.<anonymous> (login.test.js:26:13)

 PASS  ./login.test.js (109.034 s)
  ✓ Check Non-threat Site Entry (80318 ms)
  ✓ Check Malicious Site Entry (8210 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        120.252 s
Ran all test suites.
  console.log
    Threat Detected. Do not Process

      at Object.<anonymous> (login.test.js:54:13)

開発者がテストを作成すると、テスト範囲を開発中のコードの機能だけに絞ってしまいがちです。 ベストプラクティスとして、開発チームとテスト チームを分けることをおすすめします。 こうすることで、開発の枠にとらわれない包括的なテストをテスト チームが記述できるようになります。

パイプライン設定ファイルを用意する

テスト プロセスの自動化には、CircleCI で継続的インテグレーション (CI) パイプラインを構築する方法がおすすめです。

CI パイプラインをセットアップするために、パイプライン設定ファイルを用意しましょう。 このファイルは、.circleci/config.yml として作成します。 プロジェクトのルートにこの名前のファイルを作成して、以下のコードを入力してください。

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:14.18-browsers
    steps:
      - checkout
      - run:
          name: NPM の更新
          command: "sudo npm install -g npm"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: 依存関係のインストール
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: アプリケーションの実行
          command: node server.js
          background: true
      - run:
          name: テストの実行
          command: npm run test

この設定ファイルの内容は次のとおりです。

  • ブラウザーと Node.js がプリインストールされている Docker イメージをプルする
  • npm をアップデートしてプロジェクトの依存関係をインストールする
  • Webshrinker テストで読み込めるようにアプリケーションをバックグラウンドで実行する
  • テストを実行する

設定ファイルを保存して、プロジェクトを GitHub にプッシュします。

次に、リポジトリを CircleCI プロジェクトとして追加します。

プロジェクトの追加 - CircleCI

ブランチの選択 - CircleCI

CircleCI でプロジェクトをセットアップすると、直ちにテストが実行されます。 ビルドが完了して、ビルド成功を示すステータスが表示されます。

ビルド成功 - CircleCI

[Build (ビルド)] リンクをクリックして、ビルドの詳細を確認しましょう。

ビルドの詳細 - CircleCI

CircleCI のパイプラインのスケジュール実行を設定する

通常、CI パイプラインが実行されるタイミングは、パイプラインのセットアップで使用したリモート コード リポジトリに新しいコミットが行われたときだけです。 今回作成したテストのようなセキュリティ スキャンの力を最大限に活用するには、新しいコードがプッシュされていない期間でも、常にアプリケーションのセキュリティ状態を最新に保つ必要があります。

つまり、cron ジョブのようにパイプラインを一定の間隔で実行して、アプリケーションの正確なセキュリティ状態を把握するのです。 作業をスムーズに進めるため、セキュリティ テストは、バグのチェックやアプリケーション機能の検証といった機能テストとは分けることをおすすめします。

CircleCI には、セキュリティ テストを定期的に自動実行するための機能として、パイプラインのスケジュール実行機能が用意されています。

このパイプラインのスケジュール実行機能では、特定の曜日またはすべての曜日において、指定した時刻にパイプラインを実行するように設定できます。

今回のチュートリアルでは、すべての曜日で 5 分おきにパイプラインを実行するように設定します。 この実行間隔は、実際の状況や熟慮の結果、ベストプラクティスなどに基づくものではなく、 チュートリアルの実施中にパイプラインが定期的に実行されることを確認するだけのものです。

パイプラインの定期実行を設定するには、CircleCI にアクセスして [Project Settings (プロジェクト設定)]、[Triggers (トリガー)] の順に選択します。

[Trigger (トリガー)] ページの [Add Trigger (トリガーを追加)] をクリックして、トリガー設定フォームを表示します。 以下のように、すべての曜日で 5 分おきにパイプラインを実行するように設定します。

トリガーの設定 - CircleCI

[Save Trigger (トリガーを保存)] をクリックして、トリガーを作成します。

[Pipelines (パイプライン)] ページに戻り、5 分ほど待ってください。 CircleCI によってパイプラインがトリガーされます。

スケジュールの実行

パイプラインが 5 分間隔で実行されれば、設定は成功です。

おわりに

このチュートリアルでは、CircleCI のパイプラインのスケジュール実行機能を利用して、セキュリティ スキャンを定期的に実行する方法について説明しました。 セキュリティ チェックを、コードのビルド時だけでなく一定の間隔でも実行することで、問題や侵害に対する脆弱性が発生していないか把握できます。

セキュリティ スキャンは、パイプラインのスケジュール実行機能が役に立つ数あるユースケースの 1 つに過ぎません。 パイプラインのスケジュール実行機能が活躍するケースとしては、ログやデータのアーカイブなどのクリーンアップ タスクも挙げられます。 ぜひ、今回学んだことをチームでシェアして、パイプラインのスケジュール実行機能の可能性をお試しください。

Happy coding!


Fikayo Adepoju は、Web とモバイル テクノロジー、DevOps に精通した LinkedIn Learning (Lynda.com) 講師、フルスタック開発者、テクニカル ライター、テクニカル コンテンツ クリエイターです。スケーラブルな分散アプリケーション開発については 10 年以上の経験を持っています。 CircleCI、Twilio、Auth0、The New Stack のブログで 40 以上の記事を執筆するほか、個人の Medium ページでも情報を発信しており、役立つ知識を多くの開発者に広めることに専心しています。 また、Udemy で動画形式のコース (英語) も開講しています。ぜひご覧ください。