電通総研のブログ - TECH PLAY

TECH PLAY

電通総研

電通総研 の技術ブログ

全867件

みなさんこんにちは、 電通国際情報サービス (ISID)コーポレート本部 システム推進部の佐藤太一です。 先日、TECHPLAYでDataflowに関連するお話をしました。 その日の模様が公開されていますので、ご興味のある方は是非ご覧ください。 techplay.jp さて、このエントリではBigQueryにおいて利用できる 再帰 クエリ(WITH RECURSIVE句とUNION ALL)について紹介します。 再帰 クエリを使うと、表形式しか扱えない RDB において 木構造 のデータを扱えます。 木構造 のデータとは、例えば会社における組織図のようなものを想像してください。 ちなみに、 Oracle なら 再帰 クエリはSTART WITH句とCONNECT BY句で実現していましたよね。 前提となるデータ データを定義するクエリ データを投入するクエリ 一番簡単な再帰クエリ 再帰クエリに配列を導入する 再帰クエリに導入した配列のオフセット位置を使う 再帰クエリと条件分岐を組み合わせる 再帰クエリと副問い合わせを組み合わせる 再帰クエリに導入した配列をソースに副問い合わせする まとめ 前提となるデータ 再帰 クエリの話を始める前に、まずはこのエントリで取り扱うデータ構造について説明しておきましょう。 今回の説明に使うのは以下のような組織図をデータベースに格納したものです。 二つの事業部があり、それぞれに事業部長(いろは、にほへ)がいます。 事業部長が統括する事業部には、それぞれの部署には部長がいます。日本語事業部にはカタカナ部と数字部があり、英語事業部にはアルファベット部とナンバー部があります。 部長の下には副部長がいる部門(カタカナ部、アルファベット部)とそうでない部門(数字部、ナンバー部)があります。 副部長の有無に関わらず、部門内にはいくつかの課があり、それぞれに課長がいます。 データを定義するクエリ それでは、BigQueryに投入可能なCREATE TABLE文をお見せしましょう。 create or replace table taichi_test.organization ( # 1. id int64 not null, # 2. parent int64, # 3. department string, # 4. title string not null, # 5. name string not null # 6. ); taichi_test については、読者の皆さんがご自分で用意したデー タセット 名を指定してください。 idカラムはこのテーブルのプライマリキーとなるものです。なお、BigQueryに一意制約はありません。 各レコードが親となるレコードのidを指定するためのカラムです。親がいない事業部長はnullになります。例えば、部長の親レコードは事業部長です。 部署名を格納するカラムです。事業部長の部署名は事業部名になります。 図の中では箱の一行目に書かれている役職名を格納するカラムです。 図の中では箱の二行目に書かれている社員名を格納するカラムです。 データを投入するクエリ データを投入するためのINSERT文です。 insert into taichi_test.organization values (1, null, "日本語事業部", "事業部長", "いろは") , (2, 1, "カタカナ部", "部長", "ア") , (3, 2, "カタカナ部", "副部長", "イ") , (4, 2, "カタカナ部", "副部長", "ウ") , (5, 3, "カタカナ部", "課長", "エ") , (6, 3, "カタカナ部", "課長", "オ") , (7, 3, "カタカナ部", "課長", "カ") , (8, 4, "カタカナ部", "課長", "キ") , (9, 4, "カタカナ部", "課長", "ク") , (10, 1, "数字部", "部長", "一") , (11, 10, "数字部", "課長", "二") , (12, 10, "数字部", "課長", "三") , (13, 10, "数字部", "課長", "四") , (14, 10, "数字部", "課長", "五") , (15, null, "英語事業部", "事業部長", "にほへ") , (16, 15, "アルファベット部", "部長", "A") , (17, 16, "アルファベット部", "副部長", "B") , (18, 17, "アルファベット部", "課長", "C") , (19, 17, "アルファベット部", "課長", "D") , (20, 17, "アルファベット部", "課長", "E") , (21, 15, "ナンバー部", "部長", "1") , (22, 21, "ナンバー部", "課長", "2") , (23, 21, "ナンバー部", "課長", "3"); BigQueryではINSERT文を複数並べるよりも、このようにBulk INSERTをすると高速にデータの投入が終わります。 一番簡単な 再帰 クエリ では 再帰 クエリを実行していきましょう。 再帰 クエリの詳細なリファレンスはBigQueryの RECURSIVE キーワード を参照してください。 with recursive org_rec as ( select id ,parent, department, title, name, "" as manager # 1 . from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, a.name as manager # 2 . from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select department, title, name, manager from org_rec where id < 15 order by id; このクエリでは、部署名、役職名、社員名に加えて、当該社員のマネージャーが誰なのかを検索します。 再帰 クエリではカラム数を合わせる必要があり、親レコードがない場合には空文字を選択しています。 再帰 クエリにおける上位レコードから社員名を選択しています。これによってマネージャーが誰なのか分かるのです。 この SQL の実行結果を見てみましょう。 部長である ア さんと 一 さんのmanagerとして いろは さんが選択されていますね。 副部長や、課長の皆さんについても、対応するマネージャーが選択されています。 再帰 クエリに配列を導入する 次は 再帰 クエリから得られる 木構造 において、親に向かう経路を分析してみましょう。 それには、 再帰 しながら配列を組み立てていく SQL を発行します。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path # 1. from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path # 2. from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select department, title, name, array_length(path) as level, # 3. array_to_string(path, ", ") as route # 4. from org_rec where id < 15 order by id; 配列を社員名で初期化します。 上位階層から来た配列に現在の階層における社員名を追加します。 array_length関数を使って配列の長さを得ることで階層における位置を選択しています。 各社員から階層をたどる経路は、array_to_string関数を使って配列を結合すると得られます。 この SQL の実行結果を見てみましょう。 例えば、8行目の キ さんについて選択されたレコードを見てください。 組織図で キ さんから いろは 事業部長までの経路を確認するとこのようになっています。 いろは 事業部長から見て キ さんは ア 部長の部下である ウ 副部長の部下ですね。 つまり、levelカラムが示すように4層目にいて、その経路は「いろは→ア→ウ→キ」となるわけです。 再帰 クエリに導入した配列のオフセット位置を使う 再帰 クエリで 木構造 をたどるには配列を使うと説明しましたが、配列なのでオフセット位置を直接指定できます。 例えば、以下のようなクエリが考えられます。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select path[offset(0)] as bigboss, # 1. count(path[offset(0)]) as members # 2. from org_rec group by bigboss # 3. 各社員の列にある0番目の要素には必ず事業部長が入っています。 レコードとして事業部長が出現する回数を数えています。 事業部長でグルーピングしています。 この SQL の実行結果を見てみましょう。 各事業部長に連なる社員が事業部長本人を含めて何名なのか選択されていますね。 再帰 クエリと条件分岐を組み合わせる 再帰 クエリで作った配列のオフセット位置を使ってマネージャーを選択してみましょう。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select title, name, path[offset(array_length(path)-2)] as boss # 1. from org_rec order by id; オフセット位置として配列の長さから2を引いた値を使っています。 これを実行すると以下のようなエラーになって期待した通りには動作しません。 Array index -1 is out of bounds (underflow) 事業部長はマネージャーがいないので配列の長さが1です。それによってこのようなエラーになる訳です。 ここでは、case句を使って問題に対処してみましょう。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select department, title, name, (case when array_length(path) < 2 then null # 1. else path[offset(array_length(path)-2)] # 2. end) as manager from org_rec where id < 15 order by id; 配列の長さが2を下回るものについてはnullを選択しています。 配列の長さが2を下回らないものについては、長さから2を引いた値を指定することでマネージャーを選択しています。 この SQL の実行結果を見てみましょう。 最初に説明したクエリと同じ結果が得られていますね。 再帰 クエリと副問い合わせを組み合わせる 次は、 再帰 クエリで作った配列を使って各社員にとっての事業部長を選択してみましょう。 with recursive org_rec as ( select id ,parent, title, name, array[id] as path # 1. from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.title, b.name, array_concat(a.path, [b.id]) as path # 2. from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select title, name, (select name from `taichi_test.organization` where id = path[offset(0)] # 3. ) as bigboss from org_rec where id < 15 order by id; 配列をidで初期化します。 上位階層から来た配列に現在の階層におけるidを追加します。 各レコードに含まれる配列の0番目は必ず事業部長のidが入っています。つまり、特に条件を指定せずにオフセット位置を指定できます。 この SQL の実行結果を見てみましょう。 bigboss列に いろは 事業部長が入っていますね。事業部長だけは自己参照しており奇妙な状態になっていますが、これに対する対処は皆さん、ご自分でやってみてください。 説明のためにSELECT句の中で副問い合わせをしましたが、あまり褒められたものではありません。こういう時は本来INNER JOINを使って情報を付加しましょう。 with recursive org_rec as ( select id ,parent, title, name, array[id] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.title, b.name, array_concat(a.path, [b.id]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select a.title, a.name, b.name as bigboss from org_rec as a inner join `taichi_test.organization` as b on a.path[offset(0)] = b.id where a.id < 15 order by a.id; 再帰 クエリに導入した配列をソースに副問い合わせする 最後は、もう少し複雑なクエリを実行してみましょう。 所で、最初のCREATE TABLE文の説明に奇妙な部分があったのはお気づきでしょうか? departmentカラムに入るデータが、事業部長を表すレコードの時だけ事業部名が入っています。 本来的には単一のカラムに複数の意味合いを持たせることは望ましくないのですが、ついうっかりデータを圧縮するためにやってしまいました。 私の手抜きによるやらかしに、データの洗い替えをせずに SQL だけで対処してみましょう。 with recursive org_rec as ( select id ,parent, department, title, name, array[id] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.id]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select (select department from `taichi_test.organization` inner join ( select level from unnest(path) as level with offset where offset = 0 # 1. ) as levels on id = levels.level) as unit, department, title, name from org_rec order by id; BigQueryではunnest関数を使うと配列をテーブルであるかのように扱えます。また、併せてwith offset句を使うとクエリの中で配列のオフセット位置を参照できます。 この SQL の実行結果を見てみましょう。 各レコードに事業部名が選択できていますね。 まとめ 今回エントリでは、BigQueryにおける 再帰 クエリの使い方について詳しく説明しました。 再帰 クエリを使う上で配列と組み合わせることはある種のイディオムなのですが、あまり知られていません。 これを知っているだけで、データ分析の幅は確実に広がりますので是非使いこなしてください。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募をお待ちしています。 社内SE(DX推進エンジニア) 執筆: @sato.taichi 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
みなさんこんにちは、 電通国際情報サービス (ISID)コーポレート本部 システム推進部の佐藤太一です。 先日、TECHPLAYでDataflowに関連するお話をしました。 その日の模様が公開されていますので、ご興味のある方は是非ご覧ください。 techplay.jp さて、このエントリではBigQueryにおいて利用できる 再帰 クエリ(WITH RECURSIVE句とUNION ALL)について紹介します。 再帰 クエリを使うと、表形式しか扱えない RDB において 木構造 のデータを扱えます。 木構造 のデータとは、例えば会社における組織図のようなものを想像してください。 ちなみに、 Oracle なら 再帰 クエリはSTART WITH句とCONNECT BY句で実現していましたよね。 前提となるデータ データを定義するクエリ データを投入するクエリ 一番簡単な再帰クエリ 再帰クエリに配列を導入する 再帰クエリに導入した配列のオフセット位置を使う 再帰クエリと条件分岐を組み合わせる 再帰クエリと副問い合わせを組み合わせる 再帰クエリに導入した配列をソースに副問い合わせする まとめ 前提となるデータ 再帰 クエリの話を始める前に、まずはこのエントリで取り扱うデータ構造について説明しておきましょう。 今回の説明に使うのは以下のような組織図をデータベースに格納したものです。 二つの事業部があり、それぞれに事業部長(いろは、にほへ)がいます。 事業部長が統括する事業部には、それぞれの部署には部長がいます。日本語事業部にはカタカナ部と数字部があり、英語事業部にはアルファベット部とナンバー部があります。 部長の下には副部長がいる部門(カタカナ部、アルファベット部)とそうでない部門(数字部、ナンバー部)があります。 副部長の有無に関わらず、部門内にはいくつかの課があり、それぞれに課長がいます。 データを定義するクエリ それでは、BigQueryに投入可能なCREATE TABLE文をお見せしましょう。 create or replace table taichi_test.organization ( # 1. id int64 not null, # 2. parent int64, # 3. department string, # 4. title string not null, # 5. name string not null # 6. ); taichi_test については、読者の皆さんがご自分で用意したデー タセット 名を指定してください。 idカラムはこのテーブルのプライマリキーとなるものです。なお、BigQueryに一意制約はありません。 各レコードが親となるレコードのidを指定するためのカラムです。親がいない事業部長はnullになります。例えば、部長の親レコードは事業部長です。 部署名を格納するカラムです。事業部長の部署名は事業部名になります。 図の中では箱の一行目に書かれている役職名を格納するカラムです。 図の中では箱の二行目に書かれている社員名を格納するカラムです。 データを投入するクエリ データを投入するためのINSERT文です。 insert into taichi_test.organization values (1, null, "日本語事業部", "事業部長", "いろは") , (2, 1, "カタカナ部", "部長", "ア") , (3, 2, "カタカナ部", "副部長", "イ") , (4, 2, "カタカナ部", "副部長", "ウ") , (5, 3, "カタカナ部", "課長", "エ") , (6, 3, "カタカナ部", "課長", "オ") , (7, 3, "カタカナ部", "課長", "カ") , (8, 4, "カタカナ部", "課長", "キ") , (9, 4, "カタカナ部", "課長", "ク") , (10, 1, "数字部", "部長", "一") , (11, 10, "数字部", "課長", "二") , (12, 10, "数字部", "課長", "三") , (13, 10, "数字部", "課長", "四") , (14, 10, "数字部", "課長", "五") , (15, null, "英語事業部", "事業部長", "にほへ") , (16, 15, "アルファベット部", "部長", "A") , (17, 16, "アルファベット部", "副部長", "B") , (18, 17, "アルファベット部", "課長", "C") , (19, 17, "アルファベット部", "課長", "D") , (20, 17, "アルファベット部", "課長", "E") , (21, 15, "ナンバー部", "部長", "1") , (22, 21, "ナンバー部", "課長", "2") , (23, 21, "ナンバー部", "課長", "3"); BigQueryではINSERT文を複数並べるよりも、このようにBulk INSERTをすると高速にデータの投入が終わります。 一番簡単な 再帰 クエリ では 再帰 クエリを実行していきましょう。 再帰 クエリの詳細なリファレンスはBigQueryの RECURSIVE キーワード を参照してください。 with recursive org_rec as ( select id ,parent, department, title, name, "" as manager # 1 . from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, a.name as manager # 2 . from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select department, title, name, manager from org_rec where id < 15 order by id; このクエリでは、部署名、役職名、社員名に加えて、当該社員のマネージャーが誰なのかを検索します。 再帰 クエリではカラム数を合わせる必要があり、親レコードがない場合には空文字を選択しています。 再帰 クエリにおける上位レコードから社員名を選択しています。これによってマネージャーが誰なのか分かるのです。 この SQL の実行結果を見てみましょう。 部長である ア さんと 一 さんのmanagerとして いろは さんが選択されていますね。 副部長や、課長の皆さんについても、対応するマネージャーが選択されています。 再帰 クエリに配列を導入する 次は 再帰 クエリから得られる 木構造 において、親に向かう経路を分析してみましょう。 それには、 再帰 しながら配列を組み立てていく SQL を発行します。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path # 1. from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path # 2. from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select department, title, name, array_length(path) as level, # 3. array_to_string(path, ", ") as route # 4. from org_rec where id < 15 order by id; 配列を社員名で初期化します。 上位階層から来た配列に現在の階層における社員名を追加します。 array_length関数を使って配列の長さを得ることで階層における位置を選択しています。 各社員から階層をたどる経路は、array_to_string関数を使って配列を結合すると得られます。 この SQL の実行結果を見てみましょう。 例えば、8行目の キ さんについて選択されたレコードを見てください。 組織図で キ さんから いろは 事業部長までの経路を確認するとこのようになっています。 いろは 事業部長から見て キ さんは ア 部長の部下である ウ 副部長の部下ですね。 つまり、levelカラムが示すように4層目にいて、その経路は「いろは→ア→ウ→キ」となるわけです。 再帰 クエリに導入した配列のオフセット位置を使う 再帰 クエリで 木構造 をたどるには配列を使うと説明しましたが、配列なのでオフセット位置を直接指定できます。 例えば、以下のようなクエリが考えられます。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select path[offset(0)] as bigboss, # 1. count(path[offset(0)]) as members # 2. from org_rec group by bigboss # 3. 各社員の列にある0番目の要素には必ず事業部長が入っています。 レコードとして事業部長が出現する回数を数えています。 事業部長でグルーピングしています。 この SQL の実行結果を見てみましょう。 各事業部長に連なる社員が事業部長本人を含めて何名なのか選択されていますね。 再帰 クエリと条件分岐を組み合わせる 再帰 クエリで作った配列のオフセット位置を使ってマネージャーを選択してみましょう。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select title, name, path[offset(array_length(path)-2)] as boss # 1. from org_rec order by id; オフセット位置として配列の長さから2を引いた値を使っています。 これを実行すると以下のようなエラーになって期待した通りには動作しません。 Array index -1 is out of bounds (underflow) 事業部長はマネージャーがいないので配列の長さが1です。それによってこのようなエラーになる訳です。 ここでは、case句を使って問題に対処してみましょう。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select department, title, name, (case when array_length(path) < 2 then null # 1. else path[offset(array_length(path)-2)] # 2. end) as manager from org_rec where id < 15 order by id; 配列の長さが2を下回るものについてはnullを選択しています。 配列の長さが2を下回らないものについては、長さから2を引いた値を指定することでマネージャーを選択しています。 この SQL の実行結果を見てみましょう。 最初に説明したクエリと同じ結果が得られていますね。 再帰 クエリと副問い合わせを組み合わせる 次は、 再帰 クエリで作った配列を使って各社員にとっての事業部長を選択してみましょう。 with recursive org_rec as ( select id ,parent, title, name, array[id] as path # 1. from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.title, b.name, array_concat(a.path, [b.id]) as path # 2. from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select title, name, (select name from `taichi_test.organization` where id = path[offset(0)] # 3. ) as bigboss from org_rec where id < 15 order by id; 配列をidで初期化します。 上位階層から来た配列に現在の階層におけるidを追加します。 各レコードに含まれる配列の0番目は必ず事業部長のidが入っています。つまり、特に条件を指定せずにオフセット位置を指定できます。 この SQL の実行結果を見てみましょう。 bigboss列に いろは 事業部長が入っていますね。事業部長だけは自己参照しており奇妙な状態になっていますが、これに対する対処は皆さん、ご自分でやってみてください。 説明のためにSELECT句の中で副問い合わせをしましたが、あまり褒められたものではありません。こういう時は本来INNER JOINを使って情報を付加しましょう。 with recursive org_rec as ( select id ,parent, title, name, array[id] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.title, b.name, array_concat(a.path, [b.id]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select a.title, a.name, b.name as bigboss from org_rec as a inner join `taichi_test.organization` as b on a.path[offset(0)] = b.id where a.id < 15 order by a.id; 再帰 クエリに導入した配列をソースに副問い合わせする 最後は、もう少し複雑なクエリを実行してみましょう。 所で、最初のCREATE TABLE文の説明に奇妙な部分があったのはお気づきでしょうか? departmentカラムに入るデータが、事業部長を表すレコードの時だけ事業部名が入っています。 本来的には単一のカラムに複数の意味合いを持たせることは望ましくないのですが、ついうっかりデータを圧縮するためにやってしまいました。 私の手抜きによるやらかしに、データの洗い替えをせずに SQL だけで対処してみましょう。 with recursive org_rec as ( select id ,parent, department, title, name, array[id] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.id]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select (select department from `taichi_test.organization` inner join ( select level from unnest(path) as level with offset where offset = 0 # 1. ) as levels on id = levels.level) as unit, department, title, name from org_rec order by id; BigQueryではunnest関数を使うと配列をテーブルであるかのように扱えます。また、併せてwith offset句を使うとクエリの中で配列のオフセット位置を参照できます。 この SQL の実行結果を見てみましょう。 各レコードに事業部名が選択できていますね。 まとめ 今回エントリでは、BigQueryにおける 再帰 クエリの使い方について詳しく説明しました。 再帰 クエリを使う上で配列と組み合わせることはある種のイディオムなのですが、あまり知られていません。 これを知っているだけで、データ分析の幅は確実に広がりますので是非使いこなしてください。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募をお待ちしています。 社内SE(DX推進エンジニア) 執筆: @sato.taichi 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 Route 53 Resolver DNS Firewall を使ってみた話です。 VPC のセキュリティグループや AWS WAF と比較すると話題になることが少ないサービスですが、簡単に導入でき、多層防御の手段の一つとして有効であると感じたため、使ってみた際に考えた現実的な設定方法を書き残したいと思います。 Route 53 Resolver とは Route 53 Resolver DNS Firewall とは フェールクローズとフェールオープン Route 53 Resolver DNS Firewall で防げるもの、防げないもの 準備:Resolver Query Log の出力 準備:サブスクリプションフィルターで Alert / Block ログを通知する 基本編:拒否リスト方式で DNS Firewall を作成 応用編:許可リスト方式で DNS Firewall を作成 AWSサービスなどへの内部的な通信 DNS クエリで得られる CNAME デフォルトAlertがおすすめ まとめ Route 53 Resolver とは 公式ドキュメント: https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/resolver.html Route 53 Resolver は VPC 内から名前解決を担当する DNS リ ゾル バーであり、以前は Amazon Provided DNS と呼ばれていたようです。 VPC のネットワークアドレスに2をプラスした IPアドレス からアクセスできます。( VPC の IPアドレス レンジが 10.0.0.0/16 であれば 10.0.0.2) 例えば、 VPC 内にあるEC2 インスタンス が some-domain.com への名前解決を行う際には、この Route 53 Resolver に問い合わせを行い、結果の IPアドレス によって通信がルートテーブルで制御されます。(内部的には、Route 53 Resolverから他のネームサーバーへ 再帰 的にルックアップが行われます) インターネットからアクセスできるパブリック DNS ホスト名だけではなく、プライベートホストゾーンや VPC 内のプライベート DNS ホスト名の名前解決も担当します。例えばEC2 インスタンス にプライベート DNS 名「ip-10-0-0-11.ap-northeast-1.compute.internal」が付与されている場合、プライベート IPアドレス 「10.0.0.11」への解決もRoute 53 Resolverが担当します。 Route 53 Resolver DNS Firewall とは 公式ドキュメント: https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/resolver-dns-firewall-overview.html DNS Firewall は、Route 53 Resolver による特定 ドメイン への名前解決結果をブロックしたり、オーバーライドしたりできる DNS / UDP レイヤーの ファイアウォール です。以下の構成要素を設定することで使用を開始できます。 ドメインリスト DNS Firewall で評価する対象 ドメイン のリスト 各 ドメイン の先頭に ワイルドカード 「 * 」を使用できる 独自で作成できるほか、 AWSマネージドのドメインリスト が用意されており、既知の マルウェア に関連する ドメイン と ボットネット のC&Cサーバの ドメイン を簡単にブロックできる ルール 名前、説明、 ドメイン リスト、アクション、優先度の組み合わせ アクション は Allow 、 Alert 、 Block の3つが存在する。 Allow は名前解決の許可、 Alert は許可するがログにはアラートとして出力、 Block は名前解決を拒否する Block の場合、さらにレスポンスとしてどのように振る舞うかを定義する。 NODATA 、 NXDOMAIN 、 OVERRIDE の3つから選択する ルールグループ 複数のルールの集合であり、リージョン固定である 優先度の数字が小さいルールから順に評価され、ルールの ドメイン リストに該当する DNS クエリであればそこで評価が終了 いずれのルールにも該当しないクエリは、名前解決が許可される ルールグループとVPCの関連付け ルールグループと VPC を関連付けることで DNS Firewall は有効になる フェールクローズとフェールオープン VPC で DNS Firewall を有効にする場合、 DNS Firewall自身が障害などにより応答しない時のこと も考えておくと良いでしょう。デフォルトではフェールクローズと呼ばれ、 DNS Firewall が応答しない時は名前解決が失敗するようになっています。フェールオープンに変更することでセキュリティよりも可用性が優先され、 DNS Firewall が応答しない時は名前解決が成功するようにできます。 Route 53 Resolver DNS Firewall で防げるもの、防げないもの DNS Firewall は DNS レイヤーの ファイアウォール なので、特定 ドメイン への名前解決をブロックし応答を返さないことで、結果的に対象サーバへの通信を防ぎます。例えば インスタンス が侵入されたり マルウェア に感染した際、悪意のあるサーバとの DNS 名を介した通信をブロックすることで被害の拡大防止に役立ちます。 一方で ドメイン 名ではなく、直接 IPアドレス によるアクセスはRoute 53 Resolverに名前解決を要求しないため、 DNS Firewall によりブロックされません。そのため DNS Firewall による防御は攻撃への根本対策ではなくあくまでも軽減策と考え、常に他の防御手段と組み合わせることを考えるようにしましょう。 準備:Resolver Query Log の出力 ここからは、実際にどのように DNS Firewall を使っていくのが良いのかを考えてみます。 まずは準備として、 Route 53 Resolverのクエリログ を出力するように設定します。これにより、ルールのアクションが Alert や Block の時にどんなクエリが問題となったのかを見つけられるようになります。(ログはクエリが許可された場合も出力されます) CDKで作成する場合は次のようになります。ログの出力先としてCloudWatch Logsロググループ、S3 バケット 、 Kinesis Data Firehoseから選べますが、この例ではCloudWatch Logsロググループとしました。BlockやAlertログの通知を サブスクリプション フィルターで簡単に実装できるためです(後述)。 import * as logs from "aws-cdk-lib/aws-logs" ; import * as route53resolver from "aws-cdk-lib/aws-route53resolver" ; ... // CloudWatch Logsロググループを作成 const queryLogGroup = new logs.LogGroup ( this , "MyResolverQueryLogGroup" , { logGroupName: "my-resolver-query-log-group" , retention: logs.RetentionDays.ONE_MONTH , } ); // Route 53 Resolverログの設定 const queryLogConfig = new route53resolver.CfnResolverQueryLoggingConfig ( this , "MyResolverQueryLoggingConfig" , { name: "cloudwatch logs" , destinationArn: queryLogGroup.logGroupArn , } ); // Route 53 ResolverログをVPCに関連付ける new route53resolver.CfnResolverQueryLoggingConfigAssociation ( this , "MyResolverQueryLoggingConfigAssociation" , { resolverQueryLogConfigId: queryLogConfig.attrId , resourceId: vpc.vpcId , } ); これにより、設定した VPC 内で Route 53 Resolver へ DNS クエリが発生する毎にログに記録されるようになります。下の図では、ログ出力のための logs.ap-northeast-1.amazonaws.com. への名前解決が記録されています。 クエリが Alert 対象となっている場合は、次のように firewall_rule_action 、 firewall_rule_group_id 、 firewall_domain_list_id の3つのフィールドがログエントリーに追加されます。 ( Block の場合は firewall_rule_action が BLOCK になります) 準備: サブスクリプション フィルターで Alert / Block ログを通知する DNS クエリが ALERT や BLOCK 対象となった場合、ログからそれを検知して通知する仕組みを作っておくと、 DNS Firewall の設定の不足を見つけたり、攻撃に素早く反応できるようになるのでおすすめです。CloudWatch Logsから特定のログエントリーを簡単にフィルタリングする方法には、 メトリクスフィルター や サブスクリプションフィルター がありますが、ログエントリー本文(どの DNS クエリが ALERT / BLOCK されたのか)を通知に含めたいため、 サブスクリプション フィルターで実装することにします。 CDKでの実装サンプルは以下のとおりです。 import * as kms from "aws-cdk-lib/aws-kms" ; import * as lambdaNodejs from "aws-cdk-lib/aws-lambda-nodejs" ; import * as destinations from "aws-cdk-lib/aws-logs-destinations" ; import * as sns from "aws-cdk-lib/aws-sns" ; import * as subscription from "aws-cdk-lib/aws-sns-subscriptions" ; ... // SNSトピックの作成。(任意)KMSのデフォルトキーで暗号化 const defaultKey = kms.Key.fromLookup ( this , "DefaultSNSKey" , { aliasName: "alias/aws/sns" } ); const topic = new sns.Topic ( this , "MyDnsFirewallNotificationTopic" , { masterKey: defaultKey } ); // 通知送信先のEメールアドレスをサブスクライブ const sub = new subscription.EmailSubscription ( "my-name@my-domain.com" ); topic.addSubscription ( sub ); // サブスクリプションフィルターから受け取ったデータをSNSトピックに送信するLambda関数 const firewallLogFunction = new lambdaNodejs.NodejsFunction ( this , "MyDnsFirewallLogNotificationFunction" , { entry: "functions/dns-firewall-log.ts" , runtime: Runtime.NODEJS_16_X , environment: { TOPIC_ARN: topic.topicArn , } , logRetention: logs.RetentionDays.ONE_MONTH , description: "Subscribe to cloudwatch logs and publish to SNS topic" , } ); topic.grantPublish ( firewallLogFunction ); // CloudWatch Logsサブスクリプションフィルター // firewall_rule_action フィールドが ALERT もしくは BLOCK の場合に // Lambda関数に送信する queryLogGroup.addSubscriptionFilter ( "MyDnsFirewallLogFilter" , { destination: new destinations.LambdaDestination ( firewallLogFunction ), filterPattern: logs.FilterPattern.literal ( '{ $.firewall_rule_action = "ALERT" || $.firewall_rule_action = "BLOCK" }' ), } ); functions/dns-firewall-log.ts に以下のようにLambda関数を作成します。 import * as zlib from "zlib" ; import { SNSClient , PublishCommand } from "@aws-sdk/client-sns" ; import { Handler , CloudWatchLogsEvent , CloudWatchLogsDecodedData } from "aws-lambda" ; const client = new SNSClient ( { region: process .env.AWS_REGION } ); export const handler: Handler = async ( input: CloudWatchLogsEvent ) => { // ペイロードからログエントリーを取り出す // https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/SubscriptionFilters.html#LambdaFunctionExample const payload = Buffer . from( input.awslogs.data , "base64" ); const result = await new Promise < string >(( res , rej ) => { zlib.gunzip ( payload , ( e , result ) => { return e ? rej ( e ) : res ( result.toString ( "ascii" )); } ); } ); const parsedResult = JSON .parse ( result ) as CloudWatchLogsDecodedData ; const message = parsedResult.logEvents.map (( event ) => event.message ) .join ( "\n\n" ); console .log ( "Event Data:" , message ); // SNSトピックに送信 await client.send ( new PublishCommand ( { TopicArn: process .env.TOPIC_ARN , Subject: "DNS Firewall Log Notification" , Message: message , } ) ); } ; 基本編:拒否リスト方式で DNS Firewall を作成 いよいよ DNS Firewall 自体の設定をしていきます。一番簡単なのは、用意されている AWSマネージドのドメインリスト を利用することです。これらの ドメイン リストの名前解決を拒否するだけで、一定の防御効果があります。 AWSManagedDomainsBotnetCommandandControl と AWSManagedDomainsMalwareDomainList の2つの ドメイン リストが用意されているので、これらを BLOCK し、それ以外の ドメイン は全て許可する拒否リスト方式が簡単に実装できます。 コンソールでの設定方法は 公式ドキュメント を参考にすれば良いので、CDKでの実装例を掲載します。なお、 AWS マネージドの ドメイン リストIDはリージョン毎に決まっており、コンソールから確認したものを利用しました。以下のサンプルは東京リージョンのIDを使用しています。 // ルールグループ作成 const dnsFirewallRuleGroup = new route53resolver.CfnFirewallRuleGroup ( this , "MyDnsFirewallRuleGroup" , { name: "My DNS Firewall rule group" , firewallRules: [ // AWS管理のドメインリストをBlock { action: "BLOCK" , priority: 1 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-1a63d8549cca46e6" , } , { action: "BLOCK" , priority: 2 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-dc19e97bef3c454a" , } , ] , } ); // ルールグループをVPCに関連付け // priorityは1000以上を指定する new route53resolver.CfnFirewallRuleGroupAssociation ( this , "MyDnsFirewallRuleGroupAssociation" , { name: "My DNS Firewall rule group association" , priority: 1000 , firewallRuleGroupId: dnsFirewallRuleGroup.attrId , vpcId: vpc.vpcId , } ); 応用編:許可リスト方式で DNS Firewall を作成 より厳しく、名前解決する ドメイン を明示的に指定する許可リスト方式にすることもできます。しかし許可する ドメイン の一覧に漏れがあると、アプリケーションの通信に思わぬ不具合が発生する可能性があるため、慎重に設定しなければなりません。 すなわち VPC 内からの全ての正当な名前解決を明示的に許可する必要があり、これには以下のような通信が含まれます。 インターネット上の公開 ドメイン への通信 利用しているプライベートホストゾーンの ドメイン への通信 AWS サービスなどへの内部的な通信 DNS クエリで得られる CNAME 上の2つはアプリケーションから明示的に通信先を指定する場合が多いので比較的把握しやすいと思いますが、下の2つがなかなか厄介です。 AWS サービスなどへの内部的な通信 EC2 インスタンス を立ち上げただけで、次のように様々な ドメイン への名前解決要求をしていることがログからわかりました。通信先と用途を全て把握するのは難しそうです。 135.7.0.10.in-addr.arpa. does-not-exist.example.com. __cloud_init_expected_not_found__. instance-data. __cloud_init_expected_not_found__.ap-northeast-1.compute.internal. example.invalid. s3.dualstack.ap-northeast-1.amazonaws.com. amazonlinux-2-repos-ap-northeast-1.s3.dualstack.ap-northeast-1.amazonaws.com. 1.amazon.pool.ntp.org. 0.amazon.pool.ntp.org. 2.amazon.pool.ntp.org. DNS クエリで得られる CNAME 例えば tech.isid.co.jp. への dig コマンドの結果は次の通りであり、 hatenablog.com. の別名となっていることがわかります。 % dig tech.isid.co.jp ;; ANSWER SECTION: tech.isid.co.jp. 86400 IN CNAME hatenablog.com. hatenablog.com. 60 IN A 35.75.255.9 hatenablog.com. 60 IN A 54.199.90.60 これを名前解決する際には、まずは tech.isid.co.jp. への DNS クエリが発生し、その後 hatenablog.com. へのクエリが発生します。両方に対して DNS Firewall のルールが評価されるため、通信を許可するには全ての CNAME に対する名前解決を許可する必要があります。特に自組織で管理している ドメイン でない場合は突然CNAMEが変わる可能性もあります。 デフォルトAlertがおすすめ 以上により、名前解決を許可したい ドメイン を完全に把握するのは難しいケースが多いと思いますので、許可リスト方式においてはデフォルトの挙動を Block ではなく Alert にするのがおすすめです。 Alert ログを通知するように設定しているため、通知を受けたら必要な通信かどうかを都度判断し、許可する ドメイン リストに追加していく運用が良さそうです。 とはいえ AWS マネージドの ドメイン リストは明示的にブロックしたいので、これらを組み合わせたルールグループのCDK実装例は次のようになります。 // 許可するドメインのリストを定義 const allowedDomainList = new route53resolver.CfnFirewallDomainList ( this , "MyDnsFirewallAllowedDomains" , { name: "My Dns Firewall allowed list" , domains: [ "*.compute.internal." , "*.amazonaws.com." , "*.ec2.internal." , "*.compute-1.internal." , "my-domain.com." ] , } ); // デフォルトAlert用の全ドメイン const allDomains = new route53resolver.CfnFirewallDomainList ( this , "DnsFirewallAllDomains" , { name: "All domains" , domains: [ "*" ] , } ); // ルールグループ作成 const dnsFirewallRuleGroup = new route53resolver.CfnFirewallRuleGroup ( this , "MyDnsFirewallRuleGroup" , { name: "My DNS Firewall rule group" , firewallRules: [ // AWS管理のドメインリストは先にBlock { action: "BLOCK" , priority: 1 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-1a63d8549cca46e6" , } , { action: "BLOCK" , priority: 2 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-dc19e97bef3c454a" , } , // 許可するドメインリスト { action: "ALLOW" , priority: 10 , firewallDomainListId: allowedDomainList.attrId , } , // それ以外の全ドメインはAlertとする { action: "ALERT" , priority: 1000 , firewallDomainListId: allDomains.attrId , } , ] , } ); // ルールグループをVPCに関連付け // priorityは1000以上を指定する new route53resolver.CfnFirewallRuleGroupAssociation ( this , "MyDnsFirewallRuleGroupAssociation" , { name: "My DNS Firewall rule group association" , priority: 1000 , firewallRuleGroupId: dnsFirewallRuleGroup.attrId , vpcId: vpc.vpcId , } ); まとめ まとめると、Route 53 Resolver DNS Firewall の現実的な設定は次のようになります。 DNS Firewall はデフォルトでフェールクローズなので、セキュリティよりも可用性を優先したい場合はフェールオープンに変更しよう Route 53 Resolver Query Logを出力しよう Query Log の Alert と Block を通知しよう AWS マネージドの ドメイン リストを利用して悪意のあるサーバとの通信を簡単にブロックしよう より厳しく設定したい場合、許可リスト方式でデフォルト Alert のルールグループを作成しよう なお、 DNS Firewall とQuery Logの保存先には料金が発生しますので、設定時にはご留意ください。 https://aws.amazon.com/jp/route53/pricing/ お読みいただきありがとうございました。 (2022/12/21追記) いつの間にか AWS 管理の ドメイン リストに AWSManagedDomainsAggregateThreatList が追加されていました。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 セキュリティエンジニア(セキュリティ設計) 執筆: @kou.kinyo 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 Route 53 Resolver DNS Firewall を使ってみた話です。 VPC のセキュリティグループや AWS WAF と比較すると話題になることが少ないサービスですが、簡単に導入でき、多層防御の手段の一つとして有効であると感じたため、使ってみた際に考えた現実的な設定方法を書き残したいと思います。 Route 53 Resolver とは Route 53 Resolver DNS Firewall とは フェールクローズとフェールオープン Route 53 Resolver DNS Firewall で防げるもの、防げないもの 準備:Resolver Query Log の出力 準備:サブスクリプションフィルターで Alert / Block ログを通知する 基本編:拒否リスト方式で DNS Firewall を作成 応用編:許可リスト方式で DNS Firewall を作成 AWSサービスなどへの内部的な通信 DNS クエリで得られる CNAME デフォルトAlertがおすすめ まとめ Route 53 Resolver とは 公式ドキュメント: https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/resolver.html Route 53 Resolver は VPC 内から名前解決を担当する DNS リ ゾル バーであり、以前は Amazon Provided DNS と呼ばれていたようです。 VPC のネットワークアドレスに2をプラスした IPアドレス からアクセスできます。( VPC の IPアドレス レンジが 10.0.0.0/16 であれば 10.0.0.2) 例えば、 VPC 内にあるEC2 インスタンス が some-domain.com への名前解決を行う際には、この Route 53 Resolver に問い合わせを行い、結果の IPアドレス によって通信がルートテーブルで制御されます。(内部的には、Route 53 Resolverから他のネームサーバーへ 再帰 的にルックアップが行われます) インターネットからアクセスできるパブリック DNS ホスト名だけではなく、プライベートホストゾーンや VPC 内のプライベート DNS ホスト名の名前解決も担当します。例えばEC2 インスタンス にプライベート DNS 名「ip-10-0-0-11.ap-northeast-1.compute.internal」が付与されている場合、プライベート IPアドレス 「10.0.0.11」への解決もRoute 53 Resolverが担当します。 Route 53 Resolver DNS Firewall とは 公式ドキュメント: https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/resolver-dns-firewall-overview.html DNS Firewall は、Route 53 Resolver による特定 ドメイン への名前解決結果をブロックしたり、オーバーライドしたりできる DNS / UDP レイヤーの ファイアウォール です。以下の構成要素を設定することで使用を開始できます。 ドメインリスト DNS Firewall で評価する対象 ドメイン のリスト 各 ドメイン の先頭に ワイルドカード 「 * 」を使用できる 独自で作成できるほか、 AWSマネージドのドメインリスト が用意されており、既知の マルウェア に関連する ドメイン と ボットネット のC&Cサーバの ドメイン を簡単にブロックできる ルール 名前、説明、 ドメイン リスト、アクション、優先度の組み合わせ アクション は Allow 、 Alert 、 Block の3つが存在する。 Allow は名前解決の許可、 Alert は許可するがログにはアラートとして出力、 Block は名前解決を拒否する Block の場合、さらにレスポンスとしてどのように振る舞うかを定義する。 NODATA 、 NXDOMAIN 、 OVERRIDE の3つから選択する ルールグループ 複数のルールの集合であり、リージョン固定である 優先度の数字が小さいルールから順に評価され、ルールの ドメイン リストに該当する DNS クエリであればそこで評価が終了 いずれのルールにも該当しないクエリは、名前解決が許可される ルールグループとVPCの関連付け ルールグループと VPC を関連付けることで DNS Firewall は有効になる フェールクローズとフェールオープン VPC で DNS Firewall を有効にする場合、 DNS Firewall自身が障害などにより応答しない時のこと も考えておくと良いでしょう。デフォルトではフェールクローズと呼ばれ、 DNS Firewall が応答しない時は名前解決が失敗するようになっています。フェールオープンに変更することでセキュリティよりも可用性が優先され、 DNS Firewall が応答しない時は名前解決が成功するようにできます。 Route 53 Resolver DNS Firewall で防げるもの、防げないもの DNS Firewall は DNS レイヤーの ファイアウォール なので、特定 ドメイン への名前解決をブロックし応答を返さないことで、結果的に対象サーバへの通信を防ぎます。例えば インスタンス が侵入されたり マルウェア に感染した際、悪意のあるサーバとの DNS 名を介した通信をブロックすることで被害の拡大防止に役立ちます。 一方で ドメイン 名ではなく、直接 IPアドレス によるアクセスはRoute 53 Resolverに名前解決を要求しないため、 DNS Firewall によりブロックされません。そのため DNS Firewall による防御は攻撃への根本対策ではなくあくまでも軽減策と考え、常に他の防御手段と組み合わせることを考えるようにしましょう。 準備:Resolver Query Log の出力 ここからは、実際にどのように DNS Firewall を使っていくのが良いのかを考えてみます。 まずは準備として、 Route 53 Resolverのクエリログ を出力するように設定します。これにより、ルールのアクションが Alert や Block の時にどんなクエリが問題となったのかを見つけられるようになります。(ログはクエリが許可された場合も出力されます) CDKで作成する場合は次のようになります。ログの出力先としてCloudWatch Logsロググループ、S3 バケット 、 Kinesis Data Firehoseから選べますが、この例ではCloudWatch Logsロググループとしました。BlockやAlertログの通知を サブスクリプション フィルターで簡単に実装できるためです(後述)。 import * as logs from "aws-cdk-lib/aws-logs" ; import * as route53resolver from "aws-cdk-lib/aws-route53resolver" ; ... // CloudWatch Logsロググループを作成 const queryLogGroup = new logs.LogGroup ( this , "MyResolverQueryLogGroup" , { logGroupName: "my-resolver-query-log-group" , retention: logs.RetentionDays.ONE_MONTH , } ); // Route 53 Resolverログの設定 const queryLogConfig = new route53resolver.CfnResolverQueryLoggingConfig ( this , "MyResolverQueryLoggingConfig" , { name: "cloudwatch logs" , destinationArn: queryLogGroup.logGroupArn , } ); // Route 53 ResolverログをVPCに関連付ける new route53resolver.CfnResolverQueryLoggingConfigAssociation ( this , "MyResolverQueryLoggingConfigAssociation" , { resolverQueryLogConfigId: queryLogConfig.attrId , resourceId: vpc.vpcId , } ); これにより、設定した VPC 内で Route 53 Resolver へ DNS クエリが発生する毎にログに記録されるようになります。下の図では、ログ出力のための logs.ap-northeast-1.amazonaws.com. への名前解決が記録されています。 クエリが Alert 対象となっている場合は、次のように firewall_rule_action 、 firewall_rule_group_id 、 firewall_domain_list_id の3つのフィールドがログエントリーに追加されます。 ( Block の場合は firewall_rule_action が BLOCK になります) 準備: サブスクリプション フィルターで Alert / Block ログを通知する DNS クエリが ALERT や BLOCK 対象となった場合、ログからそれを検知して通知する仕組みを作っておくと、 DNS Firewall の設定の不足を見つけたり、攻撃に素早く反応できるようになるのでおすすめです。CloudWatch Logsから特定のログエントリーを簡単にフィルタリングする方法には、 メトリクスフィルター や サブスクリプションフィルター がありますが、ログエントリー本文(どの DNS クエリが ALERT / BLOCK されたのか)を通知に含めたいため、 サブスクリプション フィルターで実装することにします。 CDKでの実装サンプルは以下のとおりです。 import * as kms from "aws-cdk-lib/aws-kms" ; import * as lambdaNodejs from "aws-cdk-lib/aws-lambda-nodejs" ; import * as destinations from "aws-cdk-lib/aws-logs-destinations" ; import * as sns from "aws-cdk-lib/aws-sns" ; import * as subscription from "aws-cdk-lib/aws-sns-subscriptions" ; ... // SNSトピックの作成。(任意)KMSのデフォルトキーで暗号化 const defaultKey = kms.Key.fromLookup ( this , "DefaultSNSKey" , { aliasName: "alias/aws/sns" } ); const topic = new sns.Topic ( this , "MyDnsFirewallNotificationTopic" , { masterKey: defaultKey } ); // 通知送信先のEメールアドレスをサブスクライブ const sub = new subscription.EmailSubscription ( "my-name@my-domain.com" ); topic.addSubscription ( sub ); // サブスクリプションフィルターから受け取ったデータをSNSトピックに送信するLambda関数 const firewallLogFunction = new lambdaNodejs.NodejsFunction ( this , "MyDnsFirewallLogNotificationFunction" , { entry: "functions/dns-firewall-log.ts" , runtime: Runtime.NODEJS_16_X , environment: { TOPIC_ARN: topic.topicArn , } , logRetention: logs.RetentionDays.ONE_MONTH , description: "Subscribe to cloudwatch logs and publish to SNS topic" , } ); topic.grantPublish ( firewallLogFunction ); // CloudWatch Logsサブスクリプションフィルター // firewall_rule_action フィールドが ALERT もしくは BLOCK の場合に // Lambda関数に送信する queryLogGroup.addSubscriptionFilter ( "MyDnsFirewallLogFilter" , { destination: new destinations.LambdaDestination ( firewallLogFunction ), filterPattern: logs.FilterPattern.literal ( '{ $.firewall_rule_action = "ALERT" || $.firewall_rule_action = "BLOCK" }' ), } ); functions/dns-firewall-log.ts に以下のようにLambda関数を作成します。 import * as zlib from "zlib" ; import { SNSClient , PublishCommand } from "@aws-sdk/client-sns" ; import { Handler , CloudWatchLogsEvent , CloudWatchLogsDecodedData } from "aws-lambda" ; const client = new SNSClient ( { region: process .env.AWS_REGION } ); export const handler: Handler = async ( input: CloudWatchLogsEvent ) => { // ペイロードからログエントリーを取り出す // https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/SubscriptionFilters.html#LambdaFunctionExample const payload = Buffer . from( input.awslogs.data , "base64" ); const result = await new Promise < string >(( res , rej ) => { zlib.gunzip ( payload , ( e , result ) => { return e ? rej ( e ) : res ( result.toString ( "ascii" )); } ); } ); const parsedResult = JSON .parse ( result ) as CloudWatchLogsDecodedData ; const message = parsedResult.logEvents.map (( event ) => event.message ) .join ( "\n\n" ); console .log ( "Event Data:" , message ); // SNSトピックに送信 await client.send ( new PublishCommand ( { TopicArn: process .env.TOPIC_ARN , Subject: "DNS Firewall Log Notification" , Message: message , } ) ); } ; 基本編:拒否リスト方式で DNS Firewall を作成 いよいよ DNS Firewall 自体の設定をしていきます。一番簡単なのは、用意されている AWSマネージドのドメインリスト を利用することです。これらの ドメイン リストの名前解決を拒否するだけで、一定の防御効果があります。 AWSManagedDomainsBotnetCommandandControl と AWSManagedDomainsMalwareDomainList の2つの ドメイン リストが用意されているので、これらを BLOCK し、それ以外の ドメイン は全て許可する拒否リスト方式が簡単に実装できます。 コンソールでの設定方法は 公式ドキュメント を参考にすれば良いので、CDKでの実装例を掲載します。なお、 AWS マネージドの ドメイン リストIDはリージョン毎に決まっており、コンソールから確認したものを利用しました。以下のサンプルは東京リージョンのIDを使用しています。 // ルールグループ作成 const dnsFirewallRuleGroup = new route53resolver.CfnFirewallRuleGroup ( this , "MyDnsFirewallRuleGroup" , { name: "My DNS Firewall rule group" , firewallRules: [ // AWS管理のドメインリストをBlock { action: "BLOCK" , priority: 1 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-1a63d8549cca46e6" , } , { action: "BLOCK" , priority: 2 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-dc19e97bef3c454a" , } , ] , } ); // ルールグループをVPCに関連付け // priorityは1000以上を指定する new route53resolver.CfnFirewallRuleGroupAssociation ( this , "MyDnsFirewallRuleGroupAssociation" , { name: "My DNS Firewall rule group association" , priority: 1000 , firewallRuleGroupId: dnsFirewallRuleGroup.attrId , vpcId: vpc.vpcId , } ); 応用編:許可リスト方式で DNS Firewall を作成 より厳しく、名前解決する ドメイン を明示的に指定する許可リスト方式にすることもできます。しかし許可する ドメイン の一覧に漏れがあると、アプリケーションの通信に思わぬ不具合が発生する可能性があるため、慎重に設定しなければなりません。 すなわち VPC 内からの全ての正当な名前解決を明示的に許可する必要があり、これには以下のような通信が含まれます。 インターネット上の公開 ドメイン への通信 利用しているプライベートホストゾーンの ドメイン への通信 AWS サービスなどへの内部的な通信 DNS クエリで得られる CNAME 上の2つはアプリケーションから明示的に通信先を指定する場合が多いので比較的把握しやすいと思いますが、下の2つがなかなか厄介です。 (2024/5/8追記) DNS Firewall において ドメインのリダイレクトがサポート され、設定を行うことでCNAMEを明示的に許可する必要がなくなりました。 AWS サービスなどへの内部的な通信 EC2 インスタンス を立ち上げただけで、次のように様々な ドメイン への名前解決要求をしていることがログからわかりました。通信先と用途を全て把握するのは難しそうです。 135.7.0.10.in-addr.arpa. does-not-exist.example.com. __cloud_init_expected_not_found__. instance-data. __cloud_init_expected_not_found__.ap-northeast-1.compute.internal. example.invalid. s3.dualstack.ap-northeast-1.amazonaws.com. amazonlinux-2-repos-ap-northeast-1.s3.dualstack.ap-northeast-1.amazonaws.com. 1.amazon.pool.ntp.org. 0.amazon.pool.ntp.org. 2.amazon.pool.ntp.org. DNS クエリで得られる CNAME 例えば tech.isid.co.jp. への dig コマンドの結果は次の通りであり、 hatenablog.com. の別名となっていることがわかります。 % dig tech.isid.co.jp ;; ANSWER SECTION: tech.isid.co.jp. 86400 IN CNAME hatenablog.com. hatenablog.com. 60 IN A 35.75.255.9 hatenablog.com. 60 IN A 54.199.90.60 これを名前解決する際には、まずは tech.isid.co.jp. への DNS クエリが発生し、その後 hatenablog.com. へのクエリが発生します。両方に対して DNS Firewall のルールが評価されるため、通信を許可するには全ての CNAME に対する名前解決を許可する必要があります。特に自組織で管理している ドメイン でない場合は突然CNAMEが変わる可能性もあります。 (2024/5/8追記) DNS Firewall において ドメインのリダイレクトがサポート され、設定を行うことでCNAMEを明示的に許可する必要がなくなりました。 デフォルトAlertがおすすめ 以上により、名前解決を許可したい ドメイン を完全に把握するのは難しいケースが多いと思いますので、許可リスト方式においてはデフォルトの挙動を Block ではなく Alert にするのがおすすめです。 Alert ログを通知するように設定しているため、通知を受けたら必要な通信かどうかを都度判断し、許可する ドメイン リストに追加していく運用が良さそうです。 とはいえ AWS マネージドの ドメイン リストは明示的にブロックしたいので、これらを組み合わせたルールグループのCDK実装例は次のようになります。 // 許可するドメインのリストを定義 const allowedDomainList = new route53resolver.CfnFirewallDomainList ( this , "MyDnsFirewallAllowedDomains" , { name: "My Dns Firewall allowed list" , domains: [ "*.compute.internal." , "*.amazonaws.com." , "*.ec2.internal." , "*.compute-1.internal." , "my-domain.com." ] , } ); // デフォルトAlert用の全ドメイン const allDomains = new route53resolver.CfnFirewallDomainList ( this , "DnsFirewallAllDomains" , { name: "All domains" , domains: [ "*" ] , } ); // ルールグループ作成 const dnsFirewallRuleGroup = new route53resolver.CfnFirewallRuleGroup ( this , "MyDnsFirewallRuleGroup" , { name: "My DNS Firewall rule group" , firewallRules: [ // AWS管理のドメインリストは先にBlock { action: "BLOCK" , priority: 1 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-1a63d8549cca46e6" , } , { action: "BLOCK" , priority: 2 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-dc19e97bef3c454a" , } , // 許可するドメインリスト { action: "ALLOW" , priority: 10 , firewallDomainListId: allowedDomainList.attrId , } , // それ以外の全ドメインはAlertとする { action: "ALERT" , priority: 1000 , firewallDomainListId: allDomains.attrId , } , ] , } ); // ルールグループをVPCに関連付け // priorityは1000以上を指定する new route53resolver.CfnFirewallRuleGroupAssociation ( this , "MyDnsFirewallRuleGroupAssociation" , { name: "My DNS Firewall rule group association" , priority: 1000 , firewallRuleGroupId: dnsFirewallRuleGroup.attrId , vpcId: vpc.vpcId , } ); まとめ まとめると、Route 53 Resolver DNS Firewall の現実的な設定は次のようになります。 DNS Firewall はデフォルトでフェールクローズなので、セキュリティよりも可用性を優先したい場合はフェールオープンに変更しよう Route 53 Resolver Query Logを出力しよう Query Log の Alert と Block を通知しよう AWS マネージドの ドメイン リストを利用して悪意のあるサーバとの通信を簡単にブロックしよう より厳しく設定したい場合、許可リスト方式でデフォルト Alert のルールグループを作成しよう なお、 DNS Firewall とQuery Logの保存先には料金が発生しますので、設定時にはご留意ください。 https://aws.amazon.com/jp/route53/pricing/ お読みいただきありがとうございました。 (2022/12/21追記) いつの間にか AWS 管理の ドメイン リストに AWSManagedDomainsAggregateThreatList が追加されていました。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 セキュリティエンジニア(セキュリティ設計) 執筆: @kou.kinyo 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Web3開発入門シリーズ with Algorand(アルゴランド)、今回のテーマは、 アセットの作成 です。 今回の記事のGoogle Colab用のノートブックはこちらになります。ブラウザだけでブロックチェーンにアクセスするコードを、実際に動かしながら試せるので、おすすめです。 Ethereum などの ブロックチェーン では、貨幣的な価値を持つ トーク ンである Fungible Token (FT)や代替不可能な トーク ンである Non Fungible Token (NFT)が存在します。 Algorand では、 FT と NFT を統合したものをアセットと呼んでいます。 もう一つ理解しておきたいことは、 Algorand のアセットは、 Ethereum のようにスマート コントラ クトではないということです。 アセットに対する基本的な操作は、 Algoland の機能として実装されているので、個別のアセットで実装する必要がないのです。 もちろん、アセット固有のロジックがある場合は、スマート コントラ クトで個別に実装することもできます。 Web3開発入門 with Algorandの全てのコンテンツ アカウント TestNetでALGOをゲット 支払い アセットの作成 py-algorand-sdkのインストール アカウントの作成 アカウントにAlgorand Dispenserを使って入金する AlgodClientオブジェクトの作成 AssetConfigTxnオブジェクトの作成 署名 トランザクションをAlgorandに送る トランザクションが確定するのを待つ アセットの確認 仲間募集 これから、アセットを作成します。 py-algorand- sdk のインストール py-algorand-sdk をインストールします。 下記のコードを実行してください。 !pip3 install py-algorand-sdk アカウントの作成 アセットを作成するアカウントを作成しましょう。 下記のコードを実行してください。 from algosdk import account private_key, address = account.generate_account() print("Account Address:", address) 出力結果の例です。 Account Address: H7BCYZGX4J6QKXEG5TYMMVQVSTRHWL4WWQQVBGH725HHIYL2R72RDSMNN4 アカウントにAlgorand Dispenserを使って入金する アセットを作成するためには、 トランザクション 手数料を払う必要があります。 TestNet では、 Algorand Dispenser のサイトで、 ALGO を無料でゲットできます。 Account Address: の後ろの部分のアドレスをコピーしてください。 Algorand Dispenser のサイトにアクセスし、 I'm not a roboot をチェックし、 target address の入力エリアに先ほどコピーしたアドレスをペーストします。 Dispense のボタンをクリックしてください。 Status: Code 200 success のように表示されればOKです。 AlgodClientオブジェクトの作成 Algorand にアクセスするために、 AlgodClient オブジェクトを作成します。 下記のコードを実行してください。 from algosdk.v2client.algod import AlgodClient algod = AlgodClient("", "https://node.testnet.algoexplorerapi.io:443") AlgodClient オブジェクトを作成するときに、 https://node.testnet.algoexplorerapi.io:443 を指定しています。これにより Algoexplorer の TestNet のノードに接続します。 AssetConfigTxnオブジェクトの作成 アセットを作成するためには、 AssetConfigTxn オブジェクトを作成します。 AssetConfigTxn オブジェクトを作成するときのパラメータを説明します。 asset_name は、アセットの名前です。32バイト以内である必要があります。 unit_name は、アセットの単位名です。8バイト以内である必要があります。 total は、どれだけアセットを作成するか指定します。例えば、 1000000 を指定した場合、百万個のアセットを作成することになります。 decimals は小数点以下の桁数です。例えば、3を指定した場合、このアセットの最小の単位は、 0.001 になります。 ややこしいのは、 total が decimals の影響を受けることです。 total が 1000000 で decimals が3の場合、市場に流通するアセットは 1000.000 になります。 decimals が3の場合、アセットの最小単位は、 0.001 なので、それが total で 1000000 個、市場に出回るので、市場で流通するアセットの量は、 0.001 x 1000000 で 1000.000 になるということです。 default_frozen について説明する前に、アセットの凍結(frozen)について説明します。 アカウントAがアセットZを2つ作成し、一つをアカウントBに、もう一つをアカウントCに売ったとします。その後、アカウントBが良くない行動をとっていることが判明したため、アカウントBのアセットZを凍結します。 凍結されたアセットZは、売ることができなくなります。 アセットの凍結はアカウント単位です。アカウントBのアセットZは凍結されているので、売ることはできませんが、アカウントCのアセットZは自由に売ることができます。 default_frozen に True を指定すると、凍結された状態でアセットが作成されます。 manager には、作ったアセットを修正したり、削除したりすることができるアドレスを指定します。 reserve は、ほとんど使われていないので、気にしなくても大丈夫です。 frozen には、作ったアセットを凍結することのできるアドレスを指定します。 clawback には、作ったアセットを自由に移動(Transfer)させることのできるアドレスを指定します。 この clawback は非常に強力で、アカウントAがアカウントCにアセットを売ったあと、アカウントCが持っているアセットを強制的にアカウントAのものにすることもできます。 さらに、強力なことに、凍結されたアセットも自由に動かすことができます。 ただし、 clawback は悪いことをするために用意されているわけではなく、スマート コントラ クトの address を指定することがよく行われます。 特定の条件を満たしたときだけ、スマート コントラ クトがアセットを移動できるようにするためです。 それでは、 AssetConfigTxn オブジェクトを作成しましょう。 下記のコードを実行してください。 from algosdk.future.transaction import AssetConfigTxn sp = algod.suggested_params() txn = AssetConfigTxn( sender=address, sp=sp, asset_name="Test", unit_name="Test", total=1000, decimals=0, default_frozen=False, manager=address, reserve=address, freeze=address, clawback=address, ) 署名 トランザクション は、差出人の秘密キーで署名をする必要があります。 下記のコードを実行してください。 signed_txn = txn.sign(private_key) トランザクション をAlgorandに送る 署名した トランザクション を Algorand に送り出しましょう それを行うのが、 AlgodClientオブジェクト.send_transactions() になります。 下記のコードを実行してください。 tx_id = algod.send_transactions([signed_txn]) print('Transaction ID:', tx_id) トランザクション が確定するのを待つ トランザクション を Algoland に送っても、それで トランザクション が確定されたわけではありません。 トランザクション が確定されるのを待つ必要があります。 トランザクション が確定されるのを待つには、 wait_for_confirmation() を呼び出します。 下記のコードを実行してください。 from algosdk.future.transaction import wait_for_confirmation wait_for_confirmation(algod, tx_id, 10) アセットの確認 Algoexplorer でアセットを確認してみましょう。 アカウントのアドレスをコピーしてください。 TestNetのAlgoexplorer のサイトにアクセスし、一番上の真ん中の検索エリアにコピーして実行してください。 真ん中の方に Transactions と Assets のタブがあるので、 Assets のタブをクリックします。 先ほど作成したアセットの Balance が 1000 になってますね。 なにか感想があれば、 Twitter で@yasuo_algoにメンションしてつぶやいてください。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア 執筆: @higa 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Web3開発入門シリーズ with Algorand(アルゴランド)、今回のテーマは、 アセットの作成 です。 今回の記事のGoogle Colab用のノートブックはこちらになります。ブラウザだけでブロックチェーンにアクセスするコードを、実際に動かしながら試せるので、おすすめです。 Ethereum などの ブロックチェーン では、貨幣的な価値を持つ トーク ンである Fungible Token (FT)や代替不可能な トーク ンである Non Fungible Token (NFT)が存在します。 Algorand では、 FT と NFT を統合したものをアセットと呼んでいます。 もう一つ理解しておきたいことは、 Algorand のアセットは、 Ethereum のようにスマート コントラ クトではないということです。 アセットに対する基本的な操作は、 Algoland の機能として実装されているので、個別のアセットで実装する必要がないのです。 もちろん、アセット固有のロジックがある場合は、スマート コントラ クトで個別に実装することもできます。 Web3開発入門 with Algorandの全てのコンテンツ アカウント TestNetでALGOをゲット 支払い アセットの作成 py-algorand-sdkのインストール アカウントの作成 アカウントにAlgorand Dispenserを使って入金する AlgodClientオブジェクトの作成 AssetConfigTxnオブジェクトの作成 署名 トランザクションをAlgorandに送る トランザクションが確定するのを待つ アセットの確認 仲間募集 これから、アセットを作成します。 py-algorand- sdk のインストール py-algorand-sdk をインストールします。 下記のコードを実行してください。 !pip3 install py-algorand-sdk アカウントの作成 アセットを作成するアカウントを作成しましょう。 下記のコードを実行してください。 from algosdk import account private_key, address = account.generate_account() print("Account Address:", address) 出力結果の例です。 Account Address: H7BCYZGX4J6QKXEG5TYMMVQVSTRHWL4WWQQVBGH725HHIYL2R72RDSMNN4 アカウントにAlgorand Dispenserを使って入金する アセットを作成するためには、 トランザクション 手数料を払う必要があります。 TestNet では、 Algorand Dispenser のサイトで、 ALGO を無料でゲットできます。 Account Address: の後ろの部分のアドレスをコピーしてください。 Algorand Dispenser のサイトにアクセスし、 I'm not a roboot をチェックし、 target address の入力エリアに先ほどコピーしたアドレスをペーストします。 Dispense のボタンをクリックしてください。 Status: Code 200 success のように表示されればOKです。 AlgodClientオブジェクトの作成 Algorand にアクセスするために、 AlgodClient オブジェクトを作成します。 下記のコードを実行してください。 from algosdk.v2client.algod import AlgodClient algod = AlgodClient("", "https://node.testnet.algoexplorerapi.io:443") AlgodClient オブジェクトを作成するときに、 https://node.testnet.algoexplorerapi.io:443 を指定しています。これにより Algoexplorer の TestNet のノードに接続します。 AssetConfigTxnオブジェクトの作成 アセットを作成するためには、 AssetConfigTxn オブジェクトを作成します。 AssetConfigTxn オブジェクトを作成するときのパラメータを説明します。 asset_name は、アセットの名前です。32バイト以内である必要があります。 unit_name は、アセットの単位名です。8バイト以内である必要があります。 total は、どれだけアセットを作成するか指定します。例えば、 1000000 を指定した場合、百万個のアセットを作成することになります。 decimals は小数点以下の桁数です。例えば、3を指定した場合、このアセットの最小の単位は、 0.001 になります。 ややこしいのは、 total が decimals の影響を受けることです。 total が 1000000 で decimals が3の場合、市場に流通するアセットは 1000.000 になります。 decimals が3の場合、アセットの最小単位は、 0.001 なので、それが total で 1000000 個、市場に出回るので、市場で流通するアセットの量は、 0.001 x 1000000 で 1000.000 になるということです。 default_frozen について説明する前に、アセットの凍結(frozen)について説明します。 アカウントAがアセットZを2つ作成し、一つをアカウントBに、もう一つをアカウントCに売ったとします。その後、アカウントBが良くない行動をとっていることが判明したため、アカウントBのアセットZを凍結します。 凍結されたアセットZは、売ることができなくなります。 アセットの凍結はアカウント単位です。アカウントBのアセットZは凍結されているので、売ることはできませんが、アカウントCのアセットZは自由に売ることができます。 default_frozen に True を指定すると、凍結された状態でアセットが作成されます。 manager には、作ったアセットを修正したり、削除したりすることができるアドレスを指定します。 reserve は、ほとんど使われていないので、気にしなくても大丈夫です。 frozen には、作ったアセットを凍結することのできるアドレスを指定します。 clawback には、作ったアセットを自由に移動(Transfer)させることのできるアドレスを指定します。 この clawback は非常に強力で、アカウントAがアカウントCにアセットを売ったあと、アカウントCが持っているアセットを強制的にアカウントAのものにすることもできます。 さらに、強力なことに、凍結されたアセットも自由に動かすことができます。 ただし、 clawback は悪いことをするために用意されているわけではなく、スマート コントラ クトの address を指定することがよく行われます。 特定の条件を満たしたときだけ、スマート コントラ クトがアセットを移動できるようにするためです。 それでは、 AssetConfigTxn オブジェクトを作成しましょう。 下記のコードを実行してください。 from algosdk.future.transaction import AssetConfigTxn sp = algod.suggested_params() txn = AssetConfigTxn( sender=address, sp=sp, asset_name="Test", unit_name="Test", total=1000, decimals=0, default_frozen=False, manager=address, reserve=address, freeze=address, clawback=address, ) 署名 トランザクション は、差出人の秘密キーで署名をする必要があります。 下記のコードを実行してください。 signed_txn = txn.sign(private_key) トランザクション をAlgorandに送る 署名した トランザクション を Algorand に送り出しましょう それを行うのが、 AlgodClientオブジェクト.send_transactions() になります。 下記のコードを実行してください。 tx_id = algod.send_transactions([signed_txn]) print('Transaction ID:', tx_id) トランザクション が確定するのを待つ トランザクション を Algoland に送っても、それで トランザクション が確定されたわけではありません。 トランザクション が確定されるのを待つ必要があります。 トランザクション が確定されるのを待つには、 wait_for_confirmation() を呼び出します。 下記のコードを実行してください。 from algosdk.future.transaction import wait_for_confirmation wait_for_confirmation(algod, tx_id, 10) アセットの確認 Algoexplorer でアセットを確認してみましょう。 アカウントのアドレスをコピーしてください。 TestNetのAlgoexplorer のサイトにアクセスし、一番上の真ん中の検索エリアにコピーして実行してください。 真ん中の方に Transactions と Assets のタブがあるので、 Assets のタブをクリックします。 先ほど作成したアセットの Balance が 1000 になってますね。 なにか感想があれば、 Twitter で@yasuo_algoにメンションしてつぶやいてください。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア 執筆: @higa 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
X イノベーション 本部 オープン イノベーション ラボの飯田です。 ISIDでは、有志で論文輪読会を実施しています。 tech.isid.co.jp 最近、Stable Diffusionなどの画像生成系がかなり盛り上がっていますよね。 AI画像生成によって出力された顔画像に対して、我々はどのような印象評定をしているのでしょうか? 今回は、AI(GAN:StyleGAN2)によって生成された顔画像に対する 印象評定を行った 社会心理学 の論文を紹介したいと思います。 Nightingale, S. J., & Farid, H. (2022). AI-synthesized faces are indistinguishable from real faces and more trustworthy. Proceedings of the National Academy of Sciences, 119(8), e2120481119. 論文サマリ 背景 実験1 実験2 実験3 考察 最後に 論文サマリ AI(StyleGAN2)で生成された顔画像は、 不気味の谷 を乗り越え、本物の顔と見分けがつかない それだけでなく、実物よりも信頼を得る顔を作成できるようになっていた 背景 AI画像生成技術には、詐欺やリベンジポルノで利用されてしまうなどの脅威があると言われています。 どんな画像や動画も偽造できてしまうことにより、 デジタルメディア記録の信憑性が疑われてしまうことも懸念されます。 フェイク画像の検出技術開発も進んでいますが、現状の技術では不十分であり、 コンテンツの消費者自身で本物と偽物を見分けなければなりません。 この研究では、GAN(StyleGAN2)によって生成された顔と本物の顔を区別できるか? また、その顔がどの程度の信頼を得るか?を研究しました。 実験1 128枚の顔画像を本物か生成画像かを判定する課題を315人に実施。 結果は、平均精度は48.2%であり、チャンスレベルの50%と同等でした(下図、上のグラフ:青)。 →つまり、見分けができていないと示唆された 実験2 試行ごとに正解・不正解のフィードバックを受けながら(=見分ける学習をしながら)、 128枚の顔画像を本物か生成画像かを判定する課題を219人に実施。 結果は試行ごとのフィードバックを行ったが、時間の経過とともに精度の向上は見られなかった。 フィードバックを与えることで、精度が向上したが、精度はチャンスレベルを少し上回る程度(下図、上のグラフ:赤)。 →つまり、見分ける練習をしても、本物か生成画像かの弁別は難易度が高い 実験3 生成された顔画像から、その人が信頼できるか?を評価してもらう課題を実施。 128人の顔画像の信頼度を7件法で評価 (1:非常に信頼できない←→7:非常に信頼できる) 結果、生成された顔画像の方が本物に比べて7.7%、有意に信頼度が高い。 本物(下図、下のグラフ:青)の平均:4.48 AI生成(下図、下のグラフ:オレンジ)の平均:4.82 →つまり、本物よりもAI生成画像の方がより信頼できる 考察 AI画像生成は、本物と見分けがつかず、 不気味の谷 を超えたと思われます。 さらに、AI画像生成の方が、より”信頼できる”と感じる人が多い結果となりました。 AI画像生成の信頼性が高かった理由としては、以下のことが考えられます。 ヒトは平均的な顔のほうが、より魅力的・信頼できると言われています。 GANは学習・生成の過程で、教師データの平均的な顔に似ていく傾向があるため、 より魅力的・信頼できると感じられることが多くなったのではないかと思われます。 不気味の谷 を超えて、本物・偽物の判別がつかず、 より信頼されるような顔画像が生成可能になりました。 それは、高品質なフェイク画像を作成できることになったことを意味します。 詐欺、情報操作など、個人・社会に影響を与える可能性があります。 今後、AI開発においての倫理が必要となったり、 生成系メディアでの ガイドライン が必要となってきたり来る可能性があります。 最後に 今回は、GANで生成された顔画像の印象評価の論文について紹介しました。 Stable Diffusionが話題になっているように、今後生成系のクオリティはどんどん上がっていくかと思います。 現状でも、人間は本物か偽物かを区別できず、信頼性を高く感じるという報告がありました。 技術の進歩だけでなく、AIを使う側、作る側、それぞれでAI倫理が大切になってくるかと思います。 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア 論文輪読会をはじめとした各種勉強会に気軽に参加できるような環境で働くことに興味のある皆様、ぜひご応募お待ちしています! 執筆: @iida.michitaka 、レビュー: @higa ( Shodo で執筆されました )
X イノベーション 本部 オープン イノベーション ラボの飯田です。 ISIDでは、有志で論文輪読会を実施しています。 tech.isid.co.jp 最近、Stable Diffusionなどの画像生成系がかなり盛り上がっていますよね。 AI画像生成によって出力された顔画像に対して、我々はどのような印象評定をしているのでしょうか? 今回は、AI(GAN:StyleGAN2)によって生成された顔画像に対する 印象評定を行った 社会心理学 の論文を紹介したいと思います。 Nightingale, S. J., & Farid, H. (2022). AI-synthesized faces are indistinguishable from real faces and more trustworthy. Proceedings of the National Academy of Sciences, 119(8), e2120481119. 論文サマリ 背景 実験1 実験2 実験3 考察 最後に 論文サマリ AI(StyleGAN2)で生成された顔画像は、 不気味の谷 を乗り越え、本物の顔と見分けがつかない それだけでなく、実物よりも信頼を得る顔を作成できるようになっていた 背景 AI画像生成技術には、詐欺やリベンジポルノで利用されてしまうなどの脅威があると言われています。 どんな画像や動画も偽造できてしまうことにより、 デジタルメディア記録の信憑性が疑われてしまうことも懸念されます。 フェイク画像の検出技術開発も進んでいますが、現状の技術では不十分であり、 コンテンツの消費者自身で本物と偽物を見分けなければなりません。 この研究では、GAN(StyleGAN2)によって生成された顔と本物の顔を区別できるか? また、その顔がどの程度の信頼を得るか?を研究しました。 実験1 128枚の顔画像を本物か生成画像かを判定する課題を315人に実施。 結果は、平均精度は48.2%であり、チャンスレベルの50%と同等でした(下図、上のグラフ:青)。 →つまり、見分けができていないと示唆された 実験2 試行ごとに正解・不正解のフィードバックを受けながら(=見分ける学習をしながら)、 128枚の顔画像を本物か生成画像かを判定する課題を219人に実施。 結果は試行ごとのフィードバックを行ったが、時間の経過とともに精度の向上は見られなかった。 フィードバックを与えることで、精度が向上したが、精度はチャンスレベルを少し上回る程度(下図、上のグラフ:赤)。 →つまり、見分ける練習をしても、本物か生成画像かの弁別は難易度が高い 実験3 生成された顔画像から、その人が信頼できるか?を評価してもらう課題を実施。 128人の顔画像の信頼度を7件法で評価 (1:非常に信頼できない←→7:非常に信頼できる) 結果、生成された顔画像の方が本物に比べて7.7%、有意に信頼度が高い。 本物(下図、下のグラフ:青)の平均:4.48 AI生成(下図、下のグラフ:オレンジ)の平均:4.82 →つまり、本物よりもAI生成画像の方がより信頼できる 考察 AI画像生成は、本物と見分けがつかず、 不気味の谷 を超えたと思われます。 さらに、AI画像生成の方が、より”信頼できる”と感じる人が多い結果となりました。 AI画像生成の信頼性が高かった理由としては、以下のことが考えられます。 ヒトは平均的な顔のほうが、より魅力的・信頼できると言われています。 GANは学習・生成の過程で、教師データの平均的な顔に似ていく傾向があるため、 より魅力的・信頼できると感じられることが多くなったのではないかと思われます。 不気味の谷 を超えて、本物・偽物の判別がつかず、 より信頼されるような顔画像が生成可能になりました。 それは、高品質なフェイク画像を作成できることになったことを意味します。 詐欺、情報操作など、個人・社会に影響を与える可能性があります。 今後、AI開発においての倫理が必要となったり、 生成系メディアでの ガイドライン が必要となってきたり来る可能性があります。 最後に 今回は、GANで生成された顔画像の印象評価の論文について紹介しました。 Stable Diffusionが話題になっているように、今後生成系のクオリティはどんどん上がっていくかと思います。 現状でも、人間は本物か偽物かを区別できず、信頼性を高く感じるという報告がありました。 技術の進歩だけでなく、AIを使う側、作る側、それぞれでAI倫理が大切になってくるかと思います。 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア 論文輪読会をはじめとした各種勉強会に気軽に参加できるような環境で働くことに興味のある皆様、ぜひご応募お待ちしています! 執筆: @iida.michitaka 、レビュー: @higa ( Shodo で執筆されました )
ISIDテックブログ編集部です。今回は弊社社員が登壇するイベントを紹介します。詳細およびお申込みはリンク先サイトを参照してください。 techplay.jp ご興味あれば、ぜひ登録おねがいします。 基本情報 タイトル:Azureではじめるマルチテナント SaaS のつくり方 -変化に強い アーキテクチャ 設計・実装のコツ- 日時:2022/11/22(火) 19:00〜20:30 形態:オンライン開催 概要 変化の時代である今、DXに求められるスピーディな対応を実現するためには SaaS の活用が必要不可欠です。 では、 SaaS 開発において、変化に柔軟に対応できる アーキテクチャ の設計・実装のポイントは何でしょう? 今回はISIDが今年4月に製品版をリリースした従業員コラボレーション SaaS 『ENGAGE TAG』を題材に 「マルチテナント SaaS の開発」についてお話ししながら、そのポイントを紐解きます。 『ENGAGE TAG』は社内の新規事業創出プログラムから生まれました。 現在 PMF (Product Market Fit)を目指し、顧客のフィードバックを元に日々改善を続けています。 PMF に至るまでには事業のピボットや顧客のフィードバックへの迅速な対応など、様々な変化に直面します。 【例①】プロダクトのKPIを「チャットする」から「プロフィールを見る」ことに変更し、新機能を追加開発する 【例②】プロフィールにタグだけでなく、画像やリンクも掲載したい 【例③】新卒や中途入社した人、最近タグをつけた人など、全社で発信してほしい など、様々な変化に対して柔軟に対応すべく、 Microsoft AzureのPaaSとAzure DevOpsを最大限に活用した開発を行っています。 本イベントでは、 「第1部:Azure PaaSの概要と最新トレンド」と「第2部:マルチテナント SaaS 開発の ケーススタディ 」 の2部構成でお届けします。 第1部では、 Microsoft 佐藤氏より、 「AzureのPaaS機能の活用方法」「最新トレンド」「ベストプ ラク ティス」について マルチテナント SaaS 開発という文脈を踏まえつつお話します。 第2部では『ENGAGE TAG』の開発責任者のISID通山氏より、 クラウド で設計/実装する際に重要なポイントとなる 「信頼性」「セキュリティ」「コストの最適化」「オペレーショナル エクセレンス 」「パフォーマンス効率」の視点から「ENGAGE TAG」でどのようにして変化に強いマルチテナント SaaS を設計/実装したのかをお話します。 執筆: Ishizawa Kento (@kent) 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
ISIDテックブログ編集部です。今回は弊社社員が登壇するイベントを紹介します。詳細およびお申込みはリンク先サイトを参照してください。 techplay.jp ご興味あれば、ぜひ登録おねがいします。 基本情報 タイトル:Azureではじめるマルチテナント SaaS のつくり方 -変化に強い アーキテクチャ 設計・実装のコツ- 日時:2022/11/22(火) 19:00〜20:30 形態:オンライン開催 概要 変化の時代である今、DXに求められるスピーディな対応を実現するためには SaaS の活用が必要不可欠です。 では、 SaaS 開発において、変化に柔軟に対応できる アーキテクチャ の設計・実装のポイントは何でしょう? 今回はISIDが今年4月に製品版をリリースした従業員コラボレーション SaaS 『ENGAGE TAG』を題材に 「マルチテナント SaaS の開発」についてお話ししながら、そのポイントを紐解きます。 『ENGAGE TAG』は社内の新規事業創出プログラムから生まれました。 現在 PMF (Product Market Fit)を目指し、顧客のフィードバックを元に日々改善を続けています。 PMF に至るまでには事業のピボットや顧客のフィードバックへの迅速な対応など、様々な変化に直面します。 【例①】プロダクトのKPIを「チャットする」から「プロフィールを見る」ことに変更し、新機能を追加開発する 【例②】プロフィールにタグだけでなく、画像やリンクも掲載したい 【例③】新卒や中途入社した人、最近タグをつけた人など、全社で発信してほしい など、様々な変化に対して柔軟に対応すべく、 Microsoft AzureのPaaSとAzure DevOpsを最大限に活用した開発を行っています。 本イベントでは、 「第1部:Azure PaaSの概要と最新トレンド」と「第2部:マルチテナント SaaS 開発の ケーススタディ 」 の2部構成でお届けします。 第1部では、 Microsoft 佐藤氏より、 「AzureのPaaS機能の活用方法」「最新トレンド」「ベストプ ラク ティス」について マルチテナント SaaS 開発という文脈を踏まえつつお話します。 第2部では『ENGAGE TAG』の開発責任者のISID通山氏より、 クラウド で設計/実装する際に重要なポイントとなる 「信頼性」「セキュリティ」「コストの最適化」「オペレーショナル エクセレンス 」「パフォーマンス効率」の視点から「ENGAGE TAG」でどのようにして変化に強いマルチテナント SaaS を設計/実装したのかをお話します。 執筆: Ishizawa Kento (@kent) 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Web3開発入門シリーズ with Algorand(アルゴランド)、今回のテーマは、 支払い です。 今回の記事のGoogle Colab用のノートブックはこちらになります。ブラウザだけでブロックチェーンに対するコードを、実際に動かしながら試せるので、おすすめです。 Web3開発入門 with Algorandの全てのコンテンツ アカウント TestNetでALGOをゲット 支払い アセットの作成 py-algorand-sdkのインストール アカウントAの作成 アカウントAにAlgorand Dispenserを使って入金する アカウントBの作成 ALGO残高の確認 AlgodClientオブジェクトの作成 PaymentTxnオブジェクトの作成 署名 トランザクションをAlgorandに送る トランザクションが確定するのを待つ ALGO残高の再確認 まとめ 仲間募集 今回は、アカウントAからアカウントBに 1 ALGO を支払う支払い トランザクション を実行してみましょう。 py-algorand- sdk のインストール py-algorand-sdk をインストールします。 下記のコードを実行してください。 !pip3 install py-algorand-sdk アカウントAの作成 アカウントAを作成しましょう。 下記のコードを実行してください。 from algosdk import account a_private_key, a_address = account.generate_account() print("AccountA Address:", a_address) 出力結果の例です AccountA Address: QLK6LJT2CWOV5XIILG5XRSM6QLOMLYG77YLE3F5EFIQU37BY3WKL2HPYEM アカウントAにAlgorand Dispenserを使って入金する アカウントAがアカウントBに送金するためには、 ALGO の残高がなければいけません。 TestNet では、 Algorand Dispenser のサイトで、 ALGO を無料でゲットできます。 AccountA Address: の後ろの部分のアドレスをコピーしてください。 Algorand Dispenser のサイトにアクセスし、 I'm not a roboot をチェックし、 target address の入力エリアに先ほどコピーしたアドレスをペーストします。 Dispense のボタンをクリックしてください。 Status: Code 200 success のように表示されればOKです。 アカウントBの作成 アカウントBを作成しましょう。 下記のコードを実行してください。 from algosdk import account b_private_key, b_address = account.generate_account() print("AccountB Address:", b_address) 出力結果の例です。 AccountB Address: JO7RPRV36UVWWEAUA5JMXTPX62REHXFY5EYGNX3RBCSCITOO76PZCI2Y6E ALGO残高の確認 アカウントAとアカウントBの残高を確認してみましょう。 TestNetのAlgoexplorer のサイトにアクセスし、一番上の真ん中の検索エリアに、アカウントAのアドレスをコピー&ペーストして実行してください。 Balances(残高)に 10 と表示されていますね。 アカウントBの残高も同様に確認してみましょう。一度、ブラウザでページをリロードし、アカウントBのアドレスをコピー&ペーストして実行してください。ページをリロードしないとたまに変になることがあります。 Balances(残高)は - つまり 0 ですね。 AlgodClientオブジェクトの作成 Algorand にアクセスするために、 AlgodClient オブジェクトを作成します。 下記のコードを実行してください。 from algosdk.v2client.algod import AlgodClient algod = AlgodClient("", "https://node.testnet.algoexplorerapi.io:443") AlgodClient オブジェクトを作成するときに、 https://node.testnet.algoexplorerapi.io:443 を指定していますね。これにより Algoexplorer の TestNet のノードに接続します。 PaymentTxnオブジェクトの作成 あるアカウントが別のアカウントへALGOを支払うためには、 PaymentTxn オブジェクトを作成します。 下記のコードを実行してください。 from algosdk.future.transaction import PaymentTxn sp = algod.suggested_params() txn = PaymentTxn(sender=a_address, sp=sp, receiver=b_address, amt=1000000) この トランザクション の差出人(sender)はアカウントA(a_address)です。 この トランザクション の受取人(receiver)はアカウントB(b_address)です。 金額(amt)は、1000000 micro ALGO = 1.0 ALGOになります。 spは気にしなくても大丈夫です。 署名 トランザクション は、確かに差出人はアカウントAであるということと、 トランザクション が作成されてから変更されていないことを検証するために、そのアカウントの秘密キーで署名をする必要があります。 下記のコードを実行してください。 signed_txn = txn.sign(a_private_key) アカウントAの秘密キー(a_private_key)で署名( sign )していますね。 トランザクション をAlgorandに送る 署名した トランザクション を Algorand に送り出しましょう それを行うのが、 AlgodClientオブジェクト.send_transactions() になります。 下記のコードを実行してください。 tx_id = algod.send_transactions([signed_txn]) トランザクション を Algorand に送ると トランザクション ID(tx_id)がふられ、 Algoexplorer などで検索可能になります。正確には、次で説明する トランザクション の確定後、検索可能になります。 トランザクション が確定するのを待つ トランザクション を Algoland に送っても、それで トランザクション が確定されたわけではありません。 トランザクション がブロックに取り込まれるのを待つ必要があります。 Algorand の場合、 トランザクションがブロックに取り込まれた == トランザクションが確定された という意味になります。 Algorand の場合、3.8秒ほどで トランザクション が確定します。 トランザクション が確定されるのを待つには、 wait_for_confirmation() を呼び出します。 下記のコードを実行してください。 from algosdk.future.transaction import wait_for_confirmation wait_for_confirmation(algod, tx_id, 10) 3つ目の引数の10は、どれくらいのラウンドを待つのかの指定になります。ここで指定されたラウンドで トランザクション が確定しない場合、 wait_for_confirmation() の呼び出しから処理が返ってきます。 Algorand に何らかの問題があったときに、無限に待ち続けないためのパラメータです。 ラウンドとは、一つのブロックが作成される時間帯です。前のブロックが作成されてから、今回のブロックが作成されるまでの時間が一つのラウンドになります。 ALGO残高の再確認 アカウントAとアカウントBの残高をもう一度確認してみましょう。 TestNetのAlgoexplorer のサイトにアクセスし、一番上の真ん中の検索エリアに、アカウントAのアドレスをコピー&ペーストして実行してください。 Balances(残高)に 8.999 と表示されていますね。 10 の状態から、 1 支払ったので、 9 になりそうですが、 0.001 足りません。 0.001 は トランザクション 手数料として支払った分です。 アカウントBの残高も同様に確認してみましょう。一度、ブラウザでページをリロードし、アカウントBのアドレスをコピー&ペーストして実行してください。ページをリロードしないとたまに変になることがあります。 Balances(残高)は予想通りに 1 と表示されていますね。 まとめ 今回は、 支払い を学びました。 トランザクション を作成した後の、「 トランザクション に署名する」、「署名した トランザクション を Algorand に送り出す」の処理はどの トランザクション でも同じです。 トランザクション 処理の山は今回でこえましたよ。 なにか感想があれば、 Twitter で @yasuo_algo にメンションしてつぶやいてください。 次回は、 アセットの作成 です。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア 執筆: @higa 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Web3開発入門シリーズ with Algorand(アルゴランド)、今回のテーマは、 支払い です。 今回の記事のGoogle Colab用のノートブックはこちらになります。ブラウザだけでブロックチェーンに対するコードを、実際に動かしながら試せるので、おすすめです。 Web3開発入門 with Algorandの全てのコンテンツ アカウント TestNetでALGOをゲット 支払い アセットの作成 py-algorand-sdkのインストール アカウントAの作成 アカウントAにAlgorand Dispenserを使って入金する アカウントBの作成 ALGO残高の確認 AlgodClientオブジェクトの作成 PaymentTxnオブジェクトの作成 署名 トランザクションをAlgorandに送る トランザクションが確定するのを待つ ALGO残高の再確認 まとめ 仲間募集 今回は、アカウントAからアカウントBに 1 ALGO を支払う支払い トランザクション を実行してみましょう。 py-algorand- sdk のインストール py-algorand-sdk をインストールします。 下記のコードを実行してください。 !pip3 install py-algorand-sdk アカウントAの作成 アカウントAを作成しましょう。 下記のコードを実行してください。 from algosdk import account a_private_key, a_address = account.generate_account() print("AccountA Address:", a_address) 出力結果の例です AccountA Address: QLK6LJT2CWOV5XIILG5XRSM6QLOMLYG77YLE3F5EFIQU37BY3WKL2HPYEM アカウントAにAlgorand Dispenserを使って入金する アカウントAがアカウントBに送金するためには、 ALGO の残高がなければいけません。 TestNet では、 Algorand Dispenser のサイトで、 ALGO を無料でゲットできます。 AccountA Address: の後ろの部分のアドレスをコピーしてください。 Algorand Dispenser のサイトにアクセスし、 I'm not a roboot をチェックし、 target address の入力エリアに先ほどコピーしたアドレスをペーストします。 Dispense のボタンをクリックしてください。 Status: Code 200 success のように表示されればOKです。 アカウントBの作成 アカウントBを作成しましょう。 下記のコードを実行してください。 from algosdk import account b_private_key, b_address = account.generate_account() print("AccountB Address:", b_address) 出力結果の例です。 AccountB Address: JO7RPRV36UVWWEAUA5JMXTPX62REHXFY5EYGNX3RBCSCITOO76PZCI2Y6E ALGO残高の確認 アカウントAとアカウントBの残高を確認してみましょう。 TestNetのAlgoexplorer のサイトにアクセスし、一番上の真ん中の検索エリアに、アカウントAのアドレスをコピー&ペーストして実行してください。 Balances(残高)に 10 と表示されていますね。 アカウントBの残高も同様に確認してみましょう。一度、ブラウザでページをリロードし、アカウントBのアドレスをコピー&ペーストして実行してください。ページをリロードしないとたまに変になることがあります。 Balances(残高)は - つまり 0 ですね。 AlgodClientオブジェクトの作成 Algorand にアクセスするために、 AlgodClient オブジェクトを作成します。 下記のコードを実行してください。 from algosdk.v2client.algod import AlgodClient algod = AlgodClient("", "https://node.testnet.algoexplorerapi.io:443") AlgodClient オブジェクトを作成するときに、 https://node.testnet.algoexplorerapi.io:443 を指定していますね。これにより Algoexplorer の TestNet のノードに接続します。 PaymentTxnオブジェクトの作成 あるアカウントが別のアカウントへALGOを支払うためには、 PaymentTxn オブジェクトを作成します。 下記のコードを実行してください。 from algosdk.future.transaction import PaymentTxn sp = algod.suggested_params() txn = PaymentTxn(sender=a_address, sp=sp, receiver=b_address, amt=1000000) この トランザクション の差出人(sender)はアカウントA(a_address)です。 この トランザクション の受取人(receiver)はアカウントB(b_address)です。 金額(amt)は、1000000 micro ALGO = 1.0 ALGOになります。 spは気にしなくても大丈夫です。 署名 トランザクション は、確かに差出人はアカウントAであるということと、 トランザクション が作成されてから変更されていないことを検証するために、そのアカウントの秘密キーで署名をする必要があります。 下記のコードを実行してください。 signed_txn = txn.sign(a_private_key) アカウントAの秘密キー(a_private_key)で署名( sign )していますね。 トランザクション をAlgorandに送る 署名した トランザクション を Algorand に送り出しましょう それを行うのが、 AlgodClientオブジェクト.send_transactions() になります。 下記のコードを実行してください。 tx_id = algod.send_transactions([signed_txn]) トランザクション を Algorand に送ると トランザクション ID(tx_id)がふられ、 Algoexplorer などで検索可能になります。正確には、次で説明する トランザクション の確定後、検索可能になります。 トランザクション が確定するのを待つ トランザクション を Algoland に送っても、それで トランザクション が確定されたわけではありません。 トランザクション がブロックに取り込まれるのを待つ必要があります。 Algorand の場合、 トランザクションがブロックに取り込まれた == トランザクションが確定された という意味になります。 Algorand の場合、3.8秒ほどで トランザクション が確定します。 トランザクション が確定されるのを待つには、 wait_for_confirmation() を呼び出します。 下記のコードを実行してください。 from algosdk.future.transaction import wait_for_confirmation wait_for_confirmation(algod, tx_id, 10) 3つ目の引数の10は、どれくらいのラウンドを待つのかの指定になります。ここで指定されたラウンドで トランザクション が確定しない場合、 wait_for_confirmation() の呼び出しから処理が返ってきます。 Algorand に何らかの問題があったときに、無限に待ち続けないためのパラメータです。 ラウンドとは、一つのブロックが作成される時間帯です。前のブロックが作成されてから、今回のブロックが作成されるまでの時間が一つのラウンドになります。 ALGO残高の再確認 アカウントAとアカウントBの残高をもう一度確認してみましょう。 TestNetのAlgoexplorer のサイトにアクセスし、一番上の真ん中の検索エリアに、アカウントAのアドレスをコピー&ペーストして実行してください。 Balances(残高)に 8.999 と表示されていますね。 10 の状態から、 1 支払ったので、 9 になりそうですが、 0.001 足りません。 0.001 は トランザクション 手数料として支払った分です。 アカウントBの残高も同様に確認してみましょう。一度、ブラウザでページをリロードし、アカウントBのアドレスをコピー&ペーストして実行してください。ページをリロードしないとたまに変になることがあります。 Balances(残高)は予想通りに 1 と表示されていますね。 まとめ 今回は、 支払い を学びました。 トランザクション を作成した後の、「 トランザクション に署名する」、「署名した トランザクション を Algorand に送り出す」の処理はどの トランザクション でも同じです。 トランザクション 処理の山は今回でこえましたよ。 なにか感想があれば、 Twitter で @yasuo_algo にメンションしてつぶやいてください。 次回は、 アセットの作成 です。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア 執筆: @higa 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
電通国際情報サービス 、X イノベーション 本部デジタルエンゲージメントセンターの堀越です。 Salesforce の開発でも GitHub を使って CI/CD をできるようにしたいのですが、この記事ではそのやり方などについて書きます。 最近の Salesforce の開発では、 Salesforce DX というコンセプトのもと、 CLI を使って Salesforce 上のローコード設定を XML で取得したり反映したりできます。 XML として取り扱えれば、 GitHub などで構成管理したり、環境間の差異がないかの比較をしたりということも可能です。 当然ながらローコードだけでなくApexやVisualforce、Lightning コンポーネント といったプログラムコードも CLI で取り扱うことができます。 ここまでは 個々の開発者で CLI とGitを利用すれば実現できるところです。 が、せっかく GitHub で構成管理するのであれば、Pull Request した際に自動テストやデプロイ可否のチェック(いわゆるCI/CD)もやりたくなりますね。 これを踏まえて、ここでは Salesforce DX の CLI を日ごろから使っている & プロジェクト構成が GitHub 上の リポジトリ に登録されていることを前提に、CI/CDを組み込んでみたいと思います。 CI/CD の実行には、 GitHub 標準装備の GitHub Actions を使いたいと思います。 やりたいことのイメージは次のようなものです。 統合用のブランチにSandboxを対応させて、Pull Request されたときにそのSandboxにデプロイできるかを自動チェックできるようにしたいと思います。 CLIによる Sandbox への接続 秘密鍵とそれとペアになった証明書の用意 Salesforce 側の準備 プロファイルの許可IPアドレス設定 プロファイルに与える権限 GitHub側の準備 セキュリティの考慮で secrets を使う CLIでの接続設定のイメージ CLIでの認証に向けた設定作業 キーペアと証明書の準備 Sandbox側の設定 接続用のプロファイルの準備 接続用のユーザーの準備 接続アプリケーションの準備 GitHubリポジトリ側の設定 Secretsの設定 GitHub Actions ワークフローの作成 GitHub Actions ワークフローの接続検証 CLI認証後の操作 接続時のトラブルシューティングのヒント Salesforce側のID検証履歴を確認 余談 CLI による Sandbox への接続 Salesforce DX の CLI では、まず Sandbox に接続(認証)し、それから取得やデプロイなどの操作を行う必要があります。 CLI による認証にはいくつか方式が提供されていますが、CI/CDで利用可能なのは「JWTベアラー トーク ンフロー」です。 JWTベアラー トーク ンフローでの接続には、以下の準備が必要です。 秘密鍵 とそれとペアになった証明書( 自己署名証明書 でOK)の用意 Salesforce 側の準備 GitHub 側の準備 秘密鍵 とそれとペアになった証明書の用意 opensslで 秘密鍵 と 自己署名証明書 を準備します。 自己署名証明書 は、 Salesforce 側の「接続アプリケーション」設定に登録されることになります。 また、 秘密鍵 は GitHub 側で利用されることになります。 Salesforce 側の準備 Salesforce 側がJWTベアラー トーク ンフローでアクセスを受けるためには、「接続アプリケーション」の設定が必要となります。 「接続アプリケーション」では大きく以下の要素の設定が必要です。 JWTベアラー トーク ンフローを有効化する諸設定 JWT を検証するための証明書のアップロード 接続アプリケーションで利用するプロファイル(または権限セット)とユーザー プロファイルまたは権限セットでSandbox内の操作可否を制御する認可の設定をします。 今回は、CI/CDでの利用のため、デプロイやテストを実行するための権限を与えておく必要があります。 また、そのプロファイルまたは権限セットに属する Salesforce ユーザーで接続することになりますので、そのためのユーザーも用意しておく必要があります。 今回は、セキュリティを考慮し、デプロイやテストを実行するための最小の権限を持った専用ユーザーと専用プロファイルを用意することにしました。 また、このユーザーはブラウザでのログインを行わないものとし、 API 呼出専用のユーザーとして用意しておくことにします。 これらの一連の設定方法は、 Salesforce DXのドキュメントの以下が参照できます。 https://developer.salesforce.com/docs/atlas.ja-jp.sfdx_dev.meta/sfdx_dev/sfdx_dev_auth_connected_app.htm プロファイルの許可 IPアドレス 設定 プロファイルには、ログインが許可できるアクセス元 IPアドレス 範囲の指定が可能で、 GitHub からのアクセスを許可するように設定するようにします。 ただ、つぶさに設定するのは数が多くて現実的ではないので広めのレンジにならざるを得ないものと思います。 その分、クレデンシャル情報はしっかりと管理しておく必要があるでしょう。 なお、 GitHub のIPレンジは以下から取得できます。 https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/about-githubs-ip-addresses プロファイルに与える権限 プロファイルで与える権限(システム権限)は、以下になります。 「アプリケーションのカスタマイズ」(Customize Application) 「 メタデータ API 関数を使用した メタデータ の変更」(Modify Metadata Through Metadata API Function) 「 API の有効化」( API Enabled) 「 API 限定ユーザ」( API Only User) 「すべてのデータの編集」(Modify All Data) 必要な権限についての情報は以下を参照しています。 https://developer.salesforce.com/docs/atlas.ja-jp.238.0.api_meta.meta/api_meta/meta_modify_metadata_perm.htm なお、「すべてのデータの編集」は、全てのオブジェクトに対する CRUD 操作を行える権限となります。 デプロイに伴ってApexのテストコードの実行が必要であり、それに伴ってオブジェクトの CRUD 操作が必要となることから、この権限を与えておくのが無難でしょう。 GitHub 側の準備 JWTベアラー トーク ンフローで認証する際には、 CLI コマンドで以下を指定する必要があります。 JWT を署名するための 秘密鍵 のファイル Sandboxへの接続に利用するユーザー名 接続アプリケーションのコンシューマ鍵(OAuthのクライアントID) Salesforce 環境のログインURL 認証のコマンドは以下のようなイメージとなります。 sfdx auth:jwt:grant --instanceurl=https://test.salesforce.com/ --jwtkeyfile 【秘密鍵ファイル名】 --username 【接続ユーザー名】 --clientid 【接続アプリケーションのコンシューマ鍵】 ここで、 https://test.salesforce.com/ はログインURLであり、今回はSandboxを利用するためにこちらのURLとなっています。 もし本番環境に接続したい場合は Salesforce の「私の ドメイン 」で指定したURLを適切に指定する必要があると思われます。 「接続アプリケーションのコンシューマ鍵」は、準備した接続アプリケーション設定から取得します。 また、「接続ユーザー名」も同様に Salesforce 側の準備で作成したものを利用します。 セキュリティの考慮で secrets を使う コンシューマ鍵や 秘密鍵 ファイルなどはクレデンシャル情報なので リポジトリ 内に含めるわけにはいきません。 そこで、 GitHub の secrets に登録しておくものとします。 また 秘密鍵 ファイルはX.509形式ですが、改行が含まれるため、そのまま secrets で扱うことはできません。 そこで、 BASE64 エンコード して secrets に登録しておき、それを実行時に取り出して BASE64 デコード、一時ファイルに書き出して鍵ファイルとするものとします。 今回は secrets を利用しましたが、接続先の Sandbox が増えるようであれば、 Environments 機能を利用するのが良いでしょう。 CLI での接続設定のイメージ この接続設定をまとめると、イメージは次のようになります。 CLI での認証に向けた設定作業 以下、このイメージに沿って設定作業を進めていきます。 キーペアと証明書の準備 最初に、opensslで 秘密鍵 と 自己署名証明書 を準備します。 この手順は比較的よくある手順であり、詳しくは割愛します。 Salesforce DXの公式ドキュメントに沿った手順としては以下が参照可能です。 https://developer.salesforce.com/docs/atlas.ja-jp.238.0.sfdx_dev.meta/sfdx_dev/sfdx_dev_auth_key_and_cert.htm また、 GitHub の secrets で 秘密鍵 の内容を保持できるようにするため、 秘密鍵 を BASE64 したものを控えておきます。 以下はそのコマンドイメージです。 UNIX 系の場合: cat server.key | base64 Windows の場合: openssl base64 -A -in server.key -out sever_base64.key Sandbox側の設定 Salesforce の Sandbox に管理者でログインし、一連の設定を実施します。 プロファイルの作成と権限付与、ユーザーの作成はよくある手順ですので、詳細は割愛します。 接続用のプロファイルの準備 「設定」画面にて、「クイック検索」 ボックスに「プロファイル」と入力し、「プロファイル」を選択、設定画面に遷移します。 新しいプロファイルを作り、上述の「プロファイルの設定」で示した権限を与えます。 このプロファイルは、のちに接続アプリケーションに紐づけることになりますので、名前を控えておきます。 (今回は、「sfdx_connect」というプロファイル名を設定することとしました) 仮に他のプロファイルをコピーして作った場合には、アクセス元 IPアドレス の許可レンジが自社 IPアドレス のままなどになっている場合があるでしょう。 今回のアクセス元は GitHub Actions のサーバになるので、その指定の変更を忘れないようにしましょう。 接続用のユーザーの準備 同様に、「クイック検索」 ボックスに「ユーザ」と入力しユーザー設定画面に遷移します。 ユーザーを新規作成し、作ったプロファイルにユーザーを割り当てます。 作ったユーザーは GitHub Actionsでの接続に利用されますので、ユーザー名を控えておきます。 接続アプリケーションの準備 Salesforce DX の認証を実施するため、接続アプリケーションの設定を行います。 この一連の設定方法は、 Salesforce DXのドキュメントの以下を参照します。 https://developer.salesforce.com/docs/atlas.ja-jp.sfdx_dev.meta/sfdx_dev/sfdx_dev_auth_connected_app.htm 基本的にはこの設定手順に沿いますが、「JWTベアラー トーク ンフロー」なので「(JWTのみ)」と書いてある項目も設定が必要です。 「設定」画面にて、「クイック検索」 ボックスに「アプリケーションマネージャ」と入力し、「アプリケーションマネージャ」を選択、設定画面に遷移します。 「新規接続アプリケーション」をクリックします。 接続アプリケーションの「基本情報」を入力します。 「接続アプリケーション名」: ここでは「sfdx」などとします。 「 API 参照名」: ここでは「sfdx」などとします。 「取引先責任者 メール」: ここでは、開発チームが利用することになるため、開発者のメールアドレスを設定することでよいでしょう。 上記以外は空欄でOKです 「OAuth 設定の有効化」を選択します。 コールバック URL に「 http://localhost:1717/OauthRedirect 」と入力します。JWTベアラー トーク ンフローでは使われない設定なのですが、必須項目なので設定する必要があります。 「デジタル署名を使用」をチェックします。 「ファイルを選択」をクリックし、用意した証明書ファイルをアップロードします。 次の OAuth 範囲を追加します。 API を使用してユーザデータを管理 ( api ) Web ブラウザを使用してユーザデータを管理 (web) いつでも要求を実行 (refresh_token, offline_ access ) 残りはデフォルトとし、「保存」をクリックします。 作成した接続アプリケーションに対し、 セキュリティポリシー を設定します。 CI/CDで利用する場合には、接続のたびに認証( sfdx auth:jwt:grant コマンド)を行うため、セッション時間などは短くしておいて問題ないという考え方になります。 また、許可するユーザーもユーザー自身が登録するフローは利用できないので、事前に管理者がプロファイルとして許可したユーザーのみがアクセス可能であるように設定します。 作成した接続アプリケーションの画面から、「Manage」をクリックします。 「ポリシーを編集」 をクリックします。 「OAuth ポリシー」セクションの「更新 トーク ンポリシー」項目で「次の時間が経過したら更新 トーク ンを期限切れにする:」をクリックし、90 日以下を入力します。(ここでは1時間) 「セッションポリシー」セクションで「 タイムアウト 値」を 15 分に設定します。 「OAuth ポリシー」セクションで、「許可されているユーザ」に「管理者が承認したユーザは事前承認済み」を選択し、「OK」をクリックします。 「保存」をクリックします。 接続アプリケーションに、作成したプロファイルを関連付けます。 「プロファイルを管理する」をクリックし、接続用に準備したプロファイルを選択します。 続いて、「コンシューマ鍵」を取得します。 これは、 CLI での認証時に指定されるもので、 GitHub の secrets に登録されるものとなります。 作成した接続アプリケーションの画面から、「コンシューマの詳細を管理」をクリックします。 場合によってはID検証が実行されますので、その場合は管理者の認証を行います。 「顧客の詳細」の「コンシューマ鍵」の内容をコピーし、控えておきます。 GitHub リポジトリ 側の設定 GitHub にログインし、 リポジトリ の画面から設定を行います。 Secretsの設定 Salesforce への接続情報を secrets に設定します。 接続する リポジトリ の「Settings」タブを選択します。 左側のメニューの「Security」-「Secrets」-「Actions」を選択します。 「New repository secret」ボタンをクリックし、secrets の登録画面に遷移します。 secrets の登録画面で、以下の3つを登録します。 ORG_USER: Salesforce で作成したユーザーのユーザー名 CLIENT_ID: 接続アプリケーションのコンシューマ鍵 PRIVATE_KEY_BASE64ED: 秘密鍵 を BASE64 したもの GitHub Actions ワークフローの作成 次の GitHub Actions ワークフローを作り、統合用ブランチの .github/workflows ディレクト リに、 deploy-to-sandbox.yml などの名前で作成、プッシュします。 ここでは、統合用ブランチの名前は「integration」としています。 name: salesforce ci on: pull_request: branches: - integration jobs: deploy_to_sandbox: runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v3 - name: Make temporary directory run: | mkdir ./temp # Install Salesforce CLI - name: Install Salesforce CLI run: | wget https://developer.salesforce.com/media/salesforce-cli/sfdx/channels/stable/sfdx-linux-x64.tar.xz mkdir ./temp/sfdx-cli tar xJf sfdx-linux-x64.tar.xz -C ./temp/sfdx-cli --strip-components 1 echo "./temp/sfdx-cli/bin" >> $GITHUB_PATH - name: Display sfdx version run: sfdx version # Save into private-key - name: Save into private-key run: echo -n ${{ secrets.PRIVATE_KEY_BASE64ED }} | base64 -d > ./temp/server.key # Authenticate to access the sandbox - name: Authenticate to access the sandbox run: sfdx force:auth:jwt:grant --instanceurl=https://test.salesforce.com/ --jwtkeyfile ./temp/server.key --username ${{ secrets.ORG_USER }} --clientid ${{ secrets.CLIENT_ID }} ワークフローの流れとしては以下のようになっています。 Pull Request で GitHub Actions が実行されるようにしています。 チェックアウトののち、 Salesforce DX CLI の最新版を wget で取得して一時 ディレクト リにインストール、パスを通しています。 secrets に登録しておいた 秘密鍵 を BASE64 デコードして一時ファイルに書き出しています。 最後に、これらを使って sfdx force:auth:jwt:grant コマンドで Salesforce に接続/認証しにいきます。 GitHub Actions ワークフローの接続検証 この状態で何か統合用ブランチに対してPull Request すれば、このワークフローが実行されるでしょう。 Actionsタブからワークフローの「 salesforce ci」を見つけ、ワークフローの実行結果を確認します。 「Authenticate to access the sandbox」の部分が、以下のように表示されていれば成功です。 CLI 認証後の操作 認証さえできてしまえば、あとは通常の CLI 操作と変わりません。 デプロイやそのための事前チェックであれば sfdx force:source:deploy コマンドを利用することになるでしょう。 接続時の トラブルシューティング のヒント Salesforce 側のID検証履歴を確認 接続アプリケーションであっても、原理としては Salesforce ユーザーを介して Salesforce にログインすることになります。 このため、 Salesforce 側の「ID検証履歴」を見ることで、ある程度どういう状況であるかを確認できます。 https://help.salesforce.com/s/articleView?id=sf.security_verification_history.htm&type=5 「設定」画面にて、「クイック検索」 ボックスに「検証履歴」と入力し、「ID 検証履歴」を選択、履歴表示画面に遷移します。 CLI で利用するユーザーによるログインもこの履歴にエントリーされます。 もし、この履歴にログがない場合、以下のような状況であると思われます。 接続処理に至る前の GitHub Actions の処理で失敗している その Sandbox には指定のユーザーが存在していない 接続したいSandbox組織とログインURLが合っていない(SandboxなのにログインURLに login. salesforce .com が指定されている、あるいはその逆など) 接続に失敗した状況が履歴に記載されているようであれば、その内容をもとにして対応します。 典型的には以下のような原因が考えられるでしょう。 ユーザーに適切なプロファイルが割り当たっていない プロファイルに設定された権限が不正 アクセス元の IPアドレス で弾かれている 余談 今回は、 Salesforce CLI を wget で取得してインストールしました。 実際には、以下のに ホスティング されている Action を利用することも可能でしょう。 https://github.com/forcedotcom/salesforcedx-actions ただ、(Unofficial)という記述があるので、今回については手動でインストールしています。 実行速度などを考慮するとこちらを使ってみてもいいかもしれません。 執筆: @horikoshi.yuji 、レビュー: @hashizume.hideki ( Shodo で執筆されました )
電通国際情報サービス 、X イノベーション 本部デジタルエンゲージメントセンターの堀越です。 Salesforce の開発でも GitHub を使って CI/CD をできるようにしたいのですが、この記事ではそのやり方などについて書きます。 最近の Salesforce の開発では、 Salesforce DX というコンセプトのもと、 CLI を使って Salesforce 上のローコード設定を XML で取得したり反映したりできます。 XML として取り扱えれば、 GitHub などで構成管理したり、環境間の差異がないかの比較をしたりということも可能です。 当然ながらローコードだけでなくApexやVisualforce、Lightning コンポーネント といったプログラムコードも CLI で取り扱うことができます。 ここまでは 個々の開発者で CLI とGitを利用すれば実現できるところです。 が、せっかく GitHub で構成管理するのであれば、Pull Request した際に自動テストやデプロイ可否のチェック(いわゆるCI/CD)もやりたくなりますね。 これを踏まえて、ここでは Salesforce DX の CLI を日ごろから使っている & プロジェクト構成が GitHub 上の リポジトリ に登録されていることを前提に、CI/CDを組み込んでみたいと思います。 CI/CD の実行には、 GitHub 標準装備の GitHub Actions を使いたいと思います。 やりたいことのイメージは次のようなものです。 統合用のブランチにSandboxを対応させて、Pull Request されたときにそのSandboxにデプロイできるかを自動チェックできるようにしたいと思います。 CLIによる Sandbox への接続 秘密鍵とそれとペアになった証明書の用意 Salesforce 側の準備 プロファイルの許可IPアドレス設定 プロファイルに与える権限 GitHub側の準備 セキュリティの考慮で secrets を使う CLIでの接続設定のイメージ CLIでの認証に向けた設定作業 キーペアと証明書の準備 Sandbox側の設定 接続用のプロファイルの準備 接続用のユーザーの準備 接続アプリケーションの準備 GitHubリポジトリ側の設定 Secretsの設定 GitHub Actions ワークフローの作成 GitHub Actions ワークフローの接続検証 CLI認証後の操作 接続時のトラブルシューティングのヒント Salesforce側のID検証履歴を確認 余談 CLI による Sandbox への接続 Salesforce DX の CLI では、まず Sandbox に接続(認証)し、それから取得やデプロイなどの操作を行う必要があります。 CLI による認証にはいくつか方式が提供されていますが、CI/CDで利用可能なのは「JWTベアラー トーク ンフロー」です。 JWTベアラー トーク ンフローでの接続には、以下の準備が必要です。 秘密鍵 とそれとペアになった証明書( 自己署名証明書 でOK)の用意 Salesforce 側の準備 GitHub 側の準備 秘密鍵 とそれとペアになった証明書の用意 opensslで 秘密鍵 と 自己署名証明書 を準備します。 自己署名証明書 は、 Salesforce 側の「接続アプリケーション」設定に登録されることになります。 また、 秘密鍵 は GitHub 側で利用されることになります。 Salesforce 側の準備 Salesforce 側がJWTベアラー トーク ンフローでアクセスを受けるためには、「接続アプリケーション」の設定が必要となります。 「接続アプリケーション」では大きく以下の要素の設定が必要です。 JWTベアラー トーク ンフローを有効化する諸設定 JWT を検証するための証明書のアップロード 接続アプリケーションで利用するプロファイル(または権限セット)とユーザー プロファイルまたは権限セットでSandbox内の操作可否を制御する認可の設定をします。 今回は、CI/CDでの利用のため、デプロイやテストを実行するための権限を与えておく必要があります。 また、そのプロファイルまたは権限セットに属する Salesforce ユーザーで接続することになりますので、そのためのユーザーも用意しておく必要があります。 今回は、セキュリティを考慮し、デプロイやテストを実行するための最小の権限を持った専用ユーザーと専用プロファイルを用意することにしました。 また、このユーザーはブラウザでのログインを行わないものとし、 API 呼出専用のユーザーとして用意しておくことにします。 これらの一連の設定方法は、 Salesforce DXのドキュメントの以下が参照できます。 https://developer.salesforce.com/docs/atlas.ja-jp.sfdx_dev.meta/sfdx_dev/sfdx_dev_auth_connected_app.htm プロファイルの許可 IPアドレス 設定 プロファイルには、ログインが許可できるアクセス元 IPアドレス 範囲の指定が可能で、 GitHub からのアクセスを許可するように設定するようにします。 ただ、つぶさに設定するのは数が多くて現実的ではないので広めのレンジにならざるを得ないものと思います。 その分、クレデンシャル情報はしっかりと管理しておく必要があるでしょう。 なお、 GitHub のIPレンジは以下から取得できます。 https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/about-githubs-ip-addresses プロファイルに与える権限 プロファイルで与える権限(システム権限)は、以下になります。 「アプリケーションのカスタマイズ」(Customize Application) 「 メタデータ API 関数を使用した メタデータ の変更」(Modify Metadata Through Metadata API Function) 「 API の有効化」( API Enabled) 「 API 限定ユーザ」( API Only User) 「すべてのデータの編集」(Modify All Data) 必要な権限についての情報は以下を参照しています。 https://developer.salesforce.com/docs/atlas.ja-jp.238.0.api_meta.meta/api_meta/meta_modify_metadata_perm.htm なお、「すべてのデータの編集」は、全てのオブジェクトに対する CRUD 操作を行える権限となります。 デプロイに伴ってApexのテストコードの実行が必要であり、それに伴ってオブジェクトの CRUD 操作が必要となることから、この権限を与えておくのが無難でしょう。 GitHub 側の準備 JWTベアラー トーク ンフローで認証する際には、 CLI コマンドで以下を指定する必要があります。 JWT を署名するための 秘密鍵 のファイル Sandboxへの接続に利用するユーザー名 接続アプリケーションのコンシューマ鍵(OAuthのクライアントID) Salesforce 環境のログインURL 認証のコマンドは以下のようなイメージとなります。 sfdx auth:jwt:grant --instanceurl=https://test.salesforce.com/ --jwtkeyfile 【秘密鍵ファイル名】 --username 【接続ユーザー名】 --clientid 【接続アプリケーションのコンシューマ鍵】 ここで、 https://test.salesforce.com/ はログインURLであり、今回はSandboxを利用するためにこちらのURLとなっています。 もし本番環境に接続したい場合は Salesforce の「私の ドメイン 」で指定したURLを適切に指定する必要があると思われます。 「接続アプリケーションのコンシューマ鍵」は、準備した接続アプリケーション設定から取得します。 また、「接続ユーザー名」も同様に Salesforce 側の準備で作成したものを利用します。 セキュリティの考慮で secrets を使う コンシューマ鍵や 秘密鍵 ファイルなどはクレデンシャル情報なので リポジトリ 内に含めるわけにはいきません。 そこで、 GitHub の secrets に登録しておくものとします。 また 秘密鍵 ファイルはX.509形式ですが、改行が含まれるため、そのまま secrets で扱うことはできません。 そこで、 BASE64 エンコード して secrets に登録しておき、それを実行時に取り出して BASE64 デコード、一時ファイルに書き出して鍵ファイルとするものとします。 今回は secrets を利用しましたが、接続先の Sandbox が増えるようであれば、 Environments 機能を利用するのが良いでしょう。 CLI での接続設定のイメージ この接続設定をまとめると、イメージは次のようになります。 CLI での認証に向けた設定作業 以下、このイメージに沿って設定作業を進めていきます。 キーペアと証明書の準備 最初に、opensslで 秘密鍵 と 自己署名証明書 を準備します。 この手順は比較的よくある手順であり、詳しくは割愛します。 Salesforce DXの公式ドキュメントに沿った手順としては以下が参照可能です。 https://developer.salesforce.com/docs/atlas.ja-jp.238.0.sfdx_dev.meta/sfdx_dev/sfdx_dev_auth_key_and_cert.htm また、 GitHub の secrets で 秘密鍵 の内容を保持できるようにするため、 秘密鍵 を BASE64 したものを控えておきます。 以下はそのコマンドイメージです。 UNIX 系の場合: cat server.key | base64 Windows の場合: openssl base64 -A -in server.key -out sever_base64.key Sandbox側の設定 Salesforce の Sandbox に管理者でログインし、一連の設定を実施します。 プロファイルの作成と権限付与、ユーザーの作成はよくある手順ですので、詳細は割愛します。 接続用のプロファイルの準備 「設定」画面にて、「クイック検索」 ボックスに「プロファイル」と入力し、「プロファイル」を選択、設定画面に遷移します。 新しいプロファイルを作り、上述の「プロファイルの設定」で示した権限を与えます。 このプロファイルは、のちに接続アプリケーションに紐づけることになりますので、名前を控えておきます。 (今回は、「sfdx_connect」というプロファイル名を設定することとしました) 仮に他のプロファイルをコピーして作った場合には、アクセス元 IPアドレス の許可レンジが自社 IPアドレス のままなどになっている場合があるでしょう。 今回のアクセス元は GitHub Actions のサーバになるので、その指定の変更を忘れないようにしましょう。 接続用のユーザーの準備 同様に、「クイック検索」 ボックスに「ユーザ」と入力しユーザー設定画面に遷移します。 ユーザーを新規作成し、作ったプロファイルにユーザーを割り当てます。 作ったユーザーは GitHub Actionsでの接続に利用されますので、ユーザー名を控えておきます。 接続アプリケーションの準備 Salesforce DX の認証を実施するため、接続アプリケーションの設定を行います。 この一連の設定方法は、 Salesforce DXのドキュメントの以下を参照します。 https://developer.salesforce.com/docs/atlas.ja-jp.sfdx_dev.meta/sfdx_dev/sfdx_dev_auth_connected_app.htm 基本的にはこの設定手順に沿いますが、「JWTベアラー トーク ンフロー」なので「(JWTのみ)」と書いてある項目も設定が必要です。 「設定」画面にて、「クイック検索」 ボックスに「アプリケーションマネージャ」と入力し、「アプリケーションマネージャ」を選択、設定画面に遷移します。 「新規接続アプリケーション」をクリックします。 接続アプリケーションの「基本情報」を入力します。 「接続アプリケーション名」: ここでは「sfdx」などとします。 「 API 参照名」: ここでは「sfdx」などとします。 「取引先責任者 メール」: ここでは、開発チームが利用することになるため、開発者のメールアドレスを設定することでよいでしょう。 上記以外は空欄でOKです 「OAuth 設定の有効化」を選択します。 コールバック URL に「 http://localhost:1717/OauthRedirect 」と入力します。JWTベアラー トーク ンフローでは使われない設定なのですが、必須項目なので設定する必要があります。 「デジタル署名を使用」をチェックします。 「ファイルを選択」をクリックし、用意した証明書ファイルをアップロードします。 次の OAuth 範囲を追加します。 API を使用してユーザデータを管理 ( api ) Web ブラウザを使用してユーザデータを管理 (web) いつでも要求を実行 (refresh_token, offline_ access ) 残りはデフォルトとし、「保存」をクリックします。 作成した接続アプリケーションに対し、 セキュリティポリシー を設定します。 CI/CDで利用する場合には、接続のたびに認証( sfdx auth:jwt:grant コマンド)を行うため、セッション時間などは短くしておいて問題ないという考え方になります。 また、許可するユーザーもユーザー自身が登録するフローは利用できないので、事前に管理者がプロファイルとして許可したユーザーのみがアクセス可能であるように設定します。 作成した接続アプリケーションの画面から、「Manage」をクリックします。 「ポリシーを編集」 をクリックします。 「OAuth ポリシー」セクションの「更新 トーク ンポリシー」項目で「次の時間が経過したら更新 トーク ンを期限切れにする:」をクリックし、90 日以下を入力します。(ここでは1時間) 「セッションポリシー」セクションで「 タイムアウト 値」を 15 分に設定します。 「OAuth ポリシー」セクションで、「許可されているユーザ」に「管理者が承認したユーザは事前承認済み」を選択し、「OK」をクリックします。 「保存」をクリックします。 接続アプリケーションに、作成したプロファイルを関連付けます。 「プロファイルを管理する」をクリックし、接続用に準備したプロファイルを選択します。 続いて、「コンシューマ鍵」を取得します。 これは、 CLI での認証時に指定されるもので、 GitHub の secrets に登録されるものとなります。 作成した接続アプリケーションの画面から、「コンシューマの詳細を管理」をクリックします。 場合によってはID検証が実行されますので、その場合は管理者の認証を行います。 「顧客の詳細」の「コンシューマ鍵」の内容をコピーし、控えておきます。 GitHub リポジトリ 側の設定 GitHub にログインし、 リポジトリ の画面から設定を行います。 Secretsの設定 Salesforce への接続情報を secrets に設定します。 接続する リポジトリ の「Settings」タブを選択します。 左側のメニューの「Security」-「Secrets」-「Actions」を選択します。 「New repository secret」ボタンをクリックし、secrets の登録画面に遷移します。 secrets の登録画面で、以下の3つを登録します。 ORG_USER: Salesforce で作成したユーザーのユーザー名 CLIENT_ID: 接続アプリケーションのコンシューマ鍵 PRIVATE_KEY_BASE64ED: 秘密鍵 を BASE64 したもの GitHub Actions ワークフローの作成 次の GitHub Actions ワークフローを作り、統合用ブランチの .github/workflows ディレクト リに、 deploy-to-sandbox.yml などの名前で作成、プッシュします。 ここでは、統合用ブランチの名前は「integration」としています。 name: salesforce ci on: pull_request: branches: - integration jobs: deploy_to_sandbox: runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v3 - name: Make temporary directory run: | mkdir ./temp # Install Salesforce CLI - name: Install Salesforce CLI run: | wget https://developer.salesforce.com/media/salesforce-cli/sfdx/channels/stable/sfdx-linux-x64.tar.xz mkdir ./temp/sfdx-cli tar xJf sfdx-linux-x64.tar.xz -C ./temp/sfdx-cli --strip-components 1 echo "./temp/sfdx-cli/bin" >> $GITHUB_PATH - name: Display sfdx version run: sfdx version # Save into private-key - name: Save into private-key run: echo -n ${{ secrets.PRIVATE_KEY_BASE64ED }} | base64 -d > ./temp/server.key # Authenticate to access the sandbox - name: Authenticate to access the sandbox run: sfdx force:auth:jwt:grant --instanceurl=https://test.salesforce.com/ --jwtkeyfile ./temp/server.key --username ${{ secrets.ORG_USER }} --clientid ${{ secrets.CLIENT_ID }} ワークフローの流れとしては以下のようになっています。 Pull Request で GitHub Actions が実行されるようにしています。 チェックアウトののち、 Salesforce DX CLI の最新版を wget で取得して一時 ディレクト リにインストール、パスを通しています。 secrets に登録しておいた 秘密鍵 を BASE64 デコードして一時ファイルに書き出しています。 最後に、これらを使って sfdx force:auth:jwt:grant コマンドで Salesforce に接続/認証しにいきます。 GitHub Actions ワークフローの接続検証 この状態で何か統合用ブランチに対してPull Request すれば、このワークフローが実行されるでしょう。 Actionsタブからワークフローの「 salesforce ci」を見つけ、ワークフローの実行結果を確認します。 「Authenticate to access the sandbox」の部分が、以下のように表示されていれば成功です。 CLI 認証後の操作 認証さえできてしまえば、あとは通常の CLI 操作と変わりません。 デプロイやそのための事前チェックであれば sfdx force:source:deploy コマンドを利用することになるでしょう。 接続時の トラブルシューティング のヒント Salesforce 側のID検証履歴を確認 接続アプリケーションであっても、原理としては Salesforce ユーザーを介して Salesforce にログインすることになります。 このため、 Salesforce 側の「ID検証履歴」を見ることで、ある程度どういう状況であるかを確認できます。 https://help.salesforce.com/s/articleView?id=sf.security_verification_history.htm&type=5 「設定」画面にて、「クイック検索」 ボックスに「検証履歴」と入力し、「ID 検証履歴」を選択、履歴表示画面に遷移します。 CLI で利用するユーザーによるログインもこの履歴にエントリーされます。 もし、この履歴にログがない場合、以下のような状況であると思われます。 接続処理に至る前の GitHub Actions の処理で失敗している その Sandbox には指定のユーザーが存在していない 接続したいSandbox組織とログインURLが合っていない(SandboxなのにログインURLに login. salesforce .com が指定されている、あるいはその逆など) 接続に失敗した状況が履歴に記載されているようであれば、その内容をもとにして対応します。 典型的には以下のような原因が考えられるでしょう。 ユーザーに適切なプロファイルが割り当たっていない プロファイルに設定された権限が不正 アクセス元の IPアドレス で弾かれている 余談 今回は、 Salesforce CLI を wget で取得してインストールしました。 実際には、以下のに ホスティング されている Action を利用することも可能でしょう。 https://github.com/forcedotcom/salesforcedx-actions ただ、(Unofficial)という記述があるので、今回については手動でインストールしています。 実行速度などを考慮するとこちらを使ってみてもいいかもしれません。 執筆: @horikoshi.yuji 、レビュー: @hashizume.hideki ( Shodo で執筆されました )
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 Amazon CloudWatch RUM は Webブラウザ で発生したアプリケーションのエラーやパフォーマンス情報を収集し、モニタリングするための機能です。 ECSのブルーグリーンデプロイメントを利用してWebアプリをデプロイしているのですが、CloudWatch RUMを利用するにあたって本番昇格前のテスト環境からのデータが、分析の際のノイズにならないよう、本番環境からのデータと混ざらないようにしたいと思いました。そこで今回はテスト環境からはCloudWatch RUMにデータを送信せず、本番環境に昇格した時のみデータを送信する方法について書きます。 インフラ構成 CloudWatch RUMの利用開始方法 テスト環境のデータが混ざってしまう 解決方法 CloudFront用TLS証明書の用意 Webアプリで外部からコードスニペットを取得する コードスニペット格納用のS3バケットを作成 S3バケットのアクセス制御 S3バケットにコードスニペットをファイルで追加する CloudFrontディストリビューションの作成 ドメインのルーティング 動きの確認 インフラ構成 以下の構成でアプリケーションが稼働しています。 Webアプリケーションをコンテナとしてビルドし、ECRにイメージをプッシュ ECSサービス(Fargate)として ホスティング ECSのブルーグリーンデプロイメントを利用(詳細は こちらの記事 にて) 本番環境を443ポート、テスト環境を8443ポートとしてALBで異なるターゲットグループにルーティング カスタム ドメイン の TLS 証明書をALBで使用 CloudWatch RUMの利用開始方法 CloudWatch RUMを利用するには、まずマネジメントコンソールからアプリケーションモニターを追加します。 作成すると JavaScript の コードスニペット が表示され、それをアプリケーションに追加するだけでブラウザからデータが収集されるようになります。 テスト環境のデータが混ざってしまう アプリケーションモニターを追加する際にはデータを収集するアプリの ドメイン を指定するので、それ以外の ドメイン から送信されたデータは受け付けてくれません。すなわち localhost などでアプリを実行した場合のデータがアプリケーションモニターに登録されることはありません。 しかしデータ収集元のアプリのポート番号は、記事執筆時点では指定できませんでした。ECSのブルーグリーンデプロイメントでは同一 ドメイン の異なるポート番号で本番環境とテスト環境が存在するため、テスト環境にアクセスしたときのデータもCloudWatch RUMに登録されてしまいます。本番 トラフィック に比べてテスト トラフィック が十分に少なければ気にしなくても良いかもしれませんが、今回は本番環境からのみデータが送信される仕組みを作ってみます。 解決方法 ブルーグリーンデプロイメントの場合、本番環境とテスト環境は同一の構成であり、ルーティングだけが異なります。つまり本番環境にだけCloudWatch RUMの コードスニペット を含めたり、コンテナに渡す 環境変数 によってデータ送信の有効化を制御したりすることはできません。 そこでCloudWatch RUMの コードスニペット を直接アプリに含めるのではなくブラウザで外部から取得するようにし、取得した スクリプト へのブラウザ内アクセスをCORSで制限するようにしました。こうすることで、テスト環境では スクリプト がブラウザで実行されず、データはCloudWatch RUMに送信されません。テスト環境ではブラウザのコンソールにCORSエラーが出ますが、本番環境ではないので問題ないでしょう。 具体的にはイン フラリ ソースとしてS3 バケット を作成してCORSを設定し、そこに コードスニペット をファイルで追加します。これだけでも動くとは思いますが、S3 バケット をパブリックにしたくないため、 バケット 自体は非公開のままでCloudFront経由でコンテンツを配信するようにします。CloudFront ディストリビューション には 独自ドメイン の TLS 証明書を関連付けました。CORSを効かせるために、アプリの ドメイン とはクロス ドメイン になるようにします。 以下では順を追って設定方法を説明します。 CloudFront用 TLS 証明書の用意 TLS 証明書をあらかじめ発行しておきます。今回はアプリ ドメイン my-domain.com の サブドメイン として、 static.my-domain.com を利用するとします。CORSのオリジンは プロトコル 、 ドメイン 、ポートの3点セットで区別されるため、アプリ ドメイン とはクロスオリジンの関係になります。 参考までにCDKの場合のコードサンプルを掲載します。(証明書はCloudFrontで利用するため、us-east-1 リージョンにデプロイします) const hostedZone = route53.PublicHostedZone.fromHostedZoneAttributes ( this , "MyHostedZone" , { hostedZoneId: "<ホストゾーンID>" , zoneName: "my-domain.com" , } ); const cloudfrontCertificate = new certificatemanager.DnsValidatedCertificate ( this , "CloudFrontCertificate" , { domainName: "static.my-domain.com" , hostedZone: hostedZone , validation: certificatemanager.CertificateValidation.fromDns ( hostedZone ), } ); Webアプリで外部から コードスニペット を取得する アプリケーションモニターの コードスニペット を直接コードに含めるのではなく、以下の形で src として読み込んでもらうことを考えます。 < script src = "https://static.my-domain.com/rum.js" ></ script > <script> タグの src で外部からファイルを取得する場合、GETによる単純リク エス トになるため、CORSの プリフライトリクエスト は発生しません。すなわちOPTIONSリク エス トは送信されず、443ポートでも8443ポートでもGETリク エス トでリソースは取得され、実行されます。 そこで crossorigin属性 を利用します。これを指定することによって リクエストモード が cors となり、クロスオリジン環境下ではサイトの Origin が スクリプト を取得する際の Access-Control-Allow-Origin レスポンスヘッダーに含まれない限り、ロードした スクリプト がブラウザで実行されなくなります。(crossorigin属性を付けない場合、 <script> タグのリク エス トモードは no-cors となり、サイトの Origin に関わらずロードした スクリプト がブラウザで実行されます) (参考) https://nhiroki.jp/2021/01/07/crossorigin-attribute 以下のように、Webアプリの <head> タグ内でアプリケーションモニターの コードスニペット を読み込むようにし、crossorigin属性を設定します( rum.js ファイルはのちにS3 バケット を作成したときに追加します)。 < head > < script src = "https://static.my-domain.com/rum.js" crossorigin = "anonymous" ></ script > </ head > またcrossorigin属性を付けることにより、 スクリプト を取得する際のGETリク エス トに Origin ヘッダーが付与されるようになります。これは次に述べるS3 バケット からのレスポンスヘッダーにも影響します。 図にまとめると、crossorigin属性を使用しない場合、本番環境でもテスト環境でも スクリプト が実行されてしまいます。 crossorigin属性を使用する場合は次のようになり、本番環境のみ スクリプト が実行されます。 コードスニペット 格納用のS3 バケット を作成 S3 バケット を作成し、本番環境のみを許可するCORSを設定します。 const myBucket = new s3.Bucket ( this , "MyBucket" , { encryption: s3.BucketEncryption.S3_MANAGED , blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL , objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_ENFORCED , enforceSSL: true , cors: [ { allowedHeaders: [ "*" ] , allowedMethods: [ s3.HttpMethods.GET ] , allowedOrigins: [ "https://my-domain.com" ] , } , ] , } ); この設定をした場合の動きを確認してみました。リク エス トヘッダーに Origin: https://my-domain.com が含まれている場合、以下のレスポンスヘッダーが付与されました。 Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET Access-Control-Allow-Origin: https://my-domain.com リク エス トヘッダーが Origin: https://my-domain.com:8443 となっている場合、以上の3つの Access-Control-* レスポンスヘッダーは付与されていませんでした。 S3 バケット のアクセス制御 CORSの設定とは関係ありませんが、CloudFrontの OAC(オリジンアクセスコントロール) を利用し、S3 バケット へのアクセスを次のステップで作成するCloudFront ディストリビューション からのみに制限します。 執筆時点でCDKのL2コンスト ラク トではまだOACがサポートされていないため、以下はOACではなくOAIを利用する場合のコードサンプルです。L2コンスト ラク トでOACがサポートされたらそれを利用するのが良いでしょう。 const oai = new cloudfront.OriginAccessIdentity ( this , "MyOAI" ); myBucket.addToResourcePolicy ( new iam.PolicyStatement ( { effect: iam.Effect.ALLOW , actions: [ "s3:GetObject" ] , principals: [ new iam.CanonicalUserPrincipal ( oai.cloudFrontOriginAccessIdentityS3CanonicalUserId ) ] , resources: [ ` ${ myBucket.bucketArn } /*` ] , } ) ); S3 バケット に コードスニペット をファイルで追加する アプリケーションモニターの コードスニペット のうち、 <script> タグを除外した部分をコピーした JavaScript ファイルを作成します。今回は rum.js というファイル名としてS3 バケット にアップロードします。 CloudFront ディストリビューション の作成 CloudFront ディストリビューション を作成します。ここで重要なのは 3つのポリシー です。 まずはキャッシュポリシーとして、 Origin ヘッダーをキャッシュキーに含めるようにします。すなわちリク エス トの Origin ヘッダーが異なる値の場合は、異なるコンテンツを要求しているとみなし、本番環境とテスト環境での振る舞いを切り替えます。 const cachePolicy = new cloudfront.CachePolicy ( this , "MyCachePolicy" , { defaultTtl: cdk.Duration.days ( 1 ), maxTtl: cdk.Duration.days ( 1 ), minTtl: cdk.Duration.days ( 1 ), headerBehavior: cloudfront.CacheHeaderBehavior.allowList ( "Origin" ), } ); 次にオリジンリク エス トポリシーとして、CloudFrontからオリジンへのリク エス トに Origin ヘッダーを含めて転送するようにします。これにより、S3 バケット で設定したCORSが機能するようになります。 const originRequestPolicy = new cloudfront.OriginRequestPolicy ( this , "MyOriginRequestPolicy" , { headerBehavior: cloudfront.CacheHeaderBehavior.allowList ( "Origin" ), } ); 最後にレスポンスヘッダーポリシーとして、CloudFrontからレスポンスを返すときにCORSヘッダーを含めるようにします。 const responseHeadersPolicy = new cloudfront.ResponseHeadersPolicy ( this , "MyResponseHeadersPolicy" , { corsBehavior: { accessControlAllowCredentials: false , accessControlAllowHeaders: [ "*" ] , accessControlAllowMethods: [ "GET" ] , accessControlAllowOrigins: [ "https://my-domain.com" ] , originOverride: false , } , } ); 以上のポリシーを利用してCloudFront ディストリビューション を作成します。今回の構成ではOPTIONSメソッドは送信されないため、許可するメソッドにOPTIONSは含めていません。 const distribution = new cloudfront.Distribution ( this , "MyDistribution" , { defaultBehavior: { allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD , cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD , cachePolicy , originRequestPolicy , responseHeadersPolicy , viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS , origin: new cloudfrontOrigins.S3Origin ( myBucket ), } , priceClass: cloudfront.PriceClass.PRICE_CLASS_200 , geoRestriction: cloudfront.GeoRestriction.allowlist ( "JP" ), sslSupportMethod: cloudfront.SSLMethod.SNI , minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021 , certificate: cloudfrontCertificate , domainNames: [ "static.my-domain.com" ] , } ); ドメイン のルーティング 作成したCloudFront ディストリビューション に、 static.my-domain.com のルーティングを向けて完了です。 new route53.ARecord ( this , "StaticARecord" , { zone: hostedZone , recordName: "static" , target: route53.RecordTarget.fromAlias (new route53Targets.CloudFrontTarget ( distribution )), } ); 動きの確認 本番環境の https://my-domain.com にアクセスすると、 https://static.my-domain.com/rum.js の取得に成功していることを確認できました。実際にはさらに https://client.rum.us-east-1.amazonaws.com/1.5.x/cwr.js より スクリプト の本体をロードしており、 https://dataplane.rum.ap-notheast-1.amazonaws.com/appmonitors/ にブラウザのクライアントデータを送信していました。マネジメントコンソールのCloudWatch RUMの画面にアクセスすると、データが取得されていることがわかります。 一方、テスト環境の https://my-domain.com:8443 にアクセスすると、 https://static.my-domain.com/rum.js からのレスポンスステータスは200で返りますが、クロスオリジンの読み込みが許可されていないためブラウザは スクリプト にアクセスできず、実行されません。 これでECSのブルーグリーンデプロイメントの本番環境のみ、CloudWatch RUMにデータ送信を実現できました。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 - セキュリティエンジニア(セキュリティ設計) 執筆: @kou.kinyo 、レビュー: @yamada.y ( Shodo で執筆されました )
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 Amazon CloudWatch RUM は Webブラウザ で発生したアプリケーションのエラーやパフォーマンス情報を収集し、モニタリングするための機能です。 ECSのブルーグリーンデプロイメントを利用してWebアプリをデプロイしているのですが、CloudWatch RUMを利用するにあたって本番昇格前のテスト環境からのデータが、分析の際のノイズにならないよう、本番環境からのデータと混ざらないようにしたいと思いました。そこで今回はテスト環境からはCloudWatch RUMにデータを送信せず、本番環境に昇格した時のみデータを送信する方法について書きます。 インフラ構成 CloudWatch RUMの利用開始方法 テスト環境のデータが混ざってしまう 解決方法 CloudFront用TLS証明書の用意 Webアプリで外部からコードスニペットを取得する コードスニペット格納用のS3バケットを作成 S3バケットのアクセス制御 S3バケットにコードスニペットをファイルで追加する CloudFrontディストリビューションの作成 ドメインのルーティング 動きの確認 インフラ構成 以下の構成でアプリケーションが稼働しています。 Webアプリケーションをコンテナとしてビルドし、ECRにイメージをプッシュ ECSサービス(Fargate)として ホスティング ECSのブルーグリーンデプロイメントを利用(詳細は こちらの記事 にて) 本番環境を443ポート、テスト環境を8443ポートとしてALBで異なるターゲットグループにルーティング カスタム ドメイン の TLS 証明書をALBで使用 CloudWatch RUMの利用開始方法 CloudWatch RUMを利用するには、まずマネジメントコンソールからアプリケーションモニターを追加します。 作成すると JavaScript の コードスニペット が表示され、それをアプリケーションに追加するだけでブラウザからデータが収集されるようになります。 テスト環境のデータが混ざってしまう アプリケーションモニターを追加する際にはデータを収集するアプリの ドメイン を指定するので、それ以外の ドメイン から送信されたデータは受け付けてくれません。すなわち localhost などでアプリを実行した場合のデータがアプリケーションモニターに登録されることはありません。 しかしデータ収集元のアプリのポート番号は、記事執筆時点では指定できませんでした。ECSのブルーグリーンデプロイメントでは同一 ドメイン の異なるポート番号で本番環境とテスト環境が存在するため、テスト環境にアクセスしたときのデータもCloudWatch RUMに登録されてしまいます。本番 トラフィック に比べてテスト トラフィック が十分に少なければ気にしなくても良いかもしれませんが、今回は本番環境からのみデータが送信される仕組みを作ってみます。 解決方法 ブルーグリーンデプロイメントの場合、本番環境とテスト環境は同一の構成であり、ルーティングだけが異なります。つまり本番環境にだけCloudWatch RUMの コードスニペット を含めたり、コンテナに渡す 環境変数 によってデータ送信の有効化を制御したりすることはできません。 そこでCloudWatch RUMの コードスニペット を直接アプリに含めるのではなくブラウザで外部から取得するようにし、取得した スクリプト へのブラウザ内アクセスをCORSで制限するようにしました。こうすることで、テスト環境では スクリプト がブラウザで実行されず、データはCloudWatch RUMに送信されません。テスト環境ではブラウザのコンソールにCORSエラーが出ますが、本番環境ではないので問題ないでしょう。 具体的にはイン フラリ ソースとしてS3 バケット を作成してCORSを設定し、そこに コードスニペット をファイルで追加します。これだけでも動くとは思いますが、S3 バケット をパブリックにしたくないため、 バケット 自体は非公開のままでCloudFront経由でコンテンツを配信するようにします。CloudFront ディストリビューション には 独自ドメイン の TLS 証明書を関連付けました。CORSを効かせるために、アプリの ドメイン とはクロス ドメイン になるようにします。 以下では順を追って設定方法を説明します。 CloudFront用 TLS 証明書の用意 TLS 証明書をあらかじめ発行しておきます。今回はアプリ ドメイン my-domain.com の サブドメイン として、 static.my-domain.com を利用するとします。CORSのオリジンは プロトコル 、 ドメイン 、ポートの3点セットで区別されるため、アプリ ドメイン とはクロスオリジンの関係になります。 参考までにCDKの場合のコードサンプルを掲載します。(証明書はCloudFrontで利用するため、us-east-1 リージョンにデプロイします) const hostedZone = route53.PublicHostedZone.fromHostedZoneAttributes ( this , "MyHostedZone" , { hostedZoneId: "<ホストゾーンID>" , zoneName: "my-domain.com" , } ); const cloudfrontCertificate = new certificatemanager.DnsValidatedCertificate ( this , "CloudFrontCertificate" , { domainName: "static.my-domain.com" , hostedZone: hostedZone , validation: certificatemanager.CertificateValidation.fromDns ( hostedZone ), } ); Webアプリで外部から コードスニペット を取得する アプリケーションモニターの コードスニペット を直接コードに含めるのではなく、以下の形で src として読み込んでもらうことを考えます。 < script src = "https://static.my-domain.com/rum.js" ></ script > <script> タグの src で外部からファイルを取得する場合、GETによる単純リク エス トになるため、CORSの プリフライトリクエスト は発生しません。すなわちOPTIONSリク エス トは送信されず、443ポートでも8443ポートでもGETリク エス トでリソースは取得され、実行されます。 そこで crossorigin属性 を利用します。これを指定することによって リクエストモード が cors となり、クロスオリジン環境下ではサイトの Origin が スクリプト を取得する際の Access-Control-Allow-Origin レスポンスヘッダーに含まれない限り、ロードした スクリプト がブラウザで実行されなくなります。(crossorigin属性を付けない場合、 <script> タグのリク エス トモードは no-cors となり、サイトの Origin に関わらずロードした スクリプト がブラウザで実行されます) (参考) https://nhiroki.jp/2021/01/07/crossorigin-attribute 以下のように、Webアプリの <head> タグ内でアプリケーションモニターの コードスニペット を読み込むようにし、crossorigin属性を設定します( rum.js ファイルはのちにS3 バケット を作成したときに追加します)。 < head > < script src = "https://static.my-domain.com/rum.js" crossorigin = "anonymous" ></ script > </ head > またcrossorigin属性を付けることにより、 スクリプト を取得する際のGETリク エス トに Origin ヘッダーが付与されるようになります。これは次に述べるS3 バケット からのレスポンスヘッダーにも影響します。 図にまとめると、crossorigin属性を使用しない場合、本番環境でもテスト環境でも スクリプト が実行されてしまいます。 crossorigin属性を使用する場合は次のようになり、本番環境のみ スクリプト が実行されます。 コードスニペット 格納用のS3 バケット を作成 S3 バケット を作成し、本番環境のみを許可するCORSを設定します。 const myBucket = new s3.Bucket ( this , "MyBucket" , { encryption: s3.BucketEncryption.S3_MANAGED , blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL , objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_ENFORCED , enforceSSL: true , cors: [ { allowedHeaders: [ "*" ] , allowedMethods: [ s3.HttpMethods.GET ] , allowedOrigins: [ "https://my-domain.com" ] , } , ] , } ); この設定をした場合の動きを確認してみました。リク エス トヘッダーに Origin: https://my-domain.com が含まれている場合、以下のレスポンスヘッダーが付与されました。 Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET Access-Control-Allow-Origin: https://my-domain.com リク エス トヘッダーが Origin: https://my-domain.com:8443 となっている場合、以上の3つの Access-Control-* レスポンスヘッダーは付与されていませんでした。 S3 バケット のアクセス制御 CORSの設定とは関係ありませんが、CloudFrontの OAC(オリジンアクセスコントロール) を利用し、S3 バケット へのアクセスを次のステップで作成するCloudFront ディストリビューション からのみに制限します。 執筆時点でCDKのL2コンスト ラク トではまだOACがサポートされていないため、以下はOACではなくOAIを利用する場合のコードサンプルです。L2コンスト ラク トでOACがサポートされたらそれを利用するのが良いでしょう。 const oai = new cloudfront.OriginAccessIdentity ( this , "MyOAI" ); myBucket.addToResourcePolicy ( new iam.PolicyStatement ( { effect: iam.Effect.ALLOW , actions: [ "s3:GetObject" ] , principals: [ new iam.CanonicalUserPrincipal ( oai.cloudFrontOriginAccessIdentityS3CanonicalUserId ) ] , resources: [ ` ${ myBucket.bucketArn } /*` ] , } ) ); S3 バケット に コードスニペット をファイルで追加する アプリケーションモニターの コードスニペット のうち、 <script> タグを除外した部分をコピーした JavaScript ファイルを作成します。今回は rum.js というファイル名としてS3 バケット にアップロードします。 CloudFront ディストリビューション の作成 CloudFront ディストリビューション を作成します。ここで重要なのは 3つのポリシー です。 まずはキャッシュポリシーとして、 Origin ヘッダーをキャッシュキーに含めるようにします。すなわちリク エス トの Origin ヘッダーが異なる値の場合は、異なるコンテンツを要求しているとみなし、本番環境とテスト環境での振る舞いを切り替えます。 const cachePolicy = new cloudfront.CachePolicy ( this , "MyCachePolicy" , { defaultTtl: cdk.Duration.days ( 1 ), maxTtl: cdk.Duration.days ( 1 ), minTtl: cdk.Duration.days ( 1 ), headerBehavior: cloudfront.CacheHeaderBehavior.allowList ( "Origin" ), } ); 次にオリジンリク エス トポリシーとして、CloudFrontからオリジンへのリク エス トに Origin ヘッダーを含めて転送するようにします。これにより、S3 バケット で設定したCORSが機能するようになります。 const originRequestPolicy = new cloudfront.OriginRequestPolicy ( this , "MyOriginRequestPolicy" , { headerBehavior: cloudfront.CacheHeaderBehavior.allowList ( "Origin" ), } ); 最後にレスポンスヘッダーポリシーとして、CloudFrontからレスポンスを返すときにCORSヘッダーを含めるようにします。 const responseHeadersPolicy = new cloudfront.ResponseHeadersPolicy ( this , "MyResponseHeadersPolicy" , { corsBehavior: { accessControlAllowCredentials: false , accessControlAllowHeaders: [ "*" ] , accessControlAllowMethods: [ "GET" ] , accessControlAllowOrigins: [ "https://my-domain.com" ] , originOverride: false , } , } ); 以上のポリシーを利用してCloudFront ディストリビューション を作成します。今回の構成ではOPTIONSメソッドは送信されないため、許可するメソッドにOPTIONSは含めていません。 const distribution = new cloudfront.Distribution ( this , "MyDistribution" , { defaultBehavior: { allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD , cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD , cachePolicy , originRequestPolicy , responseHeadersPolicy , viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS , origin: new cloudfrontOrigins.S3Origin ( myBucket ), } , priceClass: cloudfront.PriceClass.PRICE_CLASS_200 , geoRestriction: cloudfront.GeoRestriction.allowlist ( "JP" ), sslSupportMethod: cloudfront.SSLMethod.SNI , minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021 , certificate: cloudfrontCertificate , domainNames: [ "static.my-domain.com" ] , } ); ドメイン のルーティング 作成したCloudFront ディストリビューション に、 static.my-domain.com のルーティングを向けて完了です。 new route53.ARecord ( this , "StaticARecord" , { zone: hostedZone , recordName: "static" , target: route53.RecordTarget.fromAlias (new route53Targets.CloudFrontTarget ( distribution )), } ); 動きの確認 本番環境の https://my-domain.com にアクセスすると、 https://static.my-domain.com/rum.js の取得に成功していることを確認できました。実際にはさらに https://client.rum.us-east-1.amazonaws.com/1.5.x/cwr.js より スクリプト の本体をロードしており、 https://dataplane.rum.ap-notheast-1.amazonaws.com/appmonitors/ にブラウザのクライアントデータを送信していました。マネジメントコンソールのCloudWatch RUMの画面にアクセスすると、データが取得されていることがわかります。 一方、テスト環境の https://my-domain.com:8443 にアクセスすると、 https://static.my-domain.com/rum.js からのレスポンスステータスは200で返りますが、クロスオリジンの読み込みが許可されていないためブラウザは スクリプト にアクセスできず、実行されません。 これでECSのブルーグリーンデプロイメントの本番環境のみ、CloudWatch RUMにデータ送信を実現できました。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 - セキュリティエンジニア(セキュリティ設計) 執筆: @kou.kinyo 、レビュー: @yamada.y ( Shodo で執筆されました )
去る10/9に実施された IPA さんの秋期 情報処理技術者試験 ですが、最近は試験直後に過去問が公開されるようになっているのを知っていますか。最近の過去問はなかなかに味わい深いと思っているので、所属部門で「味わう会」をやってみた、というお話をご紹介したいと思います。金融ソリューション事業部 石沢がレポートします。  ISIDでは応用情報以上の 情報処理技術者試験 の受験は強制でなく、昇格などの条件でもありません(新卒採用の場合、 基本情報技術者 資格の取得は原則必須としています)。一方で、受験費用は社費負担ですし、通信教育を利用することも可能です(通信教育は修了かつ試験に合格すれば社費負担になります)。というわけで、自分の スキルアップ 、腕試しなどで利用しやすい制度となっています。余談ですが、情報処理安全確保支援士など合格後に資格を維持する場合の費用も同様です(助かる~)。 今回紹介した過去問 時間の関係もあったので、令和4年度秋期 情報処理試験 のうち以下の過去問、試験区分を取り上げました。 プロジェクトマネージャ試験 午後Ⅰと午後Ⅱ データベーススペシャリスト 試験 午後Ⅰ システム監査技術者試験 午後Ⅰ 過去問はこちらのURLから参照可能です。以下の記事が面白いと感じられた人は実際の問題もぜひご確認ください。 IPA 独立行政法人 情報処理推進機構:過去問題 なお実際の「味わう会」は実際の試験日(10/9)の3日後にスピード勝負でやってみました。受験したばかりの人の記憶が薄れないうちに…… 過去問の紹介方法 紹介の目的は問題の正解を解説することでなく「試験問題を面白がる」ことにフォーカスしていたので、 ざっくりどんな問題が出たかを見てみる 受験した人が感想や意見を述べる という感じで紹介をしました。 個人の感想ですが、 IPA の 情報処理技術者試験 は継続的にアップデートされ、特に高度試験では日常われわれが直面するような課題を提示していると思っています。最近のトレンドなども割と反映されているのが味わい深いです。 具体的な紹介内容(プロジェクトマネージャ試験 編) 午後Ⅰ問2: ECサイト 刷新プロジェクトにおけるプロジェクト計画の問題 情報システム部の部長(兼PM)となった気持ちで、老舗アパレル企業の ECサイト を刷新するプロジェクトを担当する 仮想店舗( メタバース )を活用した新規事業開発がテーマ 新技術への匂い(DevOps、仮想店舗) 以上を背景にプロジェクト計画を立案する プロジェクトの最優先課題は何か 知見蓄積のためにどういう活動が必要か CI/CD導入のために組織を超えて行う活動は何か 顧客が自宅にいながら アバター として仮想店舗に来店して試着できる システム開発 プロジェクトのPMとは……。ちょっと想像しただけで大変そうです。ざっくりとした問題を紹介した上で、あーだこーだと意見交換しました(以下、同様です)。 午後Ⅰ問3:プロジェクトにおけるチームビルディングに関する問題 支配型・統制型リーダーシップが支配する玩具製造業F社においてDX推進を進めるプロジェクト。 CDO が旗を振っており、そのもとでシステム部長G氏がPMとしてプロジェクトをけん引します PMはDXを実現するために事業戦略的なITプロジェクトが必要と判断。部門横断のチーム組成を行い、自己組織化されたチーム作りを目指します いわゆるDXプロジェクトのPMです。いやぁ、こんな問題が出るとは(遠い目)。 午後Ⅱ問2: ステークホルダー とのコミュニケーション 午後Ⅱは小論文を書く問題です。問2は自身の経験に基づき「 ステークホルダー とのコミュニケーション」について記述する問題でしたが、実際に受験をしてこの設問を選んだ人にどのような事を書いたのかを紹介してもらいました。 具体的な紹介内容( データベーススペシャリスト 試験 編) 午後Ⅰ問1:アフターサービス業務の概念 モデリング 住宅設備メーカーA社が、アフターサービス業務のシステム再構築プロジェクトで業務分析を行い、概念データモデルと関係 スキーマ を作成したという状況 問題文には業務分析の ヒアリ ング結果と、新システムでの要求事項が( 自然言語 で)ズラーっと書いてある 設問は、まず現状の概念データモデルを完成させ、次に新システムの要求事項を考慮した修正後の概念データモデルを穴埋め問題で完成させるもの 受託開発などのシチュエーションでよくある局面です。特に要件定義などを実施する人はさらっと解きたい。 午後Ⅰ問2: クラウド PaaSを用いたデータベースの実装 専門商社のBは見積システムのマスター保守改善を行いつつ、 パブリッククラウド へ移行する方針 見積システムでは商品履歴の管理が出来ていないので、まずこれはDBトリガーでなんとかする さらに クラウド リフトするので、非機能の検討も行った 設問は上記の結果の穴埋め問題 保守小規模エンハンスでもありそうな機能レベルアップを DBMS だけでなんとかする問題と、 クラウド リフト案件として非機能要件(RPOやRTOなど)検討という、二つの問題を合体させたような欲張りな問題です。 午後Ⅰ問3:オンプレ販売管理システムのDBパフォーマンス改善 事務用品販売C社の運用中の販売管理システムにおける RDBMS 課題に関する問題 主要なテーブル構成、業務利用方法、現在の課題(業務ピーク日にバッチ遅延が原因の出庫遅延発生)が本文で説明されている 分析の結果 ①アクセス数の多いテーブルの再編成を検討したが、問題があったので見送りとした ② バッチ処理 の多重化を検討し、 デッドロック が発生するリスクも分析をした ③出庫遅延に関しては ボトルネック の調査を行った 問題は上記①~③の穴埋め問題、およびそれぞれの作業に関するリスクを文章で答えさせる形式 DBパフォーマンス改善、これもよくある話ですね。 具体的な紹介内容(システム監査技術者 編) 午後Ⅰ問1: ECサイト における改正 個人情報保護法 対応の十分性の監査 個人情報を取り扱う ECサイト に関して、改正 個人情報保護法 の対応が適切にされているかを監査する問題 サイトに関する監査の結果(予備調査、本調査)が問題文には示され、「どうしてそのような結論になったか。その際に調査すべき観点と対象は何か」を答えさせるもの システム監査の観点だけではなく、改正 個人情報保護法 の論点も理解できる問題でした。 午後Ⅰ問2:リモートワークに即したWF利用等業務改善の推進状況の監査 数年前にワークフロー(WF)システムを導入済の企業が、コロナ対策でリモートワークを推進したことをきっかけに「 働き方改革 」「業務効率化(申請事務の廃止やペーパーレス化)」を現在全社的に推進している 経営方針に即して対応が進められているか監査を実施した。監査の結果が問題文で示され、問ではそれぞれの調査をどのように行ったのか(確認手段と確認観点)を回答させる問題 極めて現代的な問題。ISIDもリモートワークを推進していますが、業務効率化って大変だなとしみじみ感じられる問題です。 午後Ⅰ問3:金融機関のシステム運用(再委託)適切性の監査 金融機関Cは、 クラウド サービスの活用なども進めているが、一方で既存システムの安定稼働は重要な経営課題と認識。システム部は情報システムの安定稼働の対策を継続して実施中 C社は運用を子会社D社に委託。D社は複数社に再委託している。夜間の運用は再委託先のみで実施しているが、障害発生時にD社保守担当に連絡が取れない事もあるので、その場合はD社運用管理部責任者の承認により必要最低限の対応を行うことになっている この状況を監査するにあたって、予備調査でいろいろと気になる点が出てきた。それをもとにどのような本調査を計画するかが問題となっている 「味わう会」を実施した金融ソリューション事業部としては、まさに日常的な風景な問題です。監査の際にどのような点が論点になるのかもわかる問題でした。 やってみた感想 参加者からは「想像していたよりも実務的な内容でびっくりした」「自分のチェックしていなかった試験区分に興味を持てた」などのリアクションがありました。 ソフトウェアを開発するにあたっては、スキルと経験が重要だと思っています。スキルはいいとしても、多様な経験を積むには時間を要するので、こういった問題に触れることは不足する経験を補う良いト レーニン グだと考えています。 世間的にも、また当然社内でも「資格試験は実務と乖離していて、勉強する意義はない」「内容は古臭くて、現代の業務に合っていない(から勉強する意味はない)」といった声があるのは事実ですが、実際に問題を眺めてみるとそうではないことがわかります。 本記事で興味をもった皆さんには、周囲の方と一緒に問題を眺めながら意見交換してみることをお勧めします。 私たちは同じグループで共に働いていただける仲間を募集しています。 金融システムプロジェクトマネージャー  他多数募集中です。 仕事をしながら学ぶことへの支援制度は今回ご紹介したもの以外も充実しています。また、 情報処理技術者試験 の過去問を語り合ったりする組織に興味のある方も、ぜひご検討よろしくお願いします。 執筆: Ishizawa Kento (@kent) 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
去る10/9に実施された IPA さんの秋期 情報処理技術者試験 ですが、最近は試験直後に過去問が公開されるようになっているのを知っていますか。最近の過去問はなかなかに味わい深いと思っているので、所属部門で「味わう会」をやってみた、というお話をご紹介したいと思います。金融ソリューション事業部 石沢がレポートします。  ISIDでは応用情報以上の 情報処理技術者試験 の受験は強制でなく、昇格などの条件でもありません(新卒採用の場合、 基本情報技術者 資格の取得は原則必須としています)。一方で、受験費用は社費負担ですし、通信教育を利用することも可能です(通信教育は修了かつ試験に合格すれば社費負担になります)。というわけで、自分の スキルアップ 、腕試しなどで利用しやすい制度となっています。余談ですが、情報処理安全確保支援士など合格後に資格を維持する場合の費用も同様です(助かる~)。 今回紹介した過去問 時間の関係もあったので、令和4年度秋期 情報処理試験 のうち以下の過去問、試験区分を取り上げました。 プロジェクトマネージャ試験 午後Ⅰと午後Ⅱ データベーススペシャリスト 試験 午後Ⅰ システム監査技術者試験 午後Ⅰ 過去問はこちらのURLから参照可能です。以下の記事が面白いと感じられた人は実際の問題もぜひご確認ください。 IPA 独立行政法人 情報処理推進機構:過去問題 なお実際の「味わう会」は実際の試験日(10/9)の3日後にスピード勝負でやってみました。受験したばかりの人の記憶が薄れないうちに…… 過去問の紹介方法 紹介の目的は問題の正解を解説することでなく「試験問題を面白がる」ことにフォーカスしていたので、 ざっくりどんな問題が出たかを見てみる 受験した人が感想や意見を述べる という感じで紹介をしました。 個人の感想ですが、 IPA の 情報処理技術者試験 は継続的にアップデートされ、特に高度試験では日常われわれが直面するような課題を提示していると思っています。最近のトレンドなども割と反映されているのが味わい深いです。 具体的な紹介内容(プロジェクトマネージャ試験 編) 午後Ⅰ問2: ECサイト 刷新プロジェクトにおけるプロジェクト計画の問題 情報システム部の部長(兼PM)となった気持ちで、老舗アパレル企業の ECサイト を刷新するプロジェクトを担当する 仮想店舗( メタバース )を活用した新規事業開発がテーマ 新技術への匂い(DevOps、仮想店舗) 以上を背景にプロジェクト計画を立案する プロジェクトの最優先課題は何か 知見蓄積のためにどういう活動が必要か CI/CD導入のために組織を超えて行う活動は何か 顧客が自宅にいながら アバター として仮想店舗に来店して試着できる システム開発 プロジェクトのPMとは……。ちょっと想像しただけで大変そうです。ざっくりとした問題を紹介した上で、あーだこーだと意見交換しました(以下、同様です)。 午後Ⅰ問3:プロジェクトにおけるチームビルディングに関する問題 支配型・統制型リーダーシップが支配する玩具製造業F社においてDX推進を進めるプロジェクト。 CDO が旗を振っており、そのもとでシステム部長G氏がPMとしてプロジェクトをけん引します PMはDXを実現するために事業戦略的なITプロジェクトが必要と判断。部門横断のチーム組成を行い、自己組織化されたチーム作りを目指します いわゆるDXプロジェクトのPMです。いやぁ、こんな問題が出るとは(遠い目)。 午後Ⅱ問2: ステークホルダー とのコミュニケーション 午後Ⅱは小論文を書く問題です。問2は自身の経験に基づき「 ステークホルダー とのコミュニケーション」について記述する問題でしたが、実際に受験をしてこの設問を選んだ人にどのような事を書いたのかを紹介してもらいました。 具体的な紹介内容( データベーススペシャリスト 試験 編) 午後Ⅰ問1:アフターサービス業務の概念 モデリング 住宅設備メーカーA社が、アフターサービス業務のシステム再構築プロジェクトで業務分析を行い、概念データモデルと関係 スキーマ を作成したという状況 問題文には業務分析の ヒアリ ング結果と、新システムでの要求事項が( 自然言語 で)ズラーっと書いてある 設問は、まず現状の概念データモデルを完成させ、次に新システムの要求事項を考慮した修正後の概念データモデルを穴埋め問題で完成させるもの 受託開発などのシチュエーションでよくある局面です。特に要件定義などを実施する人はさらっと解きたい。 午後Ⅰ問2: クラウド PaaSを用いたデータベースの実装 専門商社のBは見積システムのマスター保守改善を行いつつ、 パブリッククラウド へ移行する方針 見積システムでは商品履歴の管理が出来ていないので、まずこれはDBトリガーでなんとかする さらに クラウド リフトするので、非機能の検討も行った 設問は上記の結果の穴埋め問題 保守小規模エンハンスでもありそうな機能レベルアップを DBMS だけでなんとかする問題と、 クラウド リフト案件として非機能要件(RPOやRTOなど)検討という、二つの問題を合体させたような欲張りな問題です。 午後Ⅰ問3:オンプレ販売管理システムのDBパフォーマンス改善 事務用品販売C社の運用中の販売管理システムにおける RDBMS 課題に関する問題 主要なテーブル構成、業務利用方法、現在の課題(業務ピーク日にバッチ遅延が原因の出庫遅延発生)が本文で説明されている 分析の結果 ①アクセス数の多いテーブルの再編成を検討したが、問題があったので見送りとした ② バッチ処理 の多重化を検討し、 デッドロック が発生するリスクも分析をした ③出庫遅延に関しては ボトルネック の調査を行った 問題は上記①~③の穴埋め問題、およびそれぞれの作業に関するリスクを文章で答えさせる形式 DBパフォーマンス改善、これもよくある話ですね。 具体的な紹介内容(システム監査技術者 編) 午後Ⅰ問1: ECサイト における改正 個人情報保護法 対応の十分性の監査 個人情報を取り扱う ECサイト に関して、改正 個人情報保護法 の対応が適切にされているかを監査する問題 サイトに関する監査の結果(予備調査、本調査)が問題文には示され、「どうしてそのような結論になったか。その際に調査すべき観点と対象は何か」を答えさせるもの システム監査の観点だけではなく、改正 個人情報保護法 の論点も理解できる問題でした。 午後Ⅰ問2:リモートワークに即したWF利用等業務改善の推進状況の監査 数年前にワークフロー(WF)システムを導入済の企業が、コロナ対策でリモートワークを推進したことをきっかけに「 働き方改革 」「業務効率化(申請事務の廃止やペーパーレス化)」を現在全社的に推進している 経営方針に即して対応が進められているか監査を実施した。監査の結果が問題文で示され、問ではそれぞれの調査をどのように行ったのか(確認手段と確認観点)を回答させる問題 極めて現代的な問題。ISIDもリモートワークを推進していますが、業務効率化って大変だなとしみじみ感じられる問題です。 午後Ⅰ問3:金融機関のシステム運用(再委託)適切性の監査 金融機関Cは、 クラウド サービスの活用なども進めているが、一方で既存システムの安定稼働は重要な経営課題と認識。システム部は情報システムの安定稼働の対策を継続して実施中 C社は運用を子会社D社に委託。D社は複数社に再委託している。夜間の運用は再委託先のみで実施しているが、障害発生時にD社保守担当に連絡が取れない事もあるので、その場合はD社運用管理部責任者の承認により必要最低限の対応を行うことになっている この状況を監査するにあたって、予備調査でいろいろと気になる点が出てきた。それをもとにどのような本調査を計画するかが問題となっている 「味わう会」を実施した金融ソリューション事業部としては、まさに日常的な風景な問題です。監査の際にどのような点が論点になるのかもわかる問題でした。 やってみた感想 参加者からは「想像していたよりも実務的な内容でびっくりした」「自分のチェックしていなかった試験区分に興味を持てた」などのリアクションがありました。 ソフトウェアを開発するにあたっては、スキルと経験が重要だと思っています。スキルはいいとしても、多様な経験を積むには時間を要するので、こういった問題に触れることは不足する経験を補う良いト レーニン グだと考えています。 世間的にも、また当然社内でも「資格試験は実務と乖離していて、勉強する意義はない」「内容は古臭くて、現代の業務に合っていない(から勉強する意味はない)」といった声があるのは事実ですが、実際に問題を眺めてみるとそうではないことがわかります。 本記事で興味をもった皆さんには、周囲の方と一緒に問題を眺めながら意見交換してみることをお勧めします。 私たちは同じグループで共に働いていただける仲間を募集しています。 金融システムプロジェクトマネージャー  他多数募集中です。 仕事をしながら学ぶことへの支援制度は今回ご紹介したもの以外も充実しています。また、 情報処理技術者試験 の過去問を語り合ったりする組織に興味のある方も、ぜひご検討よろしくお願いします。 執筆: Ishizawa Kento (@kent) 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Web3開発入門シリーズ with Algorand(アルゴランド)、今回のテーマは、 TestNetでALGOをゲット です。 今回の記事のGoogle Colab用のノートブックはこちらになります。ブラウザだけでブロックチェーンに対するコードを、実際に動かしながら試せるので、おすすめです。 Web3開発入門 with Algorandの全てのコンテンツ アカウント TestNetでALGOをゲット 支払い アセットの作成 MainNet TestNet BetaNet Algorand Dispenser ALGOの残高を確認する まとめ 仲間募集 Algorand には、目的に合わせて、いくつかのネットワークが用意されています。 MainNet Algorand の本番用のネットワーク。みんなが Algorand と思っているのはこのネットワークです。 TestNet テスト用のネットワーク。 MainNet を使う前に、テストするために使います。基本は、 MainNet と同じバージョンです。 入門シリーズで使うのはこのネットワークです。 BetaNet ベータ版の Algorand をテストするためのネットワーク。コアな開発者向けなので、あまり使うことはないでしょう。 Algorand Dispenser Algorand は、 トランザクション 手数料が0.05円(0.001 ALGO)と非常に安価ですが、 トランザクション を処理してもらうためには必ず手数料がかかります。 MainNet では、クレジットカードや暗号資産取引所を通じて、有料でALGOを手に入れます。 TestNet では、 Algorand Dispenser のサイトで、 ALGO を無料でゲットできます。 アカウントを作成するために、 py-algorand-sdk をインストールしましょう。 下記のコードを実行してください。 !pip3 install py-algorand-sdk アカウントを作成しましょう。 下記のコードを実行してください。 from algosdk import account private_key, address = account.generate_account() print("Address:", address) 出力結果の例です。 Address: NPDQGX5PSKDOUDUK22CRCCEPMZXQY62YWE25APS73F4MNAWJQLUN5SDFRA Address: の後ろの部分のアドレスをコピーしてください。 Algorand Dispenser のサイトにアクセスし、 I'm not a roboot をチェックし、 target address の入力エリアに先ほどコピーしたアドレスをペーストします。 Dispense のボタンをクリックしてください。 Status: Code 200 success のように表示されればOKです。 ALGOの残高を確認する 先程のアカウントに本当に ALGO が入金されたのか確認してみましょう。 Algorand にアクセスするので、 AlgodClient オブジェクトを作成します。 下記のコードを実行してください。 from algosdk.v2client.algod import AlgodClient algod = AlgodClient("", "https://node.testnet.algoexplorerapi.io:443") AlgodClient オブジェクトを作成するときに、 https://node.testnet.algoexplorerapi.io:443 を指定していますね。これにより Algoexplorer の TestNet のノードに接続します。 https://node.algoexplorerapi.io:443 にすると、 MainNet に接続できます。 アカウントの情報は、 AlgodClientオブジェクト.account_info() を呼び出して取得します。 下記のコードを実行してください。 info = algod.account_info(address) info オブジェクトの amount 属性に ALGO の残高が入っています。 下記のコードを実行してください。 print("micro ALGO amount", info["amount"]) print("ALGO amount", info["amount"] / 1000000) Algorand 内部では、 ALGO は micro ALGO という単位で管理されています。1000000 micro ALGO が1 ALGO に相当します。 つまり、 micro ALGO を1000000(0六個)で割れば、 ALGO になります。 Algorand Dispenser で10 ALGOをゲットできましたね。 まとめ TestNet では、 Algorand Dispenser で簡単に ALGO をゲットできることが体験できたと思います。 次回は、 支払い です。 なにか感想があれば、 Twitter で @yasuo_algo にメンションしてつぶやいてください。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア 執筆: @higa 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Web3開発入門シリーズ with Algorand(アルゴランド)、今回のテーマは、 TestNetでALGOをゲット です。 今回の記事のGoogle Colab用のノートブックはこちらになります。ブラウザだけでブロックチェーンに対するコードを、実際に動かしながら試せるので、おすすめです。 Web3開発入門 with Algorandの全てのコンテンツ アカウント TestNetでALGOをゲット 支払い アセットの作成 MainNet TestNet BetaNet Algorand Dispenser ALGOの残高を確認する まとめ 仲間募集 Algorand には、目的に合わせて、いくつかのネットワークが用意されています。 MainNet Algorand の本番用のネットワーク。みんなが Algorand と思っているのはこのネットワークです。 TestNet テスト用のネットワーク。 MainNet を使う前に、テストするために使います。基本は、 MainNet と同じバージョンです。 入門シリーズで使うのはこのネットワークです。 BetaNet ベータ版の Algorand をテストするためのネットワーク。コアな開発者向けなので、あまり使うことはないでしょう。 Algorand Dispenser Algorand は、 トランザクション 手数料が0.05円(0.001 ALGO)と非常に安価ですが、 トランザクション を処理してもらうためには必ず手数料がかかります。 MainNet では、クレジットカードや暗号資産取引所を通じて、有料でALGOを手に入れます。 TestNet では、 Algorand Dispenser のサイトで、 ALGO を無料でゲットできます。 アカウントを作成するために、 py-algorand-sdk をインストールしましょう。 下記のコードを実行してください。 !pip3 install py-algorand-sdk アカウントを作成しましょう。 下記のコードを実行してください。 from algosdk import account private_key, address = account.generate_account() print("Address:", address) 出力結果の例です。 Address: NPDQGX5PSKDOUDUK22CRCCEPMZXQY62YWE25APS73F4MNAWJQLUN5SDFRA Address: の後ろの部分のアドレスをコピーしてください。 Algorand Dispenser のサイトにアクセスし、 I'm not a roboot をチェックし、 target address の入力エリアに先ほどコピーしたアドレスをペーストします。 Dispense のボタンをクリックしてください。 Status: Code 200 success のように表示されればOKです。 ALGOの残高を確認する 先程のアカウントに本当に ALGO が入金されたのか確認してみましょう。 Algorand にアクセスするので、 AlgodClient オブジェクトを作成します。 下記のコードを実行してください。 from algosdk.v2client.algod import AlgodClient algod = AlgodClient("", "https://node.testnet.algoexplorerapi.io:443") AlgodClient オブジェクトを作成するときに、 https://node.testnet.algoexplorerapi.io:443 を指定していますね。これにより Algoexplorer の TestNet のノードに接続します。 https://node.algoexplorerapi.io:443 にすると、 MainNet に接続できます。 アカウントの情報は、 AlgodClientオブジェクト.account_info() を呼び出して取得します。 下記のコードを実行してください。 info = algod.account_info(address) info オブジェクトの amount 属性に ALGO の残高が入っています。 下記のコードを実行してください。 print("micro ALGO amount", info["amount"]) print("ALGO amount", info["amount"] / 1000000) Algorand 内部では、 ALGO は micro ALGO という単位で管理されています。1000000 micro ALGO が1 ALGO に相当します。 つまり、 micro ALGO を1000000(0六個)で割れば、 ALGO になります。 Algorand Dispenser で10 ALGOをゲットできましたね。 まとめ TestNet では、 Algorand Dispenser で簡単に ALGO をゲットできることが体験できたと思います。 次回は、 支払い です。 なにか感想があれば、 Twitter で @yasuo_algo にメンションしてつぶやいてください。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア 執筆: @higa 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )