NTTドコモR&Dの技術ブログです。

社内向けAPIポータルサイトのCMSをAWS App Runnerで作った

はじめに

NTTドコモ サービスデザイン部の伊藤です。 RAFTELというドコモ社内向けの共通API基盤の開発を担当しています。

本記事ではRAFTELチームで取り組んだポータルサイト開発について行った取り組みについて紹介します。

RAFTELとは

NTTドコモではLeminoやdマガジンなどの大規模サービスを多数提供しています。RAFTELはこのようなサービスを高速に低コストで実装・開発するためのAPI基盤です。

私たちはドコモのサービス開発を「はやく」「やすく」するために以下の取り組みを行ってます!

  • 複数の社内基盤をRAFTELに一本化し各部署への調整。申請の負担軽減!
  • 仕様書フォーマットを統一し学習コストを軽減!
  • ドコモのサービスが共通的に使う機能をマッシュアップAPIでまるっと提供!

そして、RAFTELをユーザーに知ってもらうため・使ってもらうためにポータルサイトを運営しています。

RAFTELポータルサイト

ポータルサイトには、利用者に必要な情報を提供するポータルサイトを目指して次のコンテンツを提供しています。

  • RAFTELの概要や機能 
  • API仕様書
  • APIの利用例
  • 利用申請フォーム
  • お問い合わせ

ポータルサイトには2つ課題がありました。

1つ目はポータルサイトの修正リリースの速度です。

ポータルサイトはReactで作成されており修正はエンジニアが担当しています。しかし、文言修正などの際にビルドとデプロイが必要であり、修正のリリース速度が遅くなったり、開発者目線では手間がかかったりすることから、CMS化の機会を窺っていました。

そんな中で、新たなコンテンツとして障害情報などユーザーへのお知らせを提供したいといった要望がありました。

障害情報は速報性が求められ開発者が対応できる時間とは限らないので、 いよいよ開発者がビルドやデプロイをしていたら間に合わないぞという要件だったので、CMS化に取り組む理由ができました。

2つ目は公開範囲です。

ポータルサイトは、Amazon S3のWeb Hostingで社内IPをバケットポリシーで許可することで、社内限定で公開しています。しかし、リモートワークやスマートフォンからのアクセスを希望する声があり、社内ネットワーク外からのアクセスも許可したいという課題がありました。

課題解決に向けて

課題解決に向けてチームで議論しました。

  • 利用者へのお知らせを開発者の手を介さずにポータルサイトに掲載する
  • お知らせをSlackに連携する
  • IPアドレス or Cognito認証でのアクセス制御

ポータルサイトのリリース速度はコンテンツの作成、更新が容易になるCMS化はすぐに決まりました

ポータルサイトはすでにReactで構築されているため、バックエンドとフロントエンドを分離できる「ヘッドレスCMS」にすることで今までのポータルサイトの資産を維持しつつCMS化ができることで「ヘッドレスCMS」を導入することが決まりました。

ヘッドレスCMSはいくつか候補がありましたが、、、 Slackへの連携で使えそうなWebhook機能があり人気が高くOSSであるStrapiを採用し、 実行環境はマネージドで構築と運用の手間が少ないAWS App Runnerを使うことに決めました。

IPアドレスやCognito認証によるアクセス制御については、AWS CloudFrontとLambda@Edgeを組み合わせることで実現できることがわかりました。そこで、この方法を採用することにしました。

前置きが長くなりましたが、ポータルサイトをCMS化するにために一番苦労をした「フロントエンドとバックエンドのアクセス制御」と「Strapi」について紹介したいと思います。

フロントエンド(CloudFront)を限定公開する

社内からのアクセスは今まで通り認証なしでアクセスでき、社外からのアクセスは認証を求めるようにします。

CloudFrontにLambda@Edgeで接続元のIPに応じて処理を分岐すればよいことがわかりました。

Lambda@Edgeの抜粋

  // 許可されているIPアドレスか判定
  if (isPermittedIp(request.clientIp) {
    console.log("許可IPだったのでOK");
    return callback(null, request);
  } else {
    console.log("非許可IPだったのでCognitoにお任せ");
    return authenticator.handle(event);    
  }

これで既存のユーザーの手間を増やすことなく、アクセス制御を行うことができました!👍

バックエンド(App Runner) を限定公開する

パブリックエンドポイントで設定するとApp Runnerから払い出されたURLで公開されてしまうので、フロントエンドと同様にIPもしくはCognitoでのアクセス制御を行う必要があります。

アクセス制御方法についてはネットで情報を集めつAWSサポートに確認をとり、 次の手段で制御を行うことにしました。

  1. App Runnerの前段にCloud FrontとWAFを前段に構えてApp Runnerへ直接アクセスできないように設定する
  2. Cloud Front にフロントエンドと同じLambda@Edgeを適用し、IPもしくはCognitoでアクセス制御をできるようにする

それではWAFとCloud Frontを設定します。

  1. AWS WAF で「特定のカスタムヘッダー名・値が設定されていない場合、アクセスをブロックする」というルールを持つWeb ACLを作成

  2. パブリックエンドポイントの App Runner サービスを作成する     1. で作成した Web ACL を設定する

  3. CloudFront のディストリビューションを作成

   [カスタムヘッダーを追加] で 1. のルールによってブロックされない特定のカスタムヘッダー名・値を追加

このような構成になりました。

図2

これでCloud Frontを経由しないアクセスをブロックすることができました👍

そして、、、フロントエンドのアクセス制御で作成したLambda@EdgeをバックエンドのCloud Frontに設定しました。

フロントエンドで使用したLambda@Edgeを使うことで社内のIPもしくはCognito認証でアクセスをバックエンドでも実現できました👍👍

(Strapiを使う上でRDSをVPCコネクタで接続していますが割愛してます)

Strapiについて

ここで少しStrapiについて紹介します。

strapi.io

Strapiは、Node.jsで開発されたオープンソースのヘッドレスCMSです。

ヘッドレスCMSとは、フロントエンドとバックエンドが分離されたCMSのことです。Strapiは、REST APIを介してコンテンツを公開するため、さまざまなフロントエンドフレームワークやライブラリと組み合わせることができます。

Strapiで少し戸惑った点として、Content Type BuilderでContent Typeを作成するとコードが生成されるので変更をApp Runnerにデプロイする必要があります。

データベースで管理されるのかなと思っていたので、仕組みを理解していなくて少し戸惑いました。

理解してからは、Docker Composeで開発環境を作りGitHubにプッシュしGitHubActionsでApp Runnerにデプロイしています。

私はStrapiを今回初めて利用しましたが機能も十分揃っていてUIもわかりやすく非常に使いやすいと感じてます!

Strapi と Slackを連携する

Strapiで記事が公開された時にSlackにも同じ内容を投稿するためにStrapiのWebhookを利用しました。

WebhookをAWS Lambdaで受け取りSlackAPIを使うことで実現できました👍

感想とまとめ

Cloud Front + Lambda@Edge + WAF でApp Runnerのアクセス制御を実現することができ、 App Runner を使うことで開発者がアプリケーションの開発に集中することができました。

CMS運用を開始してから1ヶ月経ち、Strapiを使ったお知らせやFAQなどコンテンツも増え活用の範囲が広がったので、まだCMS化できていないコンテンツをCMS化する予定です。

今後はSSGの導入など技術的にもレベルアップしていきたいです。