データベースドキュメント生成コマンド tbls 更新情報 (テンプレート変更機能/ドキュメント内リンクのbaseUrl変更機能/テーブルリレーション自動検出機能)

無事マージされてさらに便利になったtblsの新機能紹介エントリです。ちなみに、自分が追加した機能は1つもありません。

tblsをさらに便利にしてくれるPull Requestに改めて感謝します。

ドキュメントのテンプレートを変更できるようになりました

https://github.com/k1LoW/tbls#personalized-templates

tblsが生成するドキュメントテンプレート( Goのtext/template ) を差し替えることができるようになりました。

tbls.ymlで、必要な設定だけ以下のように差し替えテンプレートのパスを書くだけです。

templates:
  dot:
    schema: 'templates/schema.dot.tmpl'
    table: 'templates/table.dot.tmpl'
  puml:
    schema: 'templates/schema.puml.tmpl'
    table: 'templates/table.puml.tmpl'
  md:
    index: 'templates/index.md.tmpl'
    table: 'templates/table.md.tmpl'

tbls doc で生成されるドキュメントフォーマットが気に食わない人には朗報ですね。

もう少しフォーマットをいじるためのテンプレート用関数も(デフォルトで使わなくても)追加していこうと思います。

生成するドキュメントのリンクにbaseUrlを設定できるようになりました

github.com

これは、MarkdownファイルをGitHub以外でホスティングしたいということで追加されました。

ただ、今後HTML生成機能が追加されたり、tbls Web UI(?)ができた時にも有効に使えるパラメータになると思っています。

テーブルのリレーションを自動で検出する機能がつきました

https://github.com/k1LoW/tbls/blob/master/sample/detect_relations/README.md

Ruby on Railsなどで採用されている命名規則に沿っていれば外部キー制約がなくても自動でテーブルのリレーションを検出してくれます。

いわゆる [users.id](http://users.id)posts.user_id を自動で関連づけるアレです。

外部キーレスで設計したデータベースも、これを有効にするだけでドキュメントがかなりわかりやすくなるのではないでしょうか?

現在はRoRライクな命名規則だけですが、他にもよく使われる命名規則があれば実装していきたいと思っています。是非教えてください!


というわけで、最近追加された新機能の紹介でした。

GitHubやSTNSと会話して公開鍵を取得・更新するツールkeypを作った

GMOペパボではLinuxユーザ、グループの管理にSTNSやOCTOPASSを利用しています*1

stns.jp

github.com

これらは簡単に言うとそれぞれTOMLファイルやGitHub(GHE)のユーザ情報をデータソースとしてLinuxユーザ、グループ、さらに公開鍵の管理をするものです。

正直何台あるかわからないサーバがSTNSやOCTOPASSを通じてユーザ、グループ管理されていることで「ログインできる必要があるサーバには即ログインできる」ということが実現されており、これらがない環境は正直考えられなくなってきています。ログインユーザ、グループ管理、公開鍵管理に疲弊している方は是非導入を検討して欲しいです。

さて、STNS/OCTOPASSはとても便利なのですが、実際の環境では様々な理由によりSTNS/OCTOPASS配下になっていないユーザも存在しています*2。本当に稀ですがそれらのユーザでログインするための公開鍵の登録作業が発生する事があるのも事実です。

細かいですが、上記のようなシチュエーションでも公開鍵をSTNSサーバ/GitHubと連動するようにするツールkeypを作成しました。

github.com

keyp

keypはGitHub(GHE)やSTNSサーバと会話して公開鍵を取得・更新するツールです。

例えばGitHubのユーザ alicebob と org my-org の team developers に所属しているユーザの公開鍵をまとめて取得したいときは、 keyp collect コマンドで

$ keyp collect -b github -u alice -u bob -t my-org/developers
ssh-rsa C1yc2EAAAADAQABAAACAQCmnCCt6PyH9jLZbPkMijSJYIu14nhxyFVw9M9eAkgcMQ3EsKf86GWlGPDfZcqcDqI+LP7LKQk4kAlmLOQQMavthrhGEURxxdX0Yk2A6pHjG3zrcW7X30ZBMwOzX/a6EWkPXPwPH6LcP3rM9yEIg95f2JntxO3z7l/8QjzJCoPIlqyoX4I7bxHus/rZVnRNh9C0PbUejbg/iWaTTxkNglSeEYpW+ID2k/4Absisa5XyY2zEOMw+6OyfRL9AlfGYv545J0g90qWS11iRSFnMR7A6FNUea/pVESIMmmBI56Ne+S8NmwR724u3d5kNJxuAKpmtThNPdxW/vmtuc5XBZgtPX/rzdAW0TQZvpVoLnoKaqYgfIpcrrkSAoPlcVxfq/NrpVlbIi6c9rZRZR4dcqmAK2eBGuDQZBiYJSESuPbE6i08GGnM8OblD8pshVeGMStztR+NuXywIXbRpyqNF2VNjil8r4qGNW9AB8ZVUB/1s6U8oxvtlbABoxXLrdNlKj3rl2YoPIZyCLAp9QDch8P1SnmQTEZK67YY5KNQrJZ2ql7pblo84JqsRbwuOrexTz6xrbBWFZMHWorFqF8ryX0LOw9TIaHbYqleynhqJ0a8VJHZMmwndYKjw3brtJ3SfCpXU0826LOExWXcjfBqHK65gM+MQ==
ssh-rsa AAADAQABAAACAQC8IEt4MqBed/yXQyjUTCZRdZoCUNhm0bEkOV8Ef5TduQvMIPDpBYyYIvFz7jxJyShPoiTMtIUnkkA2aDF0jhujFzqKmYm9H2tS7Tpf5iNwRJgJJJWv674tGUcu+6+ZadmDBQ//dwo8XWTHxmkWfgaybxs8/o0AlwZQ4pYFcky0q+/qP4cwPAmRW0rGCo0E5BhS/5eGssoLBXu4/Hcaz/93H8AtAe1UQrlCKma0rj0HIA9A9Q9EQtunw/zJTBtTyzE/TvxKcSMNulgdVmFSRmU6l84Ftc6tZPoiaCnxcvQUyjCEeQfy4DbtCWe1tEubyKeBLBTXTnpqWA3Gs9GryQA/bR7Ivan/03FshLFeVVnbvvO11sKNvkAJ8u417Q2/G9bcB1H30Xa9PSRE+2CbQ2maafhPVL17TJVBvkDCM5trmwxfM2tdlKA7R+mTj9nIrSLN4BYrge8IZ1fesC/sKMlMwhNEOrQYQQIZMIx8hfLAS37D8wbUPRodQFJsolrK6cHlNICR/TLcijNhCeHJkD8448EuJn1BCbYKglG7eUYKLbMXcVJAoTPlFTHPU80oaHJhmpLe0vFSxrhWVf/ha81zRefXOiye7Pbn/h+sa2qsKTnAMShpS1m+RP7QmHNmFAbHlPeTlnd0oJI/bt5Mysn5HHjX4vAJdQ==
[...]

というように一気に取得できます。

特定のLinuxユーザの ~/.ssh/authorized_key を更新するためのコマンドも用意しています。

ubuntu ユーザの ~/.ssh/authorized_key をSTNSで管理しているユーザ k1low とグループ some-team-developers に所属しているユーザの公開鍵で更新したいときは、rootユーザで以下のように keyp update-authorized-keys コマンドを実行すれば一気に更新してくれます。

$ keyp update-authorized-keys ubuntu -b stns -u k1low -g some-team-developers

また、CIで利用しているなど登録している公開鍵が上書きされたくない場合は --keep-key オプションで公開鍵の部分文字列を指定しておけば、部分文字列にマッチした鍵を上書きで消すこともありません。

上記 keyp update-authorized-keys コマンドをSystemd TimerやCronに登録しておけば簡易的な公開鍵同期の仕組みの出来上がりです。

まとめ

というわけで、ちょっと(いやかなり)細かいですがこれで公開鍵登録作業などがなくなればいいなと思っています。

他にどんな人に使ってもらえるかなーと思ったのですが、

  • ISUCONのデプロイユーザの公開鍵登録
  • 「ユーザ、グループの管理までしたいわけではないがデプロイユーザだけは公開鍵管理したい」みたいなプロジェクト
  • 公開鍵の登録方法(登録コマンド)を統一したい(プロジェクトAとBとCとDでいちいち新規メンバーの鍵をTerraformやChefやAnsibleなどのプロビジョニングツールに1つ1つ書いて運用するのが面倒)
  • 深遠なる理由でsshdの設定変更までは手が出せない

というところまでで力尽きました。

誰かに刺さればいいなー。

*1:他にも認証方式で採用しているものはあります

*2:例えば、初期構築時のユーザなど

~/.aws/(config|credentials)の設定情報を元にMFAを行い、一時的なセキュリティ認証情報を取得してコマンドを実行するawsdoを作った

久しぶりに使うAWSのprofileがありまして、そのprofileについての記憶が失われていた結果、コマンド実行成功までに時間を溶かしてしまいました。

というのも、私は普段使うprofileではaswrapでAssumeRole(と多要素認証)を透過的に便利に実行していた結果、IAMの認証設定については何も考えなくなっていて「とりあえず aswrap 」を実行していました。

そして、

$ AWS_PROFILE=myaws aswrap aws s3 ls

An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied

「あれ?認証情報無効にしたっけな 🤔」とか、トンチンカンな推測をしていました。

よくよく確認してみたらそのprofileは「MFAは必須だがAssumeRole用ではない」profileで、aws sts assume-role ではなく、 aws sts get-session-token でMFAを通す(多要素認証をする)べきものでした。

「じゃあ aswrap と同じ使い勝手で aws sts get-session-token を実行するラッパーコマンドを作ればいい」と思い立ち、書いているうちに最終的には~/.aws/(config|credentials)の設定情報を元に aws sts assume-roleaws sts get-session-token を使い分けて一時的なセキュリティ認証情報を取得してコマンドを実行するラッパーコマンドになりました。

github.com

インストール

macOSであればHomebrewでインストール可能です。

$  brew install k1LoW/tap/awsdo

その他のインストール方法はREADMEに記載しています。

使い方

使い方はaswrapとほぼ同じで、一時的なセキュリティ認証情報を使って実行したいコマンドを awsdo の引数に指定するだけです。

AWS_PROFILE=myaws awsdo -- terraform apply
Enter MFA code for arn:aws:iam::111111111111:mfa/k1low: 123456
[...]

引数に何も渡さなければ環境変数を出力します。

$ AWS_PROFILE=myaws awsdo
Enter MFA code for arn:aws:iam::111111111111:mfa/k1low: 123456
export AWS_REGION=ap-northeast-1
export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=vl/Zv5hGxdy1DPh7IfpYwP/YKU8J6645...
export AWS_SESSION_TOKEN=FwoGZXIYXdGUaFij9VStcW9fcbuKCKGAWjLxF/3hXgGSoemniFV...

awsdoの動き

awsdoは以下のように動きます。

  • まず、~/.aws/credentials~/.aws/config を読み込みます。
  • 環境変数 AWS_PROFILE--profile オプションから対象のprofileのセクションを探します。
  • 一時的なセキュリティ認証情報を取得します。対象セクションの設定によって以下のように挙動を変えます。
    1. もし対象セクションが role_arn を持っていれば、 awsdo は assume role を試みます ( sts:AssumeRole ).
      • 対象セクションに mfa_serial の記載がない場合でも、 awsdo は一度有効なMFAデバイスの有無を問い合わせます。 ( iam:ListMFADevices ).
      • 前段でMFAデバイス ( mfa_serial )を取得出来ている場合、MFAを実施します(トークンコードを要求します)
      • 一時的なセキュリティ認証情報を取得します
    2. もし対象セクションが role_arn を持っていなければ、awsdo は session token の取得を試みます ( sts:getSessionToken ).
      • 対象セクションに mfa_serial の記載がない場合でも、 awsdo は一度有効なMFAデバイスの有無を問い合わせます。 ( iam:ListMFADevices ).
      • 前段でMFAデバイス ( mfa_serial )を取得出来ている場合、MFAを実施します(トークンコードを要求します)
      • 一時的なセキュリティ認証情報を取得します
  • 一時的なセキュリティ認証情報を環境変数にセットして引数に渡されたコマンドを実行するか、環境変数をexportコマンドの形で出力します。
    • AWS_ACCESS_KEY_ID
    • AWS_SECRET_ACCESS_KEY
    • AWS_SESSION_TOKEN
    • AWS_REGION

挙動として特徴的なのは

  1. ~/.aws/credentials~/.aws/config の設定によってaws sts assume-roleaws sts get-session-token と挙動を変える
  2. 設定情報に mfa_serial がなくてもMFAデバイスを問い合わせてMFAをしようと試みる
  3. 必ず一時的なセキュリティ認証情報を取得して利用する

というところでしょうか。

1と2を実現したことで、~/.aws/(config|credentials)の設定について考えることができるだけなくなることを期待しています。

まとめ

AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY は手元にあるけどCLIでMFAを通すための設定よくわからん」「STS好き」という方は是非「とりあえず awsdo 」も試してみてください。

また、本文からもお分かりのとおり、 awsdo は aswrap の仕組みに大きな影響を受けています。aswrapの便利さを知っていたからこそawsdoの実装をしたのは間違いありません。

aswrap に限らず数々の便利OSSツール/ライブラリの発明していただいているfujiwara さんには本当に感謝しております!!

以上、久しぶりのAWS関連エントリでした。

生ログを構造化ログに変換するツールlrepに日時パース機能をつけた

k1low.hatenablog.com

の続きです。

ログからSQLiteDDLやクエリを生成できるなら日時は日時としてパースできたほうが使い勝手がいいと思い実装しました*1

github.com

名前付き正規表現でログの日時部分に名前をつけて(以下のサンプルだと time )、そのキーを --ts-key (-k) で、時刻フォーマットを --ts-format (-T) で指定することで日時をパースしてSQLiteのクエリに反映させます。

$ tail -f /var/log/access.log | lrep -t sqlite --no-m0 '^(?P<host>\S*) \S* \S* \[(?P<time>.*)\] "(?P<request>.*)" (?P<status>\S*) (?P<bytes>\S*)' -k time -T '%d/%b/%Y:%H:%M:%S %z'
CREATE TABLE IF NOT EXISTS lines (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  host TEXT,
  time TIMESTAMP,
  request TEXT,
  status TEXT,
  bytes TEXT,
  _raw TEXT,
  created TIMESTAMP NOT NULL
);
INSERT INTO lines(host, time, request, status, bytes, _raw, created) VALUES ('20.156.87.118', '2020-08-09 02:30:16', 'GET /category/electronics HTTP/1.1" 200 104 "/item/games/1927', '"Mozilla/4.0', '(com
patible;', '20.156.87.118 - - [09/Aug/2020:11:30:16 +0900] "GET /category/electronics HTTP/1.1" 200 104 "/item/games/1927" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; WOW64; Trident/4.0; GTB6; SL
CC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30618; .NET4.0C)"', datetime('now'));

日時をパースするためのフォーマットは strptime(3)のフォーマットでもGoのフォーマットでもOKです。

あとはビルトイン正規表現を増やすだけかなあ。。。

*1:SQLiteにはデータ型としての日時型はありませんが

正規表現を使ってログをカジュアルに構造化ログ/データに変換するツールlrepを作った

tl;dr

Fluentdのregexp parser pluginCLI版っぽいツールを作りました(個人的に一番伝わりやすい言い方だと思っている)。

構造化されていないログを分析するとき面倒問題

最近のログは最初からJSONやLTSVなどの構造化ログになっていたり、Fluentdなどで構造化データに変換されたうえでRDBやElasticsearch、BigQueryなどに収集されていたりすることが多くなってきていると思います。

ログの分析をしたいとき、RDBに保存されているのであればSQLが便利です。

また、構造化ログにさえなっていればtrdsqlgo-jsqliteなど、構造化ログに対してSQLクエリを実行する手段はあります。保存先がS3なのであればS3 Selectを利用することもできます。

じゃあ、構造化されていない、いわゆる生ログの分析はどうすればいいかというと、

  1. (簡単な分析であれば)UNIXコマンドなど(awkとかsedとかsortとかwcとか)を駆使するか
  2. 分析したいことに合わせて生ログを必要最低限の構造化データに変換するか

だと思います。

世の中のログを触る人々は、どちらもやった経験がありそうな気がしてなりません。

そしてどちらもちょっと面倒です*1

なので今回、私は「2. 分析したいことに合わせて生ログを必要最低限の構造化データに変換する」を簡単にするためにlrepというツールを作りました。

github.com

lrep

lrepは、正規表現のキャプチャグループ(( ) で囲まれた部分正規表現にマッチした値を参照できる機能 )でマッチした値を構造化データのフィールドとして、ログを構造化ログ/データに変換するツールです。

例えば Common Log FormatApacheアクセスログの場合、以下のような正規表現で構造化ログに変換できます。

$ tail -F /var/log/access.log | lrep '^(\S*) \S* \S* \[(.*)\] "(.*)" (\S*) (\S*)'
{"_raw":"100.21.169.226 - - [25/Jul/2020:16:25:05 +0900] \"GET /category/electronics HTTP/1.1\" 200 114","m0":"100.21.169.226 - - [25/Jul/2020:16:25:05 +0900] \"GET /category/electronics HTTP/1.1\" 200 1\
14","m1":"100.21.169.226","m2":"25/Jul/2020:16:25:05 +0900","m3":"GET /category/electronics HTTP/1.1","m4":"200","m5":"114"}
{"_raw":"104.141.81.229 - - [25/Jul/2020:16:25:05 +0900] \"GET /item/office/1680 HTTP/1.1\" 200 49","m0":"104.141.81.229 - - [25/Jul/2020:16:25:05 +0900] \"GET /item/office/1680 HTTP/1.1\" 200 49","m1":"\
104.141.81.229","m2":"25/Jul/2020:16:25:05 +0900","m3":"GET /item/office/1680 HTTP/1.1","m4":"200","m5":"49"}
{"_raw":"132.189.225.189 - - [25/Jul/2020:16:25:05 +0900] \"GET /category/office HTTP/1.1\" 200 97","m0":"132.189.225.189 - - [25/Jul/2020:16:25:05 +0900] \"GET /category/office HTTP/1.1\" 200 97","m1":"\
132.189.225.189","m2":"25/Jul/2020:16:25:05 +0900","m3":"GET /category/office HTTP/1.1","m4":"200","m5":"97"}
[...]

部分正規表現を含めそれぞれ一致した箇所が、自動でそれぞれ以下のようなフィールド名になります。

フィールド名 説明
m0 一致全体
m1 部分一致1番目
m2 部分一致2番目
... ...
_raw (デフォルト) 元の生ログ

また、名前付きキャプチャグループも利用してフィールド名を明示的に指定することも可能ですし、利用側として意図的ではない m0 (一致全体)や _raw (元の生ログ)のフィールドを除外することもできます。

$ tail -f /var/log/access.log | lrep --no-m0 --no-raw '^(?P<host>\S*) \S* \S* \[(?P<time>.*)\] "(?P<request>.*)" (?P<status>\S*) (?P<bytes>\S*)'
{"bytes":"118","host":"100.39.167.131","request":"GET /category/toys HTTP/1.1","status":"200","time":"25/Jul/2020:17:46:26 +0900"}
{"bytes":"70","host":"36.30.101.105","request":"GET /item/electronics/1293 HTTP/1.1","status":"200","time":"25/Jul/2020:17:46:26 +0900"}
{"bytes":"123","host":"212.87.25.78","request":"GET /category/software HTTP/1.1","status":"200","time":"25/Jul/2020:17:46:26 +0900"}
{"bytes":"76","host":"84.189.195.199","request":"GET /category/office HTTP/1.1","status":"200","time":"25/Jul/2020:17:46:27 +0900"}
{"bytes":"103","host":"164.78.219.152","request":"GET /item/electronics/1175 HTTP/1.1","status":"200","time":"25/Jul/2020:17:46:28 +0900"}
[...]

サポートしているフォーマット

JSON、LTSVといった一般的な構造化ログフォーマットに加えてSQLite用のクエリがあります(これが一番作りたかった)。

-t sqlite を指定するとDDLも含めたSQLite用クエリに変換できます。

$ tail -f /var/log/access.log | lrep -t sqlite --no-m0 '^(?P<host>\S*) \S* \S* \[(?P<time>.*)\] "(?P<request>.*)" (?P<status>\S*) (?P<bytes>\S*)'
CREATE TABLE IF NOT EXISTS lines (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  host TEXT,
  time TEXT,
  request TEXT,
  status TEXT,
  bytes TEXT,
  _raw TEXT,
  created NUMERIC NOT NULL
);
INSERT INTO lines(host, time, request, status, bytes, _raw, created) VALUES ('224.51.78.136', '25/Jul/2020:17:51:24 +0900', 'GET /category/books HTTP/1.1', '200', '130', '224.51.78.136 - - [25/Jul/2020:1\
7:51:24 +0900] "GET /category/books HTTP/1.1" 200 130', datetime('now'));
INSERT INTO lines(host, time, request, status, bytes, _raw, created) VALUES ('152.114.184.75', '25/Jul/2020:17:51:25 +0900', 'GET /category/finance HTTP/1.1', '200', '56', '152.114.184.75 - - [25/Jul/202\
0:17:51:25 +0900] "GET /category/finance HTTP/1.1" 200 56', datetime('now'));
INSERT INTO lines(host, time, request, status, bytes, _raw, created) VALUES ('168.57.224.190', '25/Jul/2020:17:51:25 +0900', 'GET /category/games?from=10 HTTP/1.1', '200', '60', '168.57.224.190 - - [25/J\
ul/2020:17:51:25 +0900] "GET /category/games?from=10 HTTP/1.1" 200 60', datetime('now'));
[...]

この形式の一番良いところろはパイプでダイレクトに sqlite3 コマンドに渡してSQLiteファイルを作成できることです。

$ cat /var/log/access.log | lrep -t sqlite --common | sqlite3 lines.db
$ sqlite3 lines.db
SQLite version 3.32.3 2020-06-18 14:00:33
Enter ".help" for usage hints.
sqlite> .tables
lines
sqlite> SELECT * FROM lines LIMIT 5;
1|200.159.42.212|27/Jul/2020:06:56:18 +0900|GET /item/music/1294 HTTP/1.1|200|112|200.159.42.212 - - [27/Jul/2020:06:56:18 +0900] "GET /item/music/1294 HTTP/1.1" 200 112|2020-07-26 21:56:18
2|68.54.151.135|27/Jul/2020:06:56:18 +0900|GET /item/electronics/3894 HTTP/1.1|200|96|68.54.151.135 - - [27/Jul/2020:06:56:18 +0900] "GET /item/electronics/3894 HTTP/1.1" 200 96|2020-07-26 21:56:18
3|84.93.121.21|27/Jul/2020:06:56:18 +0900|GET /category/software HTTP/1.1|200|126|84.93.121.21 - - [27/Jul/2020:06:56:18 +0900] "GET /category/software HTTP/1.1" 200 126|2020-07-26 21:56:18
4|176.36.75.163|27/Jul/2020:06:56:18 +0900|GET /category/garden HTTP/1.1|200|113|176.36.75.163 - - [27/Jul/2020:06:56:18 +0900] "GET /category/garden HTTP/1.1" 200 113|2020-07-26 21:56:18
5|40.60.150.212|27/Jul/2020:06:56:18 +0900|GET /category/games HTTP/1.1|200|75|40.60.150.212 - - [27/Jul/2020:06:56:18 +0900] "GET /category/games HTTP/1.1" 200 75|2020-07-26 21:56:18
sqlite>

ログにSQLで分析が捗ります。

ビルトイン正規表現

上記コマンドで使った --common はビルトインの正規表現です。

ビルトインの正規表現lrep builtin で確認できます。

$ lrep builtin
common       Common Log Format
combined     Combined Log Format
postgresql   PostgreSQL log
$ lrep builtin common
NAME
       common -- Common Log Format

REGEXP
       ^(?P<host>\S*) (?P<ident>\S*) (?P<user>\S*) \[(?P<time>.*)\] "(?P<method>\S+)(?: +(?P<resource>\S*) +(?P<proto>\S*?))?" (?P<status>\S*) (?P<bytes>\S*)

SAMPLE
       152.120.218.99 - - [25/Jul/2020:12:25:54 +0900] "GET /category/books HTTP/1.1" 200 67

現在はたったの3つだけですが、MySQLのログフォーマットなどを追加するなど継続して整備したいです。

今後など

今後実装していきたいこととして

  • ビルトイン正規表現をいざという時に備えて増やしておきたい
  • 時刻表現だけ特別扱いしたい(時刻としてパースしたい)

などを考えています。

是非使ってみてください。また、(特にビルトイン正規表現の)Pull Requestお待ちしています。

ところで、lrep、どこかでみたことのあるツールな気がしているんですよね。。

*1:「じゃあ最初から構造化データにしとけ」となるのですが対象のミドルウェアにはその機能がなかったり、Fluentdで捌くにもそれはそれでコンピューティングリソースを消費するし、毎回分析するようなログじゃない場合いろいろ億劫になりますよね...。そしてまた分析が必要な場面が訪れるというループ...

プロセスやファイルのケーパビリティを確認できるツールを再発明しつつLinux capabilitiesの理解を進めている

最近社内でLinux capabilitiesの話題がでていて「そういえばちゃんと理解していないな」と思ったので、夜ちょこちょこと技術エントリとかmanとかを読んでいました。

結構な頻度で同僚のエントリがヒットするのが良いです。エントリを見つけたら「わからなければ社内でカジュアルに聞ける」という福利厚生付き。

manをPro契約しているDeepLで翻訳して一気に読んでやろうと思ったら Capability Permitted Effective などが日本語に訳されてしまってむしろ混乱してしまうという出来事もありしました。辞書設定すればいけるのかな。

プロセスやファイルのCapabilitiesを確認する方法

プロセス(スレッド)のケーパビリティを確認する方法は getpcaps コマンドを、 ファイルのケーパビリティを確認する方法は getcap コマンドを利用するのが良いでしょう。

Ubuntuならlibcap2-bin パッケージに入っているので、

$ sudo apt install libcap2-bin

で手に入れることができます。

使い方も

$ getpcaps [PID]
$ getcap [PATH]

とわかりやすいです。

capv

getpcapsgetpcap を使うのが一番良いのですが、ケーパビリティセットの表記が +ep となっていたり、ファイルに(あまり意味はないが)Effectiveのケーパビリティだけをつけると

root@cd406362eb55:/go# touch /tmp/setcap_test
root@cd406362eb55:/go# setcap all+e /tmp/setcap_test
root@cd406362eb55:/go# getcap /tmp/setcap_test
/tmp/setcap_test =
root@cd406362eb55:/go#

となっていたり*1と、cap_from_text(3)形式になっていて、私はまだ慣れません。

まだわからなかったことがあるので、理解もかねてプロセスやファイルのケーパビリティを確認できるツールを作っています。

github.com

使い方は

$ capv -p [PID]
$ capv -f [PATH]

といった感じであまり変わらないのですが、表示だけ自分が理解をしやすいように変更しています。

root@cd406362eb55:/go# capv -p 1
P(permitted)
  [CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_FSETID CAP_KILL CAP_SETGID CAP_SETUID CAP_SETPCAP CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SYS_CHROOT CAP_MKNOD CAP_AUDIT_WRITE CAP_SETFCAP]
P(inheritable)
  [CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_FSETID CAP_KILL CAP_SETGID CAP_SETUID CAP_SETPCAP CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SYS_CHROOT CAP_MKNOD CAP_AUDIT_WRITE CAP_SETFCAP]
P(effective)
  [CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_FSETID CAP_KILL CAP_SETGID CAP_SETUID CAP_SETPCAP CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SYS_CHROOT CAP_MKNOD CAP_AUDIT_WRITE CAP_SETFCAP]
P(bounding)
  [CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_FSETID CAP_KILL CAP_SETGID CAP_SETUID CAP_SETPCAP CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SYS_CHROOT CAP_MKNOD CAP_AUDIT_WRITE CAP_SETFCAP]
P(ambient)
  []

先ほどのEffectiveのケーパビリティだけたてたファイルも

root@cd406362eb55:/go# capv -f /tmp/setcap_test
F(permitted)
  []
F(inheritable)
  []
F(effective)
  1

とEffectiveがついていることがわかるようになっています(ファイルケーパビリティのEffectiveだけ0か1)。

あと、これも私が理解のために実装している機能なのですが、 -p-f の両方を指定すると、そのプロセスでファイルを実行(execve(2))したあとのスレッドのケーパビリティセットを表示できるようにしています。

root@cd406362eb55:/go# capv -p 1 -f /tmp/setcap_test
P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding)) | P'(ambient)
  []
P'(inheritable) = P(inheritable)
  [CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_FSETID CAP_KILL CAP_SETGID CAP_SETUID CAP_SETPCAP CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SYS_CHROOT CAP_MKNOD CAP_AUDIT_WRITE CAP_SETFCAP]
P'(effective) = F(effective) ? P'(permitted) : P'(ambient)
  []
P'(bounding) = P(bounding)
  [CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_FSETID CAP_KILL CAP_SETGID CAP_SETUID CAP_SETPCAP CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SYS_CHROOT CAP_MKNOD CAP_AUDIT_WRITE CAP_SETFCAP]
P'(ambient) = (file is privileged) ? 0 : P(ambient)
  []

ただ、私が理解のために実装しているだけなので正しいかどうかは全く保証できません*2

残り、securebitsとバウンディングセットについて、まだわかっていないので引き続き理解を進めようと思います。

*1:正確にはコンテナ内でのファイルケーパビリティセットなのでNamespaced file capabilitiesで説明しています。挙動が異なるのかもしれませんがまだ調べていません

*2:スレッドケーパビリティの確認にシステムコールではなくprocfsをパースしていたりなど

Elispのエラーを久しぶりにデバッグした

いつからか、goplsの起動時にいつも以下のようなエラーがでて、定義ジャンプができなくなってしまっていました。プライベートのMacBookでは発生していませんでした。

(file-error "Opening directory" "Permission denied" "//.PKInstallSandboxManager-SystemSoftware").

直近だと業務でGoをガッツリ書くことがなかったので放置していたのですが、ふと業務でGoのOSSをいじることになりイラッときたので原因を特定することに(遅い)。

まずおもむろに以下を .emacs.d の適当なところに記載して、backtraceを得られるようにします。

(setq debug-on-error t)

で、Goなファイルを開くと上述したエラーがでてbacktraceつらつらとでてきました。

/.PKInstallSandboxManager-SystemSoftware がルートにあるディレクトリということから lsp--directory-files-recursively がアヤシイとにらんで軽くコードを確認してfmfmとなったあとに検索。

で、ビンゴ

github.com

以下のコマンドで / を削除しました。

M-x lsp-workspace-folders-remove

おそらく何かの拍子に /ワークスペースだと認識されてしまったんだろうなーと推測。

終わり。