Blog

  • PostgreSQL 9.6 で “Peer authentication failed”

    PostgreSQL 9.6 で “Peer authentication failed”

    既に出尽くした感はありますが、昔からの PC ユーザほどハマりやすいのではないかと思うので記録します。

    データベースへ接続する際の認証手段は「ユーザ」と「パスワード」であると思い込みがちですが、最近の RDBMS では OS 側のユーザで認証するのがデフォルトになっています。

    例えば Linux で foo というユーザでログインしていたとします。ユーザを指定せずに mysql や psql を起動すると、同じ foo というユーザでデータベースに接続します。これはこれで簡潔でわかりやすい仕組みなのですが、落とし穴があって Linux 側と RDBMS が同じユーザでないといけない制限が掛かっていたりします。

    どういうことかというと、例えば PostgreSQL 9.6 を使っている環境で、ユーザ foo さんが psql をユーザ postgres で使いたいとき、次のようにユーザを指定して接続しても認証で弾かれます。パスワードが正しくても弾かれるので、ハマりやすいポイントです。

    foo@host:~$ psql -U postgres
    psql: FATAL: Peer authentication failed for user "postgres"

    postgres で psql に入るには sudo su – postgres などとして、Linux 側でもユーザ postgres である必要があります。

    foo@host:~$ sudo su - postgres
    postgres@host:~$ psql
    psql (11.1 (Debian 11.1-1.pgdg90+1), server 9.6.10)
    Type "help" for help.

    postgres=#

    この認証の設定は /etc/postgresql/9.6/main/pg_hba.conf にあります。hba は Host Based Authentication の略のようです。

    pg_hba.conf

    pg_hba.conf を開くと、説明のコメントがほとんどを占めていますが、必要なのは次の部分です。

    ...(略)...
    
    # Database administrative login by Unix domain socket
    local   all             postgres                                peer
    
    ...(略)...
    
    local   all             all                                     peer
    
    ...(略)...
    
    host    all             all             127.0.0.1/32            md5
    
    ...(略)...

    ここで “peer” となっているのが peer authentication です。これをパスワード認証にするには password または md5 とします。password は平文通信されるので、ハッシュ化される md5 を選びましょう。

    各設定はそれぞれ次のような意味です。

    local   all             postgres                                peer

    ローカルシステム上のユーザが、ユーザ名 postgres を使った peer 認証で全てのデータベース(all)に接続できます。peer 認証なので、実質的にローカルシステムの postgres というユーザが、全てのデータベースに接続できるのと同じです。

    local   all             all                                     peer

    ローカルシステム上のユーザは誰でも(2 つ目の all) peer 認証で全てのデータベース(1 つ目の all) に接続できます。

    host    all             all             127.0.0.1/32            md5

    127.0.0.1(localhost) からの接続はパスワード認証(md5)されて、全てのユーザ(2 つ目の all) が全てのデータベース(1 つ目の all) に接続できます。

    “peer” を “md5″ に書換えて、ロールにパスワードを設定すれば完了です。”sudo system reload postgresql” などとして、設定を反映させるのを忘れないように。

    ユーザ(ロール)のパスワード設定

    postgres 等の権限があるユーザで psql に入り、ALTER ROLE または ALTER USER を使います。

    postgres=# ALTER ROLE role_name ENCRYPTED PASSWORD 'your_password';

    そのパスワード認証、必要ですか?

    ここまで書いておいて今更ですが、peer 認証の仕組み自体は、分離している PostgreSQL と OS の認証をできるだけ簡素にする、とても良い仕組みです。MariaDB でも同じ手法を用いているため、これからは(あるいはもう既に)この手法が主流になっていくのでしょう。

    これが問題になるのは外部、あるいはプログラムからのアクセスでしょう。それについては、デフォルトで 127.0.0.1(localhost) からのアクセスはパスワード認証(md5)に設定されているため、ロールにパスワードを設定するだけで事足ります。

    今回は Python から psycopg2 で接続するのが最終目的だったため、ロールにパスワードを設定するだけで要件は満たしました。慣れた手法を使いたくなりますが、それによって時代の潮流から外れるのは、後々になって自分の首を絞めることになることはわかっているので、できる限り避けたいと思っています。

    参考

  • Debian 9 Stretch で Docker のインストール

    Debian 9 Stretch で Docker のインストール

    Debian 9 Stretch に Docker をインストールして動作確認を行うまで。

    $ sudo apt update
    $ sudo apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common
    $ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -

    /etc/apt/sources.list に次の一行を追加する。

    deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable

    もう一度 apt update して更新を行ったあとにインストールを行います。

    $ sudo apt update
    $ sudo apt install docker-ce

    動作確認に hello-world の image を run してみます。

    $ sudo docker run hello-world
    Unable to find image 'hello-world:latest' locally
    latest: Pulling from library/hello-world
    d1725b59e92d: Pull complete
    Digest: sha256:0add3ace90ecb4adbf7777e9aacf18357296e799f81cabc9fde470971e499788
    Status: Downloaded newer image for hello-world:latest
    Hello from Docker!
    This message shows that your installation appears to be working correctly.
    To generate this message, Docker took the following steps:
    The Docker client contacted the Docker daemon.
    The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
    The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
    The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.
    To try something more ambitious, you can run an Ubuntu container with:
    $ docker run -it ubuntu bash
    Share images, automate workflows, and more with a free Docker ID:
    https://hub.docker.com/
    For more examples and ideas, visit:
    https://docs.docker.com/get-started/

    ローカルのイメージを表示

    $ sudo docker images
    REPOSITORY TAG IMAGE ID CREATED SIZE
    hello-world latest 4ab4c602aa5e 2 months ago 1.84kB

    動作中のコンテナを表示

    $ sudo docker ps
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

    全てのコンテナを表示

    $ sudo docker ps -a
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    4d4807dccc3c hello-world "/hello" 8 minutes ago Exited (0) 8 minutes ago friendly_chandrasekhar

    コンテナの削除

    CONTAINER ID を指定します。

    $ sudo docker rm 4d4807dccc3c
    4d4807dccc3c

    ローカルのイメージを削除

    イメージの名前か IMAGE ID を指定します。

    $ sudo docker rmi hello-world
    $ sudo docker rmi 4ab4c602aa5e

    参考

  • cannot update mailbox /home/user/Maildir for user user. cannot open file: Is a directory

    Postfix でメールサーバを建てましたが、メールの受信をテストをするために Gmail から自ドメイン宛にメールを送ってみたところ、次のようなエラーメールが返ってきました。

    調べたら原因はすぐに判明して、Postfix の設定の問題でした。Maildir 形式なのに mbox 形式として扱おうとしていたのが問題でした。

    /etc/postfix/main.cf

    ...
    home_mailbox = Maildir/
    ...

    home_mailbox = Maildir とするとmbox として、Maildir/ とすると Maildir と扱われるようです。ファイルかディレクトリかで判別しているということですね。

    参考

  • Debian 9 Stretch で locale の変更

    Debian 9 Stretch で locale の変更

    $ localectl
    System Locale: n/a
    VC Keymap: n/a
    X11 Layout: us
    X11 Model: pc105
    $ localectl list-locales
    C.UTF-8
    en_US.utf8
    $ sudo localectl set-locale LANG=en_US.UTF-8
    $ localectl
    System Locale: LANG=en_US.UTF-8
    VC Keymap: n/a
    X11 Layout: us
    X11 Model: pc105
  • Python WSGI で毎回 encode(‘utf-8’) するのが面倒

    Python WSGI で毎回 encode(‘utf-8’) するのが面倒

    Python の WSGI プログラムで出力をするたび

    yield '<h1>テスト</h1>'.encode('utf-8')
    yield '<p>あいうえお</p>'.encode('utf-8')

    と encode(‘utf-8’) を付けていましたが、どう考えてもこれは手間だし無駄な気がします。Flask を使えばいいと言われたらお終いなので、何か手はないかと考えてみました。

    Python にはデコレータ(decorator) という仕組があって、任意の関数の前後に好きな処理を追加することができます。関数も第一級オブジェクトである Python の特性をうまく利用した素敵機能ですね。

    yield を扱った decorator の例があまり見つからなくてやや苦戦しましたが、次のような decorator で実際に動作することを確認しました。

    def encode_utf8(func):
    	def wrapper(*args, **kwargs):
    		for r in func(*args, **kwargs):
    			yield r.encode('utf-8')
    	return wrapper

    前回の WSGI でフォームを扱うプログラムに適用させてみたのが次のコードです。

    # coding: utf-8
    
    from urllib.parse import parse_qsl
    
    def encode_utf8(func):
    	def wrapper(*args, **kwargs):
    		for r in func(*args, **kwargs):
    			yield r.encode('utf-8')
    	return wrapper
    
    @encode_utf8
    def application(env, res):
    	res('200 OK', [('Content-type', 'text/html; charset=utf-8')])
    
    	# Form
    	yield '<form method="get" accept-charset="UTF-8"><p><input type="text" name="get" value=""><input type="submit" value="GET"></form>'
    	yield '<form method="post" accept-charset="UTF-8"><p><input type="text" name="post" value=""><input type="submit" value="POST"></form>'
    
    	# Request method
    	method = env.get('REQUEST_METHOD')
    	yield '<p>Method: {}</p>'.format(method)
    
    	# Get query string
    	if method == 'POST':
    		wsgi_input = env['wsgi.input']
    		query = wsgi_input.read(int(env.get('CONTENT_LENGTH', 0))).decode('utf-8')
    	else:
    		query = env.get('QUERY_STRING', '')
    	yield '<p>Query: {}</p>'.format(query)
    
    	# Output form fields
    	form = parse_qsl(query)
    	for k, v in form:
    		yield '<p>{} = {}</p>'.format(k, v)

    行数は変らないのでぱっと見の変化少ないですが、書き忘れて Internal Server Error をもらう率は少し減りますね。

  • Python WSGI でフォームのデータを取得

    Python WSGI でフォームのデータを取得

    TL; DR

    POST

    query = env['wsgi.input'].read(int(env.get('CONTENT_LENGTH', 0))).decode('utf-8')

    GET

    query = env.get('QUERY_STRING', '')

    parse and split

    # dictionary
    form = urllib.parse.parse_qs(query)
    # list
    form = urllib.parse.parse_qsl(query)
    (さらに…)
  • プラグインに頼らない WordPress の移転

    プラグインに頼らない WordPress の移転

    経緯

    性能や管理の手間を考えてサーバの移転を行いました。その際に見事にハマった WordPress の移転について書きます。

    元々使っていたのは自宅サーバ上の WordPress 4.1 でした。バージョンが古いのは、楽にできるかと apt でインストールしてしまったこと、設置時にパーミッションや実行ユーザをぐちゃぐちゃにしていまい、更新できない状態であったことが理由です。

    セキュリティ上よろしくないのはもちろんのこと、そもそもプラグインやテーマを追加するのにサーバへファイルを直接転送しなければならないという、大変手間なことこの上ない状態になっていました。

    Vultr が日本で $3.5/月のインスタンスを用意してくれていたので、これを機に自宅サーバを廃止して VPS に全面移行することに決めました。

    自宅サーバでは自前のプログラム他、VPN や WordPress など様々なものを動かしていましたが、ハードの更新を何度か経験していたために自前のプログラムは特に問題にならないと思われ、VPN は不要になり、WordPress さえうまく移植できれば、ことはすぐに済むはずでした。

    WordPress にはインポートとエクスポート機能が標準で備わっているため、前回はこの機能を使ってすんなりと移植できたのをおぼえています。今回も、と思いきや、記事の文字部分はインポートされますが、何度やっても画像等のメディアファイルの取得に失敗してしまいます。

    次に WordPress では有名らしいプラグイン All-in-One WP Migration を使ってみましたが、約 700 MB 近い巨大なファイルを作り上げた上に記事の日本語が全て ? に化けてしまいました。

    その後も wp-content/uploads 内のファイルを自動登録してくれるプラグイン等、様々なものを試してみましたが、どれもどこかで不整合が起きて元通りには戻りません。そもそも標準のインポートがなぜだめなのか、原因は調べてもわからずじまいでした。

    そこで原点に戻って MySQL の DB を dump し、手作業で移植することにしたところ、ようやっとうまくいきました。その手順を以下に記します。

    環境

    移転元

    • MySQL 5.5.60
    • PHP 5.6.37
    • WordPress 4.1

    移転先

    • MariaDB 10.1.26
    • PHP 7.0.30
    • WordPress 4.9.8

    移転元での作業

    wp-content/uploads を固める

    $ tar cvzf /home/user/uploads.tar.gz /var/lib/wordpress/wp-content/uploads

    wp-content の場所は自分で探す必要があります。今回はなぜか Alias で通常とは違うところにあって、見つけるのに苦労しました。わからなかったら URL と Apache の conf をがんばって追って探すか、find 等で見つけましょう。

    $ find / -name wp-content -type d

    作成したアーカイブは scp 等で新しいサーバに転送しておきます。

    データベースの dump を作成する

    $ mysqldump --default-character-set=binary wordpress > wp.sql 

    WordPress のテーブルが独立したデータベース wordpress にある場合は上記で全てのテーブルが dump されます。別のテーブルが混ざっている場合は WordPress のテーブルだけを指定して dump してください。ユーザとパスワードは -u USERNAME -pPASSWORD を加えてください。

    作成した dump も新しいサーバに転送しておきます。

    “–default-character-set=binary” なしで dump すると、UTF-8 でも Shift-JIS でも EUC-JP でも日本語が読めないファイルが出来上がってしまいました。必ず指定しましょう。

    移転先での作業

    一度 WordPress 設置してインストールまで済ませ、WordPress が正常に動作することを確認しましょう。移転作業とインストールを同時に進めると、何かあったときに原因の切り分けが難しくなります。WordPress のバージョンは揃えた方が無難だと思いますが、今回は最新版の 4.9.8 を使ってそのままいけました。

    インストール時にデータベースやテーブルの接頭辞を指定しますが、必ず移転元と同じにしておきましょう。変更するなら WordPress の設定も変更しなければなりません。

    インストールが完了したら一点だけ確認しておきます。ログインしてダッシュボードを開き、「設定」 – 「メディア」の「ファイルアップロード」 – 「アップロードしたファイルを年月ベースのフォルダに整理」のチェックを移転元と同じ状態にしておきましょう。

    プラグインやテーマも揃えておく、あるいはコピーしてしまう方が安全だと思いますが、テーマに関してはサイトが表示されないだけで wp-login.php を開けばダッシュボードは開けるはずなので、今回は無視して進めました。

    dump から MariaDB へ

    WordPress のインストールまで済ませておくとテーブルのデータが競合してしまうので、全てのテーブルを空にしておきます。次の内容を clear_tables.sql などに保存しておきます。

    delete from wp_commentmeta;
    delete from wp_comments;
    delete from wp_links;
    delete from wp_options;
    delete from wp_postmeta;
    delete from wp_posts;
    delete from wp_term_relationships;
    delete from wp_term_taxonomy;
    delete from wp_terms;
    delete from wp_usermeta;
    delete from wp_users;
    $ mysql wordpress < clear_tables.sql
    $ mysql wordpress < wp.sql

    wp.sql は移転元で dump したデータベースの中身です。もし移転元と移転先のドメインが異なるなら、予めこのファイルをテキストエディタ等で開いてドメインを全て置換えしておきましょう。

    uploads の展開

    $ tar xvzf uploads.tar.gz
    $ cp -r uploads/* wp-content/uploads

    展開したファイルはパーミッションを適切に変更しておいてください。

    仕上げ

    uploads の移植とデータベースの復元が完了したら WordPress を開いてみましょう。データベース更新の案内が表示されるはずなので、あとは手順通り進めて完了です。お疲れさまでした。

    参考