TECH PLAY

電通総研

電通総研 の技術ブログ

836

こんにちは!金融ソリューション事業部の山下です。 本記事では、画像生成AIのStableDiffusionとデプスマップ推定AIのBoostingMonocularDepthを用いて、上記のように板を押し出し加工したような(擬似)3Dモデル生成方法を紹介します。 3Dの モデリング および レンダリング は Houdini を使用します。 実施環境/ツール 実施手順 1. Stable Diffusion web UIのインストール 2. tex2imgで画像生成 3. BoostingMonocularDepthでデプスマップ生成 4. 生成画像をデプスマップを用いて3D化 完成イメージ 所感 参考 実施環境/ツール OS: Windows 11 pro GPU : NVIDIA GeForce RTX 3070Ti Laptop DCC:Houdini Indie version 19.5.493 Stable Diffusion 2.1 実施手順 Stable Diffusion web UIのインストール tex2imgで画像生成 BoostingMonocularDepthでデプスマップ生成 Houdiniで生成画像をデプスマップを用いて3D化 1. Stable Diffusion web UIのインストール 画像生成の為に、StableDiffusionの動作環境を構築します。 手順については XI本部三浦さんの記事 や オープンイノベーションラボ比嘉さんの記事 にて既に解説記事がありますので、こちらをご確認ください。 今回の記事では、ローカル環境にStable Diffusion web UI(AUTOMATIC1111)、Stable Diffusion 2.1をインストールしました。 2. tex2imgで画像生成 StableDiffusionに、以下のプロンプトを入力して画像を生成しました。 The marble woman bust statue stood in the center of the room. white marble surface gleaming in the soft light. The woman's face was finely carved. delicate features and a serene expression. Her eyes were closed, as if in peaceful contemplation. her lips were slightly curved in a faint smile. Her hair was styled in a series of curls that cascaded down her shoulders and back. the statue exuded a sense of grace and beauty, making it a true work of art. (DeepL日本語訳) 大理石の女性胸像が部屋の中央に立っていた。 白い大理石の表面が柔らかい光に照らされて光っている。その女性の顔には繊細な彫刻が施されている。 繊細な顔立ちで、穏やかな表情をしている。彼女の目は、まるで平和な思索に耽っているかのように閉じられている。 唇はわずかに曲がって、かすかな笑みを浮かべている。 彼女の髪は、肩から背中にかけて流れるような一連のカールをしている。 この像は、優雅さと美しさを醸し出しており、まさに芸術品と呼ぶにふさわしい。 ちなみにプロンプトはChatGPUを用いて作成しております。が、特筆すべき点はない為詳細は割愛します。 プロンプト生成方法にご興味のある方は、 イノラボ比嘉さんの一連の記事 なども参考になりますのでご覧ください。 3. BoostingMonocularDepthでデプスマップ生成 Depth生成前に、webuiのインターフェースから「stable-diffusion-webui-depthmap-script」Extensionのインストールを行います。インストールを行うと、以下のように「Depth」メニューが追加されます。 補足:執筆時、Extensionインストール時にエラーが出た為、以下 GitHub issueを立てて解決しました。 https://github.com/thygate/stable-diffusion-webui-depthmap-script/issues/55#issuecomment-1364693322 私の環境ではExtensionが依存しているライブラリ(NextViT)を手動でインストールする必要がありましたが、最新バージョンでは解決されているとのことです。 次に、2.で生成した画像をインプットして「Generate」ボタンを押下します。 手順4.の3Dモデル化の際に マッピング をする為、「Match input size」のチェックをONにします。 精度を高める為に「BOOST(multi-resolution merging)」のチェックONは推奨です。 参考論文: http://yaksoy.github.io/highresdepth/ 生成されたデプスマップです。 4. 生成画像をデプスマップを用いて3D化 いよいよ2.の生成画像と3.のデプスマップを用いて、 Houdini で3D化を行います。 今回作成したノードの全体像はこちらです。 Houdiniではこのように、ノードベースでプロシージャルに処理を行っていきます。 割とシンプルな処理ですが、一つずつ解説していきます(数字は上から数えたノード順)。 まず最初に、Gridノードでベースとなる板を作成します。 Primitive Typeは「Polygon」、押し出しの解像度を合わせる為にRows/Columnsは512とします。 26万2144ポリゴンの板が作成されました。 UV ProjectノードでUVを設定します。 レンダリング 時にマテリアルを設定する際に必要になります。 パラメータはデフォルトのままで大丈夫です。 Attribute from Mapノードで、デプスマップ情報を適用します。 「Texture Map」に画像パス、「Export Attribute」に任意の名称(今回は「depth」)を設定して、データ型を「Float」に変更します。 Geometry Spreadsheetを確認すると、各Pointのデータに列「depth」が追加され、float値が格納されています。 Attribute Blur ノードで、デプスマップ情報を滑らかにします。 Attributesに「depth」を設定します。他のパラメータはお好みですが、Blurring iterationsは10、Step Sizeは0.5を設定します。 Attribute from Mapノードで、生成画像のデータを読み込みます。 「Export Attribute」には「Cd」を設定します。 Attribute VOPノードで、各Pointに対してdepthを掛け合わせてGridを押し出します。 インプットから位置情報Pのベクトルを分解してZ軸を抽出した上で、bindノードから取得したdepth情報をsubtractしています。 後から押し出し加減をコン トロール する為、depthデータ取得後にrampノードを挟んでいます。 デフォルトだと押し出しが強すぎた為、以下のようにB-Spline補完を用いてrampを設定しました。 depthの値による押し出しができました。 Group Createノードで、外縁のPointを「sides」という名称でグループ化します。 Include by EdgesをEnable、「Unshared Edges」をONにします。 Attribute Createノードで、各Pointの法線ベクトルNをZ軸+方向に設定します。 PolyExtrudeノードで、「sides」グループのみ法線ベクトルN方向に押し出します。 Normalノードで、法線ベクトルNを元に戻しておきます。 Nullノードで「OUT」という名称をつけて、3D化は完成です。 完成イメージ Soraris Karmaで レンダリング した結果がこちらです。 今回は Solaris Karmaで レンダリング を行いました。ノードの最終系は以下になります。 作成した3DモデルをSOP importで読み込み、マテリアルを設定した後、ライティングとカメラ設定を行い、Karma XPUで レンダリング を行いました。記事が長くなってしまうので、今回は レンダリング 部分の詳細説明は割愛します。 所感 今回は複数の技術(ChatGPU、画像生成AI、進度推定AI)を組み合わせました。Houdiniを使うと3Dの細かいデータやパラメータを確認しながらプロシージャルに試行錯誤ができ、非常に強力なツールです。 Project Titan のように、Houdiniは ゲームエンジン との連携も進んでいる為、今後も様々な検証を行っていきたいと思います。 また、最近のAIの進歩には目覚ましいものがあり、特にマテリアル生成の領域では実用レベルの域に達していますので、こちらも別記事で紹介していきたいと思います! 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://github.com/AUTOMATIC1111/stable-diffusion-webui https://github.com/thygate/stable-diffusion-webui-depthmap-script https://github.com/compphoto/BoostingMonocularDepth https://entagma.com/stable-diffusion-2-0-quickstart/ 執筆: @yamashita.yuki 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは!金融ソリューション事業部の山下です。 本記事では、画像生成AIのStableDiffusionとデプスマップ推定AIのBoostingMonocularDepthを用いて、上記のように板を押し出し加工したような(擬似)3Dモデル生成方法を紹介します。 3Dの モデリング および レンダリング は Houdini を使用します。 実施環境/ツール 実施手順 1. Stable Diffusion web UIのインストール 2. tex2imgで画像生成 3. BoostingMonocularDepthでデプスマップ生成 4. 生成画像をデプスマップを用いて3D化 完成イメージ 所感 参考 実施環境/ツール OS: Windows 11 pro GPU : NVIDIA GeForce RTX 3070Ti Laptop DCC:Houdini Indie version 19.5.493 Stable Diffusion 2.1 実施手順 Stable Diffusion web UIのインストール tex2imgで画像生成 BoostingMonocularDepthでデプスマップ生成 Houdiniで生成画像をデプスマップを用いて3D化 1. Stable Diffusion web UIのインストール 画像生成の為に、StableDiffusionの動作環境を構築します。 手順については XI本部三浦さんの記事 や オープンイノベーションラボ比嘉さんの記事 にて既に解説記事がありますので、こちらをご確認ください。 今回の記事では、ローカル環境にStable Diffusion web UI(AUTOMATIC1111)、Stable Diffusion 2.1をインストールしました。 2. tex2imgで画像生成 StableDiffusionに、以下のプロンプトを入力して画像を生成しました。 The marble woman bust statue stood in the center of the room. white marble surface gleaming in the soft light. The woman's face was finely carved. delicate features and a serene expression. Her eyes were closed, as if in peaceful contemplation. her lips were slightly curved in a faint smile. Her hair was styled in a series of curls that cascaded down her shoulders and back. the statue exuded a sense of grace and beauty, making it a true work of art. (DeepL日本語訳) 大理石の女性胸像が部屋の中央に立っていた。 白い大理石の表面が柔らかい光に照らされて光っている。その女性の顔には繊細な彫刻が施されている。 繊細な顔立ちで、穏やかな表情をしている。彼女の目は、まるで平和な思索に耽っているかのように閉じられている。 唇はわずかに曲がって、かすかな笑みを浮かべている。 彼女の髪は、肩から背中にかけて流れるような一連のカールをしている。 この像は、優雅さと美しさを醸し出しており、まさに芸術品と呼ぶにふさわしい。 ちなみにプロンプトはChatGPUを用いて作成しております。が、特筆すべき点はない為詳細は割愛します。 プロンプト生成方法にご興味のある方は、 イノラボ比嘉さんの一連の記事 なども参考になりますのでご覧ください。 3. BoostingMonocularDepthでデプスマップ生成 Depth生成前に、webuiのインターフェースから「stable-diffusion-webui-depthmap-script」Extensionのインストールを行います。インストールを行うと、以下のように「Depth」メニューが追加されます。 補足:執筆時、Extensionインストール時にエラーが出た為、以下 GitHub issueを立てて解決しました。 https://github.com/thygate/stable-diffusion-webui-depthmap-script/issues/55#issuecomment-1364693322 私の環境ではExtensionが依存しているライブラリ(NextViT)を手動でインストールする必要がありましたが、最新バージョンでは解決されているとのことです。 次に、2.で生成した画像をインプットして「Generate」ボタンを押下します。 手順4.の3Dモデル化の際に マッピング をする為、「Match input size」のチェックをONにします。 精度を高める為に「BOOST(multi-resolution merging)」のチェックONは推奨です。 参考論文: http://yaksoy.github.io/highresdepth/ 生成されたデプスマップです。 4. 生成画像をデプスマップを用いて3D化 いよいよ2.の生成画像と3.のデプスマップを用いて、 Houdini で3D化を行います。 今回作成したノードの全体像はこちらです。 Houdiniではこのように、ノードベースでプロシージャルに処理を行っていきます。 割とシンプルな処理ですが、一つずつ解説していきます(数字は上から数えたノード順)。 まず最初に、Gridノードでベースとなる板を作成します。 Primitive Typeは「Polygon」、押し出しの解像度を合わせる為にRows/Columnsは512とします。 26万2144ポリゴンの板が作成されました。 UV ProjectノードでUVを設定します。 レンダリング 時にマテリアルを設定する際に必要になります。 パラメータはデフォルトのままで大丈夫です。 Attribute from Mapノードで、デプスマップ情報を適用します。 「Texture Map」に画像パス、「Export Attribute」に任意の名称(今回は「depth」)を設定して、データ型を「Float」に変更します。 Geometry Spreadsheetを確認すると、各Pointのデータに列「depth」が追加され、float値が格納されています。 Attribute Blur ノードで、デプスマップ情報を滑らかにします。 Attributesに「depth」を設定します。他のパラメータはお好みですが、Blurring iterationsは10、Step Sizeは0.5を設定します。 Attribute from Mapノードで、生成画像のデータを読み込みます。 「Export Attribute」には「Cd」を設定します。 Attribute VOPノードで、各Pointに対してdepthを掛け合わせてGridを押し出します。 インプットから位置情報Pのベクトルを分解してZ軸を抽出した上で、bindノードから取得したdepth情報をsubtractしています。 後から押し出し加減をコン トロール する為、depthデータ取得後にrampノードを挟んでいます。 デフォルトだと押し出しが強すぎた為、以下のようにB-Spline補完を用いてrampを設定しました。 depthの値による押し出しができました。 Group Createノードで、外縁のPointを「sides」という名称でグループ化します。 Include by EdgesをEnable、「Unshared Edges」をONにします。 Attribute Createノードで、各Pointの法線ベクトルNをZ軸+方向に設定します。 PolyExtrudeノードで、「sides」グループのみ法線ベクトルN方向に押し出します。 Normalノードで、法線ベクトルNを元に戻しておきます。 Nullノードで「OUT」という名称をつけて、3D化は完成です。 完成イメージ Soraris Karmaで レンダリング した結果がこちらです。 今回は Solaris Karmaで レンダリング を行いました。ノードの最終系は以下になります。 作成した3DモデルをSOP importで読み込み、マテリアルを設定した後、ライティングとカメラ設定を行い、Karma XPUで レンダリング を行いました。記事が長くなってしまうので、今回は レンダリング 部分の詳細説明は割愛します。 所感 今回は複数の技術(ChatGPU、画像生成AI、進度推定AI)を組み合わせました。Houdiniを使うと3Dの細かいデータやパラメータを確認しながらプロシージャルに試行錯誤ができ、非常に強力なツールです。 Project Titan のように、Houdiniは ゲームエンジン との連携も進んでいる為、今後も様々な検証を行っていきたいと思います。 また、最近のAIの進歩には目覚ましいものがあり、特にマテリアル生成の領域では実用レベルの域に達していますので、こちらも別記事で紹介していきたいと思います! 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://github.com/AUTOMATIC1111/stable-diffusion-webui https://github.com/thygate/stable-diffusion-webui-depthmap-script https://github.com/compphoto/BoostingMonocularDepth https://entagma.com/stable-diffusion-2-0-quickstart/ 執筆: @yamashita.yuki 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは、XI 本部ソフトウェアデザインセンター所属・新卒 1 年目の松本です。 昨年10月に配属されてから 3 ヶ月間、Atlassian Forge を使った アプリ開発 を担当しました。 そこで今回は、Forge の概要と実際の開発手順を解説していきます。 Forge に関する日本語記事はほとんどなく心細い想いをしたので、本記事が誰かの助けになれば幸いです。 Forge とは UI 構築手法 2 パターン UI kit Custom UI 開発手順の例 到達目標 執筆者の環境 ① Node.js の導入 ② Forge CLI の導入 ③ プロジェクトの作成 ④ ビルド・デプロイ・インストール ⑤ TypeScript への対応 ⑥ Atlassian REST API と UI kit フックを使い、必要な情報を取得してみる ⑦ 取得した値を表示する ⑧ 編集内容を提出できるようにする 開発を便利にする Tips UI kit を使ってみた感想 おわりに Forge とは Atlassian 社が提供する FaaS プラットフォームです。Atlassian のサービスをカスタマイズ・機能拡張するためのアプリを、ユーザー自身で簡単に作成できます。現在は、Jira、Jira Service Management、Confluence の3つのサービスに対応しており、作成したアプリはこれらのサービス上から利用することとなります。 アプリの例としては、 Jira の課題パネルから多言語翻訳を使えるようにするアプリ Jira の課題の健全性を、更新の滞りなどの情報をもとに判断してくれるアプリ Confluence に、 Google フォトに保存されている写真を表示できるようにするアプリ などが公式ドキュメントで紹介されています。 https://developer.atlassian.com/platform/forge/example-apps/ アプリ開発 に伴う機能実装以外の作業(ビルド・デプロイ・権限の管理・スケーリング・テナント管理などなど)は Atlassian 側でほとんど担ってくれるため、開発者は実現したい機能の実装に注力できます。 UI 構築手法 2 パターン アプリの UI 構築手法が 2 パターン用意されており、いずれかを選ぶ必要があります。 迅速・簡便な UI kit と 自由度が高い Custom UI です。 UI kit 提供された コンポーネント を組み合わせて UI を構築します。 コンポーネント は、各種入力フォーム・ボタン・テーブルなど、豊富に用意されています。いずれも コンポーネント も Atlassian 風のデザインとなっており、自分でデザインを考える手間が省けます(逆に、カスタマイズ性はほとんどありません)。 使い方は React の コンポーネント とよく似ています。また、フック機能も提供されており、こちらもほぼ React のそれです。 なお、 レンダリング は全てサーバー側で行われるため、相応の遅延が発生します。また、現状、 コンポーネント とフックは提供されたもの以外には使うことができないため、柔軟性に欠けます(Forge 開発チームの動向を見ていると、近々できるようになるかも?)。 Custom UI HTML・ CSS ・ JavaScript などの静的リソースを使用して、独自の UI を構築します。UI kit とは異なり、 レンダリング はユーザー側で行われるため、速いです(ただし、外部リソースへのアクセスはバックエンドを経由する必要がある、といったルールがあります)。 タイトルの通り、以降の開発手順では前者の UI kit を用います。Custom UI の解説はまたの機会に…。 開発手順の例 到達目標 手順の紹介に入る前に、本記事における開発の到達目標を明確にしておきます。 ここでは、Jira Service Management に「リク エス ト内容をリク エス ター自身が編集できる機能」を追加することを目標とします。 Jira Service Management とは、Jira を拡張したサービスデスク管理ツールです。Jira Service Management では現状、届いたリク エス ト(= 困っている人からの問い合わせ)の内容を管理者側から編集することはできても、リク エス ター側からはできないという制限があります。今回実装する機能は、こうした制限を解決するものです。 なお、Jira や Confluence を対象にした場合も同じような流れで開発を進められます。 完成イメージ 設置したボタンを押すと、 編集用のモーダルが開き、 編集して submit を押すと、 反映される。 執筆者の環境 主要なものだけあげておきます。 macOS Monterey 12.6 Visual Studio Code 1.74.3 node 16.17.1 Forge CLI 6.4.0 Forge API 2.7.0 React 18.2.0 それでは開発に入っていきましょう。 ① Node.js の導入 v14 以降の LTS release が必要です。入っていない場合はインストールしてください。 ② Forge CLI の導入 Forge CLI は、Forge アプリを管理するために使用する要のパッケージです。npm からインストールしてください。 npm install --save-dev @forge/cli インストール後、 API トーク ンを使用してログインする必要があります。 詳細な手順は、以下のドキュメントを参照してください。 https://developer.atlassian.com/platform/forge/getting-started/#log-in-with-an-atlassian-api-token ③ プロジェクトの作成 以下のコマンドでプロジェクトを作成します。 forge create コマンドを実行すると 3 つ質問されるので、順に答えていきます。 (1) アプリ名 好きなアプリ名をつけましょう。 (2) UIツール UI kit と Custom UI の選択です。前述の通り、今回は UI kit を選択します。 (3) モジュールのテンプレート Forge では、Jira などのサービスにアプリを組み込むための機能を「モジュール」として提供しています。作りたいアプリに適したモジュールを選択すると、そのモジュールに合わせて良い感じのテンプレートを作成してくれます。 今回は、Jira Service Management のリク エス ト閲覧画面に編集ボタンを付けたいので、「jira-service-management- portal -request-view-action」というモジュールを選択します。これは「リク エス ト閲覧画面にボタンが追加され、ボタンをクリックすると定義したアクションを走らせることができる」というモジュールです。 モジュール一覧は以下から確認できますので、作りたいアプリに合わせて選んでみてください。 https://developer.atlassian.com/platform/forge/manifest-reference/modules/ 以上3つの選択が完了すると、自動的に以下のようなプロジェクトが作成されます。 コードも少し覗いてみましょう。 // src/index.jsx import ForgeUI, { render, Text, PortalRequestViewAction, ModalDialog, useState } from '@forge/ui'; const App = () => { const [isOpen, setOpen] = useState(true); if (!isOpen) { return null; } return ( <ModalDialog header="Hello" onClose={() => setOpen(false)}> <Text>Hello world!</Text> </ModalDialog> ); }; export const run = render( <PortalRequestViewAction> <App/> </PortalRequestViewAction> ); React そっくりですね。簡単にコードの解説をしておくと… useState は UI kit が提供するフックの1つで、使い方は React の useState とほとんど同じです。 <ModalDialog> や <Text> は UI kit componets と呼ばれ、Atlssian 風デザインのパーツを提供するための コンポーネント です。 一方、<PortalRequestViewAction> は Function components と呼ばれ、アプリ作成時に選択したモジュールが提供する コンポーネント です。今回の例では、<PortalRequestViewAction> に囲まれた UI kit componets が、リク エス ト閲覧ページのボタンをクリックした際に表示されることとなります。 ④ ビルド・デプロイ・インストール ここで 1 度アプリをデプロイし、Atlassian サービス上で確認してみましょう。 アプリのルートで以下のコマンドを実行します。これ 1 つでエラーチェック・ビルド・デプロイまでを行ってくれる強力なコマンドです。 forge deploy デプロイ先は develop、staging、production の3つが用意されており、--environment (-e) オプションで指定できます (デフォルトは development)。今回は development にします。 続いて、以下のコマンドを実行し、デプロイしたアプリを自分が管理するテナントにインストールします。実行後にテナントの ドメイン を聞かれるので入力してください。 forge install これで、Atlassianのサービスからアプリを使えるようになりました。確認してみましょう。 今回は Jira Service Management のリク エス ト閲覧画面にボタンを追加するモジュールを使ったので、当該画面を見に行きます。 すると、以下のようにボタンが設置されており、 クリックするとモーダルが開いて「 Hello World !」と表示されます。 ここまで 1 ミリもコーディングをしていません。素晴らしいですね。 ⑤ TypeScript への対応 本ステップは任意ですが、UI kit では簡単に対応できるので、ぜひやっておきましょう。 以下の 4 つの作業を行います。 (1) tsconfig. json の作成 今回は以下のように設定しました。 // tsconfig.json { "compilerOptions": { "target": "es2020", "jsx": "react", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true } } (2) ソースファイルの拡張子を変更 js(x) → ts(x) (3) TypeScript の プリプロセッサ を package. json に追加 必要な プリプロセッサ があれば追加しましょう。 ついでに、React がないと index. tsx で エラーが表示されてしまうので、react および対応する プリプロセッサ を npm からインストールするとともに、index. tsx でインポートしておきます。 // src/index.tsx import React from "react" なお、TypeScript の コンパイル は、前述した forge deploy コマンド実行時に一緒に実行してくれます。 ⑥ Atlassian REST API と UI kit フックを使い、必要な情報を取得してみる 続いて、編集に必要な情報を取得して、モーダルに表示してみます。 アプリと Atlassian サービスとのやり取りは、基本的には Atlassian が提供する REST API を介して行われます。加えて、現在閲覧しているリク エス トの ID など、UI kit のフックを介して取得する情報も一部あります。 Forgeでは、 REST API の認可は OAuth2.0 で行われており、権限を移譲されたアプリがユーザーに代わって API 呼び出しを行います。そのため、開発者は トーク ンなどの機密情報を管理する必要がなく、簡潔なコードで API を呼び出せます。 併せて、アプリにどの程度の権限(スコープ)を与えるかを、 マニフェスト ファイル (manifest.yml) に記述する必要があり、このスコープによって扱える API の範囲が決まります。 利用できる Atlassian REST API は以下から確認してください。その API に必要なスコープなどの情報も記載されています。 https://developer.atlassian.com/platform/forge/product-rest-api-reference/ 今回は、現在閲覧しているリク エス トの ID をUI kit の フック で取得し、その ID をもとに、リク エス トの設問文や問い合わせ内容を REST API で取得してみます。 現在閲覧しているページの情報は、useProductContext という UI kit フックで取得します。なお、本フックの返り値の型として ProductContext 型が提供されていますが、Jira のみに対応していて、Jira Service Management には対応していません。ですので、今回は ProductContextForJsm 型を自身で定義しています。 // src/index.tsx // ProductContextForJsm の型定義 (省略。「コード全文を見る」から確認できます。) const productContext = useProductContext() as ProductContextForJsm; const requestId = productContext.extensionContext.request.key; 次に、欲しい情報を REST API で取得します。まずは @forge/ api モジュールを npm からインストールするとともに、index. tsx でインポートします。 // src/index.tsx import api, { Route, route } from "@forge/api"; リク エス ト情報の取得には「Get customer request by id or key」という API を用います。この API を呼びだす関数を定義しましょう。 // src/index.tsx // ResponseJson の型定義 (省略。「コード全文を見る」から確認できます。) const fetchRequest = async (requestKey: string): Promise<ResponseJson> => { const response = await api .asApp() .requestJira(route`/rest/servicedeskapi/request/${requestKey}`, { headers: { Accept: "application/json", }, }); return await response.json(); }; API 呼び出しの関数は、useEffect 内で呼び出し、返り値は state に保持するのが良いでしょう。 state を追加し、 // src/index.tsx const [responseJson, setResponseJson] = useState<ResponseJson>({ requestFieldValues: [], }); useEffect 内で API を呼び出して、返り値を state にセットします。 // src/index.tsx useEffect(async () => { const responseJson = await fetchRequest(requestId); setResponseJson(responseJson); }, []); 最後に、使用する API を呼びだすために必要なスコープを、 マニフェスト ファイルに追加します。 // manifest.yml permissions: scopes: - "read:servicedesk-request" - "read:jira-work" コード全文を見る // src/index.tsx import api, { Route, route } from "@forge/api"; import ForgeUI, { render, Text, PortalRequestViewAction, ModalDialog, useState, useProductContext, useEffect, } from "@forge/ui"; import { ExtensionContext, ProductContext } from "@forge/ui/out/types"; import React from "react"; interface ProductContextForJsm extends ProductContext { extensionContext: ExtensionContextForJsm; } interface ExtensionContextForJsm extends ExtensionContext { request: { key: string }; } interface ResponseJson { requestFieldValues: Request[]; } interface Request { fieldId: string; label: string; value: string; } const App = () => { const [isOpen, setOpen] = useState(true); const [responseJson, setResponseJson] = useState<ResponseJson>({ requestFieldValues: [], }); useEffect(async () => { const responseJson = await fetchRequest(requestId); setResponseJson(responseJson); }, []); const productContext = useProductContext() as ProductContextForJsm; const requestId = productContext.extensionContext.request.key; const fetchRequest = async (requestKey: string): Promise<ResponseJson> => { const response = await api .asApp() .requestJira(route`/rest/servicedeskapi/request/${requestKey}`, { headers: { Accept: "application/json", }, }); return await response.json(); }; if (!isOpen) { return null; } return ( <ModalDialog header="Hello" onClose={() => setOpen(false)}> <Text>Hello world!</Text> </ModalDialog> ); }; export const run = render( <PortalRequestViewAction> <App /> </PortalRequestViewAction> ); ⑦ 取得した値を表示する 続いて、先ほど取得した値を画面に表示してみましょう。Jira Service Management では多様な設問タイプ(テキストボックス、 チェックボックス 、 ラジオボタン ・・・)がありますが、これら全てに対応しようとすると、本記事では収まりきりません。今回は簡易的に、リク エス トの「要約」欄のみを表示してみることにします。 これです。 Form、TextField という UI kit コンポーネント を使用するので、まずはインポートを追加します。Text コンポーネント はもう使わないので、消しておきます。 // src/index.tsx import ForgeUI, { render, PortalRequestViewAction, ModalDialog, useState, useProductContext, useEffect, Form, TextField, } from "@forge/ui"; 続いて、 REST API で受け取ったリク エス トの情報から要約欄の情報だけを抜き出し、 TextField コンポーネント に変換する関数を定義します。 // src/index.tsx const makeTextField = ( responseJson: ResponseJson ): JSX.Element | undefined => { const summary = responseJson.requestFieldValues.find( (request) => request.fieldId === "summary" ); if (!summary) { return; } return ( <TextField label={summary.label} name={summary.fieldId} defaultValue={summary.value} ></TextField> ); }; 最後に、先ほど作成した関数から返される TextField コンポーネント を Form コンポーネント で囲み、App 関数で返すようにします。現在「 Hello World 」を返している部分を変更します。 // src/index.tsx return ( <ModalDialog header="Edit" onClose={() => setOpen(false)}> <Form onSubmit={() => setOpen(false)}>{makeTextField(responseJson)}</Form> </ModalDialog> ); Form コンポーネント は onSubmit プロパティ(画面で submit ボタンが押された際に走る処理)の指定が必須ですが、一旦は、画面を閉じる処理を入れておきます。 それでは、画面で確認してみましょう、、、 の前に、アプリのスコープを変更したので、アップグレードが必要です。以下のコマンドでアップグレードを実行します。 forge install --upgrade これでアプリを利用可能になりました。 確認してみると、以下のように、要約欄の情報を取得できるようになっていると思います。 コード全文を見る // src/index.tsx import api, { Route, route } from "@forge/api"; import ForgeUI, { render, PortalRequestViewAction, ModalDialog, useState, useProductContext, useEffect, Form, TextField, } from "@forge/ui"; import { ExtensionContext, ProductContext } from "@forge/ui/out/types"; import React from "react"; interface ProductContextForJsm extends ProductContext { extensionContext: ExtensionContextForJsm; } interface ExtensionContextForJsm extends ExtensionContext { request: { key: string }; } interface ResponseJson { requestFieldValues: Request[]; } interface Request { fieldId: string; label: string; value: string; } const App = () => { const [isOpen, setOpen] = useState(true); const [responseJson, setResponseJson] = useState<ResponseJson>({ requestFieldValues: [], }); useEffect(async () => { const responseJson = await fetchRequest(requestId); setResponseJson(responseJson); }, []); const productContext = useProductContext() as ProductContextForJsm; const requestId = productContext.extensionContext.request.key; const fetchRequest = async (requestKey: string): Promise<ResponseJson> => { const response = await api .asApp() .requestJira(route`/rest/servicedeskapi/request/${requestKey}`, { headers: { Accept: "application/json", }, }); return await response.json(); }; const makeTextField = ( responseJson: ResponseJson ): JSX.Element | undefined => { const summary = responseJson.requestFieldValues.find( (request) => request.fieldId === "summary" ); if (!summary) { return; } return ( <TextField label={summary.label} name={summary.fieldId} defaultValue={summary.value} ></TextField> ); }; if (!isOpen) { return null; } return ( <ModalDialog header="Edit" onClose={() => setOpen(false)}> <Form onSubmit={() => setOpen(false)}>{makeTextField(responseJson)}</Form> </ModalDialog> ); }; export const run = render( <PortalRequestViewAction> <App /> </PortalRequestViewAction> ); ⑧ 編集内容を提出できるようにする それでは最後に、編集した内容を提出し、変更を反映できるようにします。 編集内容の確定には「Edit issue」という API を使用します。この API を呼びだす関数を作成します。この際、リク エス トボディには、submit ボタンが押された際に送られてくるデータをもとに作成した JSON を指定します。 // src/index.tsx const execEdit = async (submitted: {summary: string}) => { await api.asApp().requestJira(route`/rest/api/3/issue/${requestId}`, { method: "PUT", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: `{"fields":{"summary":"${submitted["summary"]}"}}`, }); setOpen(false); }; そして、この関数を Form コンポーネント の onSubmit プロパティで指定します。 // src/index.tsx return ( <ModalDialog header="Edit" onClose={() => setOpen(false)}> <Form onSubmit={execEdit}>{makeTextField(responseJson)}</Form> </ModalDialog> ); これでOKです。 画面で確認する前に、新たに API を追加したので、書き込み権限をアプリに与えてアップグレードしましょう。 // manifest.yml permissions: scopes: - "read:servicedesk-request" - "read:jira-work" - "write:jira-work" 以上で全ての工程が完了です。実際に使ってみます。 要約欄を編集し、submit を押して…リロードすると… 反映されています! コード全文を見る // src/index.tsx import api, { Route, route } from "@forge/api"; import ForgeUI, { render, PortalRequestViewAction, ModalDialog, useState, useProductContext, useEffect, Form, TextField, } from "@forge/ui"; import { ExtensionContext, ProductContext } from "@forge/ui/out/types"; import React from "react"; interface ProductContextForJsm extends ProductContext { extensionContext: ExtensionContextForJsm; } interface ExtensionContextForJsm extends ExtensionContext { request: { key: string }; } interface ResponseJson { requestFieldValues: Request[]; } interface Request { fieldId: string; label: string; value: string; } const App = () => { const [isOpen, setOpen] = useState(true); const [responseJson, setResponseJson] = useState<ResponseJson>({ requestFieldValues: [], }); useEffect(async () => { const responseJson = await fetchRequest(requestId); setResponseJson(responseJson); }, []); const productContext = useProductContext() as ProductContextForJsm; const requestId = productContext.extensionContext.request.key; const fetchRequest = async (requestKey: string): Promise<ResponseJson> => { const response = await api .asApp() .requestJira(route`/rest/servicedeskapi/request/${requestKey}`, { headers: { Accept: "application/json", }, }); return await response.json(); }; const makeTextField = ( responseJson: ResponseJson ): JSX.Element | undefined => { const summary = responseJson.requestFieldValues.find( (request) => request.fieldId === "summary" ); if (!summary) { return; } return ( <TextField label={summary.label} name={summary.fieldId} defaultValue={summary.value} ></TextField> ); }; const execEdit = async (submitted: { summary: string }) => { await api.asApp().requestJira(route`/rest/api/3/issue/${requestId}`, { method: "PUT", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: `{"fields":{"summary":"${submitted["summary"]}"}}`, }); setOpen(false); }; if (!isOpen) { return null; } return ( <ModalDialog header="Edit" onClose={() => setOpen(false)}> <Form onSubmit={execEdit}>{makeTextField(responseJson)}</Form> </ModalDialog> ); }; export const run = render( <PortalRequestViewAction> <App /> </PortalRequestViewAction> ); 開発を便利にする Tips 開発手順の紹介は以上ですが、開発を便利にする Tips も紹介しておきます。 ① ログの出し方 ログを出したい箇所に console.log("出したい内容") を入れ、 forge logs を実行することで、ログが確認できます。 いちいち上記コマンドを打つのは面倒ですが、次に紹介する Tunnel モードを組み合わせることで解消されます。 ② Tunnel モード ファイル保存時に自動的に再ビルドを実行してくれる機能です。ただし、development 環境へアプリをデプロイしている場合に限定されます。いちいち forge deploy コマンドを打つ手間が省けるほか、前述したログもリアルタイムで出してくれるため、非常に便利です。 使い方は以下を確認してください。 https://developer.atlassian.com/platform/forge/tunneling/ 注意点として、たまに、何のエラーも出ていないにもかかわらず変更が反映されない場合がありました。把握しておかないと永遠に時間を溶かすことになるので気をつけてください。 ③ モジュールの追加方法 forge create コマンドでプロジェクトを作成した直後はモジュールは1つだけですが、当然複数モジュールを扱うこともできます。 マニフェスト ファイル (manifest.yml) に追加すればOKです。以下のようなイメージ。 // manifest.yml modules: jiraServiceManagement:portalRequestViewAction: - key: module1 function: func1 title: モジュール1 jiraServiceManagement:queuePage: - key: module2 function: func2 title: モジュール2 function: - key: func1 handler: index.run1 - key: func2 handler: index.run2 表示するボタンの表記やアイコン、ボタンを押した際に最初に呼び出される関数などを変更したい場合も マニフェスト ファイルをいじります。 UI kit を使ってみた感想 本記事では UI kit を使って開発を進めてきましたが、メリット・デメリットともに強く感じたため、感想を記しておきます(おおむねコンセプト通りの感想ですが)。 メリット とにかく開発が速くて簡単です。デザインのことは何も考えなくて良いです(考える余地がないとも言う)。 コンポーネント の種類も割と豊富で、シンプルな機能を追加するだけであればあまり困らないと思います。 デメリット 想像以上に自由がききませんでした。現状だと、文字の色やサイズ、 コンポーネント 間の間隔などのちょっとしたところも変えられません。また、今回紹介した編集機能に関して言うと、フィールドに対応した コンポーネント が提供されておらず、工夫してもどうしようもない場合がありました(リッチテキストで入力する欄など)。 レンダリング 速度が遅いです。毎回バックエンドを経由しているので仕方ないのですが、入力値のバリデーションなどでは特に気になります。 機能が複雑化してくるとどうしても UI kit では物足りない場面があるため、今後は Custom UI を使用した開発にも取り組んでいきたいと思います。 おわりに 今回は、Atlassian Forge の概要と実際の開発手順を説明しました。データの取得・画面表示・登録と、基本的な動作をカバーしたつもりです。ニッチなツールではありますが、本記事が誰かのお役に立てば幸いです。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募お待ちしています。 ソリューションアーキテクト 執筆: @matsu ( Shodo で執筆されました )
こんにちは、XI 本部ソフトウェアデザインセンター所属・新卒 1 年目の松本です。 昨年10月に配属されてから 3 ヶ月間、Atlassian Forge を使った アプリ開発 を担当しました。 そこで今回は、Forge の概要と実際の開発手順を解説していきます。 Forge に関する日本語記事はほとんどなく心細い想いをしたので、本記事が誰かの助けになれば幸いです。 Forge とは UI 構築手法 2 パターン UI kit Custom UI 開発手順の例 到達目標 執筆者の環境 ① Node.js の導入 ② Forge CLI の導入 ③ プロジェクトの作成 ④ ビルド・デプロイ・インストール ⑤ TypeScript への対応 ⑥ Atlassian REST API と UI kit フックを使い、必要な情報を取得してみる ⑦ 取得した値を表示する ⑧ 編集内容を提出できるようにする 開発を便利にする Tips UI kit を使ってみた感想 おわりに Forge とは Atlassian 社が提供する FaaS プラットフォームです。Atlassian のサービスをカスタマイズ・機能拡張するためのアプリを、ユーザー自身で簡単に作成できます。現在は、Jira、Jira Service Management、Confluence の3つのサービスに対応しており、作成したアプリはこれらのサービス上から利用することとなります。 アプリの例としては、 Jira の課題パネルから多言語翻訳を使えるようにするアプリ Jira の課題の健全性を、更新の滞りなどの情報をもとに判断してくれるアプリ Confluence に、 Google フォトに保存されている写真を表示できるようにするアプリ などが公式ドキュメントで紹介されています。 https://developer.atlassian.com/platform/forge/example-apps/ アプリ開発 に伴う機能実装以外の作業(ビルド・デプロイ・権限の管理・スケーリング・テナント管理などなど)は Atlassian 側でほとんど担ってくれるため、開発者は実現したい機能の実装に注力できます。 UI 構築手法 2 パターン アプリの UI 構築手法が 2 パターン用意されており、いずれかを選ぶ必要があります。 迅速・簡便な UI kit と 自由度が高い Custom UI です。 UI kit 提供された コンポーネント を組み合わせて UI を構築します。 コンポーネント は、各種入力フォーム・ボタン・テーブルなど、豊富に用意されています。いずれも コンポーネント も Atlassian 風のデザインとなっており、自分でデザインを考える手間が省けます(逆に、カスタマイズ性はほとんどありません)。 使い方は React の コンポーネント とよく似ています。また、フック機能も提供されており、こちらもほぼ React のそれです。 なお、 レンダリング は全てサーバー側で行われるため、相応の遅延が発生します。また、現状、 コンポーネント とフックは提供されたもの以外には使うことができないため、柔軟性に欠けます(Forge 開発チームの動向を見ていると、近々できるようになるかも?)。 Custom UI HTML・ CSS ・ JavaScript などの静的リソースを使用して、独自の UI を構築します。UI kit とは異なり、 レンダリング はユーザー側で行われるため、速いです(ただし、外部リソースへのアクセスはバックエンドを経由する必要がある、といったルールがあります)。 タイトルの通り、以降の開発手順では前者の UI kit を用います。Custom UI の解説はまたの機会に…。 開発手順の例 到達目標 手順の紹介に入る前に、本記事における開発の到達目標を明確にしておきます。 ここでは、Jira Service Management に「リク エス ト内容をリク エス ター自身が編集できる機能」を追加することを目標とします。 Jira Service Management とは、Jira を拡張したサービスデスク管理ツールです。Jira Service Management では現状、届いたリク エス ト(= 困っている人からの問い合わせ)の内容を管理者側から編集することはできても、リク エス ター側からはできないという制限があります。今回実装する機能は、こうした制限を解決するものです。 なお、Jira や Confluence を対象にした場合も同じような流れで開発を進められます。 完成イメージ 設置したボタンを押すと、 編集用のモーダルが開き、 編集して submit を押すと、 反映される。 執筆者の環境 主要なものだけあげておきます。 macOS Monterey 12.6 Visual Studio Code 1.74.3 node 16.17.1 Forge CLI 6.4.0 Forge API 2.7.0 React 18.2.0 それでは開発に入っていきましょう。 ① Node.js の導入 v14 以降の LTS release が必要です。入っていない場合はインストールしてください。 ② Forge CLI の導入 Forge CLI は、Forge アプリを管理するために使用する要のパッケージです。npm からインストールしてください。 npm install --save-dev @forge/cli インストール後、 API トーク ンを使用してログインする必要があります。 詳細な手順は、以下のドキュメントを参照してください。 https://developer.atlassian.com/platform/forge/getting-started/#log-in-with-an-atlassian-api-token ③ プロジェクトの作成 以下のコマンドでプロジェクトを作成します。 forge create コマンドを実行すると 3 つ質問されるので、順に答えていきます。 (1) アプリ名 好きなアプリ名をつけましょう。 (2) UIツール UI kit と Custom UI の選択です。前述の通り、今回は UI kit を選択します。 (3) モジュールのテンプレート Forge では、Jira などのサービスにアプリを組み込むための機能を「モジュール」として提供しています。作りたいアプリに適したモジュールを選択すると、そのモジュールに合わせて良い感じのテンプレートを作成してくれます。 今回は、Jira Service Management のリク エス ト閲覧画面に編集ボタンを付けたいので、「jira-service-management- portal -request-view-action」というモジュールを選択します。これは「リク エス ト閲覧画面にボタンが追加され、ボタンをクリックすると定義したアクションを走らせることができる」というモジュールです。 モジュール一覧は以下から確認できますので、作りたいアプリに合わせて選んでみてください。 https://developer.atlassian.com/platform/forge/manifest-reference/modules/ 以上3つの選択が完了すると、自動的に以下のようなプロジェクトが作成されます。 コードも少し覗いてみましょう。 // src/index.jsx import ForgeUI, { render, Text, PortalRequestViewAction, ModalDialog, useState } from '@forge/ui'; const App = () => { const [isOpen, setOpen] = useState(true); if (!isOpen) { return null; } return ( <ModalDialog header="Hello" onClose={() => setOpen(false)}> <Text>Hello world!</Text> </ModalDialog> ); }; export const run = render( <PortalRequestViewAction> <App/> </PortalRequestViewAction> ); React そっくりですね。簡単にコードの解説をしておくと… useState は UI kit が提供するフックの1つで、使い方は React の useState とほとんど同じです。 <ModalDialog> や <Text> は UI kit componets と呼ばれ、Atlssian 風デザインのパーツを提供するための コンポーネント です。 一方、<PortalRequestViewAction> は Function components と呼ばれ、アプリ作成時に選択したモジュールが提供する コンポーネント です。今回の例では、<PortalRequestViewAction> に囲まれた UI kit componets が、リク エス ト閲覧ページのボタンをクリックした際に表示されることとなります。 ④ ビルド・デプロイ・インストール ここで 1 度アプリをデプロイし、Atlassian サービス上で確認してみましょう。 アプリのルートで以下のコマンドを実行します。これ 1 つでエラーチェック・ビルド・デプロイまでを行ってくれる強力なコマンドです。 forge deploy デプロイ先は develop、staging、production の3つが用意されており、--environment (-e) オプションで指定できます (デフォルトは development)。今回は development にします。 続いて、以下のコマンドを実行し、デプロイしたアプリを自分が管理するテナントにインストールします。実行後にテナントの ドメイン を聞かれるので入力してください。 forge install これで、Atlassianのサービスからアプリを使えるようになりました。確認してみましょう。 今回は Jira Service Management のリク エス ト閲覧画面にボタンを追加するモジュールを使ったので、当該画面を見に行きます。 すると、以下のようにボタンが設置されており、 クリックするとモーダルが開いて「 Hello World !」と表示されます。 ここまで 1 ミリもコーディングをしていません。素晴らしいですね。 ⑤ TypeScript への対応 本ステップは任意ですが、UI kit では簡単に対応できるので、ぜひやっておきましょう。 以下の 4 つの作業を行います。 (1) tsconfig. json の作成 今回は以下のように設定しました。 // tsconfig.json { "compilerOptions": { "target": "es2020", "jsx": "react", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true } } (2) ソースファイルの拡張子を変更 js(x) → ts(x) (3) TypeScript の プリプロセッサ を package. json に追加 必要な プリプロセッサ があれば追加しましょう。 ついでに、React がないと index. tsx で エラーが表示されてしまうので、react および対応する プリプロセッサ を npm からインストールするとともに、index. tsx でインポートしておきます。 // src/index.tsx import React from "react" なお、TypeScript の コンパイル は、前述した forge deploy コマンド実行時に一緒に実行してくれます。 ⑥ Atlassian REST API と UI kit フックを使い、必要な情報を取得してみる 続いて、編集に必要な情報を取得して、モーダルに表示してみます。 アプリと Atlassian サービスとのやり取りは、基本的には Atlassian が提供する REST API を介して行われます。加えて、現在閲覧しているリク エス トの ID など、UI kit のフックを介して取得する情報も一部あります。 Forgeでは、 REST API の認可は OAuth2.0 で行われており、権限を移譲されたアプリがユーザーに代わって API 呼び出しを行います。そのため、開発者は トーク ンなどの機密情報を管理する必要がなく、簡潔なコードで API を呼び出せます。 併せて、アプリにどの程度の権限(スコープ)を与えるかを、 マニフェスト ファイル (manifest.yml) に記述する必要があり、このスコープによって扱える API の範囲が決まります。 利用できる Atlassian REST API は以下から確認してください。その API に必要なスコープなどの情報も記載されています。 https://developer.atlassian.com/platform/forge/product-rest-api-reference/ 今回は、現在閲覧しているリク エス トの ID をUI kit の フック で取得し、その ID をもとに、リク エス トの設問文や問い合わせ内容を REST API で取得してみます。 現在閲覧しているページの情報は、useProductContext という UI kit フックで取得します。なお、本フックの返り値の型として ProductContext 型が提供されていますが、Jira のみに対応していて、Jira Service Management には対応していません。ですので、今回は ProductContextForJsm 型を自身で定義しています。 // src/index.tsx // ProductContextForJsm の型定義 (省略。「コード全文を見る」から確認できます。) const productContext = useProductContext() as ProductContextForJsm; const requestId = productContext.extensionContext.request.key; 次に、欲しい情報を REST API で取得します。まずは @forge/ api モジュールを npm からインストールするとともに、index. tsx でインポートします。 // src/index.tsx import api, { Route, route } from "@forge/api"; リク エス ト情報の取得には「Get customer request by id or key」という API を用います。この API を呼びだす関数を定義しましょう。 // src/index.tsx // ResponseJson の型定義 (省略。「コード全文を見る」から確認できます。) const fetchRequest = async (requestKey: string): Promise<ResponseJson> => { const response = await api .asApp() .requestJira(route`/rest/servicedeskapi/request/${requestKey}`, { headers: { Accept: "application/json", }, }); return await response.json(); }; API 呼び出しの関数は、useEffect 内で呼び出し、返り値は state に保持するのが良いでしょう。 state を追加し、 // src/index.tsx const [responseJson, setResponseJson] = useState<ResponseJson>({ requestFieldValues: [], }); useEffect 内で API を呼び出して、返り値を state にセットします。 // src/index.tsx useEffect(async () => { const responseJson = await fetchRequest(requestId); setResponseJson(responseJson); }, []); 最後に、使用する API を呼びだすために必要なスコープを、 マニフェスト ファイルに追加します。 // manifest.yml permissions: scopes: - "read:servicedesk-request" - "read:jira-work" コード全文を見る // src/index.tsx import api, { Route, route } from "@forge/api"; import ForgeUI, { render, Text, PortalRequestViewAction, ModalDialog, useState, useProductContext, useEffect, } from "@forge/ui"; import { ExtensionContext, ProductContext } from "@forge/ui/out/types"; import React from "react"; interface ProductContextForJsm extends ProductContext { extensionContext: ExtensionContextForJsm; } interface ExtensionContextForJsm extends ExtensionContext { request: { key: string }; } interface ResponseJson { requestFieldValues: Request[]; } interface Request { fieldId: string; label: string; value: string; } const App = () => { const [isOpen, setOpen] = useState(true); const [responseJson, setResponseJson] = useState<ResponseJson>({ requestFieldValues: [], }); useEffect(async () => { const responseJson = await fetchRequest(requestId); setResponseJson(responseJson); }, []); const productContext = useProductContext() as ProductContextForJsm; const requestId = productContext.extensionContext.request.key; const fetchRequest = async (requestKey: string): Promise<ResponseJson> => { const response = await api .asApp() .requestJira(route`/rest/servicedeskapi/request/${requestKey}`, { headers: { Accept: "application/json", }, }); return await response.json(); }; if (!isOpen) { return null; } return ( <ModalDialog header="Hello" onClose={() => setOpen(false)}> <Text>Hello world!</Text> </ModalDialog> ); }; export const run = render( <PortalRequestViewAction> <App /> </PortalRequestViewAction> ); ⑦ 取得した値を表示する 続いて、先ほど取得した値を画面に表示してみましょう。Jira Service Management では多様な設問タイプ(テキストボックス、 チェックボックス 、 ラジオボタン ・・・)がありますが、これら全てに対応しようとすると、本記事では収まりきりません。今回は簡易的に、リク エス トの「要約」欄のみを表示してみることにします。 これです。 Form、TextField という UI kit コンポーネント を使用するので、まずはインポートを追加します。Text コンポーネント はもう使わないので、消しておきます。 // src/index.tsx import ForgeUI, { render, PortalRequestViewAction, ModalDialog, useState, useProductContext, useEffect, Form, TextField, } from "@forge/ui"; 続いて、 REST API で受け取ったリク エス トの情報から要約欄の情報だけを抜き出し、 TextField コンポーネント に変換する関数を定義します。 // src/index.tsx const makeTextField = ( responseJson: ResponseJson ): JSX.Element | undefined => { const summary = responseJson.requestFieldValues.find( (request) => request.fieldId === "summary" ); if (!summary) { return; } return ( <TextField label={summary.label} name={summary.fieldId} defaultValue={summary.value} ></TextField> ); }; 最後に、先ほど作成した関数から返される TextField コンポーネント を Form コンポーネント で囲み、App 関数で返すようにします。現在「 Hello World 」を返している部分を変更します。 // src/index.tsx return ( <ModalDialog header="Edit" onClose={() => setOpen(false)}> <Form onSubmit={() => setOpen(false)}>{makeTextField(responseJson)}</Form> </ModalDialog> ); Form コンポーネント は onSubmit プロパティ(画面で submit ボタンが押された際に走る処理)の指定が必須ですが、一旦は、画面を閉じる処理を入れておきます。 それでは、画面で確認してみましょう、、、 の前に、アプリのスコープを変更したので、アップグレードが必要です。以下のコマンドでアップグレードを実行します。 forge install --upgrade これでアプリを利用可能になりました。 確認してみると、以下のように、要約欄の情報を取得できるようになっていると思います。 コード全文を見る // src/index.tsx import api, { Route, route } from "@forge/api"; import ForgeUI, { render, PortalRequestViewAction, ModalDialog, useState, useProductContext, useEffect, Form, TextField, } from "@forge/ui"; import { ExtensionContext, ProductContext } from "@forge/ui/out/types"; import React from "react"; interface ProductContextForJsm extends ProductContext { extensionContext: ExtensionContextForJsm; } interface ExtensionContextForJsm extends ExtensionContext { request: { key: string }; } interface ResponseJson { requestFieldValues: Request[]; } interface Request { fieldId: string; label: string; value: string; } const App = () => { const [isOpen, setOpen] = useState(true); const [responseJson, setResponseJson] = useState<ResponseJson>({ requestFieldValues: [], }); useEffect(async () => { const responseJson = await fetchRequest(requestId); setResponseJson(responseJson); }, []); const productContext = useProductContext() as ProductContextForJsm; const requestId = productContext.extensionContext.request.key; const fetchRequest = async (requestKey: string): Promise<ResponseJson> => { const response = await api .asApp() .requestJira(route`/rest/servicedeskapi/request/${requestKey}`, { headers: { Accept: "application/json", }, }); return await response.json(); }; const makeTextField = ( responseJson: ResponseJson ): JSX.Element | undefined => { const summary = responseJson.requestFieldValues.find( (request) => request.fieldId === "summary" ); if (!summary) { return; } return ( <TextField label={summary.label} name={summary.fieldId} defaultValue={summary.value} ></TextField> ); }; if (!isOpen) { return null; } return ( <ModalDialog header="Edit" onClose={() => setOpen(false)}> <Form onSubmit={() => setOpen(false)}>{makeTextField(responseJson)}</Form> </ModalDialog> ); }; export const run = render( <PortalRequestViewAction> <App /> </PortalRequestViewAction> ); ⑧ 編集内容を提出できるようにする それでは最後に、編集した内容を提出し、変更を反映できるようにします。 編集内容の確定には「Edit issue」という API を使用します。この API を呼びだす関数を作成します。この際、リク エス トボディには、submit ボタンが押された際に送られてくるデータをもとに作成した JSON を指定します。 // src/index.tsx const execEdit = async (submitted: {summary: string}) => { await api.asApp().requestJira(route`/rest/api/3/issue/${requestId}`, { method: "PUT", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: `{"fields":{"summary":"${submitted["summary"]}"}}`, }); setOpen(false); }; そして、この関数を Form コンポーネント の onSubmit プロパティで指定します。 // src/index.tsx return ( <ModalDialog header="Edit" onClose={() => setOpen(false)}> <Form onSubmit={execEdit}>{makeTextField(responseJson)}</Form> </ModalDialog> ); これでOKです。 画面で確認する前に、新たに API を追加したので、書き込み権限をアプリに与えてアップグレードしましょう。 // manifest.yml permissions: scopes: - "read:servicedesk-request" - "read:jira-work" - "write:jira-work" 以上で全ての工程が完了です。実際に使ってみます。 要約欄を編集し、submit を押して…リロードすると… 反映されています! コード全文を見る // src/index.tsx import api, { Route, route } from "@forge/api"; import ForgeUI, { render, PortalRequestViewAction, ModalDialog, useState, useProductContext, useEffect, Form, TextField, } from "@forge/ui"; import { ExtensionContext, ProductContext } from "@forge/ui/out/types"; import React from "react"; interface ProductContextForJsm extends ProductContext { extensionContext: ExtensionContextForJsm; } interface ExtensionContextForJsm extends ExtensionContext { request: { key: string }; } interface ResponseJson { requestFieldValues: Request[]; } interface Request { fieldId: string; label: string; value: string; } const App = () => { const [isOpen, setOpen] = useState(true); const [responseJson, setResponseJson] = useState<ResponseJson>({ requestFieldValues: [], }); useEffect(async () => { const responseJson = await fetchRequest(requestId); setResponseJson(responseJson); }, []); const productContext = useProductContext() as ProductContextForJsm; const requestId = productContext.extensionContext.request.key; const fetchRequest = async (requestKey: string): Promise<ResponseJson> => { const response = await api .asApp() .requestJira(route`/rest/servicedeskapi/request/${requestKey}`, { headers: { Accept: "application/json", }, }); return await response.json(); }; const makeTextField = ( responseJson: ResponseJson ): JSX.Element | undefined => { const summary = responseJson.requestFieldValues.find( (request) => request.fieldId === "summary" ); if (!summary) { return; } return ( <TextField label={summary.label} name={summary.fieldId} defaultValue={summary.value} ></TextField> ); }; const execEdit = async (submitted: { summary: string }) => { await api.asApp().requestJira(route`/rest/api/3/issue/${requestId}`, { method: "PUT", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: `{"fields":{"summary":"${submitted["summary"]}"}}`, }); setOpen(false); }; if (!isOpen) { return null; } return ( <ModalDialog header="Edit" onClose={() => setOpen(false)}> <Form onSubmit={execEdit}>{makeTextField(responseJson)}</Form> </ModalDialog> ); }; export const run = render( <PortalRequestViewAction> <App /> </PortalRequestViewAction> ); 開発を便利にする Tips 開発手順の紹介は以上ですが、開発を便利にする Tips も紹介しておきます。 ① ログの出し方 ログを出したい箇所に console.log("出したい内容") を入れ、 forge logs を実行することで、ログが確認できます。 いちいち上記コマンドを打つのは面倒ですが、次に紹介する Tunnel モードを組み合わせることで解消されます。 ② Tunnel モード ファイル保存時に自動的に再ビルドを実行してくれる機能です。ただし、development 環境へアプリをデプロイしている場合に限定されます。いちいち forge deploy コマンドを打つ手間が省けるほか、前述したログもリアルタイムで出してくれるため、非常に便利です。 使い方は以下を確認してください。 https://developer.atlassian.com/platform/forge/tunneling/ 注意点として、たまに、何のエラーも出ていないにもかかわらず変更が反映されない場合がありました。把握しておかないと永遠に時間を溶かすことになるので気をつけてください。 ③ モジュールの追加方法 forge create コマンドでプロジェクトを作成した直後はモジュールは1つだけですが、当然複数モジュールを扱うこともできます。 マニフェスト ファイル (manifest.yml) に追加すればOKです。以下のようなイメージ。 // manifest.yml modules: jiraServiceManagement:portalRequestViewAction: - key: module1 function: func1 title: モジュール1 jiraServiceManagement:queuePage: - key: module2 function: func2 title: モジュール2 function: - key: func1 handler: index.run1 - key: func2 handler: index.run2 表示するボタンの表記やアイコン、ボタンを押した際に最初に呼び出される関数などを変更したい場合も マニフェスト ファイルをいじります。 UI kit を使ってみた感想 本記事では UI kit を使って開発を進めてきましたが、メリット・デメリットともに強く感じたため、感想を記しておきます(おおむねコンセプト通りの感想ですが)。 メリット とにかく開発が速くて簡単です。デザインのことは何も考えなくて良いです(考える余地がないとも言う)。 コンポーネント の種類も割と豊富で、シンプルな機能を追加するだけであればあまり困らないと思います。 デメリット 想像以上に自由がききませんでした。現状だと、文字の色やサイズ、 コンポーネント 間の間隔などのちょっとしたところも変えられません。また、今回紹介した編集機能に関して言うと、フィールドに対応した コンポーネント が提供されておらず、工夫してもどうしようもない場合がありました(リッチテキストで入力する欄など)。 レンダリング 速度が遅いです。毎回バックエンドを経由しているので仕方ないのですが、入力値のバリデーションなどでは特に気になります。 機能が複雑化してくるとどうしても UI kit では物足りない場面があるため、今後は Custom UI を使用した開発にも取り組んでいきたいと思います。 おわりに 今回は、Atlassian Forge の概要と実際の開発手順を説明しました。データの取得・画面表示・登録と、基本的な動作をカバーしたつもりです。ニッチなツールではありますが、本記事が誰かのお役に立てば幸いです。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募お待ちしています。 ソリューションアーキテクト 執筆: @matsu ( Shodo で執筆されました )
こんにちは!金融ソリューション事業部の山下です。 本記事では、 オープンソース の3DCGソフト Blender を用いて、現実の地形や建物を模した3DCGモデルを生成する方法を紹介します。 今回は Blender GIS という プラグイン を用いて、衛星画像、建物データ( OSM )、標高データ(SRTM)を利用してモデルを制作しました。 実施環境/ツール 実施手順 1. BlenderにBlenderGISプラグインをインストールする 2. 衛星画像データを取得する 3. 標高データ(SRTM)の取得、反映 4. 建物、その他データ(OSM)の取得、反映 5. 建物上部へテクスチャをマッピング 6. 建物側面へのマテリアルの適用 完成イメージ 所感 参考 実施環境/ツール OS: Windows 11 pro GPU : NVIDIA GeForce RTX 3070Ti Laptop DCC: Blender 3.3.1 Plugin: Blender GIS 2.2.8 実施手順 Blender にBlenderGIS プラグイン をインストールする 衛星画像データを取得する 標高データ(SRTM)の取得、反映 建物、その他データ( OSM )の取得、反映 建物上部へテクスチャを マッピング 建物側面へのマテリアルの適用 1. Blender にBlenderGIS プラグイン をインストールする 以下の GitHub リポジトリ をダウンロードします。 Blender   GIS プラグイン https://github.com/domlysz/BlenderGIS プラグイン のインストールは、メニューのEdit > Preferences > Add-onsを選択して、ダウンロードしたZIPファイルを読み込むことで完了します。完了後、Add onの一覧にBlenderGISが追加され、チェックがつきます。 2. 衛星画像データを取得する BlenderGIS プラグイン をインストールすると、以下のように3D viewport内に「 GIS 」というメニューが表示されます。 GIS > Web geodata > Basemapを選択します。 設定はそのままで、OKを押下します。 世界地図が表示されます。 Gボタン押下で検索バーが表示されるので、表示させたい地名で検索します。 今回は「Shinagawa Station」、Zoom Levelは「16」と入力します。 品川駅周辺の地図が表示されました。 Eボタン押下で、Basemapの2D画像がExportできます。 3. 標高データ(SRTM)の取得、反映 2D画像を選択した状態で、 GIS > Web geodata > Get SRTMを選択します。 ちなみにSRTMはShuttle Radar Topography Missionの略で、 スペースシャトル に搭載したレーダーで取得した地球の標高データです。 取得にはopnetopography.orgの API Keyが必要になります。以下のサイトから API keyが無料で取得可能です。 https://portal.opentopography.org/ API Keyを入力してOKボタンを押下。 先ほどの2D画像にSRTMの標高データが反映されます。 起伏には乏しい地域ですが、高輪のグ ランドプリンス ホテル周辺や御殿山周辺は比較的標高が高いことが分かります。 4. 建物、その他データ( OSM )の取得、反映 2D画像を選択した状態で、 GIS > Web geodata > Get OSM を選択します。 ちなみに OSM は OpenStreetMap の略で、 オープンソース で運営されている地図データのプロジェクトです。 取得するデータ種類を選択します。今回は全て選択します。 また、手順3.で標高データの マッピング を行っている為、Elevation from objectのチェックをONにします。 OKボタンを押下すると、建物データや線路データなどが反映されます。 5. 建物上部へテクスチャを マッピング 4.で取得した建物データ(Areas;building)を選択した状態で、Tabキー押下でEditModeに切り替えます。 ModeをFace Selectに変更します。 どれか一つの建物上部の面を選択した状態で、Select > Select Similar > Normalにより、同じ向きの面を全て選択します。 建物上部の面が選択されている状態 次に、Edit Modeから、UV Editor Modeに切り替えます。 上部メニューから、 マッピング させる画像(EXPORT_ GOOGLE _SAT_WM)を選択します。 選択した面と、画像が一致するように、位置を修正します。 Gボタン + マウスカーソルで移動、Sボタン+マウスカーソルでスケールの調整が可能です。 ランドマークとなる建物にフォーカスすると調整しやすいです。 位置が決まったら、マテリアルを設定します。 まず、3D View右下のMaterial Propertyメニューを選択して、+ボタンでマテリアルを作成します。 今回は、2つのマテリアルスロットを作成しました(「Material 001」と「roof」)。 Assignボタン押下で、Edit Modeで選択している面に、これらのマテリアルを適用します。 roofマテリアルを選択した状態で、UV Editor ModeからShader Editor Modeに切り替えます。 Shift + Aで検索バーを出して、Image Textureノードを追加します。 インプットイメージに、先ほどUV Editorで マッピング させた画像(EXPORT_ GOOGLE _SAT_WM)を選択して、ColorをPrincipled BSDFノードのBase Colorに接続します。 建物上部の面に、衛星画像が マッピング されました。 6. 建物側面へのマテリアルの適用 続いて、建物側面のマテリアルを作成します。 建物側面のテクスチャは衛星データやオープンデータで提供されていないので、一般的なビルに近しいテクスチャを使用します。 以下のテクスチャを使用します(フリーアカウントでも商用フリーの画像がダウンロード可能です)。 Size MのAlbedo, Height, Normal, Roughness, Metalic, AO, Emissiveの7種類の画像をダウンロードします。 https://www.textures.com/download/PBR0537/138572 先ほど作成したマテリアル(Material 001)を選択した状態で、UV Editingメニューを開きます。 Principle BSDFを選択した状態で、Ctrl + Shift + Tを押下します。 Blender File Viewが表示されるので、先ほどダウンロードした画像を全て選択して、「Principled Texture Setup」を押下します。 建物の側面にもマテリアルが適用されました。 完成イメージ 完成イメージはこちらです。夜景にしてみました。 本記事ではライティング、 レンダリング のプロセスは割愛します。 所感 今回は GIS を用いてビル群を作成しました。 ハイクオリティなビルを作成するアプローチは他にも色々とありますが、 GIS を使うと大規模な都市や地形がほぼ一瞬で生成できる為、高精細なクオリティが不要な遠景等で用いる分には有用だと感じました。また、山など起伏のある地形のモデルを制作する場合は 国土地理院 地図などもう少し正確な標高データの活用も有用だと思います。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://www.esrij.com/gis-guide/gis-datamodel/gis-datamodel/ https://www.youtube.com/watch?v=YNtKnmRXVlo&t=283s https://www.youtube.com/watch?v=uk404c43pRY 執筆: @yamashita.yuki 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは!金融ソリューション事業部の山下です。 本記事では、 オープンソース の3DCGソフト Blender を用いて、現実の地形や建物を模した3DCGモデルを生成する方法を紹介します。 今回は Blender GIS という プラグイン を用いて、衛星画像、建物データ( OSM )、標高データ(SRTM)を利用してモデルを制作しました。 実施環境/ツール 実施手順 1. BlenderにBlenderGISプラグインをインストールする 2. 衛星画像データを取得する 3. 標高データ(SRTM)の取得、反映 4. 建物、その他データ(OSM)の取得、反映 5. 建物上部へテクスチャをマッピング 6. 建物側面へのマテリアルの適用 完成イメージ 所感 参考 実施環境/ツール OS: Windows 11 pro GPU : NVIDIA GeForce RTX 3070Ti Laptop DCC: Blender 3.3.1 Plugin: Blender GIS 2.2.8 実施手順 Blender にBlenderGIS プラグイン をインストールする 衛星画像データを取得する 標高データ(SRTM)の取得、反映 建物、その他データ( OSM )の取得、反映 建物上部へテクスチャを マッピング 建物側面へのマテリアルの適用 1. Blender にBlenderGIS プラグイン をインストールする 以下の GitHub リポジトリ をダウンロードします。 Blender   GIS プラグイン https://github.com/domlysz/BlenderGIS プラグイン のインストールは、メニューのEdit > Preferences > Add-onsを選択して、ダウンロードしたZIPファイルを読み込むことで完了します。完了後、Add onの一覧にBlenderGISが追加され、チェックがつきます。 2. 衛星画像データを取得する BlenderGIS プラグイン をインストールすると、以下のように3D viewport内に「 GIS 」というメニューが表示されます。 GIS > Web geodata > Basemapを選択します。 設定はそのままで、OKを押下します。 世界地図が表示されます。 Gボタン押下で検索バーが表示されるので、表示させたい地名で検索します。 今回は「Shinagawa Station」、Zoom Levelは「16」と入力します。 品川駅周辺の地図が表示されました。 Eボタン押下で、Basemapの2D画像がExportできます。 3. 標高データ(SRTM)の取得、反映 2D画像を選択した状態で、 GIS > Web geodata > Get SRTMを選択します。 ちなみにSRTMはShuttle Radar Topography Missionの略で、 スペースシャトル に搭載したレーダーで取得した地球の標高データです。 取得にはopnetopography.orgの API Keyが必要になります。以下のサイトから API keyが無料で取得可能です。 https://portal.opentopography.org/ API Keyを入力してOKボタンを押下。 先ほどの2D画像にSRTMの標高データが反映されます。 起伏には乏しい地域ですが、高輪のグ ランドプリンス ホテル周辺や御殿山周辺は比較的標高が高いことが分かります。 4. 建物、その他データ( OSM )の取得、反映 2D画像を選択した状態で、 GIS > Web geodata > Get OSM を選択します。 ちなみに OSM は OpenStreetMap の略で、 オープンソース で運営されている地図データのプロジェクトです。 取得するデータ種類を選択します。今回は全て選択します。 また、手順3.で標高データの マッピング を行っている為、Elevation from objectのチェックをONにします。 OKボタンを押下すると、建物データや線路データなどが反映されます。 5. 建物上部へテクスチャを マッピング 4.で取得した建物データ(Areas;building)を選択した状態で、Tabキー押下でEditModeに切り替えます。 ModeをFace Selectに変更します。 どれか一つの建物上部の面を選択した状態で、Select > Select Similar > Normalにより、同じ向きの面を全て選択します。 建物上部の面が選択されている状態 次に、Edit Modeから、UV Editor Modeに切り替えます。 上部メニューから、 マッピング させる画像(EXPORT_ GOOGLE _SAT_WM)を選択します。 選択した面と、画像が一致するように、位置を修正します。 Gボタン + マウスカーソルで移動、Sボタン+マウスカーソルでスケールの調整が可能です。 ランドマークとなる建物にフォーカスすると調整しやすいです。 位置が決まったら、マテリアルを設定します。 まず、3D View右下のMaterial Propertyメニューを選択して、+ボタンでマテリアルを作成します。 今回は、2つのマテリアルスロットを作成しました(「Material 001」と「roof」)。 Assignボタン押下で、Edit Modeで選択している面に、これらのマテリアルを適用します。 roofマテリアルを選択した状態で、UV Editor ModeからShader Editor Modeに切り替えます。 Shift + Aで検索バーを出して、Image Textureノードを追加します。 インプットイメージに、先ほどUV Editorで マッピング させた画像(EXPORT_ GOOGLE _SAT_WM)を選択して、ColorをPrincipled BSDFノードのBase Colorに接続します。 建物上部の面に、衛星画像が マッピング されました。 6. 建物側面へのマテリアルの適用 続いて、建物側面のマテリアルを作成します。 建物側面のテクスチャは衛星データやオープンデータで提供されていないので、一般的なビルに近しいテクスチャを使用します。 以下のテクスチャを使用します(フリーアカウントでも商用フリーの画像がダウンロード可能です)。 Size MのAlbedo, Height, Normal, Roughness, Metalic, AO, Emissiveの7種類の画像をダウンロードします。 https://www.textures.com/download/PBR0537/138572 先ほど作成したマテリアル(Material 001)を選択した状態で、UV Editingメニューを開きます。 Principle BSDFを選択した状態で、Ctrl + Shift + Tを押下します。 Blender File Viewが表示されるので、先ほどダウンロードした画像を全て選択して、「Principled Texture Setup」を押下します。 建物の側面にもマテリアルが適用されました。 完成イメージ 完成イメージはこちらです。夜景にしてみました。 本記事ではライティング、 レンダリング のプロセスは割愛します。 所感 今回は GIS を用いてビル群を作成しました。 ハイクオリティなビルを作成するアプローチは他にも色々とありますが、 GIS を使うと大規模な都市や地形がほぼ一瞬で生成できる為、高精細なクオリティが不要な遠景等で用いる分には有用だと感じました。また、山など起伏のある地形のモデルを制作する場合は 国土地理院 地図などもう少し正確な標高データの活用も有用だと思います。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://www.esrij.com/gis-guide/gis-datamodel/gis-datamodel/ https://www.youtube.com/watch?v=YNtKnmRXVlo&t=283s https://www.youtube.com/watch?v=uk404c43pRY 執筆: @yamashita.yuki 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは。X イノベーション 本部ソフトウェアデザインセンターの陳です。 この記事ではNext.jsの getServerSideProps の利用でハマったことについて話します。 Server-side Rendering( SSR )とgetServerSideProps Server-side Renderingはリク エス トごとにサーバー側で レンダリング を行い、HTMLページを生成する機能です。 頻繁に変更されるデータを画面に表示させたい時はServer-side Renderingを利用します。Server-side Renderingを実現するには、サーバー側からデータを受け取る処理を行う getServerSideProps 関数を使います。 Next.jsの 公式ドキュメント では以下の例を掲載しています。 function Page({ data }) { // Render data... } // This gets called on every request export async function getServerSideProps() { // Fetch data from external API const res = await fetch(`https://.../data`) const data = await res.json() // Pass data to the page via props return { props: { data } } } export default Page Blue/Greenデプロイでハマったこと 今回はNext.jsの API routes を使ってバックエンド処理の API を作成しました。 例えば、以下はデータベースからarticle名のリストを取得する処理を行う API routeです。 // pages/api/articles.ts export default function handler(_req: NextApiRequest, res: NextApiResponse) { const articles: Article[] = await getArticlesFromDB(); const articleNames = articles.map((article) => article.name ); res.status(200).json(articleNames); } getServerSideProps で API からデータを受け取って ブラウザー に渡します。Axiosの baseURL を 環境変数 で指定していました。 // pages/index.tsx function Page({ articleNames }) { // Render articles data... } export async function getServerSideProps() { const baseURL = process.env.SERVER ? process.env.SERVER : "http://localhost:3000"; const res = await Axios.get<string[]>(`{baseURL}/api/articles`) return { props: { articleNames: res.data } } } export default Page このコードは一見問題なさそうですが、Blue/GreenデプロイでAxiosの baseURL を正しく指定しないと問題が起きます(Fetch API を利用する場合も同様です)。 Blue/Greenデプロイは、本番環境と検証環境を交互に入れ替えることにより、ダウンタイムを最小にするデプロイ手法です。 本来であれば、 API のパスはウェブサーバーと同一環境のものにならないといけません。例えば、本番環境のパスは https://example.com であれば、 API のパスは https://example.com/api/articles になります。 今回はAxiosのbaseURLを本番環境のパスに指定しましたため、検証環境ウェブサーバーにアクセスしても、叩いた API は本番環境のものでした。 Blue/Greenデプロイはルーティング制御により、本番環境と検証環境の入れ替えが行われるため、 API のホスト名も環境と共に切り替える必要があります。 アクセスした環境は本番環境か検証環境かを識別し、 API のパスを変換する仕組みが必要ですが、なかなか難しいです。 解決方法 そもそも getServerSideProps と API routes は両方ともサーバー側で実行されるため、 getServerSideProps で API routes を呼びだすのは蛇足ですね。データの取得などのバックエンド処理は関数で作成し、 getServerSideProps で呼び出せばいいです。 修正後のコードはこちらです。 API routesの作成をやめて、データベースの処理を関数にしました。 // logic/articles.ts export async function fetchArticleNames(): Promise<string[]> { const articles: Article[] = await getArticlesFromDB(); return articles.map((article) => article.name ); } 続いて、 getServerSideProps で api/articles を介さずに、 fetchArticleNames() 関数でarticle名のリストを取得するように修正しました。 // pages/index.tsx function Page({ articleNames }) { // Render article names... } export async function getServerSideProps() { const articleNames = await fetchArticleNames(); return { props: { articleNames } } } export default Page これで、Blue/Greenデプロイの API パス問題を回避し、コードもすっきりしました。 まとめ この記事では、Next.jsの getServerSideProps を使ってBlue/Greenデプロイでハマったことについてまとめました。 getServerSideProps はサーバー側で実行されますので、データの取得処理は API routes を介さずに関数で呼び出しましょう。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募お待ちしています。 - ソリューションアーキテクト 執筆: @chen.xinying 、レビュー: @sato.taichi ( Shodo で執筆されました )
こんにちは。X イノベーション 本部ソフトウェアデザインセンターの陳です。 この記事ではNext.jsの getServerSideProps の利用でハマったことについて話します。 Server-side Rendering( SSR )とgetServerSideProps Server-side Renderingはリク エス トごとにサーバー側で レンダリング を行い、HTMLページを生成する機能です。 頻繁に変更されるデータを画面に表示させたい時はServer-side Renderingを利用します。Server-side Renderingを実現するには、サーバー側からデータを受け取る処理を行う getServerSideProps 関数を使います。 Next.jsの 公式ドキュメント では以下の例を掲載しています。 function Page({ data }) { // Render data... } // This gets called on every request export async function getServerSideProps() { // Fetch data from external API const res = await fetch(`https://.../data`) const data = await res.json() // Pass data to the page via props return { props: { data } } } export default Page Blue/Greenデプロイでハマったこと 今回はNext.jsの API routes を使ってバックエンド処理の API を作成しました。 例えば、以下はデータベースからarticle名のリストを取得する処理を行う API routeです。 // pages/api/articles.ts export default function handler(_req: NextApiRequest, res: NextApiResponse) { const articles: Article[] = await getArticlesFromDB(); const articleNames = articles.map((article) => article.name ); res.status(200).json(articleNames); } getServerSideProps で API からデータを受け取って ブラウザー に渡します。Axiosの baseURL を 環境変数 で指定していました。 // pages/index.tsx function Page({ articleNames }) { // Render articles data... } export async function getServerSideProps() { const baseURL = process.env.SERVER ? process.env.SERVER : "http://localhost:3000"; const res = await Axios.get<string[]>(`{baseURL}/api/articles`) return { props: { articleNames: res.data } } } export default Page このコードは一見問題なさそうですが、Blue/GreenデプロイでAxiosの baseURL を正しく指定しないと問題が起きます(Fetch API を利用する場合も同様です)。 Blue/Greenデプロイは、本番環境と検証環境を交互に入れ替えることにより、ダウンタイムを最小にするデプロイ手法です。 本来であれば、 API のパスはウェブサーバーと同一環境のものにならないといけません。例えば、本番環境のパスは https://example.com であれば、 API のパスは https://example.com/api/articles になります。 今回はAxiosのbaseURLを本番環境のパスに指定しましたため、検証環境ウェブサーバーにアクセスしても、叩いた API は本番環境のものでした。 Blue/Greenデプロイはルーティング制御により、本番環境と検証環境の入れ替えが行われるため、 API のホスト名も環境と共に切り替える必要があります。 アクセスした環境は本番環境か検証環境かを識別し、 API のパスを変換する仕組みが必要ですが、なかなか難しいです。 解決方法 そもそも getServerSideProps と API routes は両方ともサーバー側で実行されるため、 getServerSideProps で API routes を呼びだすのは蛇足ですね。データの取得などのバックエンド処理は関数で作成し、 getServerSideProps で呼び出せばいいです。 修正後のコードはこちらです。 API routesの作成をやめて、データベースの処理を関数にしました。 // logic/articles.ts export async function fetchArticleNames(): Promise<string[]> { const articles: Article[] = await getArticlesFromDB(); return articles.map((article) => article.name ); } 続いて、 getServerSideProps で api/articles を介さずに、 fetchArticleNames() 関数でarticle名のリストを取得するように修正しました。 // pages/index.tsx function Page({ articleNames }) { // Render article names... } export async function getServerSideProps() { const articleNames = await fetchArticleNames(); return { props: { articleNames } } } export default Page これで、Blue/Greenデプロイの API パス問題を回避し、コードもすっきりしました。 まとめ この記事では、Next.jsの getServerSideProps を使ってBlue/Greenデプロイでハマったことについてまとめました。 getServerSideProps はサーバー側で実行されますので、データの取得処理は API routes を介さずに関数で呼び出しましょう。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募お待ちしています。 - ソリューションアーキテクト 執筆: @chen.xinying 、レビュー: @sato.taichi ( Shodo で執筆されました )
皆さん、こんにちは。イノラボ小林です。 今回の記事では1月3日~1月8日にラスベガスで開催されたCES2023について取材をしてきたので、その中でも気になったクイックインタビューを数点紹介させて頂きます。 尚、その他の動画については、下記リンクから飛んでいただけるとまとめて視聴可能です。 https://www.tiktok.com/@tyoreporter 名称:CES®(世界最大級のテックイベント) 主催:Consumer Technology Association ( CTA ) 日程:Media Days:2023.1.3 – 4 ( 2日間 )    CES2023 :2023.1.5 – 8 ( 4日間 ) ■ マイクロソフト (カテゴリー:XR) Hololens2を活用した展示解説ツアー @tyoreporter #ces2023 #ces #AR #HoloLens #マイクロソフト ♬ オリジナル楽曲 - TYO-REPORTER ■ BMW (カテゴリー:モビリティ) 32色に変色する車体パネル @tyoreporter #ces2023 #ces #車 #bmw ♬ オリジナル楽曲 - TYO-REPORTER ■ ARHT(カテゴリー:コミュニケーション) リアルタイムで1/1を映し出す遠隔コミュニケーションディスプレイ @tyoreporter #ディスプレイ #メタバース #デジタルツイン #サイネージ #ces #ces2023 #マーケティング ♬ オリジナル楽曲 - TYO-REPORTER PPT資料(PDF)もあるので、閲覧/配布希望の方はご連絡下さい。 連絡先:kobayashi.kentaro@isid.co.jp 執筆: @tyoreporter 、レビュー: @sato.taichi ( Shodo で執筆されました )
皆さん、こんにちは。イノラボ小林です。 今回の記事では1月3日~1月8日にラスベガスで開催されたCES2023について取材をしてきたので、その中でも気になったクイックインタビューを数点紹介させて頂きます。 尚、その他の動画については、下記リンクから飛んでいただけるとまとめて視聴可能です。 https://www.tiktok.com/@tyoreporter 名称:CES®(世界最大級のテックイベント) 主催:Consumer Technology Association ( CTA ) 日程:Media Days:2023.1.3 – 4 ( 2日間 )    CES2023 :2023.1.5 – 8 ( 4日間 ) ■ マイクロソフト (カテゴリー:XR) Hololens2を活用した展示解説ツアー @tyoreporter #ces2023 #ces #AR #HoloLens #マイクロソフト ♬ オリジナル楽曲 - TYO-REPORTER ■ BMW (カテゴリー:モビリティ) 32色に変色する車体パネル @tyoreporter #ces2023 #ces #車 #bmw ♬ オリジナル楽曲 - TYO-REPORTER ■ ARHT(カテゴリー:コミュニケーション) リアルタイムで1/1を映し出す遠隔コミュニケーションディスプレイ @tyoreporter #ディスプレイ #メタバース #デジタルツイン #サイネージ #ces #ces2023 #マーケティング ♬ オリジナル楽曲 - TYO-REPORTER PPT資料(PDF)もあるので、閲覧/配布希望の方はご連絡下さい。 連絡先:kobayashi.kentaro@isid.co.jp 執筆: @tyoreporter 、レビュー: @sato.taichi ( Shodo で執筆されました )
おはようございます!こんにちは!こんばんは! 電通国際情報サービス  X(クロス) イノベーション 本部 デジタルエンゲージメントセンター 1年目社員の根本康平です。 今回は「 Salesforce 」が一体どういうものなのかを簡単な言葉だけで紹介する記事です。 難しい言葉は一切使いません! 中高生でも分かるように紹介します。 『 Salesforce 聞いたことあるけど何ができるか分からない』という方向けの記事です。『業務で Salesforce を使う機会無いから・・』という方も是非ご覧ください。課題解決の手段の1つとして知っておくに越したことはないと思います!IT業界に興味のある「就活生」の方々もぜひご覧ください! Salesforceの概要 Salesforceがどのように使われるのか?(ネット通販編) Salesforceを使うとどのようなメリットがある? まとめ Salesforce の概要 まずは「 Salesforce 」という単語について。 Salesforce とは アメリ カに本社を置くITの会社のことです。 Salesforce が提供しているサービスは世界中の様々な会社で使われています。 では「 Salesforce が提供しているサービス」とはどういうものでしょうか。今回はイメージしやすいように我々に身近な「ネット通販」で Salesforce が利用されたことを想定して紹介します。 Salesforce がどのように使われるのか?(ネット通販編) まずは登場人物・登場する会社についてです。「A会社」が「A通販」を運営しています。A通販を利用するお客さんが「佐藤さん」と「田中さん」です。A会社に勤めている社員は「兵藤社員」と「 利根川 社員」です。 では「 Salesforce 」の説明に戻ります。 Salesforce が提供しているサービスは「お客さんの情報を一元管理・社員の営業活動を支援するシステム」です。 「お客さんの情報」というのは佐藤さんや田中さんの「名前」「住所」「性別」「年齢」「購入した商品」「A会社への問い合わせ履歴」・・・などの情報を指します。 「社員の営業活動」というのは兵藤社員や 利根川 社員が行った「会議の記録」や「取引情報」・・などを指します。 これらの情報を1か所にまとめて管理(一元管理)するシステムを提供しているのが「 Salesforce 」です。 一元管理されたお客さんの情報を社員が見るとこのような感じで確認できます。 情報は一元管理されていますので、佐藤さん・田中さんに関する情報は全てここに集約されます!全ての情報が一か所に集まっていてスムーズに情報を閲覧できることは非常に大事なことです。 ここまで理解できれば Salesforce の7割を理解できたといっても過言ではないと思います! Salesforce を使うとどのようなメリットがある? 情報共有が容易 例えば、佐藤さんの「プリンターインクの不備について」という問い合わせ対応を 利根川 社員が担当したとしましょう。 利根川 社員が調べた結果、その問い合わせの情報・やり取りを別部署の兵藤社員に報告する必要が発生しました。こういう時に情報を一元管理していると、佐藤さんの氏名や住所、購入履歴や問い合わせ履歴を簡単に兵藤社員に共有できます。 このようにいろいろな部署や部門間でスムーズな情報共有ができます。 情報分析とニーズの予測 例えば、兵藤社員から 利根川 社員に「A通販の食料品の売り上げに関するレポートを作ってください」と依頼があったとしましょう。 利根川 社員はどうすればいいでしょうか? なんと、 Salesforce を使うと数クリックの簡単な操作だけで食料品に関するレポートを作ることができるんです!月ごとの売り上げグラフを作ることも、食品別の売り上げ割合もとても簡単に作ることができます!これも Salesforce で情報を一元管理しているおかげです。 兵藤社員からの依頼を超スピーディーにやり遂げることができれば 利根川 社員の評価もうなぎ登りですね。 営業活動の効率化 Salesforce は社員の情報も管理できます。 例えば、 利根川 社員は兵藤社員から「A通販は洋服商品が全然ないから、洋服を売っている色々な会社に『A通販でも販売しませんか?』とお願いしてきてください」と依頼されたとしましょう。 利根川 社員は洋服を売っている色々な会社に行って、打ち合わせや取引の手続きをします。その打ち合わせの情報やお客さんの会社の情報を Salesforce に入力して記録を残します。 そうすると、 Salesforce で 利根川 社員の活動状況について、画像のように分かりやすく確認できます。兵藤社員は Salesforce で 利根川 社員の活動状況を確認して「11/29 青木洋服に声かけてからすごく時間が経っているけど大丈夫?」「契約できそうなのが2件あるんだね!順調だね!」などコメントできます。また、全営業パーソンの状況を確認すれば、今期の契約数の予測も簡単に出せそうですね! このように、 Salesforce を用いると営業活動の効率化が期待できます。 アプリケーション開発が容易にできる Salesforce ではアプリケーション開発が簡単にできます。例えば、「A会社独自のスケジュールアプリが必要」という要望も Salesforce を使って独自に開発できます。イメージとして、 Apple Store や Google Play ストアにあるような便利アプリを Salesforce で作れる! Salesforce で利用できる!というような感じです。 その他 他にも様々なメリットがあります。セキュリティ対策がある程度できている・マウスをクリックするだけでほとんどのことができる・ iPhone /PCなど様々な端末に対応しやすい・簡単にWebサイトや問い合わせサイトを作れる・・・まだまだ出てきそうです! まとめ 何となく Salesforce が何か、何ができるのかをご理解いただけたでしょうか? Salesforce は「お客さんの情報を一元管理・社員の営業活動を支援するシステム」ができるものでしたね! 少しでも Salesforce に興味を持っていただけていたら嬉しいです。 Salesforce を導入してみたい・話を聞いてみたい!という方は 電通国際情報サービス (ISID) デジタルエンゲージメントセンター までお問い合わせください! 興味を持ってくださった就活生の方々、ISIDは新卒採用も積極的に行っています!ぜひ下のリンクから確認してみてください! 私たちは一緒に働いてくれる仲間を募集しています! 【全社集約】CRMソリューションコンサルタント IT業界に興味のある就活生の皆さま、ぜひご応募ください! 【新卒採用】ISID リクルートサイト 執筆: @nemoto.kouhei 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
おはようございます!こんにちは!こんばんは! 電通国際情報サービス  X(クロス) イノベーション 本部 デジタルエンゲージメントセンター 1年目社員の根本康平です。 今回は「 Salesforce 」が一体どういうものなのかを簡単な言葉だけで紹介する記事です。 難しい言葉は一切使いません! 中高生でも分かるように紹介します。 『 Salesforce 聞いたことあるけど何ができるか分からない』という方向けの記事です。『業務で Salesforce を使う機会無いから・・』という方も是非ご覧ください。課題解決の手段の1つとして知っておくに越したことはないと思います!IT業界に興味のある「就活生」の方々もぜひご覧ください! Salesforceの概要 Salesforceがどのように使われるのか?(ネット通販編) Salesforceを使うとどのようなメリットがある? まとめ Salesforce の概要 まずは「 Salesforce 」という単語について。 Salesforce とは アメリ カに本社を置くITの会社のことです。 Salesforce が提供しているサービスは世界中の様々な会社で使われています。 では「 Salesforce が提供しているサービス」とはどういうものでしょうか。今回はイメージしやすいように我々に身近な「ネット通販」で Salesforce が利用されたことを想定して紹介します。 Salesforce がどのように使われるのか?(ネット通販編) まずは登場人物・登場する会社についてです。「A会社」が「A通販」を運営しています。A通販を利用するお客さんが「佐藤さん」と「田中さん」です。A会社に勤めている社員は「兵藤社員」と「 利根川 社員」です。 では「 Salesforce 」の説明に戻ります。 Salesforce が提供しているサービスは「お客さんの情報を一元管理・社員の営業活動を支援するシステム」です。 「お客さんの情報」というのは佐藤さんや田中さんの「名前」「住所」「性別」「年齢」「購入した商品」「A会社への問い合わせ履歴」・・・などの情報を指します。 「社員の営業活動」というのは兵藤社員や 利根川 社員が行った「会議の記録」や「取引情報」・・などを指します。 これらの情報を1か所にまとめて管理(一元管理)するシステムを提供しているのが「 Salesforce 」です。 一元管理されたお客さんの情報を社員が見るとこのような感じで確認できます。 情報は一元管理されていますので、佐藤さん・田中さんに関する情報は全てここに集約されます!全ての情報が一か所に集まっていてスムーズに情報を閲覧できることは非常に大事なことです。 ここまで理解できれば Salesforce の7割を理解できたといっても過言ではないと思います! Salesforce を使うとどのようなメリットがある? 情報共有が容易 例えば、佐藤さんの「プリンターインクの不備について」という問い合わせ対応を 利根川 社員が担当したとしましょう。 利根川 社員が調べた結果、その問い合わせの情報・やり取りを別部署の兵藤社員に報告する必要が発生しました。こういう時に情報を一元管理していると、佐藤さんの氏名や住所、購入履歴や問い合わせ履歴を簡単に兵藤社員に共有できます。 このようにいろいろな部署や部門間でスムーズな情報共有ができます。 情報分析とニーズの予測 例えば、兵藤社員から 利根川 社員に「A通販の食料品の売り上げに関するレポートを作ってください」と依頼があったとしましょう。 利根川 社員はどうすればいいでしょうか? なんと、 Salesforce を使うと数クリックの簡単な操作だけで食料品に関するレポートを作ることができるんです!月ごとの売り上げグラフを作ることも、食品別の売り上げ割合もとても簡単に作ることができます!これも Salesforce で情報を一元管理しているおかげです。 兵藤社員からの依頼を超スピーディーにやり遂げることができれば 利根川 社員の評価もうなぎ登りですね。 営業活動の効率化 Salesforce は社員の情報も管理できます。 例えば、 利根川 社員は兵藤社員から「A通販は洋服商品が全然ないから、洋服を売っている色々な会社に『A通販でも販売しませんか?』とお願いしてきてください」と依頼されたとしましょう。 利根川 社員は洋服を売っている色々な会社に行って、打ち合わせや取引の手続きをします。その打ち合わせの情報やお客さんの会社の情報を Salesforce に入力して記録を残します。 そうすると、 Salesforce で 利根川 社員の活動状況について、画像のように分かりやすく確認できます。兵藤社員は Salesforce で 利根川 社員の活動状況を確認して「11/29 青木洋服に声かけてからすごく時間が経っているけど大丈夫?」「契約できそうなのが2件あるんだね!順調だね!」などコメントできます。また、全営業パーソンの状況を確認すれば、今期の契約数の予測も簡単に出せそうですね! このように、 Salesforce を用いると営業活動の効率化が期待できます。 アプリケーション開発が容易にできる Salesforce ではアプリケーション開発が簡単にできます。例えば、「A会社独自のスケジュールアプリが必要」という要望も Salesforce を使って独自に開発できます。イメージとして、 Apple Store や Google Play ストアにあるような便利アプリを Salesforce で作れる! Salesforce で利用できる!というような感じです。 その他 他にも様々なメリットがあります。セキュリティ対策がある程度できている・マウスをクリックするだけでほとんどのことができる・ iPhone /PCなど様々な端末に対応しやすい・簡単にWebサイトや問い合わせサイトを作れる・・・まだまだ出てきそうです! まとめ 何となく Salesforce が何か、何ができるのかをご理解いただけたでしょうか? Salesforce は「お客さんの情報を一元管理・社員の営業活動を支援するシステム」ができるものでしたね! 少しでも Salesforce に興味を持っていただけていたら嬉しいです。 Salesforce を導入してみたい・話を聞いてみたい!という方は 電通国際情報サービス (ISID) デジタルエンゲージメントセンター までお問い合わせください! 興味を持ってくださった就活生の方々、ISIDは新卒採用も積極的に行っています!ぜひ下のリンクから確認してみてください! 私たちは一緒に働いてくれる仲間を募集しています! 【全社集約】CRMソリューションコンサルタント IT業界に興味のある就活生の皆さま、ぜひご応募ください! 【新卒採用】ISID リクルートサイト 執筆: @nemoto.kouhei 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 CI/CD環境として GitHub Actions を使っているときに、コンテナをビルド、プッシュする前に distroless ベースイメージの署名を検証する方法について紹介します。(自前でビルドしたイメージの署名やその検証については、この記事では扱いません) (2023/8/21追記) Cosign 2.0 よりKeyless Signingが推奨されるようになっているため、記事の一部を更新しました。 はじめに Cosignとは 署名の検証で期待したいこと cosign verify コマンド cosign verify は何をやっているのか 追加の検証をするべき状況 署名を検証するワークフロー 署名の検証にかかる時間 はじめに コンテナを利用する場合、軽量で最小限のファイルのみを含んでいる distroless イメージをベースイメージとして使うことが推奨されます。 distroless イメージは Cosign で署名されており、 GitHub リポジトリ の README でも、イメージの使用前に 署名を検証することが推奨されています : All distroless images are signed by cosign. We recommend verifying any distroless image you use before building your image. Cosignとは Cosign はコンテナなどを署名するためのライブラリであり、 OSS コミュニティである Sigstore が提供するツールの1つです。Sigstore は 2022年10月にGAを発表 しました。 distroless イメージは全て Cosign の Keyless signing で署名されています。 署名の検証で期待したいこと コンテナのベースイメージの署名を検証することで、イメージが改ざんされておらず(完全性)、検証時に指定する公開鍵とペアの 秘密鍵 で署名されていること(真正性)が期待できます。イメージが不正に差し替えられた場合には、それを利用する前に検知できるようになります。 一方、署名用の 秘密鍵 への正当なアクセスを持つユーザーや、アクセス権を入手した攻撃者がベースイメージを置き換えた場合、署名も書き換えることが可能になるので、署名の検証による防御の対象外になります。また 秘密鍵 にアクセスできなくても、検証に使用する公開鍵を差し替えできてしまう場合も、署名の検証による防御の対象外です。 このような攻撃の成立を難しくするために、 Keyless signing が推奨されています。Keyless signing の流れは次の通りです。 署名者が OIDC ( Google や GitHub など)で認証する 署名者は発行されたOIDCのID トーク ンを利用し、署名に利用する鍵ペアを生成する 生成した公開鍵とID トーク ンをSigstoreの Fulcio に送信する FulcioはID トーク ンを検証し、短命の証明書(公開鍵が含まれる)を発行する 署名者は署名を行い、証明書と署名をSigstore の Rekor という透明性ログサービスに記録する 秘密鍵 は破棄される 署名検証時では、利用者はRekorから証明書およびそれに含まれる公開鍵を取得し、証明書が有効だった期間に署名が行われたことを検証する この仕組みにより、公開鍵を自前で公開する手段を用意する必要はなく、 秘密鍵 も短命のものを利用し署名後は破棄されるため、盗まれるリスクも大幅に低減されます。また仮にOIDCの認証が突破されたとしても署名の記録はRekorにタイムスタンプ付きで公開されるため、仕組みとしては身に覚えのない署名を検知できるようになっています。 なお署名の流れが複雑に見えますが、そのほとんどはCosignの CLI コマンドで自動化されます。 cosign verify コマンド さて cosign verify コマンドで署名者のEメールアドレスと、署名者が利用したOIDCプロバイダーを指定すると署名の検証ができ、例えば distroless/nodejs:18 イメージに対しては次のように出力されます $ cosign verify gcr.io/distroless/nodejs:18 --certificate-oidc-issuer https://accounts.google.com --certificate-identity keyless@distroless.iam.gserviceaccount.com Verification for gcr.io/distroless/nodejs:18 -- The following checks were performed on each of these signatures: - The cosign claims were validated - Existence of the claims in the transparency log was verified offline - The code-signing certificate was verified using trusted certificate authority certificates (以下略) Cosign による署名と検証について こちらの記事 で非常に詳しく説明されています。 cosign verify は何をやっているのか cosign による署名と検証について、自分なりの理解をまとめると次のようになります。 コンテナイメージを保管する OCI(Open Container Initiative) Registry には、Manifest、Config、Blob の3種の Artifact を保存できる Manifest ファイルには Config や Blob(コンテナのレイヤー)への参照がダイジェストで記載されており、従ってコンテナレイヤーが変わるとイメージの Manifest ファイルも変わる Cosign による署名はイメージの Manifest ファイルに対して作成される Cosign でコンテナイメージを署名すると、イメージの Manifest ファイルのダイジェストを Signature Claim という JSON ファイルに埋め込む Signature Claim も Artifact として OCI Registry にアップロードされる。この署名の Manifest ファイルには、Signature Claim のダイジェストを 秘密鍵 で暗号化したものが signature として記載されている 署名の検証は、まずイメージの Manifest ファイルから特定の規則に従い、署名の Manifest ファイルの格納場所を算出する。これにはイメージの Manifest ファイルのダイジェストが利用される 署名の Manifest ファイルに書かれた signature を公開鍵で復号し、Signature Claim のダイジェストと一致しているかを検証する 検知したいと思っている「コンテナイメージをすり替えたが、署名と公開鍵をすり替えることができない」状況を想像します。イメージレイヤーが変わるとイメージの Manifest ファイルも変更になり、 Manifest ファイルのダイジェストから算出される署名の格納場所も変わります。しかし攻撃者はその場所に従来の 秘密鍵 で署名を格納することができない前提で考えているため、結果としてコンテナイメージのすり替えを検知できます。 追加の検証をするべき状況 cosign verify だけでは十分ではなく、追加の検証をした方が良い状況も存在します。それは「イメージ署名の検証」と「実際のイメージのpull」に時間的な差分があるときです。 cosign verify はコンテナのダイジェストを直接検証に使用するわけではないため、 cosign verify する時点の OCI Registry にあるイメージと、pull してきたイメージが同一ではない可能性が無視できない場合、 docker inspect コマンドで pull してきたイメージのダイジェストと署名に記載されたダイジェストの比較が必要です。 署名を検証するワークフロー さて前置きが長くなりましたが、ここからはイメージ署名の検証とイメージ pull の時間差を無視して良く、追加の検証を行わない場合を考えます。 GitHub Actions でコンテナのビルドやプッシュを行う前に、以下のステップを追加することで distroless ベースイメージの署名を検証できます。 steps : # 自身のリポジトリのチェックアウト。署名の検証とは無関係 - name : Checkout uses : actions/checkout@v3 # 1. Cosign のインストール - name : Install cosign uses : sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.1.1 # 2. Cosign による検証。例として nodejs:18 イメージのものを対象としている - name : cosign verify run : cosign verify gcr.io/distroless/nodejs:18 --certificate-oidc-issuer https://accounts.google.com --certificate-identity keyless@distroless.iam.gserviceaccount.com # これ以降コンテナのビルドやプッシュを行う 説明: sigstore/cosign-installer アクションを利用し、cosign をインストールする ここではさらに安全性を高めるためにアクションをバージョン指定ではなく、 コミットハッシュでコードをピン留め している --certificate-oidc-issuer と --certificate-identity オプションを指定し、Keyless署名されたコンテナイメージを検証する ここでは gcr.io/distroless/nodejs:18 イメージを検証している 署名の検証にかかる時間 cosign のインストールと署名の検証はいずれも数秒以内に終わるため、特にCI/CDの妨げにはならない印象です。 数行のアクションを追加するだけでベースイメージが不正にすり替えられていないか検証できるため、万が一に備えて distroless イメージの署名検証をやってみてはいかがでしょうか。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 セキュリティエンジニア(セキュリティ設計) 執筆: @kou.kinyo 、レビュー: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました )
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 CI/CD環境として GitHub Actions を使っているときに、コンテナをビルド、プッシュする前に distroless ベースイメージの署名を検証する方法について紹介します。(自前でビルドしたイメージの署名やその検証については、この記事では扱いません) (2023/8/21追記) Cosign 2.0 よりKeyless Signingが推奨されるようになっているため、記事の一部を更新しました。 はじめに Cosignとは 署名の検証で期待したいこと cosign verify コマンド cosign verify は何をやっているのか 追加の検証をするべき状況 署名を検証するワークフロー 署名の検証にかかる時間 はじめに コンテナを利用する場合、軽量で最小限のファイルのみを含んでいる distroless イメージをベースイメージとして使うことが推奨されます。 distroless イメージは Cosign で署名されており、 GitHub リポジトリ の README でも、イメージの使用前に 署名を検証することが推奨されています : All distroless images are signed by cosign. We recommend verifying any distroless image you use before building your image. Cosignとは Cosign はコンテナなどを署名するためのライブラリであり、 OSS コミュニティである Sigstore が提供するツールの1つです。Sigstore は 2022年10月にGAを発表 しました。 distroless イメージは全て Cosign の Keyless signing で署名されています。 署名の検証で期待したいこと コンテナのベースイメージの署名を検証することで、イメージが改ざんされておらず(完全性)、検証時に指定する公開鍵とペアの 秘密鍵 で署名されていること(真正性)が期待できます。イメージが不正に差し替えられた場合には、それを利用する前に検知できるようになります。 一方、署名用の 秘密鍵 への正当なアクセスを持つユーザーや、アクセス権を入手した攻撃者がベースイメージを置き換えた場合、署名も書き換えることが可能になるので、署名の検証による防御の対象外になります。また 秘密鍵 にアクセスできなくても、検証に使用する公開鍵を差し替えできてしまう場合も、署名の検証による防御の対象外です。 このような攻撃の成立を難しくするために、 Keyless signing が推奨されています。Keyless signing の流れは次の通りです。 署名者が OIDC ( Google や GitHub など)で認証する 署名者は発行されたOIDCのID トーク ンを利用し、署名に利用する鍵ペアを生成する 生成した公開鍵とID トーク ンをSigstoreの Fulcio に送信する FulcioはID トーク ンを検証し、短命の証明書(公開鍵が含まれる)を発行する 署名者は署名を行い、証明書と署名をSigstore の Rekor という透明性ログサービスに記録する 秘密鍵 は破棄される 署名検証時では、利用者はRekorから証明書およびそれに含まれる公開鍵を取得し、証明書が有効だった期間に署名が行われたことを検証する この仕組みにより、公開鍵を自前で公開する手段を用意する必要はなく、 秘密鍵 も短命のものを利用し署名後は破棄されるため、盗まれるリスクも大幅に低減されます。また仮にOIDCの認証が突破されたとしても署名の記録はRekorにタイムスタンプ付きで公開されるため、仕組みとしては身に覚えのない署名を検知できるようになっています。 なお署名の流れが複雑に見えますが、そのほとんどはCosignの CLI コマンドで自動化されます。 cosign verify コマンド さて cosign verify コマンドで署名者のEメールアドレスと、署名者が利用したOIDCプロバイダーを指定すると署名の検証ができ、例えば distroless/nodejs:18 イメージに対しては次のように出力されます $ cosign verify gcr.io/distroless/nodejs:18 --certificate-oidc-issuer https://accounts.google.com --certificate-identity keyless@distroless.iam.gserviceaccount.com Verification for gcr.io/distroless/nodejs:18 -- The following checks were performed on each of these signatures: - The cosign claims were validated - Existence of the claims in the transparency log was verified offline - The code-signing certificate was verified using trusted certificate authority certificates (以下略) Cosign による署名と検証について こちらの記事 で非常に詳しく説明されています。 cosign verify は何をやっているのか cosign による署名と検証について、自分なりの理解をまとめると次のようになります。 コンテナイメージを保管する OCI(Open Container Initiative) Registry には、Manifest、Config、Blob の3種の Artifact を保存できる Manifest ファイルには Config や Blob(コンテナのレイヤー)への参照がダイジェストで記載されており、従ってコンテナレイヤーが変わるとイメージの Manifest ファイルも変わる Cosign による署名はイメージの Manifest ファイルに対して作成される Cosign でコンテナイメージを署名すると、イメージの Manifest ファイルのダイジェストを Signature Claim という JSON ファイルに埋め込む Signature Claim も Artifact として OCI Registry にアップロードされる。この署名の Manifest ファイルには、Signature Claim のダイジェストを 秘密鍵 で暗号化したものが signature として記載されている 署名の検証は、まずイメージの Manifest ファイルから特定の規則に従い、署名の Manifest ファイルの格納場所を算出する。これにはイメージの Manifest ファイルのダイジェストが利用される 署名の Manifest ファイルに書かれた signature を公開鍵で復号し、Signature Claim のダイジェストと一致しているかを検証する 検知したいと思っている「コンテナイメージをすり替えたが、署名と公開鍵をすり替えることができない」状況を想像します。イメージレイヤーが変わるとイメージの Manifest ファイルも変更になり、 Manifest ファイルのダイジェストから算出される署名の格納場所も変わります。しかし攻撃者はその場所に従来の 秘密鍵 で署名を格納することができない前提で考えているため、結果としてコンテナイメージのすり替えを検知できます。 追加の検証をするべき状況 cosign verify だけでは十分ではなく、追加の検証をした方が良い状況も存在します。それは「イメージ署名の検証」と「実際のイメージのpull」に時間的な差分があるときです。 cosign verify はコンテナのダイジェストを直接検証に使用するわけではないため、 cosign verify する時点の OCI Registry にあるイメージと、pull してきたイメージが同一ではない可能性が無視できない場合、 docker inspect コマンドで pull してきたイメージのダイジェストと署名に記載されたダイジェストの比較が必要です。 署名を検証するワークフロー さて前置きが長くなりましたが、ここからはイメージ署名の検証とイメージ pull の時間差を無視して良く、追加の検証を行わない場合を考えます。 GitHub Actions でコンテナのビルドやプッシュを行う前に、以下のステップを追加することで distroless ベースイメージの署名を検証できます。 steps : # 自身のリポジトリのチェックアウト。署名の検証とは無関係 - name : Checkout uses : actions/checkout@v3 # 1. Cosign のインストール - name : Install cosign uses : sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.1.1 # 2. Cosign による検証。例として nodejs:18 イメージのものを対象としている - name : cosign verify run : cosign verify gcr.io/distroless/nodejs:18 --certificate-oidc-issuer https://accounts.google.com --certificate-identity keyless@distroless.iam.gserviceaccount.com # これ以降コンテナのビルドやプッシュを行う 説明: sigstore/cosign-installer アクションを利用し、cosign をインストールする ここではさらに安全性を高めるためにアクションをバージョン指定ではなく、 コミットハッシュでコードをピン留め している --certificate-oidc-issuer と --certificate-identity オプションを指定し、Keyless署名されたコンテナイメージを検証する ここでは gcr.io/distroless/nodejs:18 イメージを検証している 署名の検証にかかる時間 cosign のインストールと署名の検証はいずれも数秒以内に終わるため、特にCI/CDの妨げにはならない印象です。 数行のアクションを追加するだけでベースイメージが不正にすり替えられていないか検証できるため、万が一に備えて distroless イメージの署名検証をやってみてはいかがでしょうか。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 セキュリティエンジニア(セキュリティ設計) 執筆: @kou.kinyo 、レビュー: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました )
テックブログ編集部です。今回は弊社社員が登壇するイベントを紹介します。詳細およびお申込みはリンク先サイトを参照してください。 techplay.jp ご興味あれば、ぜひ登録おねがいします。 基本情報 タイトル:《研究開発から社会実装まで》AIを活用した五感拡張デ バイス や VR を用いた遠隔 幻肢痛 セラピーシステムの開発事例大公開! 日時:2023/02/15(水) 19:00〜20:30 形態:オンライン開催 概要 深刻化する社会課題の解決には一企業の力だけでは限界があり、立場は違えど、社会を形成するさまざまな ステークホルダー の知恵を集結し共創することが不可欠です。 幅広い業界のトップクラスの企業へ最適なソリューションを提案する 電通国際情報サービス (ISID)に先端技術を活用して社会課題の解決をミッションとする「 イノベーション ラボ」という組織があることはご存じでしょうか?「イノラボ」はまだオープン イノベーション が日本に浸透していない2011年4月に設立された先駆的な組織であり、課題発見から社会実装までを 一気通貫 で支援しています。 本勉強会では、世界的アワードの日本版である「Innovators Under 35」​でISID賞を受賞した 神戸大学 大西氏より、現在の研究開発事例についてお話します。 事例(一部) "AIを活用して人間の 疲労 を克服する ウェアラブル デ バイス "の開発 またイノラボ岡田氏からは研究開発に留めることなく、プロダクトとして実現していく上で直面した課題や乗り越えるための工夫について事例をもとにお話します。 事例(一部) 実空間でト ラッキング した体の情報を VR 空間に反映させ、異なる地点間にいる人の細かな動きを伝え合うことを可能にする"遠隔 VR 幻肢痛 セラピーシステム"の開発 勉強会の後半では、大学と企業と立場は違えど、他者と共創することに共通点を持つ両者から、共創する上での難しさと乗り越え方についてパネルディスカッションを行います。 執筆: @nakamura.toshihiro 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
テックブログ編集部です。今回は弊社社員が登壇するイベントを紹介します。詳細およびお申込みはリンク先サイトを参照してください。 techplay.jp ご興味あれば、ぜひ登録おねがいします。 基本情報 タイトル:《研究開発から社会実装まで》AIを活用した五感拡張デ バイス や VR を用いた遠隔 幻肢痛 セラピーシステムの開発事例大公開! 日時:2023/02/15(水) 19:00〜20:30 形態:オンライン開催 概要 深刻化する社会課題の解決には一企業の力だけでは限界があり、立場は違えど、社会を形成するさまざまな ステークホルダー の知恵を集結し共創することが不可欠です。 幅広い業界のトップクラスの企業へ最適なソリューションを提案する 電通国際情報サービス (ISID)に先端技術を活用して社会課題の解決をミッションとする「 イノベーション ラボ」という組織があることはご存じでしょうか?「イノラボ」はまだオープン イノベーション が日本に浸透していない2011年4月に設立された先駆的な組織であり、課題発見から社会実装までを 一気通貫 で支援しています。 本勉強会では、世界的アワードの日本版である「Innovators Under 35」​でISID賞を受賞した 神戸大学 大西氏より、現在の研究開発事例についてお話します。 事例(一部) "AIを活用して人間の 疲労 を克服する ウェアラブル デ バイス "の開発 またイノラボ岡田氏からは研究開発に留めることなく、プロダクトとして実現していく上で直面した課題や乗り越えるための工夫について事例をもとにお話します。 事例(一部) 実空間でト ラッキング した体の情報を VR 空間に反映させ、異なる地点間にいる人の細かな動きを伝え合うことを可能にする"遠隔 VR 幻肢痛 セラピーシステム"の開発 勉強会の後半では、大学と企業と立場は違えど、他者と共創することに共通点を持つ両者から、共創する上での難しさと乗り越え方についてパネルディスカッションを行います。 執筆: @nakamura.toshihiro 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
はじめに こんにちは、ISID 金融ソリューション事業部の岡崎です。 今回はEpicGames社が提供する ゲームエンジン 、Unreal Engine5 を利用して VR や メタバース のベースとなる、ワールドの地形を作成する手順を紹介します。 手順 今回は下記手順でUnrealEngine上にワールドを作成しました。 ランドスケープ モードを使用して地形作成 地形のマテリアル作成 マテリアルを使用してペイント 3Dアセットの追加 海を追加 ランドスケープ モードを使用して地形作成 ワールドの地形を生成するためにはまず、ワールドを生成するためのファイルを作成する必要があります。 Epic Games Launcherから Unreal Editorを起動し、左側の「ゲーム」のパネルから「Blank」を選びプロジェクトを始めます。 生成されたプロジェクトから地形を生成するためにはまず、 ランドスケープ ツールを使用します。 ランドスケープ モードには ・管理 ・スカルプト ・ペイント という3つのモードがあります。 まずは、フィールドの基盤になるメッシュを作成するために、管理モードから コンポーネント 数を指定し、メッシュを作成します。今回は コンポーネント 数を8x8で行いました。 メッシュの作成が終わると、 コンポーネント で選択した領域にメッシュが作成され、選択されていた管理モードから、自動的にスカルプトモードに変更されます。 今回はスカルプトモードと侵食モードを利用して、島や山を表現しています。 基本的にはスカルプトモードで山や、島の突起を作成し、侵食モードで山肌や谷を作成していく流れで作業を行いました。 (山の輪郭を作成します) (侵食モードで山肌を作成します) 次に作成した山や谷のワールドに海を追加します。本格的な海は後続の処理で作成するので、今回は仮の状態として海を追加しました。 海の追加方法は、上記タブのコンテンツ作成ボタンから「形状」→「Plane」を選択します。次に右側のトランスフォームの位置と大きさの数値を任意のものに変更します。 今回は海面の高さを陸地より少し上にしたかったので、位置のZ数値を150と設定しました。 作成が完了すると、海ができるはずの箇所に薄いグレーの「Plane」コンテンツが見えます。次にこの薄いグレーのコンテンツに海のマテリアルを追加します。 右側の詳細タブから「マテリアル」を選択し、検索欄に「water」と入力し、「M_Water_Ocean」というマテリアルを選択します。 以上の工程で、海の追加が完了しました。 地形のマテリアル作成 スカルプトモードで作成した地形にマテリアルを使って岩肌や草、海岸などを作成していきます。 まず、マテリアルを使うためには、プロジェクト内にマテリアルの追加を行わないといけません。 マテリアルの追加方法は画面上部のコンテンツ作成ボタンから「Quixel Bridge」を開きます。 「Quixel Bridge」内で任意のマテリアルを選択し、プロジェクト内で使用します。今回は草のマテリアルである「Grass」を追加しました。 画面下のコンテンツドロワーから追加したマテリアルを確認することができます。 追加したマテリアルをそのままプロジェクトでは使うことができないので、 岩肌や草、海岸など、使用するマテリアルを全て一つのプロジェクト用のマテリアルに集約をしました。 コンテンツ配下に「M_landscape」という本プロジェクト用のマテリアルを作成します。 次にマテリアル作成画面に移動し、エディタ上で右クリックを押し「Layer Blend」を作成します。 ここでプロジェクトで使用するひとつひとつのマテリアル(岩肌や草など)に対して、影の強さや発色の強さ、2つのマテリアルの ブレンド などを行います。 本作業はプロジェクト独自の絵の具パレットを作成するようなイメージです。 独自のマテリアル作成が完了すると、いよいよマテリアルを使用したペイント作業になります。   マテリアルを使用してペイント マテリアルを使用したペイントを行うためには、 ランドスケープ モードに変更して、ペイントタブを選択することでマテリアルのレイヤー(色のついた筆のイメージ)を選択できます。 レイヤーの中には先ほどマテリアル作成で行った「Layer Blend」を選ぶことができます。 ここで任意のレイヤーを選択し、作成した地形にペイントを行っていきます。 また、スカルプトモードと同様で、ツールの強度やブラシサイズなどもここで変更できます。ツールの強度を弱くすることで、複数のレイヤーのマテリアルを重ね塗りすることができます。 ここでは、浜辺を作成するために、「sand1」レイヤーを使い砂のマテリアルを塗った上から、「Glass1」の芝生を薄く重ね塗りを行いました。 山肌も同様にペイントをしていき、岩のレイヤーや草野レイヤー、砂浜のレイヤーを重ねて塗っていきワールド地形を仕上げていきます。 3Dアセットの追加 地形のスカルプト、ペイントが完了し完成に近づいたので、次は作成した地形に3Dアセットを設置します。 もちろん別の3Dオブジェクト作成ツールなどを使って作成したアセットを設置しても良いですが、今回は Unreal Engine でデフォルトで用意されている3Dアセットを追加し設置します。 マテリアルの作成でも使用した「Quixel Bridge」を開き、「3D Assets」を選択することで、作成した地形にあったアセットを探すことができます。 今回は「Nature」の中から「ROCK」を選択し、任意の岩のアセットを探し追加します。 追加したアセットは「コンテンツドロワー」内の「Megascans」→「3D_Assets」内に保存されているのでドラッグして任意の場所に配置します。 海を追加 最後に海の追加を行います。 前の手順で追加した「M_Water_Ocean」でも良いのですが、 Unreal Engine には別の海の機能も搭載されているので、そちらを利用します。 上部の編集タブから プラグイン を選択し、検索欄に「water」と入力し、「Water」の プラグイン を有効にします。現段階ではまだ実験段階と表示がありますが、問題なく使用できるので、今回は使用します。 次にコンテンツ作成ボタンから「アクタ配置パネル」を選択し、「Water Body Custom」を選択することで、新しい海をワールドに追加することができます。 以上で、ワールド上に地形を作成するための工程が完了しました。 おわりに 今回は、Unreal Engine5 を使用して、ゼロからワールドに地形を生成する方法を紹介しました。 初めて使用してみましたが、初心者でも簡単にハイクオリティな地形を作ることができました。 私は macOS 12.6 Monterey のPCで地形制作を行いました。 ランドスケープ モードのスカルプトを行う際にカーソルの操作が早すぎて上手くいかない点がありましたが、ペンタブなどで対応することで特に不自由なく作業を行うことができました。 今回の実践では、まだまだ Unreal Engine5 の一部の機能しか触れていないので、他の機能のキャッチアップも行っていき、ゲーム以外の領域でも効率的な使用方法を探っていきたいとおもいます。 最後までお読みいただき、ありがとうございました。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 ・ ランドスケープの作成 | Unreal Engine ドキュメント ・ 【はじめて学ぶ人のための】Unreal Engine 5 風景制作 基礎講座 執筆: @okazaki.wataru 、レビュー: @okazaki.wataru ( Shodo で執筆されました )
はじめに こんにちは、ISID 金融ソリューション事業部の岡崎です。 今回はEpicGames社が提供する ゲームエンジン 、Unreal Engine5 を利用して VR や メタバース のベースとなる、ワールドの地形を作成する手順を紹介します。 手順 今回は下記手順でUnrealEngine上にワールドを作成しました。 ランドスケープ モードを使用して地形作成 地形のマテリアル作成 マテリアルを使用してペイント 3Dアセットの追加 海を追加 ランドスケープ モードを使用して地形作成 ワールドの地形を生成するためにはまず、ワールドを生成するためのファイルを作成する必要があります。 Epic Games Launcherから Unreal Editorを起動し、左側の「ゲーム」のパネルから「Blank」を選びプロジェクトを始めます。 生成されたプロジェクトから地形を生成するためにはまず、 ランドスケープ ツールを使用します。 ランドスケープ モードには ・管理 ・スカルプト ・ペイント という3つのモードがあります。 まずは、フィールドの基盤になるメッシュを作成するために、管理モードから コンポーネント 数を指定し、メッシュを作成します。今回は コンポーネント 数を8x8で行いました。 メッシュの作成が終わると、 コンポーネント で選択した領域にメッシュが作成され、選択されていた管理モードから、自動的にスカルプトモードに変更されます。 今回はスカルプトモードと侵食モードを利用して、島や山を表現しています。 基本的にはスカルプトモードで山や、島の突起を作成し、侵食モードで山肌や谷を作成していく流れで作業を行いました。 (山の輪郭を作成します) (侵食モードで山肌を作成します) 次に作成した山や谷のワールドに海を追加します。本格的な海は後続の処理で作成するので、今回は仮の状態として海を追加しました。 海の追加方法は、上記タブのコンテンツ作成ボタンから「形状」→「Plane」を選択します。次に右側のトランスフォームの位置と大きさの数値を任意のものに変更します。 今回は海面の高さを陸地より少し上にしたかったので、位置のZ数値を150と設定しました。 作成が完了すると、海ができるはずの箇所に薄いグレーの「Plane」コンテンツが見えます。次にこの薄いグレーのコンテンツに海のマテリアルを追加します。 右側の詳細タブから「マテリアル」を選択し、検索欄に「water」と入力し、「M_Water_Ocean」というマテリアルを選択します。 以上の工程で、海の追加が完了しました。 地形のマテリアル作成 スカルプトモードで作成した地形にマテリアルを使って岩肌や草、海岸などを作成していきます。 まず、マテリアルを使うためには、プロジェクト内にマテリアルの追加を行わないといけません。 マテリアルの追加方法は画面上部のコンテンツ作成ボタンから「Quixel Bridge」を開きます。 「Quixel Bridge」内で任意のマテリアルを選択し、プロジェクト内で使用します。今回は草のマテリアルである「Grass」を追加しました。 画面下のコンテンツドロワーから追加したマテリアルを確認することができます。 追加したマテリアルをそのままプロジェクトでは使うことができないので、 岩肌や草、海岸など、使用するマテリアルを全て一つのプロジェクト用のマテリアルに集約をしました。 コンテンツ配下に「M_landscape」という本プロジェクト用のマテリアルを作成します。 次にマテリアル作成画面に移動し、エディタ上で右クリックを押し「Layer Blend」を作成します。 ここでプロジェクトで使用するひとつひとつのマテリアル(岩肌や草など)に対して、影の強さや発色の強さ、2つのマテリアルの ブレンド などを行います。 本作業はプロジェクト独自の絵の具パレットを作成するようなイメージです。 独自のマテリアル作成が完了すると、いよいよマテリアルを使用したペイント作業になります。   マテリアルを使用してペイント マテリアルを使用したペイントを行うためには、 ランドスケープ モードに変更して、ペイントタブを選択することでマテリアルのレイヤー(色のついた筆のイメージ)を選択できます。 レイヤーの中には先ほどマテリアル作成で行った「Layer Blend」を選ぶことができます。 ここで任意のレイヤーを選択し、作成した地形にペイントを行っていきます。 また、スカルプトモードと同様で、ツールの強度やブラシサイズなどもここで変更できます。ツールの強度を弱くすることで、複数のレイヤーのマテリアルを重ね塗りすることができます。 ここでは、浜辺を作成するために、「sand1」レイヤーを使い砂のマテリアルを塗った上から、「Glass1」の芝生を薄く重ね塗りを行いました。 山肌も同様にペイントをしていき、岩のレイヤーや草野レイヤー、砂浜のレイヤーを重ねて塗っていきワールド地形を仕上げていきます。 3Dアセットの追加 地形のスカルプト、ペイントが完了し完成に近づいたので、次は作成した地形に3Dアセットを設置します。 もちろん別の3Dオブジェクト作成ツールなどを使って作成したアセットを設置しても良いですが、今回は Unreal Engine でデフォルトで用意されている3Dアセットを追加し設置します。 マテリアルの作成でも使用した「Quixel Bridge」を開き、「3D Assets」を選択することで、作成した地形にあったアセットを探すことができます。 今回は「Nature」の中から「ROCK」を選択し、任意の岩のアセットを探し追加します。 追加したアセットは「コンテンツドロワー」内の「Megascans」→「3D_Assets」内に保存されているのでドラッグして任意の場所に配置します。 海を追加 最後に海の追加を行います。 前の手順で追加した「M_Water_Ocean」でも良いのですが、 Unreal Engine には別の海の機能も搭載されているので、そちらを利用します。 上部の編集タブから プラグイン を選択し、検索欄に「water」と入力し、「Water」の プラグイン を有効にします。現段階ではまだ実験段階と表示がありますが、問題なく使用できるので、今回は使用します。 次にコンテンツ作成ボタンから「アクタ配置パネル」を選択し、「Water Body Custom」を選択することで、新しい海をワールドに追加することができます。 以上で、ワールド上に地形を作成するための工程が完了しました。 おわりに 今回は、Unreal Engine5 を使用して、ゼロからワールドに地形を生成する方法を紹介しました。 初めて使用してみましたが、初心者でも簡単にハイクオリティな地形を作ることができました。 私は macOS 12.6 Monterey のPCで地形制作を行いました。 ランドスケープ モードのスカルプトを行う際にカーソルの操作が早すぎて上手くいかない点がありましたが、ペンタブなどで対応することで特に不自由なく作業を行うことができました。 今回の実践では、まだまだ Unreal Engine5 の一部の機能しか触れていないので、他の機能のキャッチアップも行っていき、ゲーム以外の領域でも効率的な使用方法を探っていきたいとおもいます。 最後までお読みいただき、ありがとうございました。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! 私たちは同じ事業部で共に働いていただける仲間を募集しています! みなさまのご応募、お待ちしています! 電通総研中途採用ページ 参考 ・ ランドスケープの作成 | Unreal Engine ドキュメント ・ 【はじめて学ぶ人のための】Unreal Engine 5 風景制作 基礎講座 執筆: @okazaki.wataru 、レビュー: @okazaki.wataru ( Shodo で執筆されました )
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 2022年12月始めに AWS CDK の GitHub リポジトリ の wiki に公開された Security And Safety Dev Guide を読んでみました。CDKアプリをデプロイする時の権限と、CDKアプリ内で作成する権限の両方について、管理方法と推奨事項が書かれています。 この記事ではざっくりとした要約と感想を筆者の観点で書いていきます(分かりやすさのために、幾分か内容の再構築や意訳が入っています)。翻訳記事ではないので、正確で詳細な内容は元のドキュメントをご確認ください。 https://github.com/aws/aws-cdk/wiki/Security-And-Safety-Dev-Guide イントロダクション CDKのデプロイではどのような権限を使うべきか 権限昇格することなくIAMロールの作成を許可する CDKデプロイで使われる権限の管理 DefaultStackSynthesizer デプロイのフロー Bootstrap CliCredentialsStackSynthesizer CDK内のIAMアイデンティティとポリシーを取り扱う CDKにIAMロールとポリシーの管理を任せる 事前に手動で作成したロールを利用する Permissions boundary と SCP ざっくり要約をさらに要約 イントロダクション <ざっくり要約> CDKはあらゆる AWS サービスを構築・設定できる強力なツールで、効率的な開発に寄与する一方、組織は コンプライアンス や コストコ ン トロール の目的で開発者の権限を制限したい場合もあり、セキュリティと生産性はしばしば競合する。このドキュメントでは開発者の生産性を損なうことなく、大半のセキュリティに関する ユースケース を満たすだろう推奨事項を紹介する。 <感想> CDKに限らず、組織内のセキュリティ施策が開発を阻害するものとして捉えられる傾向があるのは、セキュリティ担当と開発担当で見ている景色が違うからだろうと思います。セキュリティ担当は最悪の事態を想像して対策を提示するのに対して、開発担当は普段の日々の開発を楽にすることにより価値を置きます。セキュリティ担当の立場としては、開発担当の気持ちを理解し、 原理主義 にならずに対話型でセキュリティ対策を考える状態を目指したいと考えています。ちなみに筆者が所属するセキュリティを担当するグループでは、開発側の気持ちを理解する方法の一つとして自ら社内向けに システム開発 も行っています。 CDKのデプロイではどのような権限を使うべきか <ざっくり要約> 権限付与の方法は一般的に、許可リスト方式と拒否リスト方式の2種類ある。 CDK/CloudFormationにおいては ロールバック もあり、許可リストでは最小権限を網羅するのは難しい。 AWS Config, AWS CloudTrail, AWS Security Hub, AWS CloudFormation Hooksなどによってガードレール、 コンプライアンス ルール、監査、モニタリングを実施した上で、それらを変更する以外の全ての権限を付与する拒否リスト方式の方が推奨される。 コンプライアンス に適合する範囲で開発者に自由を与えることができる。 <感想> IaCの経験がある方であれば、許可リスト方式での権限付与は厳しいことはよく分かると思います。特に CDK/CloudFormation における ロールバック は Terraform にはない特徴の一つなので、権限付与に限らず、CDKを使うときはそれを意識するのが良いでしょう。 権限昇格することなくIAMロールの作成を許可する <ざっくり要約> CDKでリソースをデプロイするときは、デフォルトで最小権限になるように必要なIAMロールを自動作成する。手動でロールを作らずに、このデフォルトの挙動に任せるのが推奨である。 アプリ開発 者にロールを作成させない運用ルールがある場合、その目的は主に開発者の権限昇格を防ぐことである。権限昇格を防ぐ代替手段としてPermissions boundary(アクセス許可境界)とSCP(サー ビスコ ン トロール ポリシー)がある。開発者のロールにPermissions boundaryをアタッチすることで、開発者自身の権限と、開発者のロールが作成するロールの権限両方を制限できる。 <感想> IAMロールやポリシーはIaCで作る他のリソースを参照するため、手動で管理せずにIaCの開発サイクルに含めた方が管理しやすいのは納得できる説明です。 CDKでインフラをデプロイすると、いつの間にかたくさんのIAMロールが作られていて把握しきれないと感じていましたが、そもそも人間が把握できる程度のシンプルさでは最小権限は実現できていないのだろうと説明を読んで思いました。 CDKデプロイで使われる権限の管理 <ざっくり要約> CDKはCloudFormationを使って変更をデプロイする。 デプロイを開始するアクター(ユーザーか自動化システム)はIAM アイデンティティ (ユーザーかロール)を持ち、それとは別にデプロイを実行する CloudFormation にもロールが与えられる。 スタックデプロイ時にCDKが使用するロールとアセットコンテナ(後述)は、Stack Synthesizer が決定する。 <感想> cdk deploy を実行する時の権限と、実際に CloudFormation がリソースをデプロイする時の権限は分けられることが書いてあります。これも Terraform と比較した時の CDK の特徴ですね。意識的に2つの権限を区別して考えるとこの後の説明が理解しやすいと思います。 Stack Synthesizer については DefaultStackSynthesizer と CliCredentialsStackSynthesizer の2種類の説明がこの後に続きます。 DefaultStackSynthesizer デプロイのフロー <ざっくり要約> DefaultStackSynthesizer は CDK v2 のデフォルトのStack Synthesizerであり、基本的にはこれを使うことになる。 CDKによるデプロイは次のフローの通りである CDKデプロイを開始するアクターの AWS 権限を認可する CDKデプロイを開始するアクターは「CDKデプロイロール」を引き受け(AssumeRole)、「CDKデプロイロール」は「CFN実行ロール」をCloudFormationに渡す(PassRole)。 「CFN実行ロール」は CloudFormationサービスロール で、スタックをデプロイする時にCloudFormationに利用される これ以降、デプロイに使われるのはデプロイを開始したアクターの権限ではなく、「CFN実行ロール」の権限になる 必要に応じてCDKデプロイを開始したアクターは「file-publishing-role」「image-publishing-role」「lookup-role」をそれぞれ引き受ける(AssumeRole) 「file-publishing-role」と「image-publishing-role」は、デプロイで使われるファイルとDockerイメージをS3 バケット とECR リポジトリ にプッシュする時に使われる。ここのS3 バケット とECR リポジトリ はbootstrap時に作られたものである。「CFN実行ロール」にはこれらのアセットを参照する権限が必要である 「lookup-role」はCDKのsynth時に既存のリソースを参照する場合に使われる。デフォルトではReadOnly管理ポリシーが付与されている (図は https://github.com/aws/aws-cdk/wiki/Security-And-Safety-Dev-Guide より) <感想> 「CDKデプロイを開始するアクターの権限」「CDKデプロイロール」「CFN実行ロール」「file-publishing-role」「image-publishing-role」「lookup-role」と権限が6種類登場しています。 「CDKデプロイを開始するアクターの権限」はCDKを利用する前に手動で作成するもので、「CDKデプロイロール」「file-publishing-role」「image-publishing-role」「lookup-role」の4つをAssumeRoleできれば良いです。すなわち、 cdk deploy を実行する時の権限は AdministratorAccess である必要はありません。 「CDKデプロイロール」「CFN実行ロール」「file-publishing-role」「image-publishing-role」「lookup-role」の5つは cdk bootstrap するときに自動で作成されるもので、手で管理する必要はありません。bootstrapの説明はこの後続きます。 Bootstrap <ざっくり要約> DefaultStackSynthesizer を使うためにはまず AWS 環境を bootstrap する必要がある。bootstrap は各リージョンに一度だけ必要な操作で、Synthesizerに必要なIAMロール群とアセットコンテナ(S3 バケット とECR リポジトリ )が作成される。 cdk boostrap することでCloudFormationスタックがデプロイされる。デフォルトのテンプレートを使うこともできるし、 カスタマイズ することもできる。 コンプライアンス 管理をする場合、bootstrap時のテンプレートをカスタマイズする必要がある。例えばカスタマイズによって「CFN実行ロール」の権限を制限できる(ポリシーの変更、Permission boundaryの追加などによって権限昇格を防ぐ)。デフォルトでは「CFN実行ロール」はAdministratorAccessとなる。 また「CDKデプロイロール」「file-publishing-role」「image-publishing-role」を誰が AssumeRole できるのかを制限することもできる。デフォルトでは同じ AWS アカウントの全ての アイデンティティ が AssumeRole できる。 CDKがbootstrapしたロールを引き受けるためには、以下のポリシー ステートメント を アイデンティティ に追加する(=CDKデプロイを開始するアクターは以下の権限を持てば良い) { " Version ": " 2012-10-17 ", " Statement ": [{ " Sid ": " AssumeCDKRoles ", " Effect ": " Allow ", " Action ": " sts:AssumeRole ", " Resource ": " * ", " Condition ": { " ForAnyValue:StringEquals ": { " iam:ResourceTag/aws-cdk:bootstrap-role ": [ " image-publishing ", " file-publishing ", " deploy ", " lookup " ] } } }] } cdk bootstrap の実行はAdministratorAccessの権限を使うことを推奨する。bootstrapスタックの内容は変わりうるので、将来的に必要な権限は予測できない。またbootstrapによって任意の権限を持った新しいロールが作られるので、bootstrapを実行する権限を制限する実メリットがない。 <感想> cdk bootstrap は CDKToolkit という名前のCloudFormationスタックを作る程度の認識でしたが、実際に何をやっているのかの説明がされていました。デフォルト設定で作成された CDKToolkit スタックを実際に見てみると、確かに説明されていた通りのリソースが作られています。 ECR リポジトリ 「ContainerAssetsRepository」 S3 バケット 「StagingBucket」 CDKデプロイロール「DeploymentActionRole」 CloudFormation、S3、KMSの権限や、CFN実行ロールをPassRoleする権限が付いている aws-cdk:bootstrap-role : deploy リソースタグが付いている CFN実行ロール「CloudFormationExecutionRole」 AdministratorAccess ポリシーが付いている リソースタグは付いていない file-publishing-role「FilePublishingRole」 S3とKMSへの権限が付いている aws-cdk:bootstrap-role : file-publishing リソースタグが付いている image-publishing-role「ImagePublishingRole」 ECRへの権限が付いている aws-cdk:bootstrap-role : image-publishing リソースタグが付いている lookup-role「LookupRole」 AWS 管理の ReadOnlyAccess ポリシーが付いている aws-cdk:bootstrap-role : lookup リソースタグが付いている 注目すべきは、紹介されているポリシー ステートメント ではリソースタグで「CDKデプロイロール」「file-publishing-role」「image-publishing-role」「lookup-role」のAssumeRoleを許可していて、AdministratorAccess を持つ「CFN実行ロール」をAssumeRoleできないようになっている点です。つまりこれを使うことで、「CDKデプロイを開始するアクター」はAdministratorAccess権限をAssumeRoleできません。 (しかし厳密に考えると、「CDKデプロイロール」は CloudFormationスタックを作ることができて、「CFN実行ロール」をPassRoleできるため、CloudFormationに強権限のIAMロールを作らせて然るべきリソースタグを付けることで、「CDKデプロイを開始するアクター」から強権限のロールに最終的にAssumeRoleできるように思います。AssumeRoleを許可する対象リソースはリソースタグではなくロール名で指定した方がより安全でしょう) bootstrapで作成されるリソースは将来的に変わる可能性もあるとのことですが、「CDKデプロイを開始するアクター」がAssumeすべきロールの種類が増えていかないかは気になるところです。 CliCredentialsStackSynthesizer <ざっくり要約> DefaultStackSynthesizer の代わりに CliCredentialsStackSynthesizer を使うことができる。IAMロールをAssumeするのではなく、「CDKデプロイを開始するアクター」の権限を利用する。CloudFormationコールは直接実行され、サービスロールは渡されない。つまり「CDKデプロイを開始するアクター」は bootstrap が本来作成するロールの全ての権限を持つ必要がある。なお --role-arn パラメータを利用することで「CFN実行ロール」を別のロールとすることはできる。 <感想> CliCredentialsStackSynthesizer を使いたいケースがどのような場合があるのか気になります。「CDKデプロイを開始するアクター」(ローカルやデプロイパイプラインに保存するアクセスキー)が強い権限を持つ必要があるので、基本的には DefaultStackSynthesizer の方を使うのが良いのではないでしょうか。 CDK内のIAM アイデンティティ とポリシーを取り扱う <ざっくり要約> CDKにIAMロールとポリシーの作成を任せるのが推奨。もしそれを許可できない場合、事前に人の手で作成し、CDKアプリから参照するようにする。 <感想> CDKデプロイ自体の権限についての説明は終わり、ここからはなじみやすいイン フラリ ソースの権限についてです。 CDKにIAMロールとポリシーの管理を任せる <ざっくり要約> CDKに権限の生成を任せる場合、 grant メソッドを活用するのが良い。例えば暗号化されたS3 バケット からの読み取りをLambda関数に許可する場合、 bucket.grantRead(handler) のように書ける。 import { aws_s3 as s3 , aws_lambda as lambda , aws_kms as kms , Stack , } from 'aws-cdk-lib' ; const stack = new Stack ( app , 'LambdaStack' ); const key = new kms.Key ( stack , 'BucketKey' ); const bucket = new s3.Bucket ( stack , 'Bucket' , { encryptionKey: key , } ); const handler = new lambda. Function ( stack , 'Handler' , { runtime: lambda.Runtime.NODEJS_16_X , handler: 'index.handler' , code: lambda.Code.fromAsset ( path.join ( __dirname , 'lambda-handler' )), } ); bucket.grantRead ( handler ); handler.addToRolePolicy() でLambda関数に直接権限を与えることもできるが、 grant メソッドが使える場合はそれが推奨される。 <感想> CDKには明示的に権限を付与しなくても、(なるべく)最小権限を自動で付与される方法が多く用意されています。例のS3 バケット の grantRead では、以下の権限がLambda関数の実行ロールに付与されます。 対象 バケット への s3:GetObject* 対象 バケット への s3:GetBucket* 対象 バケット への s3:List* 対象 バケット が利用するKMSキーへの kms:Decrypt 対象 バケット が利用するKMSキーへの kms:DescribeKey もしLambda関数が GetObject しか実行しない場合、 GetBucket* や List* の権限も付与されているので厳密には最小権限ではありませんが、読み取りしか許可されていない意味では気になる状況は少ないでしょう。 grand メソッドの名前だけでは具体的に付与される権限が分からなかったりするので、メソッドの説明などで確認すると良いでしょう。例えば現状、DynamoDBテーブルのコンスト ラク トには grantReadWriteData メソッドが用意されており、実際に付与される権限がメソッドの説明で分かります。 アイテムをPutするだけで、Deleteする権限を与えたくない場合もあるかと思いますので、そのような時は addToRolePolicy などを使うことになります。 事前に手動で作成したロールを利用する <ざっくり要約> CDKでIAMロールとポリシーの作成が許可されていない場合、事前に手動で作成し、 Role.fromRoleName() で参照する。 あるいは Role.customizeRoles() を利用して、CDKアプリに必要となる全てのロールとポリシーを実際に作成せずに、レポートとして書き出してIAMロールを管理する担当者に連携することができる。 const stack = new Stack(app, 'LambdaStack'); // ロールのsynthをしないようにこれを追加する iam.Role.customizeRoles(stack); // 以下のコードは同じ const key = new kms.Key(stack, 'BucketKey'); const bucket = new s3.Bucket(stack, 'Bucket', { encryptionKey: key, }); const handler = new lambda.Function(stack, 'Handler', { runtime: lambda.Runtime.NODEJS_16_X, handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), }); // 権限が必要であることをCDKに教えるため、通常通り grantRead() などは必要 bucket.grantRead(handler); 人の手によってロールが作成されたら、そのロール名を customizeRoles に追加することでCDKで利用できるようになる。 const stack = new Stack ( app , 'LambdaStack' ); iam.Role.customizeRoles ( stack , { usePrecreatedRoles: { 'LambdaStack/Handler/ServiceRole' : 'lambda-service-role' , } , } ); <感想> customizeRoles はCDK v2.51.0でリリースされた機能です。IAM権限を開発者が自由に作成できないような環境では役に立ちそうです。 Permissions boundary と SCP <ざっくり要約> セキュリティガードレールのためにPermissions boundaryを使うことができる。 権限昇格を防ぐために、同じPermissions boundaryを含まないようなユーザーやロールを作ることを禁止できる。 cdk bootstrap で --custom-permissions-boundary フラグを追加することで、「CFN実行ロール」にPermissions boundaryをアタッチできる。 CDKアプリ内で作るロールにPermissions boundaryをアタッチする場合は以下のとおりになる。 const stack = new Stack ( app , 'MyStack' ); new iam.Role ( stack , 'Role' , { assumeRolePolicy: new iam.ServicePrincipal ( 'lambda.amazonaws.com' , permissionsBoundary: iam.ManagedPolicy.fromManagedPolicyName ( `cdk- ${ stack.synthesizer.bootstrapQualifier } -permissions-boundary- ${ stack.account } - ${ stack.region } ` ); } ); cdk.json にてグローバルで設定もできる。 { " context ": { " @aws-cdk/core:permissionsBoundary ": { " name ": " cdk-permissions-boundary " } } } App や Stage レベルで設定もできる。 <感想> Permissions boundary をCDKで利用する方法について詳細に書かれていました。 --custom-permissions-boundary フラグは CDK v2.54.0 に追加された機能 です。 ざっくり要約をさらに要約 CDKのデプロイに与える権限は許可リストではなく、拒否リストが推奨である IAMロールは手動で作るのではなく、CDKの自動作成に任せるのが推奨である 開発者の権限昇格を防ぎたい場合、Permissions boundary(アクセス許可境界)とSCP(サー ビスコ ン トロール ポリシー)を利用する bootstrap 時のCloudFormationテンプレートをカスタマイズする cdk bootstrap はAdministratorAccessの権限で行う 基本的に cdk deploy にAdministratorAccessは必要なく、bootstrapで作成された4種類のIAMロールをAssumeRoleできれば良い CDKに権限の生成を任せる場合、なるべく grant メソッドを利用する Role.customizeRoles() を利用することで、CDKデプロイせずに必要となる権限一覧をレポート出力できる お読みいただいてありがとうございました。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 セキュリティエンジニア(セキュリティ設計) 執筆: @kou.kinyo 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 2022年12月始めに AWS CDK の GitHub リポジトリ の wiki に公開された Security And Safety Dev Guide を読んでみました。CDKアプリをデプロイする時の権限と、CDKアプリ内で作成する権限の両方について、管理方法と推奨事項が書かれています。 この記事ではざっくりとした要約と感想を筆者の観点で書いていきます(分かりやすさのために、幾分か内容の再構築や意訳が入っています)。翻訳記事ではないので、正確で詳細な内容は元のドキュメントをご確認ください。 https://github.com/aws/aws-cdk/wiki/Security-And-Safety-Dev-Guide イントロダクション CDKのデプロイではどのような権限を使うべきか 権限昇格することなくIAMロールの作成を許可する CDKデプロイで使われる権限の管理 DefaultStackSynthesizer デプロイのフロー Bootstrap CliCredentialsStackSynthesizer CDK内のIAMアイデンティティとポリシーを取り扱う CDKにIAMロールとポリシーの管理を任せる 事前に手動で作成したロールを利用する Permissions boundary と SCP ざっくり要約をさらに要約 イントロダクション <ざっくり要約> CDKはあらゆる AWS サービスを構築・設定できる強力なツールで、効率的な開発に寄与する一方、組織は コンプライアンス や コストコ ン トロール の目的で開発者の権限を制限したい場合もあり、セキュリティと生産性はしばしば競合する。このドキュメントでは開発者の生産性を損なうことなく、大半のセキュリティに関する ユースケース を満たすだろう推奨事項を紹介する。 <感想> CDKに限らず、組織内のセキュリティ施策が開発を阻害するものとして捉えられる傾向があるのは、セキュリティ担当と開発担当で見ている景色が違うからだろうと思います。セキュリティ担当は最悪の事態を想像して対策を提示するのに対して、開発担当は普段の日々の開発を楽にすることにより価値を置きます。セキュリティ担当の立場としては、開発担当の気持ちを理解し、 原理主義 にならずに対話型でセキュリティ対策を考える状態を目指したいと考えています。ちなみに筆者が所属するセキュリティを担当するグループでは、開発側の気持ちを理解する方法の一つとして自ら社内向けに システム開発 も行っています。 CDKのデプロイではどのような権限を使うべきか <ざっくり要約> 権限付与の方法は一般的に、許可リスト方式と拒否リスト方式の2種類ある。 CDK/CloudFormationにおいては ロールバック もあり、許可リストでは最小権限を網羅するのは難しい。 AWS Config, AWS CloudTrail, AWS Security Hub, AWS CloudFormation Hooksなどによってガードレール、 コンプライアンス ルール、監査、モニタリングを実施した上で、それらを変更する以外の全ての権限を付与する拒否リスト方式の方が推奨される。 コンプライアンス に適合する範囲で開発者に自由を与えることができる。 <感想> IaCの経験がある方であれば、許可リスト方式での権限付与は厳しいことはよく分かると思います。特に CDK/CloudFormation における ロールバック は Terraform にはない特徴の一つなので、権限付与に限らず、CDKを使うときはそれを意識するのが良いでしょう。 権限昇格することなくIAMロールの作成を許可する <ざっくり要約> CDKでリソースをデプロイするときは、デフォルトで最小権限になるように必要なIAMロールを自動作成する。手動でロールを作らずに、このデフォルトの挙動に任せるのが推奨である。 アプリ開発 者にロールを作成させない運用ルールがある場合、その目的は主に開発者の権限昇格を防ぐことである。権限昇格を防ぐ代替手段としてPermissions boundary(アクセス許可境界)とSCP(サー ビスコ ン トロール ポリシー)がある。開発者のロールにPermissions boundaryをアタッチすることで、開発者自身の権限と、開発者のロールが作成するロールの権限両方を制限できる。 <感想> IAMロールやポリシーはIaCで作る他のリソースを参照するため、手動で管理せずにIaCの開発サイクルに含めた方が管理しやすいのは納得できる説明です。 CDKでインフラをデプロイすると、いつの間にかたくさんのIAMロールが作られていて把握しきれないと感じていましたが、そもそも人間が把握できる程度のシンプルさでは最小権限は実現できていないのだろうと説明を読んで思いました。 CDKデプロイで使われる権限の管理 <ざっくり要約> CDKはCloudFormationを使って変更をデプロイする。 デプロイを開始するアクター(ユーザーか自動化システム)はIAM アイデンティティ (ユーザーかロール)を持ち、それとは別にデプロイを実行する CloudFormation にもロールが与えられる。 スタックデプロイ時にCDKが使用するロールとアセットコンテナ(後述)は、Stack Synthesizer が決定する。 <感想> cdk deploy を実行する時の権限と、実際に CloudFormation がリソースをデプロイする時の権限は分けられることが書いてあります。これも Terraform と比較した時の CDK の特徴ですね。意識的に2つの権限を区別して考えるとこの後の説明が理解しやすいと思います。 Stack Synthesizer については DefaultStackSynthesizer と CliCredentialsStackSynthesizer の2種類の説明がこの後に続きます。 DefaultStackSynthesizer デプロイのフロー <ざっくり要約> DefaultStackSynthesizer は CDK v2 のデフォルトのStack Synthesizerであり、基本的にはこれを使うことになる。 CDKによるデプロイは次のフローの通りである CDKデプロイを開始するアクターの AWS 権限を認可する CDKデプロイを開始するアクターは「CDKデプロイロール」を引き受け(AssumeRole)、「CDKデプロイロール」は「CFN実行ロール」をCloudFormationに渡す(PassRole)。 「CFN実行ロール」は CloudFormationサービスロール で、スタックをデプロイする時にCloudFormationに利用される これ以降、デプロイに使われるのはデプロイを開始したアクターの権限ではなく、「CFN実行ロール」の権限になる 必要に応じてCDKデプロイを開始したアクターは「file-publishing-role」「image-publishing-role」「lookup-role」をそれぞれ引き受ける(AssumeRole) 「file-publishing-role」と「image-publishing-role」は、デプロイで使われるファイルとDockerイメージをS3 バケット とECR リポジトリ にプッシュする時に使われる。ここのS3 バケット とECR リポジトリ はbootstrap時に作られたものである。「CFN実行ロール」にはこれらのアセットを参照する権限が必要である 「lookup-role」はCDKのsynth時に既存のリソースを参照する場合に使われる。デフォルトではReadOnly管理ポリシーが付与されている (図は https://github.com/aws/aws-cdk/wiki/Security-And-Safety-Dev-Guide より) <感想> 「CDKデプロイを開始するアクターの権限」「CDKデプロイロール」「CFN実行ロール」「file-publishing-role」「image-publishing-role」「lookup-role」と権限が6種類登場しています。 「CDKデプロイを開始するアクターの権限」はCDKを利用する前に手動で作成するもので、「CDKデプロイロール」「file-publishing-role」「image-publishing-role」「lookup-role」の4つをAssumeRoleできれば良いです。すなわち、 cdk deploy を実行する時の権限は AdministratorAccess である必要はありません。 「CDKデプロイロール」「CFN実行ロール」「file-publishing-role」「image-publishing-role」「lookup-role」の5つは cdk bootstrap するときに自動で作成されるもので、手で管理する必要はありません。bootstrapの説明はこの後続きます。 Bootstrap <ざっくり要約> DefaultStackSynthesizer を使うためにはまず AWS 環境を bootstrap する必要がある。bootstrap は各リージョンに一度だけ必要な操作で、Synthesizerに必要なIAMロール群とアセットコンテナ(S3 バケット とECR リポジトリ )が作成される。 cdk boostrap することでCloudFormationスタックがデプロイされる。デフォルトのテンプレートを使うこともできるし、 カスタマイズ することもできる。 コンプライアンス 管理をする場合、bootstrap時のテンプレートをカスタマイズする必要がある。例えばカスタマイズによって「CFN実行ロール」の権限を制限できる(ポリシーの変更、Permission boundaryの追加などによって権限昇格を防ぐ)。デフォルトでは「CFN実行ロール」はAdministratorAccessとなる。 また「CDKデプロイロール」「file-publishing-role」「image-publishing-role」を誰が AssumeRole できるのかを制限することもできる。デフォルトでは同じ AWS アカウントの全ての アイデンティティ が AssumeRole できる。 CDKがbootstrapしたロールを引き受けるためには、以下のポリシー ステートメント を アイデンティティ に追加する(=CDKデプロイを開始するアクターは以下の権限を持てば良い) { " Version ": " 2012-10-17 ", " Statement ": [{ " Sid ": " AssumeCDKRoles ", " Effect ": " Allow ", " Action ": " sts:AssumeRole ", " Resource ": " * ", " Condition ": { " ForAnyValue:StringEquals ": { " iam:ResourceTag/aws-cdk:bootstrap-role ": [ " image-publishing ", " file-publishing ", " deploy ", " lookup " ] } } }] } cdk bootstrap の実行はAdministratorAccessの権限を使うことを推奨する。bootstrapスタックの内容は変わりうるので、将来的に必要な権限は予測できない。またbootstrapによって任意の権限を持った新しいロールが作られるので、bootstrapを実行する権限を制限する実メリットがない。 <感想> cdk bootstrap は CDKToolkit という名前のCloudFormationスタックを作る程度の認識でしたが、実際に何をやっているのかの説明がされていました。デフォルト設定で作成された CDKToolkit スタックを実際に見てみると、確かに説明されていた通りのリソースが作られています。 ECR リポジトリ 「ContainerAssetsRepository」 S3 バケット 「StagingBucket」 CDKデプロイロール「DeploymentActionRole」 CloudFormation、S3、KMSの権限や、CFN実行ロールをPassRoleする権限が付いている aws-cdk:bootstrap-role : deploy リソースタグが付いている CFN実行ロール「CloudFormationExecutionRole」 AdministratorAccess ポリシーが付いている リソースタグは付いていない file-publishing-role「FilePublishingRole」 S3とKMSへの権限が付いている aws-cdk:bootstrap-role : file-publishing リソースタグが付いている image-publishing-role「ImagePublishingRole」 ECRへの権限が付いている aws-cdk:bootstrap-role : image-publishing リソースタグが付いている lookup-role「LookupRole」 AWS 管理の ReadOnlyAccess ポリシーが付いている aws-cdk:bootstrap-role : lookup リソースタグが付いている 注目すべきは、紹介されているポリシー ステートメント ではリソースタグで「CDKデプロイロール」「file-publishing-role」「image-publishing-role」「lookup-role」のAssumeRoleを許可していて、AdministratorAccess を持つ「CFN実行ロール」をAssumeRoleできないようになっている点です。つまりこれを使うことで、「CDKデプロイを開始するアクター」はAdministratorAccess権限をAssumeRoleできません。 (しかし厳密に考えると、「CDKデプロイロール」は CloudFormationスタックを作ることができて、「CFN実行ロール」をPassRoleできるため、CloudFormationに強権限のIAMロールを作らせて然るべきリソースタグを付けることで、「CDKデプロイを開始するアクター」から強権限のロールに最終的にAssumeRoleできるように思います。AssumeRoleを許可する対象リソースはリソースタグではなくロール名で指定した方がより安全でしょう) bootstrapで作成されるリソースは将来的に変わる可能性もあるとのことですが、「CDKデプロイを開始するアクター」がAssumeすべきロールの種類が増えていかないかは気になるところです。 CliCredentialsStackSynthesizer <ざっくり要約> DefaultStackSynthesizer の代わりに CliCredentialsStackSynthesizer を使うことができる。IAMロールをAssumeするのではなく、「CDKデプロイを開始するアクター」の権限を利用する。CloudFormationコールは直接実行され、サービスロールは渡されない。つまり「CDKデプロイを開始するアクター」は bootstrap が本来作成するロールの全ての権限を持つ必要がある。なお --role-arn パラメータを利用することで「CFN実行ロール」を別のロールとすることはできる。 <感想> CliCredentialsStackSynthesizer を使いたいケースがどのような場合があるのか気になります。「CDKデプロイを開始するアクター」(ローカルやデプロイパイプラインに保存するアクセスキー)が強い権限を持つ必要があるので、基本的には DefaultStackSynthesizer の方を使うのが良いのではないでしょうか。 CDK内のIAM アイデンティティ とポリシーを取り扱う <ざっくり要約> CDKにIAMロールとポリシーの作成を任せるのが推奨。もしそれを許可できない場合、事前に人の手で作成し、CDKアプリから参照するようにする。 <感想> CDKデプロイ自体の権限についての説明は終わり、ここからはなじみやすいイン フラリ ソースの権限についてです。 CDKにIAMロールとポリシーの管理を任せる <ざっくり要約> CDKに権限の生成を任せる場合、 grant メソッドを活用するのが良い。例えば暗号化されたS3 バケット からの読み取りをLambda関数に許可する場合、 bucket.grantRead(handler) のように書ける。 import { aws_s3 as s3 , aws_lambda as lambda , aws_kms as kms , Stack , } from 'aws-cdk-lib' ; const stack = new Stack ( app , 'LambdaStack' ); const key = new kms.Key ( stack , 'BucketKey' ); const bucket = new s3.Bucket ( stack , 'Bucket' , { encryptionKey: key , } ); const handler = new lambda. Function ( stack , 'Handler' , { runtime: lambda.Runtime.NODEJS_16_X , handler: 'index.handler' , code: lambda.Code.fromAsset ( path.join ( __dirname , 'lambda-handler' )), } ); bucket.grantRead ( handler ); handler.addToRolePolicy() でLambda関数に直接権限を与えることもできるが、 grant メソッドが使える場合はそれが推奨される。 <感想> CDKには明示的に権限を付与しなくても、(なるべく)最小権限を自動で付与される方法が多く用意されています。例のS3 バケット の grantRead では、以下の権限がLambda関数の実行ロールに付与されます。 対象 バケット への s3:GetObject* 対象 バケット への s3:GetBucket* 対象 バケット への s3:List* 対象 バケット が利用するKMSキーへの kms:Decrypt 対象 バケット が利用するKMSキーへの kms:DescribeKey もしLambda関数が GetObject しか実行しない場合、 GetBucket* や List* の権限も付与されているので厳密には最小権限ではありませんが、読み取りしか許可されていない意味では気になる状況は少ないでしょう。 grand メソッドの名前だけでは具体的に付与される権限が分からなかったりするので、メソッドの説明などで確認すると良いでしょう。例えば現状、DynamoDBテーブルのコンスト ラク トには grantReadWriteData メソッドが用意されており、実際に付与される権限がメソッドの説明で分かります。 アイテムをPutするだけで、Deleteする権限を与えたくない場合もあるかと思いますので、そのような時は addToRolePolicy などを使うことになります。 事前に手動で作成したロールを利用する <ざっくり要約> CDKでIAMロールとポリシーの作成が許可されていない場合、事前に手動で作成し、 Role.fromRoleName() で参照する。 あるいは Role.customizeRoles() を利用して、CDKアプリに必要となる全てのロールとポリシーを実際に作成せずに、レポートとして書き出してIAMロールを管理する担当者に連携することができる。 const stack = new Stack(app, 'LambdaStack'); // ロールのsynthをしないようにこれを追加する iam.Role.customizeRoles(stack); // 以下のコードは同じ const key = new kms.Key(stack, 'BucketKey'); const bucket = new s3.Bucket(stack, 'Bucket', { encryptionKey: key, }); const handler = new lambda.Function(stack, 'Handler', { runtime: lambda.Runtime.NODEJS_16_X, handler: 'index.handler', code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), }); // 権限が必要であることをCDKに教えるため、通常通り grantRead() などは必要 bucket.grantRead(handler); 人の手によってロールが作成されたら、そのロール名を customizeRoles に追加することでCDKで利用できるようになる。 const stack = new Stack ( app , 'LambdaStack' ); iam.Role.customizeRoles ( stack , { usePrecreatedRoles: { 'LambdaStack/Handler/ServiceRole' : 'lambda-service-role' , } , } ); <感想> customizeRoles はCDK v2.51.0でリリースされた機能です。IAM権限を開発者が自由に作成できないような環境では役に立ちそうです。 Permissions boundary と SCP <ざっくり要約> セキュリティガードレールのためにPermissions boundaryを使うことができる。 権限昇格を防ぐために、同じPermissions boundaryを含まないようなユーザーやロールを作ることを禁止できる。 cdk bootstrap で --custom-permissions-boundary フラグを追加することで、「CFN実行ロール」にPermissions boundaryをアタッチできる。 CDKアプリ内で作るロールにPermissions boundaryをアタッチする場合は以下のとおりになる。 const stack = new Stack ( app , 'MyStack' ); new iam.Role ( stack , 'Role' , { assumeRolePolicy: new iam.ServicePrincipal ( 'lambda.amazonaws.com' , permissionsBoundary: iam.ManagedPolicy.fromManagedPolicyName ( `cdk- ${ stack.synthesizer.bootstrapQualifier } -permissions-boundary- ${ stack.account } - ${ stack.region } ` ); } ); cdk.json にてグローバルで設定もできる。 { " context ": { " @aws-cdk/core:permissionsBoundary ": { " name ": " cdk-permissions-boundary " } } } App や Stage レベルで設定もできる。 <感想> Permissions boundary をCDKで利用する方法について詳細に書かれていました。 --custom-permissions-boundary フラグは CDK v2.54.0 に追加された機能 です。 ざっくり要約をさらに要約 CDKのデプロイに与える権限は許可リストではなく、拒否リストが推奨である IAMロールは手動で作るのではなく、CDKの自動作成に任せるのが推奨である 開発者の権限昇格を防ぎたい場合、Permissions boundary(アクセス許可境界)とSCP(サー ビスコ ン トロール ポリシー)を利用する bootstrap 時のCloudFormationテンプレートをカスタマイズする cdk bootstrap はAdministratorAccessの権限で行う 基本的に cdk deploy にAdministratorAccessは必要なく、bootstrapで作成された4種類のIAMロールをAssumeRoleできれば良い CDKに権限の生成を任せる場合、なるべく grant メソッドを利用する Role.customizeRoles() を利用することで、CDKデプロイせずに必要となる権限一覧をレポート出力できる お読みいただいてありがとうございました。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 セキュリティエンジニア(セキュリティ設計) 執筆: @kou.kinyo 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )