Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

週刊Railsウォッチ: 端末文字幅とRubyのreline、SQLのプリペアドステートメント、Terraformほか(20220222後編)

こんにちは、hachi8833です。本日2022年2月22日は2づくしですね。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙏

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Ruby

🔗 端末の文字幅問題


つっつきボイス:「BPS社内Slackに貼っていただいた記事です」「East Asian Widthの話とかいろいろ面白かった👍」「東アジアの文字幅が一定しないのは、踏むと厄介な問題ですね」「記事は一般的な端末の文字幅の話ですが、途中でRubyのrelineの話も出てきた↓」「relineといえばaycabtaさんがメンテしている、Rubyのirbでも重要な役割を果たしているgemですね」「そうそう、GNU ReadlineライブラリのRuby版」

ruby/reline - GitHub

参考: 東アジアの文字幅 - Wikipedia
参考: GNU Readline - Wikipedia

「relineで面白かったのは、正確な文字幅を取得するためにいったん(U+25BD)という幅があいまいになりがちな文字を一瞬だけ画面に出力して現在のカーソル位置を取得していること↓」「なるほど、たしかにレンダリングして測定すれば正確な文字幅は取れますね」「そうそう、泥臭いけど実用性は高い: レンダリング後は即座にを消して何事もなかったようにするけど、環境によっては一瞬見えることがあるらしい」「Vimも同じようなことをしてると書かれてる、へ〜!」

# 同記事より
private def may_req_ambiguous_char_width
  @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
  return if defined? @ambiguous_width
  Reline::IOGate.move_cursor_column(0)
  begin
    output.write "\u{25bd}"
  rescue Encoding::UndefinedConversionError
    # LANG=C
    @ambiguous_width = 1
  else
    @ambiguous_width = Reline::IOGate.cursor_pos.x
  end
  Reline::IOGate.move_cursor_column(0)
  Reline::IOGate.erase_after_cursor
end

「記事後半ではいろいろなCLIツールで文字幅がちゃんと表示されるかを調べてる」「PuTTYとか懐かしい」

参考: PuTTY - Wikipedia

👨‍👩‍👧‍👦みたいな合字もこういう話題でよく引き合いに出されますね」「relineはこういうのをちゃんと表示できてるんですって」「すごい」「relineは大丈夫でも、tmuxやbyobuあたりを通してRubyを使うと結局文字幅は崩れてしまうんですけどね」「これはRubyだけではどうしようもない...」「tmuxは普段からとてもよく使っているので文字幅が乱れるとつらい😢」

できるtmux-5分でわかる?仮想端末入門-

🔗 Glimmer DSL for OpalをRails 7で動かす(Ruby Weeklyより)

AndyObtiva/glimmer-dsl-opal - GitHub

つっつきボイス:「Glimmerって何だろうと思ったらこれみたいです↓」「Glimmer DSL for OpalはそれをOpalで使えるラッパーらしい」

AndyObtiva/glimmer - GitHub

opal/opal - GitHub

「Glimmer DSL for Opalのデモサイト↓を見てみると、普通のWebアプリっぽく動きますね: Glimmer DSLでGUIを書くと、こうやってWebでもデスクトップGUIにもできるということみたい」「お〜」

「ノリとしてはTcl/Tkみたいな感じっぽいかな」「Grimmerのリポジトリを見ると、Tcl/TkやGTKや、JRubyのSWTなどいろんなGUIに対応していますね」「こんなに移植されているとは」「JFX(JavaFX)にまで対応している、懐かしい」「JavaFXって初めて聞きました」

参考: Tcl/Tk - Wikipedia
参考: JavaFX

🔗 その他Ruby

つっつきボイス:「こちらは無料で読めます」「他の言語の人たちがRubyを使ったときに驚く点というのは目の付け所がいいですね👍」「Rubyに慣れた人が他の言語を使ったときの驚きについても読みたいかも」「そういえば今度なりゆきでrakeタスクをGoで書き直すことになりそうです...」

🔗DB

🔗 SQLのプリペアドステートメント


つっつきボイス:「はてブで見つけた記事です」「SQLインジェクション怖い」「この記事読みました: PHPなどのライブラリではとっくの昔に解決されている脆弱性が、今の時代にNode.jsのメジャーなライブラリで見つかったというのは、かなり驚きでしたね」

参考: SQLインジェクション - Wikipedia


なお、https://github.com/mysqljs/mysqlはREADME以外は最近ほとんど更新されていません。また、以下の記事によるとMySQL 8ではnode-mysql2が必要だそうです↓。node-mysql2は活発に更新されているようです。

参考: Node.js Tips: mysql8にはmysql2を使う | マサトッシュブログ

sidorares/node-mysql2 - GitHub


「おそらくですが、最終的にSQLのプリペアドステートメントを使わない形になってしまったんじゃないかな: 今どきデータベースライブラリでプリペアドステートメントを使わないというのはありえないと思います」

参考: プリペアドステートメント(prepared statement)とは - IT用語辞典 e-Words

「今さらですが、プリペアドステートメントとは?」「たとえば記事にあった以下のようなコード↓を見れば。誰でも内部で安全に処理されていると思いますよね」「思います思います、中でうまいことやってくれてるはずだって」

// 同記事より
...

app.post("/auth", function (request, response) {
 var username = request.body.username;
 var password = request.body.password;
 if (username && password) {
  connection.query(
   "SELECT * FROM accounts WHERE username = ? AND password = ?",
   [username, password],
   function (error, results, fields) {
    ...
   }
  );
 }
});

...

「記事によると、このライブラリはSQLクエリを単なるフラットな文字列に展開していたそうです」「え?」「ライブラリがDBのAPIを呼び出すときにプリペアドステートメントを使っていれば、変数部分に渡された値の内容がSQLとして解釈されることは仕組み上起こり得ない」「たしかに」「詳しくはこのあたりの記事を読むとよいと思います↓」

参考: IPA ISEC セキュア・プログラミング講座:Webアプリケーション編 第6章 入力・注入対策:SQL注入攻撃: #1 実装における対策


「もしプリペアドステートメントが使われていなかったとしたら、文字列で生クエリを発行していることになるので、SQLのクエリプランキャッシュもろくに効かなくなってたんじゃないかな」「毎回愚直にSQLクエリを解析する感じですか?」「そうそう、たとえばidだけが違って他は同じSQLクエリをたくさん発行する場合にクエリプランキャッシュが効かなくなる」「あ〜そういうことですか」

「そもそもプリペアドステートメントを使えば、たとえば以下のコード↓のように$1などで変数化されるので、クエリオプティマイザが以前の同型のクエリプランキャッシュを再利用できる、つまり高速化されるわけです」「主語や述語は違うけど文は同じとみなしてくれるから構文解析しなくてよくなるわけですね、なるほど」

// www.ipa.go.jpより
<?php
String parameter = ユーザが入力した値;
$dbc = pg_connect("dbname=pg_db");  // データベース接続

// 値を埋め込む前の形のSQL文をコンパイルし、構文を確定
$result = pg_prepare($dbc, "query1", 'SELECT name, price FROM product_table WHERE code=$1');

// $1の場所に値を指定してクエリの実行
$result = pg_execute($dbc, "query1", array(parameter));
?>

「でも値が文字列としてSQLクエリに入ってくると、値が変わるたびにSQL文の構文木も変わってしまうので、クエリオプティマイザが毎回SQL文を解析しなければならなくなってしまう」「クエリプランのキャッシュまで効かなくなるとは悲しすぎますね...」「クエリプランのキャッシュはクエリ結果のキャッシュよりは小さいですが、チリも積もれば影響は無視できなくなります: プリペアドステートメントを使わないと、セキュリティもパフォーマンスもいっぺんに損なわれてしまいます」

「プリペアドステートメントでは、$1?のように後で変数に置き換わる部分を含んだSQL文をDBMSに送信し、変数の値そのものはその直後に別途送信して、DBMS側で値を置き換えます: つまりDBMSのAPIレベルでSQL文と値が正しく分離される」「ふむふむ」

「しかし、たとえばJSならJSのライブラリが、ユーザーから受け取った値を自分で展開して、それをSQLクエリで本来プリペアドステートメントの変数にすべき場所に文字列として直接置くと、値を含むクエリ全体が単なる文字列の生SQLになってしまいます: そんな状態でライブラリがユーザー入力で邪悪なSQL文を値として受け取ると、それを含んだSQL文全体がそのままDBMSで実行されてSQLインジェクションが発生してしまう」「なるほど!」

「ちょうどIPAの同じ記事にも、PEARというPHPのライブラリで同じことが起きていたと書かれてますね↓」「PEARは覚えてますけど、今も使われてるのかな?」「何しろ2007年の記事ですから」「そんな昔の問題がNode.jsのMySQLライブラリで繰り返されていたとは...」

ただし、PEAR(PHP Extension and Application Repository)を使ってDBアクセスを行い、PEARでのプリペアドステートメントを利用している場合は、注意が必要である。
PEARは、コミュニティーにより運営されるプロジェクトのことで、コード配布およびパッケージ管理のためのシステムやPHPのコード作成に関する標準スタイル、PHP拡張モジュール・コミュニティライブラリ等を提供することを目的にしている。
ここでは、PEARをPHPの拡張モジュールとして記述している。
このPEARのプリペアドステートメントは内部的にバインド変数をエスケープしてSQL文を組み立るため、有効に機能していない。
IPA ISEC セキュア・プログラミング講座:Webアプリケーション編 第6章 入力・注入対策:SQL注入攻撃: #1 実装における対策より

🔗クラウド/コンテナ/インフラ/Serverless

🔗 Terraform


つっつきボイス:「今日のWebチーム内発表がTerraformの話題だったので取り上げてみました」「Terraform v4でS3の破壊的変更が入るのがちょっと面倒そう: よく使われる部分なので」「最近Terraformを追いかけてない...」

参考: HashiCorp Terraform AWS Provider Introduces Significant Changes to Amazon S3 Bucket Resource

「上の記事によると、たとえば以下のserver_side_encryption_configurationがこれまでaws_s3_bucketというリソースのオプションとして入っていたのが、変更後はリソースの外に切り出されてアタッチする形になったらしい」「へ〜」「この変更だけなら特に大変でもなさそうだけど、他にも変更はあるようなので、どのリソースが切り出されるか調べないといけなさそう...」

# www.infoq.comより: 変更前
resource "aws_s3_bucket" "example" {
  # ... other configuration ...
  server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        kms_master_key_id = aws_kms_key.mykey.arn
        sse_algorithm     = "aws:kms"
      }
    }
  }
}
# www.infoq.comより: 変更後
resource "aws_s3_bucket" "example" {
  # ... other configuration ...
}

resource "aws_s3_bucket_server_side_encryption_configuration" "example" {
  bucket = aws_s3_bucket.example.id

  rule {
    apply_server_side_encryption_by_default {
      kms_master_key_id = aws_kms_key.mykey.arn
      sse_algorithm     = "aws:kms"
    }
  }
}

「Terraformのデバッグは手間取りがち」「わかります」


「2つ目のTerraform Importの記事は?」「Terraformはスクラッチから組むよりも、最初はTerraform Importから始める方が実用性が高いかなと思います」「既存のリソースを書き出してくれるんでしたっけ?」「正確には書き出しではなくて、既存リソースをTerraformの管理下に置く」「なるほど」「Terraform Importはそれなりに泥臭い作業にはなりますけどね」

「たとえばVPCやVPCサブネットのようなリソースはTerraformで作り直したくないですよね」「それ、やりたくない作業です😅」「Terraformで作り直すと、VPCに依存するリソースを全部消さないといけなくなるので、そういうのはTerraform Importでやることがよくあります」

「そもそもVPCのようなクリティカルなリソースは、間違えて消したときのリスクが大きすぎるのでTerraformに置かないことも多い」「わかります」「場合によっては安全のためにインフラ用のTerraformとWebアプリ用のTerraformを分けることもあります」

🔗 capistrano、itamae、ansible

「Terraformで思い出したんですが、capistranoやitamaeとの使い分けはどういう感じでしょうか?機能もスコープも違うとは思うんですが」「最近はコンテナでデプロイすることが増えてきたので、capistranoやitamae、あとansibleあたりは新規ではあまり使わないかも」「ansible、言われて思い出しました」

capistrano/capistrano - GitHub

itamae-kitchen/itamae - GitHub

ansible/ansible - GitHub

「今の3つのツールのように、動いているインスタンスにsshして作業するIaaSツールはトレンドではなくなりつつあるかなとは思いますが、もちろん今でもよく使われています」「たしかに一度作ったものは使い続けますよね」

参考: IaaS「イアース」|気になるIT用語| NECフィールディング

「コンテナでのデプロイはそれなりに時間もかかりますし、たとえばEC2インスタンスにDBや画像のような巨大なファイルを置かないといけないようなものはコンテナデプロイにまったく向いていないので、capistranoのようなツールでやることになると思います」「コンテナにでかいファイルを置きたくないですよね」「コンテナを差し替えずにsshでやるタイプのデプロイなら、そういうツールを使うのは全然ありだと思います」

「自分はcapistranoとの付き合いも長いし、やっていることもそんなに複雑ではないので、そういう要件なら今でもcapistranoでいいと思っていますが、新たにcapistranoを導入するプロジェクトは少し大変かもしれませんね」「ふむふむ」「既にそういうツールを使っているプロジェクトならいいんですが」

「なおTerraformは低レベルを含むインフラを管理するものなので、Railsのようなアプリケーションをデプロイするそうしたツールとは位置づけがまったく違いますね」「なるほど」

🔗言語/ツール/OS/CPU

🔗 書籍『ソフトウェアアーキテクチャの基礎』

つっつきボイス:「3月8日に発売されるそうです」「これ買うつもり👍」


後編は以上です。

バックナンバー(2022年度第1四半期)

週刊Railsウォッチ: orderでコレーション指定をサポート、awesome_nested_set、GitHub Copilotほか(20220221前編)

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Ruby Weekly

Publickey

publickey_banner_captured

Serverless Status

serverless_status_banner


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。