Corredor

ウェブ、プログラミングの勉強メモ。

Amazon Fire 7 タブレット (2019年モデル) を高速化・Google Play 導入。root 化はできず

昨年2019年、セールで3,280円になっていたのでとりあえず買った Amazon Fire 7 タブレット。性能が悪くてやたらとモッサリするので、すぐに使わなくなっていた。

neos21.hateblo.jp

しかし、画面を切った状態からでも Alexa を呼び出せるハンズフリー機能があるので、スマートスピーカー代わりに使えるかもな?と思い直し、それに合わせて少し手を入れることにした。

前提条件

  • Fire 7 : 2019年モデル・容量 16GB
  • Fire OS : v6.3.1.5

開発者オプションで高速化する

「設定」アプリ → 「デバイスオプション」と移動し、「シリアル番号」欄を7回タップする。すると「設定」アプリのトップメニューに「開発者オプション」が登場する。

この「開発者オプション」で、

  • ウィンドウアニメスケール
  • トランジションアニメスケール
  • Animator 再生時間スケール

の3つを「1x」から「オフ」にすると、見た目の動作が少し速くなる。

アニメーションを無効にすることで描画に不具合が出た場合は「オフ」ではなく「.5x」で様子見すると良いかと。

あとは

  • バックグラウンドプロセスの上限
    • 2 くらいの小さい値にしておくと、裏で動くモノが減らせる (お好みで)
  • GPU レンダリングを使用
    • オンにする

あたりを設定しておく。

Google Play を入れてみる

上のサイトが一番分かりやすかった。Fire OS のバージョンに合わせて4つの APK をダウンロードし、順番にインストールするだけで出来た。2019年モデルの Fire 7 だと以下を 1. から順番にインストールする。

  1. Google Account Manager 7.1.2
  2. Google Services Framework 7.1.2
  3. Google Play services 14.3.66 (040300-213742215)
  4. Google Play Store 12.0.19-all 0 PR 215617186

インストール後、再起動するとホーム画面に「Play ストア」が追加されている。コレで高機能な Gmail アプリや、Chrome ブラウザなどが入れられる。

Chrome ブラウザのユーザアカウント同期はすぐに開始できない場合がある。時間を置いて再チャレンジしたりするとうまく行った。謎。

ちなみに Brave ブラウザはインストールできたが対応していないということでエラーが出て動かなかった。

Fire OS 6 の場合、「Google 設定」アプリはホーム画面に表示されていないので、次の手順で開く。

「Quick Shortcut Maker」で「Google 設定」「Android 設定」を開く

Google Play ストアで「Quick Shortcut Maker」というアプリをダウンロードする。

アプリを開いたら、

  • com.google.android.gms/com.google.android.gms.app.settigns.GoogleSettingsLink
    • Google Play 開発者サービス
  • com.android.settings/com.android.settings.Settings$UserAndAccountDashboardActivity
    • Android 設定 … ユーザとアカウント

を探して「起動」すれば OK。なおショートカットは作成できないので、このアプリ内の「お気に入り」を使って管理する。

Swapper & Tools で仮想メモリを増やす → Fire OS 6 では出来ない

仮想メモリを増やす Swapper & Tools という Android アプリがあるらしいのだが、Fire OS 6 では使えなかった。

/sdcard という SD カードを示すディレクトリが権限不足で見られなかったため、機能を有効にできなかった。

Termux アプリは導入できるが root 化はできず

Google Play ストアから Termux というターミナルアプリがインストールできるのだが、コレを使った root 化はできなかった。

どうやら Fire OS v6.3.1.2 以降は root 化するための穴が塞がれており、自分の Fire 7 は v6.3.1.5 にアップデートされてしまっていたために動かなかった。

Fire 7 (2019) には Echo Show モードがない

なんや色々とうまくいかないところが出てきているな…。おまけに Fire 7 はスマートディスプレイとして使える「Show モード」に対応していないので、画面を常時点灯させてホームコンテンツを表示させたりできない。

開発者オプションにて、「充電中は画面をスリープモードにしない」という設定が出来るので、コレを使って擬似的に Show モード風の運用は可能ではあるが、なんだか残念。

ところで、Fire タブレットの各種ブラウザ (Silk や Firefox など) では、HTML の Fullscreen API が動作した。全画面表示に対応させた Web アプリを自作して、それを表示させれば、UI はもう少しイイカンジにできるかもしれない。と思ったり。

とりあえず以上

とりあえず、開発者オプションと Play ストアの導入で、もう少し快適に使える端末にはなった。

しかしやはりメモリが少なく、動作がモッサリしていて、スマートディスプレイ的な使い方以外は厳しそうだ。

ということで今後は、よりスマートディスプレイ的に使えるように、Fullscreen API を使った Web アプリでも作ってみようと思う。

Fire HD 10 タブレット ブラック (10インチHDディスプレイ) 32GB

Fire HD 10 タブレット ブラック (10インチHDディスプレイ) 32GB

  • 発売日: 2019/10/30
  • メディア: エレクトロニクス

Node.js スクリプトを CGI として動かしてみる

CGI という仕組みは、Perl・Ruby・PHP などの言語に限らず、標準入力と標準出力を扱える言語なら何でもいいらしい。ということは、Node.js をランタイムにした CGI も可能だと思われる。

と、ふと思い立って実際にやってみた。

前例がある:CGI-Node

同じようなことを考えていた人がいた。

$ cat cgi-bin/node_test.cgi
#!/usr/bin/node

console.log('Content-type: text/plain');
console.log('');
console.log('Hello World');

$ chmod 755 cgi-bin/node_test.cgi

シンプルなコードが掲載されていたが、コレでやれるみたいだ。

1行目の Shebang は node (Windows なら node.exe) へのフルパスを渡してやる必要があり、/usr/bin/node/usr/bin/env node なんかだとうまく行かないことがあるみたい。Nodebrew なんかを使っている場合はそもそも ~/.nodebrew/ 配下にあったりするので、この辺は実行環境に合わせてフルパスを書いてやる必要がありそうだ。

Node.js の組み込みパッケージである fs なんかは使えそうだが、それ以外の npm パッケージを require() するのはできないんじゃないかな。試してないけど。

CGI として動かせる一式が組まれた、CGI-Node というスクリプトも存在した。

リクエストヘッダとかを処理してくれるのは分かったが、.htaccess にて cgi-node.js を挟んで処理するよう設定する必要があるらしい。若干面倒臭い。

Node.js ならサーバを立てて動かせばいいじゃん、とも思うだろうが、Apache や nginx から、localhost:3000 とかで動いてるサーバにリクエストをフォワードしてやるのも面倒臭い気がする。既存の CGI サーバで、CGI スクリプトとして動かしたい時に、開発言語として Node.js を選びたい、という時のための検証である。w

前提条件

今回の前提として、Apache サーバで CGI が動かせるようにしてあって、拡張子は .cgi を対象としている。お好みで .jss とかを CGI として動かせるようにしておくと良いかと (.js を対象にしてしまうと通常の JS ファイルと混在して辛い)。

Node.js は Nodebrew でインストールし、$ type node コマンドで確認できたフルパスを Shebang に書くこととする。

最低限 CGI として動かせるコード

Node-CGI は大仰なので、一切の依存を持たず、完全なシングルファイルで CGI として動作するスクリプトを組んでみる。最低限以下を書けば、CGI として動かせる。

  • example-node-cgi.js.cgi
    • 拡張子は CGI として動かせるモノなら何でも良い
    • 実行権限を付与しておく
#!/usr/bin/node
// ↑実行パスは適宜調整する

(async () => {
  let postBody = '';  // POST 時にリクエストボディを取得する
  if(process.env.REQUEST_METHOD === 'POST') for await(const chunk of process.stdin) postBody += chunk;
  
  // HTTP ヘッダを出力する
  console.log('Content-Type: text/html; charset=UTF-8\n\n');
  
  // 以下、任意の処理
  console.log('<h1>Hello Node.js CGI</h1>');
})();

asyncawait を使っている他、for await of を使っているので Node.js v12 以降が対象。それ以前の古いバージョンで動かすには、次のようなコードにすれば動かせる。

#!/usr/bin/node

new Promise((resolve) => {
  // POST 時にリクエストボディを取得する
  if(process.env.REQUEST_METHOD !== 'POST') return resolve();
  
  let postBody = '';
  process.stdin.on('data', (chunk) => { postBody += chunk; });
  process.stdin.on('end', () => { resolve(postBody); });
}).then((postBody) => {
  // HTTP ヘッダを出力する
  console.log('Content-Type: text/html; charset=UTF-8\n\n');
  
  // 以下、任意の処理
  console.log('<h1>Hello Node.js CGI</h1>');
});

POST 時のリクエストボディのみ、process.stdin から取得するため、このような処理を入れている。GET でしか処理しないのであれば、これらの前処理すら省いていきなり「HTTP ヘッダの出力」から実装しても良い。

Node.js を CGI として使うためのアレコレ

POST 時のリクエストボディのみ、取得方法が特殊なので上のとおり実装しておいたが、それ以外の情報はどのように受け取れば良いか。

GET 時のクエリ文字列や、サーバの情報、リクエスト情報などは、全て process.env を見ることで確認できる。代表的なモノは以下のとおり。

環境変数名 内容
HTTP_HOST サーバの Host Name or Public IP
SERVER_NAME
SERVER_ADDR サーバの Private IP
SERVER_PORT サーバのポート (80 とか 443 とか)
DOCUMENT_ROOT Apache サーバ等のドキュメントルート
CONTEXT_DOCUMENT_ROOT
REMOTE_ADDR リクエスト元の Public IP
HTTP_USER_AGENT リクエスト元の User Agent
HTTPS HTTPS 接続時は on が設定される
REQUEST_SCHEME プロトコル (httphttps)
REQUEST_METHOD メソッド (GETPOST)
SCRIPT_FILENAME CGI ファイルのフルパス
SCRIPT_NAME CGI ファイルへのルート相対パス (/ 始まり)
REQUEST_URI リクエストパス (/ 始まり、クエリ文字列を含む)
QUERY_STRING クエリ文字列 (? は含まない)

レスポンスは process.stdout.write()console.log() で書けば良い。

console.log('<pre>', process.env, '</pre>');

などとコーディングすれば、リクエスト時の環境変数一覧が出力できる (セキュリティ的に注意が必要だが)。

その他、エラー発生時の処理やプロセス終了時の処理を process.on で定義しておくと安全であろう。

// エラー発生時に行う処理
process.on('uncaughtException', (error) => {
  // HTTP ヘッダを出力してあるかどうか確認できるようフラグ変数を管理しておくと良いだろう
  console.log('Error :', error);
});

// 終了時の処理を予め定義する
process.on('exit', () => {
  // 全ての処理が終わった後に以下が実行される。ちゃんとレスポンスに乗る
  console.log('Exit');
});

ボイラープレートを作った

こうしたノウハウをまとめた、ボイラープレートプロジェクトを作った。以下の GitHub にある index.js をベースとして利用してもらえればと思う。

github.com

以上

ちゃんと Node.js でも CGI として動かせた。外部 npm パッケージが使えなさそうだが、単一ファイルとして動かせるようにビルドしてやればイケるかも?

最速攻略 CGI/Perl サンプル大全集

最速攻略 CGI/Perl サンプル大全集

  • 作者:KENT
  • 発売日: 2011/04/23
  • メディア: 大型本

Raspberry Pi 4 に RetroPie を入れてレトロゲーをやってみる

Raspberry Pi 4 の RAM 4GB モデルを買ったものの、用途が思い付かずほったらかしになっていた。巷では RG350M とか RK2020 とかいう、エミュレータを搭載した Linux ポータブルゲーム機が流行っているらしく、ラズパイで似たようなことができないかと思い、今回 RetroPie を入れてみることにした。

RetroPie とは

RetroPie は、Raspbian OS 上に複数ハードのエミュレータ一式を導入できるツール。

本来、ゲームのエミュレータは個別に開発がされていて統一感がないワケだが、ユーザと各エミュレータの間を取り持つ RetroArch という仕組みがあり、コレが UI を統一してくれている。

RetroArch が提供するホーム画面は PlayStation 風のモノになっていて、ココから全体設定をしたり、ハードを選択してゲームを開始したりできる。

しかし RetroArch のホーム画面はキーボード操作が前提になっていて扱いづらい場合もある。そこで RetroPie では、EmulationStation というフロントエンドを用意していて、コチラはジョイパッド (コントローラ) で操作できる UI を提供している。

RetroPie は、これら RetroArch と EmulationStation を統合してインストールできる仕組みだ。

RetroPie をインストールする

本稿執筆時点の RetroPie は v4.6 になっていて、Raspberry Pi 4 に正式対応したバージョンになっている。

GitHub に用意されているセットアップ用プロジェクトを使ってインストールを進めていく。以下、ラズパイ4のターミナルで作業。

# 各種パッケージを更新しておく
$ sudo apt-get update
$ sudo apt-get dist-upgrade -y

# Git が未導入であればインストールする
sudo apt-get install -y git

# 任意のディレクトリに移動し、セットアッププロジェクトを取得する
$ cd ~/
$ git clone --depth=1 https://github.com/RetroPie/RetroPie-Setup.git
$ cd RetroPie-Setup/

# セットアップスクリプトを起動する
$ sudo ./retropie_setup.sh

古い文献では、ラズパイ4向けの開発ブランチに git checkout fkms_rpi4 するような記述も見られたが、現在はそのようなブランチはなくなっており、master ブランチを使用して問題ない。

セットアップスクリプトを起動すると TUI が表示される。初回は

  • Basic Install

を選択し、フルインストールされるのをしばし待つ。

フルインストールが終わると同じ TUI 画面に戻ってくる。特に他に設定がなければ終了する。

Samba を有効化する

ラズパイに ROM ファイルを持ち込んだりする時に、Samba でディレクトリ共有しておくと楽だろう。RetroPie には Samba をセットアップする機能も用意されているので、お好みで有効化しておこう。

retropie_setup.sh の TUI で Configuration / tools を選択し、次の画面から samba - Configure Samba ROM Shares を選択する。そして Install RetroPie Samba shares を選択すれば、Samba と共有ディレクトリの設定が一気に行われる。

RetroPie の起動方法を設定する

RetroPie (実際は EmulationStation の UI) をどのように起動するかを設定できる。

Raspbian OS の起動画面をすっ飛ばして、いきなり RetroPie を開くようにすれば、ラズパイをゲーム専用機にできる。たまにゲームがしたいだけなら、今までどおり Raspbian OS のデスクトップが開くように設定できる。

retropie_setup.sh の TUI で Configuration / tools を選択し、次の画面から autostart を選択する。次の画面で、

  • Start EmulationStart at boot

を選択すればいきなり RetroPie が開く挙動になるし、

  • Boot to desktop (auto login as pi)

を選択すれば pi ユーザでログインした状態で Raspbian OS のデスクトップが開く挙動になる。

手動で RetroPie を立ち上げる際は、ターミナルで $ emulationstation と打ち込めば EmulationStation が起動する。

各種設定が終わったら TUI を終了し、一度再起動をかけておくと良いだろう。

EmulationStation は初回起動時の操作に注意

セットアップが完了したら、EmulationStation を起動してみる。初回はコントローラの設定があり、ココの操作が戸惑うので注意して操作する。

今回はとりあえずキーボードで初期設定を行うことにする。

EmulationStation が起動したら、初回は「Welcome」画面が開くはず。

No gamepads detected

Hold a button on your device to configure it.
Press F4 to quit at any time.

と表示されていると思うので、キーボードの何らかのキーを長押しして、キーボードを選択する。

次に「Configuring」画面に移動したら、ココからの操作は慎重に行う。

D-Pad (十字キー) の上から順に、押下したキーが適用される。途中での訂正が効かず、間違えても一旦最後まで設定し終えないといけないので慎重に設定していく。

ひととおりの設定が完了したら、設定したキーに応じて、十字キーの上下で項目移動「OK」ボタンの選択は A ボタンで行うことになる。「A ボタンに設定したキー」を押下しないと「OK」が押せないのが罠。適当に Enter や Space キーを押して反応しなくてしばらくつまづいた。

設定を終えて「OK」ボタンを押せば、RetroPie のホーム画面に移動するはずだ。

ROM を何も用意していないと、何のハードも選択できないはずなので、Samba を経由したりして ROM を指定のディレクトリに入れよう。

遊んでみた感じ

自分は GB・GBC・GBA・FC (NES)・SFC (SNES)・N64 の ROM を入れて少し遊んでみた。

N64 以外はほぼ遅延もなく快適に遊べた。VNC でリモート操作してみたのだが、それでもスーファミ程度ならほとんど遅延がなく、シビアなアクションゲームでなければリモートでも遊べちゃいそうなレベルだった。

まぁ GPU がない普通のノート PC でもスーファミレベルなら問題なく遊べる時代なので、ラズパイ4ならこのくらいサクサク動くのは当たり前かなー、という感じ。

N64 は実用に達しない

問題は N64 だった。動きがカクカクで、遊べたもんじゃない。どうやらラズパイのハード性能的に限界らしい。

N64 のエミュレートは RK2020 が対応しているようだが、RK2020 は画面が小さすぎてどうだかなーという感じ。本当に遊びたいなら PC で遊ぶか…。w

以上

RetroPie v4.6 はラズパイ4へのインストールも簡単で、スーファミ世代ぐらいまでのゲームであれば何のチューニングもせずに快適にプレイできることが分かった。

ラズパイにモバブーと小型液晶とジョイパッドを繋ぎ、レトロゲームを楽しむとしよう。

参考文献

GCP に中国からのアクセスがあり課金されたのでブロックする

中国のせいで金を取られた!(乱暴な言い方)

経緯

GCP で Always Free な IaaS (GCE) を立てて遊んでいる。無料だ無料だーと喜んでいたら、先月なぜか1円課金されていた。

請求書を見てみると、

  • Network Internet Egress from Americas to China

という項目で 0.021 ギガバイトの通信が発生していたようである。

調べてみると、Compute Engine の Always Free 制限にこう書かれていた。

1 GB の北米から全リージョン宛ての下り(外向き)ネットワーク(1 か月あたり、中国とオーストラリアを除く

ふむ、どうも中国からのアクセスに対してレスポンスしてしまったために、中国への下り通信が発生したようである。

今回は1円の課金で済んだし、設定していた請求アラートで1円課金された時点で気付けたから良いものの、コレ以上の意図しない課金は防ぎたい。そこで、中国との通信をブロックして、今後課金されないようにする。

中国の IP をファイアウォールでブロックする

今回の作業は、上のサイトを参考に行った。

GCP のファイアウォール設定で、中国の Public IP からのアクセスを全部ブロックしてやれば、下りのアクセスも発生しなくて課金されないだろう、ということである。まぁまぁ手順が多いので覚悟せよ。w

中国の Public IP 一覧ファイルを取得する

まずは上のサイトにアクセスし、右側の「Download」から「アクセス制御用ひな形」を押下する。プルダウンから「Nginx」を選択し、cn.nginx.txt をダウンロードする。

本稿執筆時点で、5351件分の CIDR ブロックが記述されていると思う。これらは全て、中国からの Public IP を封じるための設定である。

Public IP 一覧ファイルを JSON 形式に加工する

ダウンロードしたテキストファイルを開くと、次のような形式で記述されていると思う。

deny 1.0.1.0/24;
deny 1.0.2.0/23;

コレをテキストエディタで一括置換して、次のように書き換えていく。

[
  "1.0.1.0/24",
  "1.0.2.0/23",
    … (中略、以下は最終行を示す)
  "223.255.252.0/23"
]

トップレベルが配列の JSON ファイルとしてパースできるようにするワケだ。このようなテキストを china-ip.json というファイル名で保存したとする。

そしたら、この5351件の配列を256件分ずつの配列に分割する。

GCP のファイアウォールルールは、1ルールにつき CIDR ブロックを256件までしか設定できない。5351件分の CIDR をブロックするには、複数のルールを作らないといけないため、その下準備として配列を256件ずつに分解しようとしているのである。

今回は Node.js スクリプトを書いて、分割した JSON を吐いてやる。

  • script-chunk.js
const fs = require('fs');

// 元の JSON ファイルを require で読み込む
const chinaIp = require('./china-ip.json');

const arrayChunk = ([...array], size) => {
  return array.reduce((acc, value, index) => index % size ? acc : [...acc, array.slice(index, index + size)], []);
};

// 256件ずつに分割する
const chunks = arrayChunk(chinaIp, 256);
// ファイルに書き出す
fs.writeFileSync('./china-ip-chunk.json', JSON.stringify(chunks, null, '  '), 'utf-8');

このようなスクリプトを

$ node script-chunk.js

と実行してやると、china-ip-chunk.json が生成され、中身が次のようになっているはずである。

[
  [
    "1.0.1.0/24",
    "1.0.2.0/23",
      … (中略)
    "43.225.224.0/20"
  ],
  [
    "43.225.240.0/21",
      … (以下略)
  ]
]

配列の配列になっている。子の配列には CIDR ブロックが256件ずつ格納されている状態だ。

GCP の API Key を発行する

薄々気付いていると思うが、5351件もの CIDR を手作業でファイアウォールルールに追加していくのは大変だ。そこで、GCP の REST API を使って、一括登録してやろうと思う。

GCP の REST API を叩くには API KeyAccess Token が必要になる。API Key はすぐに発行できるのだが、Access Token の発行には OAuth Client ID というのをコネコネしてやらないといけなくて、若干面倒である。

とりあえず、まずは簡単に発行できる API Key を発行してしまおう。

Google APIs Console にログインし、画面上部で対象プロジェクトを選択する。続いて左メニューから「認証情報」を開く。

画面上部の「+ 認証情報を作成」を押下し、「API キー」を選択する。

「API キーを作成しました」ダイアログが表示されたら、発行された API キーをコピーしておく。

GCP の Access Token を発行する

続いて Access Token を発行するが、コレがちょっと分かりづらい。

上のサイトを参考に、「OAuth クライアント ID」と「スコープ」を取得しておき、これらを元にして Access Token を作成する。

OAuth クライアント ID を作成する

Google APIs Console にログインし、画面上部で対象プロジェクトを選択する。続いて左メニューから「認証情報」を開く。

画面上部の「+ 認証情報を作成」を押下し、「OAuth クライアント ID」を選択する。

f:id:neos21:20200806162327p:plain

初回だと「OAuth クライアント ID を作成するには、まず同意画面でプロダクト名を設定する必要があります」と表示されていると思う。そこで「同意画面を設定」ボタンを押下する。

f:id:neos21:20200806162331p:plain

「OAuth 同意画面」に移動したら、「User Type」は「外部」を選択して「作成」ボタンを押下する。

f:id:neos21:20200806162336p:plain

次の画面はとりあえず「アプリケーション名」だけ適当に入れておけば良い。画面下部の「保存」ボタンを押下する。

f:id:neos21:20200806162340p:plain

次のように「同意画面」が作れたと思う。

f:id:neos21:20200806162345p:plain

そしたらまた「+ 認証情報を作成」に戻る。今度は「OAuth クライアント ID の作成」画面でクライアント ID が作成できるようになっているはずだ。適当な名前を入力して「作成」ボタンを押下する。

f:id:neos21:20200806162349p:plain

「OAuth クライアントを作成しました」ダイアログが表示されたら、クライアント ID とクライアントシークレットをコピーしておく。

f:id:neos21:20200806162353p:plain

スコープ情報を取得する

スコープというのは、作成する Access Token が操作できる Google Cloud のサービスを指定するモノみたい。一度作った Access Token で、あれもこれも設定変更できたりしないようにするようだ。

今回行いたい操作は、GCP における VPC のファイアウォール登録だ。リファレンスを探すと、次の REST API リファレンスが見つかる。

この画面の右側に「Try this API」というペインがあり、その下部に「Google OAuth 2.0」という項目がある。ココの「Show scopes」を押下すると、

  • https://www.googleapis.com/auth/cloud-platform
  • https://www.googleapis.com/auth/compute

という2つの URL が表示される。この2つの URL をスコープとして定義してやれば良いようだ。

2つの URL をスペースで区切り、URI エンコードしてやることで、スコープ指定文字列として使えるようになる。今回はブラウザの開発者コンソールとかを使って、JavaScript で加工してやる。

// 次のようにスペース区切りにする
const scopeUrls = 'https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/compute';

// URI エンコードする
const scope = encodeURIComponent(scopeUrls);

// → https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcompute
// このような文字列が出力されるので、コレをコピーしておく

Authorization Code を発行する

OAuth クライアント ID を発行したら、まずは Authorization Code というモノを発行する。ターミナルで次のようなコマンドを組み立てていく。

CLIENT_ID='【クライアント ID】'
CLIENT_SECRET='【クライアントシークレット】'
REDIRECT_URI='urn:ietf:wg:oauth:2.0:oob'
SCOPE='【さきほど作成した URI エンコードしたスコープの URL 文字列】'

echo "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=${SCOPE}&access_type=offline"

最後の echo で、URL が組み立てられて出力されるので、コレをコピーしてブラウザで開く。

すると次のような画面が表示されるので、「許可」を選んでいく。

f:id:neos21:20200806162357p:plain

f:id:neos21:20200806162401p:plain

最後に Authorization Code が表示されるので、コピーしておく。

f:id:neos21:20200806162406p:plain

Authorization Code を利用し Access Token を取得する

Authorization Code を発行したら、先程のターミナルに戻り、次のようにコマンドを流していく。

AUTHORIZATION_CODE='【Authorization Code】'
curl \
  'https://www.googleapis.com/oauth2/v4/token' \
  --data "code=${AUTHORIZATION_CODE}" \
  --data "client_id=${CLIENT_ID}" \
  --data "client_secret=${CLIENT_SECRET}" \
  --data "redirect_uri=${REDIRECT_URI}" \
  --data "grant_type=authorization_code" \
  --data "access_type=offline"

f:id:neos21:20200806162411p:plain

curl のレスポンスとして JSON が表示され、access_token および refresh_token が取得できるのでコピーしておく。

コレでようやく Access Token が取得できた。

プロジェクト ID を確認する

最後にプロジェクト ID の確認。GCP 管理コンソールに移動して、プロジェクト選択欄でプロジェクト ID をコピーしておく。

REST API をコールするコマンドを構築していく

コレで API Key と Access Token が用意できた。GCP の REST API は curl でコールできるが、リクエストヘッダやパラメータを渡してやる必要があるので、curl コマンド一式を Node.js スクリプトで組み立ててやることにする。

  • script-generate-curl.js
const fs = require('fs');

// 256件ずつに分解した CIDR ブロック一覧の JSON ファイルを読み込む
const chinaIpChunk = require('./china-ip-chunk.json');

const projectId   = '【プロジェクト ID】';
const apiKey      = '【API Key】';
const accessToken = '【Access Token】';

// curl コマンド1つ分のテンプレート
const template = (projectId, apiKey, accessToken, firewallRuleName, ips) => `curl --request POST \\
  "https://compute.googleapis.com/compute/v1/projects/${projectId}/global/firewalls?key=${apiKey}" \\
  --header 'Authorization: Bearer ${accessToken}' \\
  --header 'Accept: application/json' \\
  --header 'Content-Type: application/json' \\
  --data '{"name":"${firewallRuleName}","denied":[{"IPProtocol":"all"}],"description":"","direction":"INGRESS","disabled":false,"enableLogging":false,"kind":"compute#firewall","logConfig":{"enable":false},"network":"projects/${projectId}/global/networks/default","priority":100,"selfLink":"projects/${projectId}/global/firewalls/${firewallRuleName}","sourceRanges":${ips}}' \\
  --compressed
`;

// curl コマンドを複数個組み立てていく
const script = chinaIpChunk.reduce((acc, ips, index) => {
  const num = `0${index + 1}`.slice(-2);
  const firewallRuleName = `china-${num}`;  // ファイアウォールのルール名は「china-00」(連番) とする
  const curl = template(projectId, apiKey, accessToken, firewallRuleName, JSON.stringify(ips));
  return acc + curl + '\n';
}, '');

// 組み立てた curl コマンドをファイルに書き出す
fs.writeFileSync('./exec.sh', script, 'utf-8');

コレを

$ node script-generate-curl.js

と実行すると、次のようなファイルが生成されるはずだ。

  • exec.sh
curl --request POST \
  "https://compute.googleapis.com/compute/v1/projects/【プロジェクト ID】/global/firewalls?key=【API Key】" \
  --header 'Authorization: Bearer 【Access Token】' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{"name":"china-01","denied":[{"IPProtocol":"all"}],"description":"","direction":"INGRESS","disabled":false,"enableLogging":false,"kind":"compute#firewall","logConfig":{"enable":false},"network":"projects/【プロジェクト ID】/global/networks/default","priority":100,"selfLink":"projects/【プロジェクト ID】/global/firewalls/china-01","sourceRanges":["1.0.1.0/24",【…中略…】,"43.225.224.0/20"]}' \
  --compressed

curl --request POST \
  "https://compute.googleapis.com/compute/v1/projects/【プロジェクト ID】/global/firewalls?key=【API Key】" \
  --header 'Authorization: Bearer 【Access Token】' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{"name":"china-02","denied":[{"IPProtocol":"all"}],"description":"","direction":"INGRESS","disabled":false,"enableLogging":false,"kind":"compute#firewall","logConfig":{"enable":false},"network":"projects/【プロジェクト ID】/global/networks/default","priority":100,"selfLink":"projects/【プロジェクト ID】/global/firewalls/china-02","sourceRanges":["43.225.240.0/21",【…中略…】,"43.255.232.0/22"]}' \
  --compressed

# 全部で curl コマンドが21個分できているはず (5351件 / 256件 = 20.9 ≒ 21)

分かりづらいが、--data 内の sourceRanges 部分で、CIDR ブロックを256件ずつ指定している。ルールの優先度は "priority":100 で指定しているので、変更したい場合は script-generate-curl.js に記載のテンプレート部分を調整してやる。

REST API をコールする

こうして exec.sh が生成できたら、

$ sh ./exec.sh

で実行してやろう。ファイアウォールルールが生成されるはずだ。

スクリプトを流し終えたら、GCP 管理コンソールでもファイアウォールルールを見てみよう。ルールの優先度などが問題なければコレで完了。

以上

このルール設定によって本当に課金されなくなるのか、しばらく様子見してみる。

GCPの教科書

GCPの教科書

  • 作者:吉積礼敏
  • 発売日: 2019/05/29
  • メディア: Kindle版