Linuxデスクトップのログイン時にスマホのプッシュ通知を利用する

# これはなに？

PAM モジュールを作りログイン認証をカスタマイズした物語
スマホへのプッシュ通知を利用してパスワードレスログインを実現しました

(ちなみに PAM は Pluggable Authentication Modules らしいので PAM モジュールという書き方はおかしい)

# やったこと

- プッシュ通知を送るための PWA 化したページを用意
- PAM モジュール作成
- PAM の設定ファイル修正

# シーケンス図

```mermaid
sequenceDiagram
    participant ユーザー
    participant Linuxデスクトップ
    participant プッシュ通知サーバー
    participant スマホ

    ユーザー->>Linuxデスクトップ: ログイン
    Linuxデスクトップ->>プッシュ通知サーバー: プッシュ通知リクエスト
    Linuxデスクトップ->>プッシュ通知サーバー: ステータス確認リクエスト
   プッシュ通知サーバー->>スマホ: プッシュ通知
    ユーザー->>スマホ: 通知クリック
    スマホ->>プッシュ通知サーバー: ログインリクエスト
    プッシュ通知サーバー->>Linuxデスクトップ: ステータス確認レスポンス
    Linuxデスクトップ->>ユーザー: ログイン完了
```

# くわしく

それでは一つずつ見ていきましょう

## プッシュ通知を送るための PWA 化したページを用意

### やったこと

とりあえずプッシュ通知を送るための PWA を用意しました
今回は自分の PC のログインを行うのと同一 LAN 内にサーバーを立てたので、雑にログインユーザーの登録をするフォームを作り、 service worker をそのページで読み込むようにしました

docker-compose で Node.js のバックエンドサーバー、MySQL のデータベースサーバー、Valkey の KVS サーバーを用意しました

ホスト側にインストールした Nginx で Node.js のバックエンドサーバーにリバースプロキシしました
出来たウェブページにスマホでアクセスし、プッシュ通知の登録をしておくことを忘れずに

ログインしたいコンピューターからこのサーバーへプッシュ通知のリクエストを送ることでプッシュ通知が送信されます

https://github.com/m-m-n/login-push

## PAM モジュール作成

### やったこと

C を利用して PAM モジュールを作成しました
Rustを使ってみようと思いましたが PAM 周りのクレートがよくわからなかったので諦めました
多分クレート使わずに自力でも実装できると思うので今後の課題

C を利用したのでプッシュ通知を送信するための処理はシェルスクリプトの呼び出しで行うようにしました
久々に C を触りましたがやはり文字列操作は面倒なので極力やりたくないですねぇ……

https://github.com/m-m-n/pam-push-auth

## PAM の設定ファイル修正

### やったこと

まずファイルの全体像です

```conf:/etc/pam.d/common-auth
#
# /etc/pam.d/common-auth - authentication settings common to all services
#
# This file is included from other service-specific PAM config files,
# and should contain a list of the authentication modules that define
# the central authentication scheme for use on the system
# (e.g., /etc/shadow, LDAP, Kerberos, etc.).  The default is to use the
# traditional Unix authentication mechanisms.
#
# As of pam 1.0.1-6, this file is managed by pam-auth-update by default.
# To take advantage of this, it is recommended that you configure any
# local modules either before or after the default block, and use
# pam-auth-update to manage selection of other modules.  See
# pam-auth-update(8) for details.

# ログインプッシュ通知
auth [success=3 default=ignore] pam_push_auth.so

# here are the per-package modules (the "Primary" block)
auth    [success=2 default=ignore]      pam_unix.so nullok
auth    [success=1 default=ignore]      pam_sss.so use_first_pass
# here's the fallback if no module succeeds
auth    requisite                       pam_deny.so
# prime the stack with a positive return value if there isn't one already;
# this avoids us returning an error just because nothing sets a success code
# since the modules above will each just jump around
auth    required                        pam_permit.so
# and here are more per-package modules (the "Additional" block)
auth    optional                        pam_cap.so 
# end of pam-auth-update config
```

下記の箇所を追記しました

> ```
> # ログインプッシュ通知
> auth [success=3 default=ignore] pam_push_auth.so
> ```

パスワード認証が pam_unix.so によって行われるようで、その前にプッシュ通知での認証を行いたい
また、プッシュ通知での認証が通ればログインに成功したい
という要件のため `auth sufficient` としました

# さいごに

これでログイン時にユーザー名を選択した際にスマホにプッシュ通知が飛んできて、通知のログインボタンを押すことでパスワードレスでログインできるようになりました
プッシュ通知を送るサーバーへアクセスできない場合は通常のパスワード認証に進むのでプッシュ通知サーバーが死んでいてもログインできなくなるようなことはありません

今回プッシュ通知のユーザー登録を手動でやっていますが、ユーザー ID の情報を組み込んだQRコードを表示し、手動でユーザー ID を入力せずにプッシュ通知の登録画面を用意できるとよりユーザーフレンドリーかもしれません
(たとえばクエリストリングに付与しておき、プッシュ通知登録処理の際にクエリストリングからユーザー ID を取得するなど)

このアイデアは Web システムにも組み込めるとは思いますが、 Web システムでやるならパスキーを利用したほうが良いと思われます

---

それでは良い Linux ライフを