プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

OpenStreetMap のタイルサーバーの公開

さて、せっかくいろいろと設定して立ち上げたタイルサーバーですので、公開してみたいと思います。 自宅サーバーの公開なんて、滅多にやらないので、問題なさそうな範囲でやったことまとめておきます。

IP アドレスを固定化

IPアドレスはDHCPで割り振っているので、まずこれを固定IPアドレス(LAN内なのでプライベートアドレス)にします。

/etc/network/interface を編集すればOK。変更前はこんな感じ

mor@map:/etc/network$ cat interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto enp2s0
iface enp2s0 inet dhcp

mor@map:/etc/network$ 

dhcpの部分をstaticにして、IPアドレスなどの設定を追加します。

# The primary network interface
auto enp2s0
# Change a method getting IP address
# from DHCP to static setting.
#
# 2018/8/31, Junichi MORI
#iface enp2s0 inet dhcp
iface enp2s0 inet static
address 192.168.xxx.yyy
netmask zzz.zzz.zzz.zzz
gateway デフォルトゲートウェイのIPアドレス
dns-nameservers DNSサーバーのIPアドレス

mor@map:/etc/network$ 

設定出来たら、再起動して、 ifconfig で確認しておきます。

mor@map:~$ ifconfig
enp2s0    Link encap:イーサネット  ハードウェアアドレス xx:xx:xx:xx:xx:xx  
          inetアドレス:192.168.xxx.yyy  ブロードキャスト:192.168.xxx.255  マスク:zzz.zzz.zzz.zzz
          (略)
mor@map:~$ 

問題なさそうですね。

Web サーバー関連の設定

Web サーバーのポート番号変更

このタイルサーバーですが、特定の(自分で作った)アプリケーションからのみアクセスすることを想定しているので、ポート番号を変えておきます。

apache2 が Listen するポートを変更します。

/etc/apache2/ports.conf

mor@map:/etc/apache2$ cat ports.conf
# If you just change the port or add more ports here, you will likely also
# have to change the VirtualHost statement in
# /etc/apache2/sites-enabled/000-default.conf

#Listen 80
Listen xxxx

<IfModule ssl_module>
        Listen 443
</IfModule>

<IfModule mod_gnutls.c>
        Listen 443
</IfModule>

# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
mor@map:/etc/apache2$ 

バーチャルホスト設定も併せて変更します。

/etc/apache2/sites-available/000-default.conf

mor@map:/etc/apache2/sites-available$ cat 000-default.conf
<VirtualHost *:xxxx>
        # The ServerName directive sets the request scheme, hostname and port that
        (略)
</VirtualHost>

設定ができたら、apache2 を再起動します。

mor@map:~$ sudo service apache2 restart
参考

ubuntu + apache2 で8080ポートも併用する場合 - プログラミング備忘録

Ubuntu14.04 でApache2 設定ファイルの場所(ポート番号変更) - Name Not Found

Apache バーチャルホスト説明書 - Apache HTTP サーバ バージョン 2.4

サーバー情報が出るのを抑える

存在しないページを表示した際、エラーページが表示されますが、そこにサーバーのバージョン番号等が載っています。

f:id:junichim:20180831222648p:plain

なので、これを非表示にします。/etc/apache2/conf-available/security.conf を開いて

#ServerSignature On
ServerSignature Off

ServerSignature を Offにしておきます。

同じく、レスポンスヘッダにもサーバーのバージョンが載っています。

f:id:junichim:20180831222953p:plain

これも Apache のようにソフトウェア名だけにしておきます。 同じく、 /etc/apache2/conf-available/security.conf を開いて

#ServerTokens OS
ServerTokens Prod

ServerTokens を Prod に変更します。

設定変更を保存後、

mor@map:/etc/apache2/conf-available$ sudo /etc/init.d/apache2 restart
[ ok ] Restarting apache2 (via systemctl): apache2.service.
mor@map:/etc/apache2/conf-available$ 

で設定ファイルを反映して、さきほどと同じく存在しないページを表示すると

f:id:junichim:20180831223345p:plain

f:id:junichim:20180831223355p:plain

のようにバージョン番号を表示しなくなりました。

ssh の設定を変更

公開前に、 ssh 接続を見直します。主な設定内容としては、

  • ポート番号を変更
  • パスワード認証を禁止
  • 公開鍵認証のみを許可
  • 接続可能ユーザーを限定(ルートはもちろん禁止)

sshd_config の変更箇所は下記のような感じになりました。

mor@map:/etc/ssh$ diff sshd_config.org sshd_config
5c5,6
< Port 22
---
> #Port 22
> Port xxxxx
25a27,29
> # user restriction
> AllowUsers ユーザー名
> 
28c32,33
< PermitRootLogin prohibit-password
---
> #PermitRootLogin prohibit-password
> PermitRootLogin no
52a58
> PasswordAuthentication no
mor@map:/etc/ssh$ 

設定変更後は、

mor@map:/etc/ssh$ sudo sshd -t

設定ファイルのテストをして問題なければ、

mor@map:~$ sudo systemctl restart ssh

でsshdを再起動します。

なお、パスワード認証を無効にする前に、別途公開鍵を作り、一度、クライアントから接続して公開鍵でログイン出来ることを確認しておきます。 そのあとで、パスワード認証を無効にします(設定ミスでログインできなくなる、というのを避けたいためです)。

参考

Ubuntu 16.04 LTS : SSHサーバー : SSH 鍵認証 : Server World

sshd_config&PAMの設定 | OpenGroove

ファイアウォールを設定

自宅のLAN内で使っている分には、ファイアウォールとかもそんなに気にしなくてよかったのですが、公開するのでちゃんと設定してみます。 Ubuntu は iptables で設定ができるそうです。でも、直接 iptables コマンドで操作しなくても ufw というツール経由で設定するのが楽とのことなので、これで試してみます。

mor@map:/etc/ufw$ sudo ufw status
状態: 非アクティブ
mor@map:/etc/ufw$ sudo ufw default DENY
デフォルトの incoming ポリシーは 'deny' に変更しました
(適用したい内容に基づいて必ずルールを更新してください)
mor@map:/etc/ufw$ sudo ufw allow "Apache for Public"
ルールをアップデートしました
ルールをアップデートしました(v6)
mor@map:/etc/ufw$ sudo ufw allow "OpenSSH for Public"
ルールをアップデートしました
ルールをアップデートしました(v6)
mor@map:/etc/ufw$ sudo ufw limit "OpenSSH for Public"
ルールをアップデートしました
ルールをアップデートしました(v6)
mor@map:/etc/ufw$ sudo ufw status
状態: 非アクティブ
mor@map:/etc/ufw$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? 
ファイアウォールはアクティブかつシステムの起動時に有効化されます。
mor@map:/etc/ufw$ sudo ufw status
状態: アクティブ

To                         Action      From
--                         ------      ----
Apache for Public          ALLOW       Anywhere
OpenSSH for Public         LIMIT       Anywhere
Apache for Public (v6)     ALLOW       Anywhere (v6)
OpenSSH for Public (v6)    LIMIT       Anywhere (v6)

mor@map:/etc/ufw$ 

limit を使うと30秒間に6回以上の接続が合った場合に制限がかかります。ブルートフォースアタック対策ですね。

この時点で、一度、再起動して、設定を有効にします。 WebサーバーとSSHが問題なくつながりますね。

application について

上記の設定はポート番号を直接指定するのではなく、"Apache for Public" とか "OpenSSH for Public" とかのアプリケーション名で指定しています。 これらは、/etc/ufw/application.d 以下にアプリケーション定義を作成して、その名前使って設定しているものになります。

例えば、 "OpenSSH for Public" というアプリケーション名は、/etc/ufw/application.d/openssh-server-for-public というファイル(ファイル名は任意っぽい)を作成して、そこで定義しています。 ちなみにその中身は、

mor@map:/etc/ufw/applications.d$ cat openssh-server-for-public 
[OpenSSH for Public]
title=Secure shell server, an rshd replacement
description=OpenSSH is a free implementation of the Secure Shell protocol.
ports=xxxxxx/tcp
mor@map:/etc/ufw/applications.d$ 

という感じです。 "Apache For Public" も同じですね。

あと、若干微調整しておきます。

IPv6 の削除

/dev/default/ufw を編集して、IPv6を無効にします。

mor@map:/etc/default$ cat ufw
# /etc/default/ufw
#

# Set to yes to apply rules to support IPv6 (no means only IPv6 on loopback
# accepted). You will need to 'disable' and then 'enable' the firewall for
# the changes to take affect.
#IPV6=yes
IPV6=no
SSH の接続元を限定

SSH 接続はローカルLANからのみ認めるものとします。また、ローカルLANからの接続もLIMITをつけておきます。

mor@map:/etc/default$ sudo ufw limit from 192.168.xxx.yyy/zzz to any app "OpenSSH for Public"
ルールを追加しました
mor@map:/etc/default$ sudo ufw status
状態: アクティブ

To                         Action      From
--                         ------      ----
Apache for Public          ALLOW       Anywhere
OpenSSH for Public         LIMIT       Anywhere
OpenSSH for Public         LIMIT       192.168.xxx.yyy/zzz

mor@map:/etc/default$ sudo ufw status numbered
状態: アクティブ

     To                         Action      From
     --                         ------      ----
[ 1] Apache for Public          ALLOW IN    Anywhere
[ 2] OpenSSH for Public         LIMIT IN    Anywhere
[ 3] OpenSSH for Public         LIMIT IN    192.168.xxx.yyy/zzz

mor@map:/etc/default$ sudo ufw delete 2
削除:
 limit 'OpenSSH for Public'
操作を続けますか (y|n)? y
ルールを削除しました
mor@map:/etc/default$ sudo ufw status
状態: アクティブ

To                         Action      From
--                         ------      ----
Apache for Public          ALLOW       Anywhere
OpenSSH for Public         LIMIT       192.168.xxx.yyy/zzz

mor@map:/etc/default$ 
参考

第76回 Ubuntuのソフトウェアファイアウォール:UFWの利用(1):Ubuntu Weekly Recipe|gihyo.jp … 技術評論社

第77回 Ubuntuのソフトウェアファイアウォール:ufwの利用(2):Ubuntu Weekly Recipe|gihyo.jp … 技術評論社

iptablesについて

ファイアウォールiptablesを簡単解説~初心者でもよくわかる!VPSによるWebサーバー運用講座(4) | さくらのナレッジ

ルータの設定(静的IPマスカレードの設定)

ここまで設定が終わったら、外部からのアクセスを受け付けるために、ルータのIPマスカレードを設定します。 ルータの特定のポートに来たものを、このタイルサーバーに投げるという設定です。

下記のマニュアルを見て設定しました。

NetGenesis GigaLink1000 ユーザーズマニュアル 第4版

動作確認

ここまでできれば、最後は動作確認を行います。 Leaflet を使って地図を表示する際に、

    sample.mymap = L.map('map').setView([34.5007, 136.7058], 13);
    var tile_layer = L.tileLayer('http://タイルサーバーのIPアドレス:ポート番号/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors',
        maxZoom: 20
    });
    tile_layer.addTo(sample.mymap);

のように、公開したタイルサーバーを指定します。

ブラウザ表示させると、無事に地図が表示されています。 めでたしめでたし、でした。

あとは、おいおい様子を見ていきたいと思います。

OpenStreetMap のタイルサーバーの更新失敗時にメールを飛ばす

前から、OpenStreetMap のタイルサーバーを立てて、更新もできるようにして、エラーにもめげずに運用していました。

blog.mori-soft.com

blog.mori-soft.com

が、今日見てみたら、またエラーで更新が止まっていました。あーあ。

こちらのエラーの原因自体は、ググったらすぐにわかりまして、更新情報を取得しているURLが http のままだったためのようで、 https に変更したらすぐに動きました。

具体的には、設定ファイル /var/lib/mod_tile/.osmosis/configuration.txt

# The URL of the directory containing change files.
baseUrl=http://planet.openstreetmap.org/replication/hour

# Defines the maximum time interval in seconds to download in a single invocation.
# Setting to 0 disables this feature.
#maxInterval = 3600
maxInterval = 7200

にある baseUrl を http から https に修正するだけです。

ちなみにこのサーバー上だと、エラーはこんな感じででていました。

/var/log/tiles/osmosis.log

8 29, 2018 10:15:01 午後 org.openstreetmap.osmosis.core.Osmosis run
情報: Pipeline executing, waiting for completion.
8 29, 2018 10:15:02 午後 org.openstreetmap.osmosis.core.pipeline.common.ActiveTaskManager waitForCompletion
重大: Thread for task 1-read-replication-interval failed
org.openstreetmap.osmosis.core.OsmosisRuntimeException: The replication state doesn't contain a timestamp property.
        at org.openstreetmap.osmosis.replication.common.ReplicationState.loadProperty(ReplicationState.java:65)
        at org.openstreetmap.osmosis.replication.common.ReplicationState.load(ReplicationState.java:78)
        at org.openstreetmap.osmosis.replication.common.ReplicationState.<init>(ReplicationState.java:59)
        at org.openstreetmap.osmosis.replication.common.ServerStateReader.getServerState(ServerStateReader.java:108)
        at org.openstreetmap.osmosis.replication.common.ServerStateReader.getServerState(ServerStateReader.java:50)
        at org.openstreetmap.osmosis.replication.v0_6.BaseReplicationDownloader.runImpl(BaseReplicationDownloader.java:290)
        at org.openstreetmap.osmosis.replication.v0_6.BaseReplicationDownloader.run(BaseReplicationDownloader.java:383)
        at java.lang.Thread.run(Thread.java:748)
(後略)

まあ、こんな感じで、理由はいろいろあるにせよ、たまにエラーで更新が止まっているのを見ると、やはりエラー時にはメールか何かで通知が必要だとと思います。

ということで、更新失敗時にメールを飛ばす設定を行ってみました。

メールサーバーの導入

タイルサーバーを作ったとき、メールサーバーなんて入れてませんでしたので、改めて入れます。 方法としては、こちらの記事

blog.mori-soft.com

で試したように、 ssmtp と mailutil をインストールして、メールの送信だけ可能にします。

mor@map:~$ sudo apt-get install ssmtp
mor@map:~$ sudo apt-get install mailutils

sSMTP の設定(/etc/ssmtp/ssmtp.conf)

mor@map:/etc/ssmtp$ cat ssmtp.conf
#
# Config file for sSMTP sendmail
#
# The person who gets all mail for userids < 1000
# Make this empty to disable rewriting.
root=postmaster

# The place where the mail goes. The actual machine name is required no 
# MX records are consulted. Commonly mailhosts are named mail.domain.com
#mailhub=mail
mailhub=メールサーバー:ポート番号

# Where will the mail seem to come from?
#rewriteDomain=

# The full hostname
hostname=map

# Are users allowed to set their own From: address?
# YES - Allow the user to specify their own From: address
# NO - Use the system generated From: address
#FromLineOverride=YES

# added for SMTP auth, 2018/8/30
AuthUser=ユーザー名
AuthPass=パスワード
AuthMethod=cram-md5

mor@map:/etc/ssmtp$ 

テストします。

mor@map:~$ echo test | mail -s test メールアドレス

指定したメールアドレスに無事メールが届いていればOKです。

注意点

何度も設定しているのに、下記にひっかかりました。ご注意ください。

  • /etc/ssmtp/ssmtp.conf はメール送信者が読める必要があります。ですが、認証パスワードが生のまま書かれているので、パーミッションにはご注意ください。
  • /etc/ssmtp/ssmtp.conf のメールサーバが間違っていると mail コマンドでテスト送信した際に失敗します。ちなみに、コンソールから試したときは、単にfailed と出て失敗したり、何も応答がなく終了するのではなく、入力中のようになるけど、入力が終了できないような状態になりました(Ctrl+Zで一旦バックグラウンドにまわしてkillしました)。お気を付けください。
  • 今回設定していたサーバー(Ubuntu 16.04.5 LTS)の場合、メールの送信に成功しても、 /va/log/mail.log にメールの送信ログが出力されませんでした。強引に下記のように自分でファイルを用意してやったら、ログが残るようになりました。別のサーバーだと問題なかったのでちょっと不思議ですね。
mor@map:~$ cd /var/log
mor@map:/var/log$ touch mail.log
mor@map:/var/log$ sudo chown syslog:adm mail.log
mor@map:/var/log$ sudo chmod 640 mail.log
mor@map:/var/log$ service syslog restart

タイルサーバー更新失敗時にメールを飛ばす設定

元々の更新に使っているスクリプト( mod_tile/openstreetmap-tiles-update-expire )を見ると、エラーが起きた場合は、 m_error とうい関数を呼び出して終了しているようです。 なのですが、この関数が exit だけでスクリプトを終了してしまいます。

m_error()
{
    echo "[`date +"%Y-%m-%d %H:%M:%S"`] $$ [error] $1" >> "$RUNLOG"    m_info "resetting state"
    /bin/cp $WORKOSM_DIR/last.state.txt $WORKOSM_DIR/state.txt || true    rm "$CHANGE_FILE" || true
    rm "$EXPIRY_FILE.$$" || true
    rm "$LOCK_FILE"
    exit
}

なので、この最後を

m_error()
{
    (略)
    exit 1
}

として、終了ステータスを設定するように変更します。

次に、終了ステータスを見てメール送信をおこなうスクリプトを書きます。 例えば、 ~/bin/osm-tile-update-expire-w-mail という名前で保存しておきます。

#!/bin/sh
#
# do openstreetmap-tiles-update-expire with mail notification if update failed.
#
# 2018/8/30, Junichi MORI

ACCOUNT=osm

SCRIPT_DIR=/home/${ACCOUNT}/src/mod_tile/
SCRIPT=openstreetmap-tiles-update-expire

MAIL_TO=メールアドレス


${SCRIPT_DIR}/${SCRIPT} > /dev/null 2>&1
STS=$?
#echo "status is " $STS 
if [ ${STS} != 0 ]
then
  echo "osm update script failed. Please check log files on /var/log/tiles . Last exit code is " ${STS} | mail -s "error: osm update" ${MAIL_TO}
fi
テスト

上記で作ったスクリプトを呼んでみます。 最初に言った修正はまだ反映していない状態としておきます(エラーが起きる状態です)。 そうすると、上記のスクリプト実行時に、エラーが起きて、メールが通知されます。無事メールが確認できました。

cron に設定

テストで問題がなければ、 cron で定期的に呼び出すようにします。

osm@map:~/bin$ crontab -e
osm@map:~/bin$ crontab -l
# Edit this file to introduce tasks to be run by cron.
# 
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
# 
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').# 
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
# 
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
# 
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
# 
# For more information see the manual pages of crontab(5) and cron(8)
# 
# m h  dom mon dow   command
#6 */1 * * *  /home/osm/src/mod_tile/openstreetmap-tiles-update-expire > /dev/null 2>&1
6 */1 * * *  /home/osm/bin/osm-tile-update-expire-w-mail > /dev/null 2>&1
osm@map:~/bin$ 

これで OpenStreetMap のデータ更新に失敗したらすぐに通知がくるので、すぐ気づけると思います。

pre タグを使った場合のリンク不具合について

はてなブログで記事を書いているときに気づいたのですが、preタグを使った場合、preタグの後ろのリンクが正しく生成されない場合があります。 (2018/8/24時点の話です。いつからこうなっていたかは定かではありません。あと、私の環境での話です。他の方の環境は知らないです。)

たとえば、

<pre>
test
</pre>

のようにpreタグをいれると

test

このpreタグの後ろではURLを入れても、fotolifeの画像をfotolife記法で書いても

https://blog.mori-soft.com

[f:id:junichim:20180824163634j:plain]

のように正しくリンクが解決されません(fotolife記法がそのまま表示されていると思います)。

ここで、 pre タグに data-unlink="" 属性をつけていれると

<pre data-unlink="">
test2
</pre>
test2

https://blog.mori-soft.com

f:id:junichim:20180824163634j:plain

のように正しく表示されます。

もともと SyntaxHighlighter を使っていたりするので、それが原因かもと思い、SyntaxHighlighter の jsファイルやCSSの読み込みを外しても現象は変わりませんでした。 あ、編集は Markdown モードでやってます。

もし、同じような現象が出てる方がいれば、何かの参考になればと思います。 ではでは。