WordPress の内部 HTTPS 通信の問題を解決する

こんにちは。masm11 です。今回は WordPress に関連した話を書きたいと思います。

WordPress はテーマやプラグインがいろいろあって便利です。 テーマが特定のプラグインを必要としている場合は、テーマのインストール時に そのプラグインをインストールする機能もあるようです。 しかし、プラグインがサーバ内にダウンロードされ、そこから改めてダウンロード、イン ストールが行われます。 自サイトが https の場合、ダウンロードする際も当然 https になります。

さて、ここでダウンロードに失敗しました。エラーメッセージは以下のとおりでした。

f:id:masm11:20181228201132p:plain

ダウンロードに失敗しました。cURL error 60: SSL certificate problem: unable to get local issuer certificate

ダウンロードしようとしたファイルの URL も表示されています。 これを解決したいと思います。

curl コマンド

エラーメッセージに cURL とあるので、curl コマンドでアクセスを試みたところ、 確かに同じエラーメッセージが表示されました。

$ curl https://ingage.jp
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.
$ 

エラーメッセージから、SSL 証明書絡みであることはすぐに解ります。

SSL 証明書は CA (証明書発行機関) の証明書によって確認でき、CA の証明書はさらに上位の CA の証明書によって確認できる、 という木構造をしています。今回のエラーメッセージはそのことに関連したメッセージのようです。

ブラウザからサイトにアクセスして証明書を確認してみます。

f:id:masm11:20181228203130p:plain

CA の証明書は GeoTrust RSA CA 2018 です。

さて、curl コマンドは

$ type -a curl
curl is /opt/bitnami/common/bin/curl
curl is /usr/bin/curl

PATH 的に /opt/bitnami/common/bin/curl になっているようです。 このファイルは shell script で、その中には

CURL_CA_BUNDLE="/opt/bitnami/common/openssl/certs/curl-ca-bundle.crt"

という行があります。

しかし、この curl-ca-bundle.crt というファイルの中を見ても GeoTrust RSA CA 2018 がありません。 これが問題かもしれません。

中間証明書を

https://www.geotrust.co.jp/resources/repository/intermediate_dc.html

からコピーして curl-ca-bundle.crt の最後に追記してみます。以下のようになります。

... (略)
N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm
m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
-----END CERTIFICATE-----

GeoTrust RSA CA 2018
===============================
-----BEGIN CERTIFICATE-----
MIIEizCCA3OgAwIBAgIQBUb+GCP34ZQdo5/OFMRhczANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
... (略)
O9PuiHMKrC6V6mgi0s2sa/gbXlPCD9Z24XUMxJElwIVTDuKB0Q4YMMlnpN/QChJ4
B0AFsQ+DU0NCO+f78Xf7
-----END CERTIFICATE-----

(GeoTrust RSA CA 2018 の行以下が追加した部分です)

すると、curl コマンドで正常につながるようになりました。

$ curl https://ingage.jp
$ 

つまり、必要な CA 証明書が curl-ca-bundle.crt になかったため、ingage.jp の証明書を cURL が確認できなかった、 というのが原因でした。

WordPress

しかし WordPress からは依然としてダウンロードできません。 おそらく、curl コマンドでなく libcurl.so を使っているのでしょう。

散々探し回った結果、 /opt/bitnami/apps/wordpress/htdocs/wp-includes/certificates/ca-bundle.crt というファイルを見つけました。このファイルにも GeoTrust RSA CA 2018 が含まれてい なかったので、同様に追記したところ、見事、正常にダウンロードできるようになりまし た。

まとめと感想

今回は、WordPress が内部で行なっている HTTPS 通信で発生した問題を解決しました。

まさか WordPress 自身が CA 証明書を持っているとは思っていませんでした。 WordPress は知識の少ない人も含めていろんな人を対象にしていると思っているのですが、 そのわりにはいろいろ難しいし知識が必要だなぁ、と感じています。 皆さんはどうでしょうか?

ではまた。

Bit Journey さんに遊びに行ってきた

f:id:kizashi1122:20180206113531j:plain

東京出張が入ったこともあり、Bit Journey さんに遊びにいってきました。 @gfx こと藤さん、@michimochi こと道川さんとランチがてら色々お話しさせていただきました。

BJさんのサービス https://kibe.la と弊社のサービスはともに SaaS というだけでなく、利用技術に共通点が多く、非常に有意義な情報交換ができました。 といっても、書けることは少ないのでごく一部。

  • API Keyの管理大事
  • Twitter PWAがんばってる
  • SAML 大変
  • Intercom は Slack 連携をやり直すと通知に会社名が含まれるようになる(なった)

ほとんど情報量ないな・・・。

次回は、他のSaaS企業の方ともお会いできそうで楽しみです。

Developers Summit Kansai 2018 (デブサミ)で登壇してきました

インゲージ永田です。

event.shoeisha.jp

こちらでランチスポンサーをさせていただき、登壇してきました。



長いタイトルですみません。 翔泳社の運営者サイドには割と早い段階で伝える必要があり、(仮)付きで伝えていたのですが、それがそのまま本タイトルになりました。

最初に資料を作っていたときは、開発観点、営業観点の両方の視点からゼロからどうやって顧客を獲得していったのかを話そうと思っていました。 しかし30分の枠では、バランスよく話すことは案外難しく、また開発者のためのイベントということも考えて、営業観点のスライドはすべて削除しました

しかし後ほど質問してくださった方、複数人より、「どうやって顧客を獲得していったかを知りたかった」というお話をいただきました。 確かツイートでも同様の内容でつぶやいていた方もいらっしゃったと思います。

このことは意外でした。 是非またの機会に話してみたいと思います。

また言語に特化したイベントでもないため「遭遇したトラブル」も言語に依存しないトラブル事例を書こうと思いましたが、過去の記録を漁っても都合のいいトラブルはなく、泣く泣くRailsに依存したトラブルを書かざるを得ませんでした。もっと事前に時間があれば他に紹介しておくべきトラブル事例が見つかったかもしれません。これもまたの機会にお話ししようと思います。

上記スライドには自己紹介の中にウッドパームレストの話が出てきます。

写真は実際にニレ(正しくは神代ニレ)の板を使ってウッドパームレストであり、これを自作したという話をしました。 そしてこのパームレストはFILCOが製品として出しているパームレストと同様に、手前側に斜面がついています。 これをどうやって作るかという話はしなかったのですが、また別の記事で書きたいと思います。

ウッドパームレストいいですよ。

最後に。

ご来場、ご清聴いただいた皆様、どうもありがとうございました。

Rails エンジニア、 iOS エンジニアを募集してます!

Python で openpyxl を使ってみる

こんにちは。masm11 です。

Python で .xlsx ファイルを作成したかったので openpyxl に挑戦してみました。 簡単に使えましたので、その方法を紹介したいと思います。

インストールはコマンドラインで以下のようにします。

pip install openpyxl

では、まずは簡単に A1〜C3 セルに数値を入れる方法から。 なお、以下のコードでは、Python 3.6 以上を想定した書き方をしています。

#!/usr/bin/env python

import openpyxl

wb = openpyxl.Workbook()
ws = wb.active

num = 0
for r in [ '1', '2', '3' ]:
    for c in [ 'A', 'B', 'C' ]:
        ws[f'{c}{r}'] = num
        num += 1

wb.save('sample-1.xlsx')

これで sample-1.xlsx ができます。LibreOffice で開いた画面は以下のようになりました。

f:id:masm11:20180910150446p:plain

次に、各数値を今度は文字列にしてみましょう。

#!/usr/bin/env python

import openpyxl

wb = openpyxl.Workbook()
ws = wb.active

num = 0
for r in [ '1', '2', '3' ]:
    for c in [ 'A', 'B', 'C' ]:
        ws[f'{c}{r}'] = str(num)
        num += 1

wb.save('sample-2.xlsx')

これでもいいと言えばいいのですが、セルの値の先頭に ' が付いて、 '0 などとなってしまいました。

f:id:masm11:20180910150513p:plain

ここは書式設定をするのが良いでしょう。

数値形式は以下のページの FORMAT_*** が使えそうです。

https://openpyxl.readthedocs.io/en/stable/_modules/openpyxl/styles/numbers.html

この中から FORMAT_TEXT を指定してみました。

#!/usr/bin/env python

import openpyxl

wb = openpyxl.Workbook()
ws = wb.active

num = 0
for r in [ '1', '2', '3' ]:
    for c in [ 'A', 'B', 'C' ]:
        ws[f'{c}{r}'].number_format = openpyxl.styles.numbers.FORMAT_TEXT
        ws[f'{c}{r}'] = str(num)
        num += 1

wb.save('sample-3.xlsx')

これで以下のようにテキストになりました。

f:id:masm11:20180910150541p:plain

以上のように、openpyxl はとても使いやすそうです。 今後もお世話になりそうな予感がしています。

ではまた。

プログラムの出力をリダイレクトする

こんにちは、masm11 です。

今回は、プログラムの出力を操作する話を書きたいと思います。 「なんだそんなことか」と思われるかもしれませんが、 なかなか高度なこともできます。

ファイルディスクリプタとは

プログラムの出力を操作するには「ファイルディスクリプタ」というものについて知っておく必要があります。 リダイレクトの前にこれについて軽く勉強してみます。

C言語で

int fd = open("/tmp/foo", O_WRONLY);

などとファイルを開くと、その結果として 0 以上の番号が得られます。 これがファイルディスクリプタです。

ファイルに対して入出力する時には、このファイルディスクリプタを使って、

write(fd, "123", 3);

などとします。fd は先程得られたファイルディスクリプタです。 fd が示すファイル(つまり /tmp/foo)に対して 123 という3バイトを書き込んでいます。

そして、ファイルディスクリプタ 0〜2 は特別な意味を持っていて、

  • 0 は標準入力
  • 1 は標準出力
  • 2 は標準エラー出力

をそれぞれ示すことになっています。

dup2() とは

open() の他に、dup2() というシステムコールがあります。

これは、元々のファイルディスクリプタとは別の、指定したファイルディスクリプタからでも使えるように する機能があります。

例えば

int fd = open("/tmp/foo", O_WRONLY);

とすると、fd を使って /tmp/foo に書き込むことができますが、dup2() を使って

dup2(fd, 1);

とすると、fd の代わりに 1 でも /tmp/foo に書き込むことができるようになります。

リダイレクト

さて、リダイレクトです。

program > log.txt

よく目にする、普通のリダイレクトです。

シェルは、今までに説明した open()dup2() を使って、

int fd = open("log.txt", O_WRONLY);
dup2(fd, 1);

をした後に program を実行します。すると、program にとって標準出力のファイルディスクリプタ 1 は log.txt につながっているため、program が普通に標準出力に出力するだけで、log.txt への出力となります。

2>&1 とは

次に、

program > log.txt 2>&1

これもよく目にすると思います。

シェルは

// > log.txt の処理
int fd = open("log.txt", O_WRONLY);
dup2(fd, 1);

// 2>&1 の処理
dup2(1, 2);

をした後に program を実行します。

dup2(1, 2); をすることで、1 だけでなく 2 でも log.txt に出力されるようになっているわけです。

なお、間違って

program 2>&1 > log.txt

と実行し、「あれ?」と思った方、いませんか?

// 2>&1 の処理
dup2(1, 2);

// > log.txt の処理
int fd = open("log.txt", O_WRONLY);
dup2(fd, 1);

dup2(1, 2); の時、1 につながっているファイルに 2 からでも出力されるようになるわけですが、 通常では 1 には端末がつながっているため、2 も同じ端末へ出力されるようになります。 ただ、通常では(つまりリダイレクトしなければ) 2 は 1 と同じ端末に出力されるようになっていますので、 このタイミングでの 2>&1 は無意味となります。

そしてその後に > log.txt が処理され、1 が log.txt につながります。

そのため、標準エラー出力は引き続き端末のままとなるわけです。

順序には気をつけましょう。

標準出力と標準エラー出力を入れ替える

では最後に、標準出力と標準エラー出力を入れ替えてみます。

program 3>&2 2>&1 1>&3 3>&-

こうすることで入れ替えられます。順を追って見ていきます。

// 3>&2
dup2(2, 3);

こうすると、2 につながっているファイルに対して、3 からでも出力できるようになります。 3 というファイルディスクリプタは、標準入力でも標準出力でも標準エラー出力でもない、普通のファイルディスクリプタです。

// 2>&1
dup2(1, 2);

こうすると、1 につながっているファイルに対して、2 からでも出力できるようになります。

さて、その上で、

// 1>&3
dup2(3, 1);

です。3 につながっているファイルに対して、1 からでも出力できるようになります。 ここで 3 は、上に書いたとおり、元々 2 につながっていたファイルです。 ですので、1 からは元々 2 につながっていたファイルに出力されることになります。

以上で、program を実行すると、標準出力は標準エラー出力へ、標準エラー出力は標準出力へ出力されるようになるわけです。

ただ、3 は、1 と 2 を入れ替えるために一時的に割り当てたもので、 program には不要です。ですので、

// 3>&-
close(3);

で 3 を閉じています。

実際に試してみましょう。

bash-3.2$ (
>   (
>     echo stdout
>     echo stderr >&2
>   ) 3>&2 2>&1 1>&3 3>&-
> ) > stdout.txt 2>stderr.txt
bash-3.2$ cat stdout.txt 
stderr
bash-3.2$ cat stderr.txt 
stdout
bash-3.2$ 

ちゃんと入れ替わっているようです。

この例では、この記事で説明しなかった書き方もしています。 >&22>stderr.txt の部分ですね。 どんな操作なのかは考えてみて下さい。

では。

Let's Encrypt でワイルドカード証明書を取得する

(2018/07/23 追記しました)

こんにちは、masm11 です。

弊社では社内でいくつものサブドメインを使っています。abc.example.com, def.example.com, ghi.example.com, ... そしていずれも HTTPS でアクセスできるようにするため、Let's Encrypt で証明書を取得しています。 しかし、サブドメインは時々増え、そのたびに証明書を新たに作っていました。 わりと手間です。

その手間を軽減するため、Let's Encrypt でワイルドカード証明書を取得することにしました。 ワイルドカード証明書があれば、*.example.com 全てに使えるため、 サブドメインが増えても証明書を新たに作らなくてすみます。

そこで今回は、Let's Encrypt を使ったワイルドカード証明書の取得方法について書きたいと思います。 ただ、今回の作業が、certbot-auto が既に動いていて、それを使ってワイルドカード証明書を取得する、 というものだったため、 この記事は「今まで certbot-auto で取得・更新していて、それをワイルドカード証明書に移行したい」 という人向けとなります。

さて、Let's Encrypt でワイルドカード証明書を取得するには、DNS を使った認証をする必要があります。 そして、せっかく Let's Encrypt を使うのですから、更新を自動化したいものです。 そこで、DNS による認証を自動化します。

DNS サービスは世の中にいくつもあり、certbot(-auto) はそれらの中のいくつかの方式に対応しています。 弊社では Route53 を使っていますので、Route53 用のプラグインを使いました。

1. certbot-auto について

certbot-auto や、それが使っている Python のライブラリは、自動更新されているはずです。 従って、改めて更新する必要はありません。

2. certbot-dns-route53 プラグインのインストール

/opt/eff.org/certbot/venv/ に certbot-auto が使っている python の virtualenv がありますので、 ここに certbot-dns-route53 をインストールします。

sudo /opt/eff.org/certbot/venv/bin/pip install certbot-dns-route53

./certbot-auto plugins と実行すると、使えるプラグインのリストが表示され、 その中に dns-route53 が存在するのが確認できます。

* dns-route53
Description: Obtain certificates using a DNS TXT record (if you are using AWS
Route53 for DNS).
Interfaces: IAuthenticator, IPlugin
Entry point: dns-route53 = certbot_dns_route53.dns_route53:Authenticator

3. AWS 的な準備

AWS に新たなユーザ(IAM)を作ります。このユーザの権限で DNS を操作することになります。

このユこのユーザに与える権限は、JSON で与えるのが簡単です。 JSON は以下のようになります (/opt/eff.org/certbot/venv/lib/python2.7/site-packages/certbot_dns_route53/__init__.py そのままです)。

{
    "Version": "2012-10-17",
    "Id": "certbot-dns-route53 sample policy",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "route53:ListHostedZones",
                "route53:GetChange"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect" : "Allow",
            "Action" : [
                "route53:ChangeResourceRecordSets"
            ],
            "Resource" : [
                "arn:aws:route53:::hostedzone/YOURHOSTEDZONEID"
            ]
        }
    ]
}

これの YOURHOSTEDZONEID の部分を書き換えてアップロードすると、 権限を設定できます。 hostedzone ID は aws console に表示されていますので、それに書き換えて下さい。

このユーザの credentials を /root/.aws/credentials に保存します。 ファイルの中身は以下のようになります。

[default]
aws_access_key_id = ...
aws_secret_access_key = ...

4. 証明書を取得

いよいよ証明書の取得です。

sudo ./certbot-auto certonly \
  --server https://acme-v02.api.letsencrypt.org/directory \
  --email メールアドレス \
  --agree-tos \
  --dns-route53 \
  -d \*.ドメイン

ワイルドカード証明書を取得するには、letsencrypt のサーバを default とは異なる acme-v02 にする必要があるので、 それを指定しています。

また、サーバを変更するとメールアドレスも再度登録する必要があるので、--email を指定しています。 再登録となるので、同意確認もあります。同意確認のために --agree-tos を付けています。

--dns-route53 は Route53 を使った認証をする指定です。

実行すると、少し時間がかかります。DNS を使うため、TXT レコードを設定した後に、前の TXT レコードの TTL が切れるのを 期待して少し待っているようです。

5. nginx の設定を修正

sudo vi /etc/nginx/nginx.conf

証明書と秘密鍵は /etc/letsencrypt/live/ドメイン/ にあるので、設定を修正します。 ドメインは、*. を除いたものになります。

設定を修正したら nginx を restart します。

ブラウザからアクセスして、ワイルドカード証明書に切り替わっていることを確認しましょう。 問題なければ取得はこれで完了です。

6. 証明書を更新

これはまだ試せていないのですが、 証明書を取得した際のオプションは /etc/letsencrypt/renewal/ に保存されているので、 更新時にはオプションは必要ないはずで、

sudo ./certbot-auto renew --post-hook '/etc/init.d/nginx restart'

だけでできるはずです。

7. 旧証明書を revoke

これもまだ試していないのですが、 証明書の自動更新まで確認できたら、旧証明書は revoke しておくのが良いでしょう。

sudo ./certbot-auto revoke --cert-path /etc/letsencrypt/live/ホスト名/fullchain.pem

revoke すると、証明書ファイルは削除され、それ以降は renew による更新の対象にならなくなります。

まとめ

弊社ではこの手順で取得しました。今のところ問題ありませんが、更新が問題なくできるか、ちょっとドキドキです。


2018/07/23 追記。

これだけでうまくいくと思っていたのですが、予想外の事態が発生しました。 certbot-dns-route53 が削除されてしまいます。

certbot-auto は、実行された時に certbot-auto 自身や必要としている python ライブラリを更新しています。 おそらくこの時に削除されているものと思われます。

いくつか対策を実施して、現在動作確認中です。 今回の証明書更新は手動で実行したので、2ヶ月後に確認できると良いな、と思っています。


ではまた。

Ruby 関西でマルチテナントアプリの発表してきました

rubykansai.doorkeeper.jp

テーマが同じだけに、 id:gfx さんの

railsdmでマルチテナント・ウェブアプリの話をしました - Islands in the byte stream

と、内容が似通ってしまいましたが、自分なりにまとめてみました。

運用が始まり、ユーザ(テナント)が増えるとと色々難しい局面にも出くわします。 それは、また別の機会にお話しできれば。