H2Oをdocker上で80/443で動作させたい場合、起動後setuidによりh2o.confのuserとして動くため、Dockerfile中ではrootとしてH2Oを起動する
背景
http serverはnon rootで動かしたい。
そして複数のIPアドレスを持つ(IPv4 dhcp,autoip,IPv6など)ため、IPアドレスを指定せずに任意のアドレスINADDR_ANY(0.0.0.0)で待ち受けたい。
しかしport:80などの1024未満のポート番号は、CAP_NET_BIND_SERVICE権限を持つプロセスのみがbindできるとなっているため、権限がないとエラー(EACCES)になってしまう。
マニュアル
nginx
nginxはプロセス分離といった対策を取っている。rootで起動、port:80をbind,その後forkし、一般ユーザーで動く
H2O
H2Oはrootで起動、port:80をbind, その後setuidで一般ユーザーになる
with docker
しかしdocker上で、wwwといったユーザーを作り、port:80で待ち受けようとすると、H2Oで以下のエラーがでる。
$ ./h2o -c /usr/www/conf/h2o.conf
failed to listen to port ANY:80: Permission denie
$ ./h2o -m master -c /usr/www/conf/h2o.conf
failed to bind to 0.0.0.0:80:Permission denied at /usr/www/share/h2o/start_server line 9
原因
原因はDockerfile内、および、docker run時のオプションでH2Oをwwwユーザーとして起動していたため。
INADDR_ANY(0.0.0.0)かつ1024未満のポート番号であるため、CAP_NET_BIND_SERVICE権限が要求されるが、wwwユーザーにはない。
対策
対策はrootユーザーで起動する事。もちろんH2O起動後によりsetuidによりNon rootとなるため、rootで動き続ける危険はない。
user: www
listen: 80
listen:
port: 443
ssl:
certificate-file: tls/server.crt
key-file: tls/server.key
hosts:
"*":
paths:
/:
mruby.handler-file: mruby/header_add.rb
file.dir: doc_root
file.dirlisting: ON
access-log: /dev/stdout
# buildに必要なtoolchain,h2oのbuildは以下を参照
# https://github.com/kawaway/h2o_docker
RUN groupadd www && useradd -m -d /usr/www -g www www
USER www
WORKDIR /usr/www
RUN mkdir -p conf && \
mkdir -p tls && \
mkdir -p doc_root && \
mkdir -p share/h2o && \
mkdir -p mruby && \
cp -r /usr/local/share/h2o/* share/h2o && \
cp /usr/local/share/doc/h2o/examples/h2o/server.key tls && \
cp /usr/local/share/doc/h2o/examples/h2o/server.crt tls
# H2O run as root for bind port:80/443(require CAP_NET_BIND_SERVICE, cause EACCES) first. then www by setuid
USER root
ENV H2O_ROOT=/usr/www
CMD ["h2o", "-m", "master", "-c", "/usr/www/conf/h2o.conf"]
version: '3'
services:
h2o:
build: .
ports:
- "80:80"
- "443:443"
volumes:
- ./doc_root:/usr/www/doc_root
- ./conf:/usr/www/conf
- ./mruby:/usr/www/mruby
docker-compose buildでbuild
docoker-compose up -dでコンテナ起動
参考
明日使えない Linux の capabilities の話
[Go] port 80を開いたあとroot権限を捨てるwebサーバの例