2019/09/01

neovim で plugin を利用せずに sudo を使う

root 権限が必要なファイルを neovim や vim で編集する事があると思います。
その際、 sudoers で許可されているユーザならば
  • :write ! sudo tee % > /dev/null
で sudo を使って root として書き込むことができます。

ですが、 neovim では
  • :write ! sudo tee % > /dev/null
    • sudo: no tty present and no askpass program specified
    • shell returned 1
と言われて sudo が使えません。


解決策

以下のようなコマンドを定義することで対応できます。
function! s:sudo_write_current_buffer() abort
if has('nvim')
let s:askpass_path = '/tmp/askpass'
let s:password = inputsecret("Enter Password: ")
let $SUDO_ASKPASS = s:askpass_path
try
call delete(s:askpass_path)
call writefile(['#!/bin/sh'], s:askpass_path, 'a')
call writefile(["echo '" . s:password . "'"], s:askpass_path, 'a')
call setfperm(s:askpass_path, "rwx------")
write ! sudo -A tee % > /dev/null
finally
unlet s:password
call delete(s:askpass_path)
endtry
else
write ! sudo tee % > /dev/null
endif
endfunction
command! SudoWriteCurrentBuffer call s:sudo_write_current_buffer()
このコマンドは vim と neovim の両方で利用できます。

vim で利用する場合は ~/.vimrc に追記します。
neovim で利用する場合は ~/.cofig/nvim/init.vim に追記します。


使い方

  • $ nvim /etc/hosts
    • root 権限が必要なファイルを開いて編集します。保存の際は
  • :SudoWriteCurrentBuffer
    • を実行します。
    • パスワードを入力すれば root で書き込む事ができます。


動作確認をした環境

大元の環境(vim と neovim で動作確認済)
  • OS: macOS Mojave 10.14.6 (18G95)
  • Homebrew: 2.1.11
    • Homebrew/homebrew-core: (git revision 883ee; last commit 2019-09-01)
  • neovim: v0.3.8
  • vim: 8.0.1365
  • sudo: version 1.8.17p1
    • Sudoers policy plugin version 1.8.17p1
    • Sudoers file grammar version 45
    • Sudoers I/O plugin version 1.8.17p1
  • docker: 19.03.1, build 74b1e89

    docker を使っていくつかの distribution でも動作を確認
    • Alpine linux: 3.10.2
      • neovim: v0.3.7
      • vim: 8.1.1365
      • sudo: version 1.8.27
        • Sudoers policy plugin version 1.8.27
        • Sudoers file grammar version 46
        • Sudoers I/O plugin version 1.8.27
    • CentOS: release 7.6.1810 (Core)
      • vim: 7.4.1099
      • sudo: version 1.8.23
        • Sudoers policy plugin version 1.8.23
        • Sudoers file grammar version 46
        • Sudoers I/O plugin version 1.8.23
    • Fedora: release 30
      • vim: 8.1.1912
      • sudo: version 1.8.27
        • Sudoers policy plugin version 1.8.27
        • Sudoers file grammar version 46
        • Sudoers I/O plugin version 1.8.27
    • Ubuntu: 18.04.3
      • vim: 8.0.1453
      • sudo: version 1.8.21p2
        • Sudoers policy plugin version 1.8.21p2
        • Sudoers file grammar version 46
        • Sudoers I/O plugin version 1.8.21p2
    • Debian: 10.0 (buster-20190812)
      • vim: 8.1.1401
      • sudo: version 1.8.27
        • Sudoers policy plugin version 1.8.27
        • Sudoers file grammar version 46
        • Sudoers I/O plugin version 1.8.27


    何故 neovim では sudo が使えないのか

    ここから先は先程の Vimscript を作るに至った経緯を書きます。
    興味のある方はご覧ください。

    まず sudo の error message
    • sudo: no tty present and no askpass program specified
    を見ると、 tty と askpass が無いと言っています。


    tty と askpass

    実際、vim では
    • : ! tty
      • /dev/ttys004 
    などが得られますが、 neovim では
    • : ! tty
      • not a tty
      • shell returned 1
    と言われてしまいます。

    askpass は使ったことが無いのですが、fedora の container で dnf search すると
    [root@9015bf44beee /]# dnf search askpass
    Fedora Modular 30 - x86_64 1.5 MB/s | 1.9 MB 00:01
    Fedora Modular 30 - x86_64 - Updates 2.0 MB/s | 2.8 MB 00:01
    Fedora 30 - x86_64 - Updates 5.0 MB/s | 23 MB 00:04
    Fedora 30 - x86_64 5.2 MB/s | 61 MB 00:11
    Last metadata expiration check: 0:00:01 ago on Sat Aug 31 00:41:37 2019.
    =============================== Name & Summary Matched: askpass ================================
    lxqt-openssh-askpass-l10n.x86_64 : Translations for lxqt-openssh-askpass
    lxqt-openssh-askpass.x86_64 : Askpass openssh transition dialog for LXQt desktop suite
    ==================================== Name Matched: askpass =====================================
    R-askpass.x86_64 : Safe Password Entry for R, Git, and SSH
    ksshaskpass.x86_64 : A ssh-add helper that uses kwallet and kpassworddialog
    openssh-askpass.x86_64 : A passphrase dialog for OpenSSH and X
    x11-ssh-askpass.x86_64 : A passphrase dialog for X and not only for OpenSSH
    の様に、いくつかの askpass が確認できます。のでちょっと試してみます。
    • x11-ssh-askpass
    • # dnf install -y x11-ssh-askpass
      # find . -name '*askpass*' -type f
      ./etc/profile.d/x11-ssh-askpass.sh
      ./etc/profile.d/x11-ssh-askpass.csh
      ./usr/libexec/openssh/x11-ssh-askpass
      # /usr/libexec/openssh/x11-ssh-askpass
      Error: Can't open display:
      # /usr/libexec/openssh/x11-ssh-askpass --help
      Error: Can't open display:
      # dnf remove -y x11-ssh-askpass
    • openssh-askpass
    • # dnf install -y openssh-askpass
      # find . -name '*askpass*' -type f
      ./etc/profile.d/gnome-ssh-askpass.csh
      ./etc/profile.d/gnome-ssh-askpass.sh
      ./usr/libexec/openssh/gnome-ssh-askpass
      # /usr/libexec/openssh/gnome-ssh-askpass
      (gnome-ssh-askpass:112): Gtk-WARNING **: 00:51:47.072: cannot open display:
      # /usr/libexec/openssh/gnome-ssh-askpass --help
      (gnome-ssh-askpass:113): Gtk-WARNING **: 00:52:10.596: cannot open display:
      # dnf remove -y openssh-askpass
    • ksshaskpass
    • # dnf install -y ksshaskpass
      # find . -name '*askpass*' -type f
      ./etc/xdg/plasma-workspace/env/ksshaskpass.sh
      ./usr/share/locale/en_GB/LC_MESSAGES/ksshaskpass.mo
      ./usr/bin/ksshaskpass
      /usr/bin/ksshaskpass
      # /usr/bin/ksshaskpass --help
      qt.qpa.xcb: could not connect to display
      qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
      This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
      Available platform plugins are: eglfs, linuxfb, minimal, minimalegl, offscreen, vnc, xcb.
      Aborted
      # dnf remove -y ksshaskpass
    という感じで全然起動してくれません。
    ですがエラーの内容的に display を開こうとしているのが分かります。
    なので、おそらく GUI で password を聞くような物だと思われます。


    askpass を作る

    askpass について調べていると、どうやら
    • password を stdout に出力するもの
    であることが分かりました

    よって
    • neovim 内で password を取得
    • password を stdout に出力する
    • password の履歴はどこにも残さない
    ようなものを作れば解決できそうです。

    それで完成したのが /tmp/askpass を作るこの Vimscript です。

    本当はファイル名は固定でなく tempfile 的な物で対応するなど、改善点はいくつも存在するかと思います。
    ですが私は Vimscript に明るくないので、"3つの条件を満たして動けばOK" という条件で作成しました。
    他により良い対応方法があればご指摘頂けると幸いです。


    その他の解決策

    調べた結果、 suda.vim というプラグインがあるようです。
    また、他にも sudo.vim など、似たようなプラグインはいくつかあるらしいです。

    これを使わなかった理由としては
    • docker のコンテナ内で作業をする事が多々ある
    • init.vim を配置するスクリプトは既にある
    • plugin を install するスクリプトはまだ無い
    • 全コンテナに plugin を入れるのも面倒だし結構サイズがある
      • $ du -sh ~/.config/nvim/repos
        • 100M    /Users/atton/.config/nvim/repos
    などがあります。

    また、"sudo が使えるならこの際 root になってしまう" という解決策もあるのですが
    • 前述した理由の後半3つ
    • 複数人数のユーザが使う環境だと、 /root 配下に自分専用の設定を置くことになる
    のでこちらも無しで。

    他には、 "sudoers で NOPASSWD を設定する" という解決策もあります。
    が、sudo を no password でガンガン使えば実質 root で作業しているのと変わらず、危険すぎるのでこちらも無し。

    ということでこの Vimscript を書くに至った、という訳なのです。


    おまけ: 環境変数の unlet

    スクリプト内部では askpass の場所を示す SUDO_ASKPASS を設定している部分があります。
    具体的には
    • let $SUDO_ASKPASS  = s:askpass_path
    ですね。

    この環境変数を定義していると、 sudo -A した際に指定したファイルが実行されます。
    一応、 :SudoWriteCurrentBuffer の実行後にはなるべく元の環境に戻すために
    • unlet $SUDO_ASKPASS
    を書いていた時もありました。

    ですが動作確認中、 Vim 8.0.1365 で unlet 時にエラーが出てしまいました。
    また 、-A を指定しなければ sudo の動作は変わらないので 、残っていても無害と判断して unlet しない形になりました。


    おまけ: shebang を指定しないとどうなるのか

    ちなみに askpass の shebang を消すと
    • sudo: unable to run /tmp/askpass: Exec format error
    • sudo: no password was provided
    と言われる為、きちんと書いておく必要があります。


    おまけ: x flag を付けないとどうなるのか

    setfperm(s:askpass_path, "rwxr-xr-x")  をせずに 644 のままだと
    • sudo: unable to run /tmp/askpass: Permission denied
      • sudo: no password was provided
      と言われるので、こちらもきちんと指定しておく必要があります。


      参考

      0 件のコメント:

      コメントを投稿