WordPressの管理に疲れたので静的ページに落としてS3で公開した話

(追記 7/11)
qiitaの記事を転機し,動作テストとする.

(追記 7/10)
cloudfrontの設定を追記した.
意外と手間がかかっていて,wordpressを普通に運営した方がいいんじゃねーのってツッコミもありうる.

注意
ドメイン名を晒していますが,403 Forbiddenが返ってしまった方がいるかも知れません.
過去にアタック性のあるURLへのアクセスが検出されたアクセス元IPアドレスは,全て機械的に拒否設定しているため,そのリストに加えられている可能性があります.

遊び半分で「w00tw00t」なんて文字列の入ったパスにアクセスしちゃダメ,絶対!

疲れた…

私の運営するwebsiteは,wordpressがメインコンテンツになっている.
運用するのは結構大変で,

もう疲れたよ!

この記事の概略

https://wp.hadacchi.com がサンプル (なぜか wp_blog/index.html だけ,index.htmlまで指定しないと読んでくれない)

※スクリーンショットを何回にも分けて取ったので,ドメイン名がコロコロかわっている点はご留意いただきたい.

対策→構成変更

ということで,手間を少なくblogを運営する方法を模索することにした.
構成っぽく書くが,AWS構成はフルマネージドのサービスを使っているので,実際の構成とはきっと違うし雰囲気だけ掴めれば良いことにする.

構成(イメージ)

[the Internet]
       |
     [FW]
       |
       +-- [Server] (nginx + wordpress)
       |
       +-- [Object Storage]
 [the Internet]
        |
[AWS Cloud Front]
        |
   [Amazon S3] <--(awscli)-- [FW] -- [local machine] (nginx + wordpress)

要は,従来のクラウド上の機能をローカルの非公開NWに落として,静的コンテンツに変換したものをS3にアップロードする.で,CloudFront経由で公開する.
しかも,今のクラウド料金はVMとオブジェクトストレージを合わせて400円/月くらいだが,この構成なら200円/月くらいになるはず.
あーなんで,この方法に気付かなかったんだろ.

実は,この記事[1]で行けるんだけど,ハマりどころがあるのでもう少し細かく書いたものが,本記事である.
基本的には,素晴らしき先達のノウハウを活用されたい.

S3 webhostingの設定

S3でindex.htmlを公開する

簡単には,この記事の通りである→[2]
ただ,いくつか端折られてたりするので,もちょっとだけ詳しく書く.

なおAWSアカウントは,もうあるものとする.(既に持ってるので再現できない)

awscli導入

awscliを,まず導入してしまおう.
pipが使えるものとし,awscliをインストールする.

# user権限で入れると ~/.local/bin/ にインストールされる
pip install --user awscli
export PATH=${HOME}/.local/bin:${PATH}
aws --version

すると,こんな感じの結果が返ってくる.

aws-cli/1.15.49 Python/2.7.9 Linux/3.16.0-4-amd64 botocore/1.10.48

文献[2]の通りバケットを作成する

仮ドメインをtmp.hadacchi.comとすると,バケット名もtmp.hadacchi.comとする.
※他の設定を正しくしても,この名前が合ってないとs3 webhostingでは404が返るので注意.
※cloudfrontを設定した後はこの限りでない

indexesを.htmにしたい場合は,index.htmと書けば良い.
ただし,後述のstaticpressというwordpressをhtml化するプラグインはindex.htmlとして吐くので,あえてhtmにするメリットはない.

バケットポリシーは,Resourceのところを,「バケットポリシーエディター ARN: arn:aws:s3:::tmp.hadacchi.com」と編集画面で書かれているところからコピペして修正すること.

書くならこんな感じ.
Versionの値の"2012-10-17"は,このJSONの従うフォーマットのバージョンなので,この値で良いことに注意.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AddPerm",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::tmp.hadacchi.com/*"
        }
    ]
}

IAMの作成(権限作成)

wordpressが動作しているAWS外のホストから,awscliで接続できるよう設定したい.
AWSのアクセス管理は,ユーザ(IAM)への権限付与で行なうので,まずIAMユーザを作る.

AWSコンソールの左上の「サービス」をクリックし「IAM」を選ぶ.

先にポリシーを作成する.

ビジュアルエディタでポチポチ選べば作成できる.
設定はS3を選んで,権限を選んで,対象リソースや対象オブジェクトを指定する.
具体的には,以下の通り.(ListAllMyBucketsにobjectの指定は不要なので,設定後にListAllMyBucketsだけ別グループに自動で切り出される)

サービス:S3
アクション(リスト):ListAllMyBuckets, ListBucket
アクション(読み込み):GetObject
アクション(書き込み):DeleteObject, PutObject
リソース(bucket):arn:aws:s3:::tmp.hadacchi.com
リソース(object):arn:aws:s3:::tmp.hadacchi.com/*

最後にReview policyを選んでからポリシーセットに名前をつけてcreate policyで作成できる.

IAMの作成(ユーザ作成)

今作ったポリシーを付与するユーザを作る.

とりあえずCLIからアクセスするキーさえあれば良い.

上で作成したポリシーをアタッチする.

で,スクロールして右下の確認→作成で完成.
次の画面のアクセスキーとシークレットアクセスキー(要はID/PW)はこの画面でしか確認できないので,ダウンロードしておく.
(もし確認を忘れたら,新しいキーを発行すれば良い.それは割愛する)

得たアクセスキーとシークレットキーにawscliに設定

~/.local/bin/awsへ設定を付与する.
Tokyo regionはap-northeast-1.

aws configure
AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Default region name [None]: ap-northeast-1
Default output format [None]: json

設定がうまくできていれば,バケット一覧(aws s3 ls)が見れるし,バケット内(aws s3 ls s3://BUCKETNAME)も見れる.
下の例はindex.htmlを置いてみた後の実行結果.

aws s3 ls
2018-07-01 15:42:56 tmp.hadacchi.com
aws s3 ls s3://tmp.hadacchi.com
2018-07-01 15:44:17       3417 index.html

コンテンツアップロード

サイト内コンテンツへのリンクを相対パスで書いていれば,今のpublic_html以下をそのまんまputすれば静的コンテンツだけは動く.
下のコマンドの例では,public_htmlというディレクトリは作成されない.(public_html/index.htmls3://tmp.hadacchi.com/index.htmlになる)
※注意: 以下のコマンドラインでアップロードしたところで,phpは動作しないので,当然ながらwordpressも動作しない.

aws s3 cp --recursive public_html s3://tmp.hadacchi.com

web公開設定の確認

文献[2]に記載の通りs3 website hostingのエンドポイントに書かれたURLへアクセスすれば,静的コンテンツであれば表示されるはず.
更に自分のDNSレコードをCNAMEでこのエンドポイントのFQDNに転送すれば,独自ドメインで引ける.この時,バケット名が独自ドメインと合致してないとエラーが出る.

wordpressの静的コンテンツ化

[1]の通りなんだけど,nginxを使っていると.htaccessを使ったrewriteが走らないためpermalinkの設定が反映されずハマる.

permalinkの設定反映

個人的にurlにはコンテンツに関する情報が入って欲しくないので,idで設定する.
%post_id%.html でOK

nginxの設定ファイルでrewriteを記述

[3]の方法でphp-fpmにpathを送ってあげれば良くて,重要なのは以下に改変して抜粋したこの部分.
なぜか我が家の環境では最後の1行だけで動作する.

    if (!-e $request_filename) {
        #rewrite ^.+?(/wp-.*) $1 last;  # ←なくても動く
        #rewrite ^.+?(/.*\.php)$ $1 last; # ←なくても動く
        rewrite ^ /wp_blog/index.php last;
    }

timeout値の変更

次の手順のstaticpressのrebuildでタイムアウトによりエラー停止することがあるので,事前に伸ばしておく.
だいたい500sで私はうまくいった.
設定場所は上のrewriteを追記したserverディレクティブの中で,php-fpmを呼び出しているところ.


     location ~ \.php$ {
         # With php5-fpm:
+        fastcgi_read_timeout 500s;
         fastcgi_pass unix:/var/run/php5-fpm.sock;
         fastcgi_index index.php;
         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

staticpressのbasic認証のところを改変

もし,wordpress本体側にBASIC認証をかける場合,staticpressのBASIC認証周りはバグ(?)で設定ページでどれだけBASIC認証情報を入れてもDBにその情報を記録してくれないので,ハードコードするかDBに書き込んでおくかしなければならない.
方法は文献[[4]](#cite4)を参照.
ハードコードするとすげー早くなったので,ハードコードオススメ.

--- class-static_press_admin.php.org
+++ class-static_press_admin.php
@@ -56,7 +56,8 @@
 	}
 
 	static public function basic_auth(){
-		return get_option(self::OPTION_STATIC_BASIC, false);
+		//return get_option(self::OPTION_STATIC_BASIC, false);
+		return "XXXXXXXXXXXXXXXXXXX";
 	}
 
 	static public function timeout() {

XXXXXXXXXXXは「ID:PW」なる文字列をbase64にかけたもの.
以下で得られる文字列である.(python2.Xの場合は,.encode('ascii')はいらない.)

>>> import base64
>>> print(base64.b64encode('hoge:fuga'.encode('ascii')))
b'aG9nZTpmdWdh'

staticpressでbuild

staticpress optionから設定.
Static URLはs3でアクセスするためのドメインtmp.hadacchi.comとしておく.その下のパス名は,s3にアップロードする時のディレクトリ名を指定すれば良いので,ルートに置く場合はなくても良い.
SaveDIRはstaticpressがdumpするhtmlの格納先のディレクトリ名で,ubuntuの場合はwww-dataに書き込み権限がないとエラーとなる.

あとは,staticpressのRebuildボタンで生成されるのを数分まつ.

アップロード

fromで指定したディレクトリの下のファイルを,toで指定したディレクトリの直下に置く,と覚えておく.

aws s3 cp --recursive /home/hoge/public_html/wp_blog/static/wp_blog s3://tmp.hadacchi.com/wp_blog

DNSの設定(S3を直接指定)

S3のwebhostingのエンドポイントを確認する.
エンドポイントのアドレスで,まずコンテンツが表示されるはず.(内部リンクはつながらないけど)

次に,DNSでこのエンドポイントに飛ぶよう CNAME を設定する.bindを例にすると,こんな感じ.
末尾のピリオドを忘れるとgTLDとか親レベルのドメイン名(ここではhadacchi.com)が勝手に補完されるので注意.

tmp   IN   CNAME tmp.hadacchi.com.s3-website-ap-northeast-1.amazonaws.com.

この時点で,DNSの設定をCNAMEでwebページは表示されるようになっているはず.

CloudFrontでSSL化と独自ドメインからの参照の設定

証明書の作成 (Certificate Manager)

CloudFrontの設定といいつつ,Certificate Managerで証明書を先に作る.

ここでのハマりポイントは認証.

DNSの認証の場合,レジストラのDNSサービスに乗っている人だと,レジストラの設定ページに依存するので各自で当該サービスに問い合わされたい.

ここで上の「DNSの認証」を選択すると,このようなCNAMEの設定をDNSに加えることが求められる.

これを設定しようと思うと,以下に相当する設定をレジストラのDNS設定ページから登録する必要がある.
各FQDNの最後のピリオドを忘れると,gLTD(ここではhadacchi.com)が補完されるので注意.

_15370fbba1c4b1cadb21f882e5078517.tmp	IN	CNAME	_39dc4142a943daade594b688b198dfc5.acm-validations.aws.
; または
_15370fbba1c4b1cadb21f882e5078517.tmp.hadacchi.com.	IN	CNAME	_39dc4142a943daade594b688b198dfc5.acm-validations.aws.

Eメールの場合,admin@,webmaster@,postmaster@などよくあるユーザ名にメールが送付されてくるので,それらメールで受け取った情報から認証する.
こちらの場合,MXレコードを設定しメールを受けられるようにしていないとうまくいかない.

いずれの方法を取るにせよ,ドメイン管理ができていれば大丈夫なはずなので,ここでは認証できたものとする.
Certificate Managerで認証が通って証明書が発行されると,CloudFrontの設定の途中で独自ドメインのSSLを参照できる.

CloudFrontの設定

CloudFrontのサービスページから「Create Distribution」をクリックしコンテンツ配信設定を作る.
続いて「Web」の方のGetStartedから開始する.

その次の画面で設定するのは,多分赤枠のところくらい.

「Origin Domain Name」に入力しようとするとドロップダウンリストでS3の公開設定されているリソースなどが表示されるので,該当のものを選ぶ.
「Viewer Protocol Policy」はお好みで.私は「Redirect HTTP to HTTPS」が好みだけど,今は実験的に「HTTP and HTTPS」にしている.

下へスクロールして続き.

「Alternate Domain Names」に独自ドメイン名を入れる.
「SSL Certificate」で「Custom SSL Certificate」を選択すると,その下の入力ボックスにお待ちかねの上(Certificate Managerの設定)で発行した証明書が入るので,Alternate Domain Namesと合うものを選択する.
すぐ下のCustom SSL Client SupportはSNIのみにしておかないと鬼のような請求が来るので注意.
世界各地のClourFrontのエッジの固定IPを占有するんだろうから,仕方ないけど.

更にスクロールして,設定するのはTSLやHTTPのバージョンくらいか.
Security Policyは,私の周りではTLSv1.2のみで何の問題も起きてないけど,古いブラウザや設定に対応したいならTLSv1.1とかv1でないとダメかも.
Supported HTTP Versionsは,もともと個人サイトでSSLを導入するモチベーションはHTTP/2による転送の高速化くらいだと思うんで,HTTP/2を選べば良いと思う.

DNSの設定(CloudFront)

CloudFrontのWebコンソールに「Domain Name」という列がある.これがCloudFrontのエッジを指定する時のSNIによるドメイン名(FQDN)であるので,自分の独自ドメインからCNAMEでこのFQDNを指すように設定してやれば良い.
設定の方法は,Certificate Managerの設定を参照されたい.

配信

以上を設定して「Create Distribution」を押せば完了だが,この設定をエッジに配布するのに15分くらいかかる.
また,15分経過するとAWSのコンソール上でStatusがProceedからDeployedになるが,実際にhttpアクセスが通るまでにもう少しかかる印象.
そのため,これまでの手順に漏れやミスがあってもCloudFrontの設定配信が切り分けのボトルネックになる.

以上のことから,まずはCloudFrontなしで全てのページが表示されることを確認した上で,CloudFrontの設定を行なうのが良い.

参考文献

[1] WordPress + StaticPress S3で静的WebサイトをS3でホストする - Qiita
[2] 独自ドメインを使ってAmazon S3で静的Webサイトをホストする - Qiita
[3] nginx で WordPress のパーマリンク設定を使用する - 暇人じゃない
[4] StaticPressとS3で爆速で激安な静的サイトを作ろう

手動インストールしたnginxを維持しながらowncloudをupgrade

前提

apache2は2.4で設定が変わった折に捨てた.
nginxはwebdavを使うためやhttp2を使うためなどの理由でdebianのパッケージでは不足であったため公式サイトの安定リリースを手動コンパイルしインストールしている.
owncloudは公式のドキュメント(Install package owncloud, owncloud-files)にあるリポジトリを使用する.

手順

owncloudをリポジトリからapt-getで入れる場合,httpdセクションのパッケージが入っていないと依存関係が問題になる.
owncloud-filesで依存関係を無視して入れるか,ダミーパッケージを作成して依存関係を解決させるかが必要となる.
今回は後者を選択した.

リポジトリ修正
owncloudはメジャーアップデートをスキップすることはできない.
例えば8.1が入っている場合,8.1->8.2->9.0->9.1とアップデートしなければならない.
なので8.2を入れる.
なんかリポジトリのアドレスが変わってたっぽいのでついでに対応する.

@@ -1 +1 @@
-deb http://download.opensuse.org/repositories/isv:/ownCloud:/desktop/Debian_8.0/ /
+deb http://download.owncloud.org/download/repositories/8.2/Debian_8.0/ /
更新準備(ダミーパッケージインストール)
まず,
equivsでダミーパッケージを作って入れる.

  1. equivsを入れて
    $ sudo apt-get update && sudo apt-get install equivs
    
  2. 適当なディレクトリでダミーパッケージ作り.
    $ mkdir nginx-dummy && cd nginx-dummy
    $ equivs-control nginx-dummy
    
  3. 設定ファイルの編集
    ただし,php5-cgiは8.2のみ依存関係のため入れている.8.2のアンインストール後,つまり9.0インストール後はphp5-cgiを抜いてインストールし直して良い.(9.0~は依存関係は「php5-cgi | php5-fpm」となっているので)

    @@ -6,14 +6,14 @@
     # Homepage: 
     Standards-Version: 3.9.2
    
    -Package: 
    +Package: nginx-dummy
     # Version: 
     # Maintainer: Your Name 
     # Pre-Depends: 
     # Depends: 
     # Recommends: 
     # Suggests: 
    -# Provides: 
    +Provides: httpd,php5-cgi
     # Replaces: 
     # Architecture: all
     # Copyright: 
    @@ -22,7 +22,7 @@
     # Extra-Files: 
     # Files: 
     #  
    -Description: 
    - long description and info
    +Description: dummy package for nginx
    + dummy package for nginx installed manually.
      .
    - second paragraph
    + nginx-dummy provides httpd and php5-cgi
    
  4. ダミーパッケージを作ってインストール
    $ equivs-build nginx-dummy; sudo dpkg -i nginx-dummy_1.0_all.deb
    
パッケージ更新
で,やっとownCloudを更新する.

$ sudo apt-get update && sudo apt-get install owncloud=8.2
ファイル更新
展開済のowncloudを更新する.

$ cd /var/www/owncloud
$ sudo -u www-data ./occ upgrade
メンテナンスモード終了
動作確認は設定ファイルでmaintenanceをfalseにしないとできない.

@@ -21,3 +21,3 @@
   'updatechecker' => false,
-  'maintenance' => true,
+  'maintenance' => false,
   'theme' => '',
繰り返す
8.2->9.0
9.0->9.1

10.0も出てるけど,9.1はまだサポートされてるし,今回はパス.
と思ったら,9.0もまだサポートされてた.アップデートしすぎた.でもダウングレードはサポートされてないので,9.1で行く.
Maintenance and Release Schedule · owncloud/core Wiki

nginx のメモ

よく忘れるので,メモ.

設定について

add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains;';
HSTS 設定.SSLでアクセスするようブラウザに指示するHTTPヘッダを追加する.
sendfile
ファイルI/Oにカーネルキャッシュを利用する…らしい
tcp_nopush
ヘッダとデータをチャンクして送信するようになるらしい
tcp_nodelay
パケット転送時に他にまとめられるものがないか,ちょっと待つのを止めさせるらしい
types_hash_max_size
typesのハッシュテーブルの最大サイズ
server_token
レスポンスヘッダに含むサーバの情報について制御する

インストールについて

debianの場合,jessie-backportsで入れれば1.10.3が入る(2017.3時点)ので,自分でコンパイルするのをやめた.
WebDAVをまともに使おうと思った場合,libnginx-mod-http-dav-extを一緒に入れると良い.

起動スクリプトについて

systemctl start nginx.service で PID ファイルに関するエラーが出る場合は,起動スクリプトを修正すると良い.

@@ -20,6 +20,7 @@
 PIDFile=/run/nginx.pid
 ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
 ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
+ExecStartPost=/bin/sleep 0.1
 ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
 ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid
 TimeoutStopSec=5

nginx で StartSSL のクライアント証明書を使ったクライアント認証をやる

--- 追記 ここから ---

注意

startsslの証明書が主なブラウザから弾かれるようになりました.
詳しくは,この辺とか→StartSSL の証明書が使い物にならなくなっていた件 | ぴんくいろにっき

個人的には,移行をおすすめします.
私は,2017年4月から別の認証局へ移行しております.

--- 追記 ここまで ---

毎回,認証が表示されるのが面倒臭いので,クライアント認証を導入する.

環境

nginxのバージョンを上げるか,opensslのバージョンを上げるかしないとエラーを吐くので,nginxを1.9.12以降に上げるか(#901 (Changes in openssl master wrt SSL_shutdown()) – nginx),opensslを1.1.0以降に上げる(SSL error NginX 1.9.10

本記事では,試験鯖でnginx公式リポジトリ(Debian 8 (Jessie) - Web サーバ Nginx 構築(Nginx 公式リポジトリ使用)! - mk-mode BLOG)を使用して入れた1.10.2-1と,
jessie-backportsを使用して入れた1.0.2k-1により動作確認することとする.

ちなみに,この鯖はそれらとは環境がまた違います.

nginxのアップデート

ownCloudを入れていてnginxを使っている時にnginx-commonを消すとapache2が入ってしまい,/etc/systemd/system/nginx.service/dev/nullを指すようになってしまったので,/lib/systemd/systemにnginx.serviceを書いて(NGINX systemd service file | NGINX),おまじないを唱える.

sudo systemctl unmask nginx.service

そうすると,systemdで制御できるようになるようなので,service nginx startとかが効く.
nginx.serviceに書いてあるバイナリーのバージョンを確認して,問題なければnginxの設定に入る.

nginxの設定

startsslのログインにクライアント証明書を使ったクライアント認証が必要(最近はメールアドレスへのワンタイムパスがサポートされたけど,昔は証明書を失くしたら二度とログインできなかった)なので,当然インストール済だとして,その証明書を使うことにする.
証明書のsubjectは以下とする.

/description=For login authentication only/CN=mail@address.com/emailAddress=mail@address.com

subjectを確認する方法は

openssl pkcs12 -nokeys -info -in CERTIFICATIONFILE.pfx

とかやる.pkcs12の部分は証明書の形式に応じて変更する.

あとはnginxのserverあたりに設定を書く.
基本的な設定は,この辺を流用すると楽チン.Generate Mozilla Security Recommended Web Server Configuration Files

server {
    listen 443 http2;
    server_name YOURDOMAIN;

    ssl on;
    ssl_verify_client on; # クライアント認証を有効化
    ssl_verify_depth 2;   # 認証局を辿る数

    # certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
    ssl_certificate /path/to/crt/of/your/server.crt;
    ssl_certificate_key /path/to/key/of/your/server.key;
    ssl_client_certificate /path/to/crt/of/your/CA.crt;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;


    # modern configuration. tweak to your needs.
    ssl_protocols TLSv1.2;
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
    ssl_prefer_server_ciphers on;

    # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
    add_header Strict-Transport-Security max-age=15768000;

    # OCSP Stapling ---
    # fetch OCSP records from URL in ssl_certificate and cache them
    ssl_stapling on;
    ssl_stapling_verify on;

    # Add headers to serve security related headers
    # Before enabling Strict-Transport-Security headers please read into this topic first.
    #add_header Strict-Transport-Security "max-age=15552000; includeSubDomains";
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Robots-Tag none;
    add_header X-Download-Options noopen;
    add_header X-Permitted-Cross-Domain-Policies none;

    # Path to the root of your installation
    root /path/to/your/document/root;
    index index.php index.htm;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    if ($ssl_client_s_dn !~ "/CN=mail@address.com/emailAddress=mail@address.com")
    {
           return 401;
    }

    # あとは普通の設定と同様
}

ね,簡単でしょ?

debian jessie に owncloud 8.1 を導入する

方法

owncloudの入れ方には,大きく分けて2種類

  • apt-getで入れる
    • 普通に入れる
    • backportsから入れる
  • ソースからコンパイル

ついでに,owncloudはwebdavが必要になるので,httpdも入れる.

  • apache2
  • nginx

で,本記事では

  • apt-get で backportsから owncloudを入れる (apache2を使いたくないから)
  • nginx で運用する

手順

  1. nginxと,mysql or mariadbは使える前提
  2. owncloudを入れてみる
    1. sudo apt-get install owncloud
    2. なんか apache2 が入ってきたものの,nginx で十分なので backports で入れることにする
      echo 'deb http://ftp.jp.debian.org/debian/ jessie-backports main contrib non-free' | sudo tee -a /etc/apt/sources.list
      sudo apt-get update; sudo apt-get upgrade
      sudo apt-get -t jessie-backports install owncloud
  3. apache2とかを消す
    1. 普通に消そうとすると,php5-cgiが入ろうとする.これも要らない.php5-cgiも消そうとしたら,php5-fpmが入ろうとする.これは使う.
      sudo apt-get remove apache2 php5-cgi
    2. 他に一緒に入ったものをまとめて消す
      sudo apt-get autoremove
    3. spdyでも良ければ,これでOK
      http2を使う場合は,nginxとopensslもbackportsで入れる
  4. mysqlの設定
    • DB作成.owncloudにログインする時に初期設定するので,何でもOK.ユーザも別で作って良い.
  5. nginxの設定
    • 基本はnginx Example Configurations — ownCloud 9.2 Server Administration Manual 9.2 documentationに従いつつ,Generate Mozilla Security Recommended Web Server Configuration Filesあたりを参照して,自分の環境に直す.
      私の場合,

      -upstream php-handler {
      -    server 127.0.0.1:9000;
      -    #server unix:/var/run/php5-fpm.sock;
      -}
      -
       server {
           listen 80;
      -    server_name cloud.example.com;
           # enforce https
           return 301 https://$server_name$request_uri;
       }
      
       server {
      -    listen 443 ssl;
      -    server_name cloud.example.com;
      +    listen 443 http2;
      +    server_name YOURDOMAINNAME;
      
      -    ssl_certificate /etc/ssl/nginx/cloud.example.com.crt;
      -    ssl_certificate_key /etc/ssl/nginx/cloud.example.com.key;
      +    ssl on;
      +
      +    # certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
      +    ssl_certificate /path/to/certificate/file;
      +    ssl_certificate_key /path/to/certificate/key/file;
      +    ssl_session_timeout 1d;
      +    ssl_session_cache shared:SSL:50m;
      +    ssl_session_tickets off;
      +
      +
      +    # modern configuration. tweak to your needs.
      +    ssl_protocols TLSv1.2;
      +    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
      +    ssl_prefer_server_ciphers on;
      +
      +    # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
      +    add_header Strict-Transport-Security max-age=15768000;
      +
      +    # OCSP Stapling ---
      +    # fetch OCSP records from URL in ssl_certificate and cache them
      +    ssl_stapling on;
      +    ssl_stapling_verify on;
      
           # Add headers to serve security related headers
           # Before enabling Strict-Transport-Security headers please read into this topic first.
      @@ -83,7 +97,9 @@
               fastcgi_param HTTPS on;
               fastcgi_param modHeadersAvailable true; #Avoid sending the security headers twice
               fastcgi_param front_controller_active true;
      -        fastcgi_pass php-handler;
      +        fastcgi_pass unix:/var/run/php5-fpm.sock;
      +        fastcgi_index index.php;
               fastcgi_intercept_errors on;
               fastcgi_request_buffering off; #Available since nginx 1.7.11
           }
      
  6. あと,この辺も設定しておかないとwebdavで大容量ファイルの転送に失敗すると思われる. nginx + php-fpmでphpを動かす - Qiita
    要するにファイルサイズの上限解除.

nginx で rewrite を辞めたら PCRE library を使ってくれなくなるので注意

nginx の makefile 作成時に --without-http_rewrite_module をつけて rewrite オプションを外すと,PCRE のライブラリを使ってくれなくなるため,location ディレクティブとかで正規表現を使っている場合にエラーを起こす.
回避策が分からんので,rewrite が要らなくなっても,location ディレクティブで正規表現したい時は外しちゃだめ.絶対.

たまにページ転送を rewrite でリクエストの改変により実装している人がいるが,重いので辞めた.

(略)
    # https でリダイレクト
    return 302 https://$http_host$request_uri;
    # ↑ ↓ 同じ
    rewrite ^(.*) https://$http_host$1 permanent;

こういうケースね.

StartSSLでSHA2署名の証明書

最近のChromeはSHA1署名の証明書に厳しいので,SHA2署名の証明書を発行しようとしたが,StartSSLでうまく認識できなくて困っていた.
この記事の通りにやってみたら,うまくSHA2の証明書として認識された.→ StartSSLで取得した証明書がsha1で認証されてしまう - Qiita
なんか,公式が証明書出してたので修正しました→StartCom • View topic - can I use a SHA256 Intermediate certificate
なんか,crtのダウンロード形式が変わったので修正しました

以下,nginxでsslを使う場合.
apacheで使う場合は,中間証明書をリンク先のものと差し替えるだけなんだけど,apache2.4以降の設定をほとんど真面目にやったことないので,詳しくは分かんない.

$ openssl genrsa -aes256 2048 > server.key
$ openssl req -new -sha256 -key server.key -out server.csr

StartSSLで証明書を発行したあと…
fqdn.zip 的なアーカイブをダウンロードできるようになるので,落として,解凍すると有名なhttpdサーバ毎に必要ファイルをまとめたzipが入っているので,必要なやつを更に解凍する.
なければ,OtherServer.crtにルート・中間認証局の証明書と,fqdn用に発行された証明書があるので,それを使う.

dovecotとかならパスフレーズを指定できるらしいけど,nginxはできんかった気がするので

$ openssl rsa -in server.key.org > server.key

httpとhttpsで動作を分ける場合は両方に記述を追加すること(SPDYも注意)

nginx+uwsgiで80番ポートのあるアクセスにpythonスクリプトの出力を流すようにしていたのだが,443ポートでのアクセスは想定していなかったので,特に何も設定していなかった.
そうしたら,httpsでアクセスすると表示されないはずのindex.htmが表示されることに気付いた.
さらに,pythonスクリプトファイル名をダイレクトに指定すると,pythonスクリプトがダウンロードできることも判明した.
そのファイルにこそ,パスワードの類は入れていないものの,インポートしているファイルの一覧が見えてしまうし,これはかなり危険だ.

で,443ポートのserverディレクティブに80番と同じ設定をコピペしたが,動かない…
firefoxやchromeでアクセスしていたため,443ポートとは別に443 spdyが有効になっていたのだ!
二段の罠だった.

Webサーバのログを眺めてみよう

この記事を書いた頃は,apacheを使っていたが,今では使っていない.「apache のエラーログを真面目に読んでみた | hadacchi blog
存在しないURLを適当に入れたら出てしまうから書いちゃうけど,このウェブサイトは,nginxを使って運営している.

で,まぁそのログを眺めようという前回に引き続いての企画.
思いも寄らぬエラーが出てたりするので,勉強になると思うよ.

access.log
  • 標準だと,多分/var/log/nginx/access.logあたりだと思う.
    生で見てもいいんだけど,アタックによく使われるphpmyadminあたりへのアクセスをひっかけるため,こんなコマンドでまずはIPとリクエストURL,サーバのレスポンスコード,ユーザエージェントを一覧する.

    # cut -d' ' -f1,7,9,12-  nginx/access.log | grep dmin | less

    そもそも,ここで200になっていたら,phpmyadminのログとか見た方がいいと思う.
    私は使ったことがないから知らない.

  • 次は,そこでひっかかったIPアドレスをひっかけて,他にヤバそうなアクセスを受けていないか確認する.
    # cut -d' ' -f1,7,9,12-  nginx/access.log | grep xxx.xxx.xxx.xxx | less

    そうすると,例えばこんなURLへのリクエストが飛びまくっていたので,この辺りに脆弱性のあるプログラムがあるんだろーなー,とか分かる.

    /w00tw00t.at.blackhats.romanian.anti-sec:)
    /muieblackcat
    /components/com_jinc/classes/graphics/php-ofc-library/ofc_upload_image.php?name=magic.php
    

    ぐぐってみると,1つ目と2つ目はよくひっかかる.
    3つ目は,なんかopen flash chartとかいうのが出てくる.
    あと,明らかにSMTPを狙ったログも出てきた.

    CONNECT 126mx00.mxmail.netease.com:25 HTTP/1.0

    ぐぐってみると,中国語っぽいサイトが出てくる.
    アクセス元IPアドレスをIP-HOST変換かけてドメインでぐぐると,台湾からのアクセスらしいという情報も出てくる.

error.log
基本は,access.logと同じだが,nginxの場合はphpへのアクセスを全てphp-fpmへ流していると,php-fpmからのエラーも全部記録される.
設置していないURLでの*.phpへのアクセスは,別にさばいてログを分けた方がいい…のかな?まだ考え中.
他には,設置をミスった証明書のエラーとかも出てくる.

# grep -v 'No such file or directory' nginx/error.log | less
php-fpm.logとか,unicorn.logとか
phpとかrubyとか使ってると,この辺りもチェックすることになると思う.pythonならuwsgi.logとかかしら.
この辺りを動かしている人は,自分の環境に併せて調べてみてね.

Redmine on Debian がアップデートでごちゃごちゃしたので整理

追記:
最近はrbenv+ruby-buildが流行りらしい.apt-getで入るけど,それで入れたら入れられるrubyは古いものしかなかった.

前提:サーバで動作している rubygems, ruby 系は全部消した上で,autoremove した状態.
それまで動いていた,redmine の環境はいじってない.

ruby
rvmでインストールする.
環境が整っていれば,webにあるように

root# curl -L https://get.rvm.io | bash -s stable

でいいんだけど,最初はうまくいかないので,インストール用のシェルスクリプトを落としてきて何度も実行することになる.

$ wget -L https://get.rvm.io
$ cat index.html | bash -s stable

そうしたら色々と足りないものを入れろと言われる.
RedmineをDebianに導入する - Qiita を見て入れたものは,これら.
make環境が入ってたので,少なめ.

root# apt-get install git
root# apt-get install libssl-dev
root# apt-get install libreadline-gplv2-dev
root# apt-get install libyaml-dev
root# apt-get install curl

んで,もういっかい,

$ cat index.html | bash -s stable

そしたら環境を読み込んでrubyを入れる.

$ source $HOME/.rvm/scripts/rvm
$ rvm install x.x.x # <- 入れたいバージョン

この辺りで,rootになってないことに気付いたけど,$HOME/.rvm 以下にインストールされてたので,いいやと思うことにする.

$ rvm list

として,インストールされたrubyが出てきたらOK.

mysql関連gems
これはスキップできなかった.

$ sudo apt-get install libmysqlclient-dev
$ gem install mysql2
bundler
$ gem install bundler --no-rdoc --no-ri

これでローカルに入るはず.

redmine用のgems
なんか gemset とか使って,環境を切り替えられるらしいんだけど,使わないことにする.

$ cd /path/to/redmine
$ bundle install --without development test postgresql sqlite3 rmagick

なぜか,sqlite3 はインストールされてしまう…

起動スクリプト
source コマンドでスクリプトを読み込めるbashにする.

#!/bin/bash
source /home/username/.rvm/scripts/rvm
cd /path/to/redmine
bundle exec unicorn_rails -D -E production -c config/unicorn.rb