letsencryptの証明書更新で”‘Namespace’ object has no attribute ‘standalone_supported_challenges'”が発生する

OSをUbuntu 18.04LTSからUbuntu 20.04LTSに移行したところ、旧システムから移行してきた証明書の更新に、以下のようなエラーを表示して失敗しました。

> sudo letsencrypt renew
...
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/www.example.net.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Renewal configuration file /etc/letsencrypt/renewal/www.example.net.conf (cert: www.example.net) produced an unexpected error: 'Namespace' object has no attribute 'standalone_supported_challenges'. Skipping.
...

letsencryptのバージョンが上がったことによりstandalone_supported_challengesオプションがサポートされなくなったことが原因です。

/etc/letsencrypt/renewal/www.example.net.confを編集して、最後の行のstandalone_supported_challengesをコメントアウトすると正常に動作するようになりました。

# renew_before_expiry = 30 days
cert = /etc/letsencrypt/live/www.example.net/cert.pem
privkey = /etc/letsencrypt/live/www.example.net/privkey.pem
chain = /etc/letsencrypt/live/www.example.net/chain.pem
fullchain = /etc/letsencrypt/live/www.example.net/fullchain.pem
version = 0.31.0
archive_dir = /etc/letsencrypt/archive/www.example.net

# Options and defaults used in the renewal process
[renewalparams]
authenticator = standalone
account = 52bc4f19233c146191f7f576945ed0d9
server = https://acme-v01.api.letsencrypt.org/directory
# standalone_supported_challenges = "tls-sni-01,http-01"

参考:https://github.com/certbot/certbot/issues/6984

.NET COREでLinuxで動作するサービスを作成する

.NET Core + C#でLinuxのDeamon作れないかなと思ったら、思ったより簡単に作れるようになっていたので覚え書き。Program.csとDaemonService.csの中味はほぼ定型なのでサンプルからそのまま流用します。

以下のサンプルは.NET Core 3.1で記述しています。

// Program.cs

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace DaemonSample
{
    class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = new HostBuilder()
                .ConfigureAppConfiguration((hostingContext, config) =>
                {
                    config.AddEnvironmentVariables();

                    if (args != null)
                    {
                        config.AddCommandLine(args);
                    }
                })
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddOptions();
                    services.Configure<DaemonConfig>(hostContext.Configuration.GetSection("Daemon"));

                    services.AddSingleton<IHostedService, DaemonService>();
                })
                .ConfigureLogging((hostingContext, logging) => {
                    logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                    logging.AddConsole();
                });

            await builder.RunConsoleAsync();
        }
    }
}
// DaemonService.cs

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace DaemonSample
{
    class DaemonService : IHostedService, IDisposable
    {
        private readonly ILogger _logger;
        private readonly IOptions<DaemonConfig> _config;
        public DaemonService(ILogger<DaemonService> logger, IOptions<DaemonConfig> config)
        {
            _logger = logger;
            _config = config;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Starting daemon: " + _config.Value.DaemonName);
            _logger.LogInformation("arg1:{0}", _config.Value.arg1);
            _logger.LogInformation("arg2:{0}", _config.Value.arg2);

            // ここでサーバー本体の処理をTaskとして起動します。

            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Stopping daemon.");
            return Task.CompletedTask;
        }

        public void Dispose()
        {
            _logger.LogInformation("Disposing....");
        }
    }
}
// DaemonConfig.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace DaemonSample
{
    class DaemonConfig
    {
        public string DaemonName { get; set; }
        public string arg1{ get; set; }
        public string arg2{ get; set; }
    }
}

起動時のコマンドラインは下記の通り。コマンドライン引数の受け渡し方が非常に独特です。

/usr/bin/dotnet run --project deamonsample --configuration Release --Daemon:DaemonName="Sample Daemon" Daemon:arg1="argument 1" Daemon:arg2="argument 2"

Demonとして起動するにはsystemdを使用します。/etc/systemd/systemにdeamonsample.serviceファイルを作成し、下記の様に記述します。

[Unit]
Description=deamon sample
After=network.target

[Service]
ExecStart=/usr/bin/dotnet run --project atozipsrv --configuration Release --Daemon:DaemonName="deamonsample " Daemon:arg1="argument 1" Daemon:arg2="argument 2"
WorkingDirectory=/usr/src/deamonsample
Restart=always
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=atozip
Environment=DOTNET_CLI_HOME=/var/src/deamonsample

[Install]
WantedBy=multi-user.target

後は通常のsystemdのDeamon同様に以下のコマンドで起動します。

$ systemctl start deamonsample

参考:Creating a Daemon with .NET Core (Part 1)

Linux上のClosedXMLで画像データを扱うときにSystem.DllNotFoundExceptionが発生する

Linux(Ubuntu 18.04)環境に.NET Core SDK 2.2をインストールし、ClosedXmlで画像を含むExcelファイルを扱おうとしたところ、下記の様な例外が発生して実行出来ません。

System.TypeInitializationException: The type initializer for 'Gdip' threw an exception. ---> System.DllNotFoundException: Unable to load shared library 'libdl' or one of its dependencies. In order to help diagnose loading problems, consider setting th
 e LD_DEBUG environment variable: liblibdl: cannot open shared object file: No such file or directory at Interop.Libdl.dlopen(String fileName, Int32 flag)
     at System.Drawing.SafeNativeMethods.Gdip.LoadNativeLibrary()
     at System.Drawing.SafeNativeMethods.Gdip..cctor()
     --- End of inner exception stack trace ---
     at System.Drawing.SafeNativeMethods.Gdip.GdipLoadImageFromDelegate_linux(StreamGetHeaderDelegate getHeader, StreamGetBytesDelegate getBytes, StreamPutBytesDelegate putBytes, StreamSeekDelegate doSeek, StreamCloseDelegate close, StreamSizeDelegate size, IntPtr& image)
     at System.Drawing.Image.InitFromStream(Stream stream)
     at ClosedXML.Excel.Drawings.XLPicture..ctor(IXLWorksheet worksheet, Stream stream)
     at ClosedXML.Excel.Drawings.XLPictures.Add(Stream stream)
     at ClosedXML.Excel.XLWorkbook.LoadDrawings(WorksheetPart wsPart, IXLWorksheet ws)
     at ClosedXML.Excel.XLWorkbook.LoadSpreadsheetDocument(SpreadsheetDocument dSpreadsheet)
     at ClosedXML.Excel.XLWorkbook.LoadSheets(Stream stream)

.NET Core Runtimeインストール時に依存関係が処理されていないことが原因です。下記のコマンドを実行してGDI+関連のライブラリをインストールすると解決します。

sudo apt install libc6-dev 
sudo apt install libgdiplus
cd /usr/lib
sudo ln -s <a rel="noreferrer noopener" target="_blank" href="http://libgdiplus.so/">libgdiplus.so</a> gdiplus.dll

環境によってはLD_LIBRARY_PATHの設定も必要になるようです。

Ubuntu 18.04 LTSにDockerをインストール

Ubuntu 16.04 LTSから18.04LTSに移行すべく準備中。
移行作業に伴うメモなどを残しておく。新サーバは最近のはやりを反映してDockerをベースに構築するぞ。

Ubuntu 18.04 LTSへのDockerのインストールは下記ドキュメントの通りで問題なく動作する。
Get Docker CE for Ubuntu
ただし、2018.06.01時点ではstableリリースはUbuntu 17.06にしか対応しない。step 4.で次のコマンドを実行するときにstableをedgeに変更する。

$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   edge"

iRedMailでmailserverを構成してみた時の記録

iRedMailはPostfixの導入を初めてとして、Internet Mail Serverに必要な諸々の設定をWizardに答えるだけで行ってくれる便利なツールです。SMTP、POP3、IMAP4は当然として、Postfixの設定を行うためのWEBコンソールや、メールの送受信の為のWEBメール、ウィルス対策ソフトやスパムフィルタの設定までサポートしてくれます。セキュリティ上行うべき設定も一通りは自動で行ってくれるので、僕みたいな素人管理者には有り難いツールです。

使い方はとっても簡単。
iRedMail.shをダウンロードしたら、チュートリアルに従ってインストール先環境で実行してウィザードに答えていくだけです。

sudo bash iRedMail.shと起動して、ウィザードに従って操作していきます。
ウィザードは次の様な感じです。
・Welcomeメッセージ
・メールの保存先パス(デフォルトでOK)
・使用するDBMS(今ならMariaDBあたりで)
・DBMSのパスワード
・使用するWEBサーバ(今ならNginxあたりで)
・メールのドメイン名(@code-lab.netならcode-lab.netといれます)
・postmasterのパスワード
・一緒に導入するオプション(WEBメール等を選択します。デフォルトでOK。)
・・・・、と言ったことに、順番に答えていきます。
最後に設定の確認画面が表示されるので、yを入力します。
大量のパッケージが導入され、設定も適切に変更されていくので気長に待ちます。

この後にFirewallの設定を変更するかを聞いてきます。

< Question > Would you like to use firewall rules provided by iRedMail?
< Question > File: /etc/default/iptables, with SSHD port: 22. [Y|n]

[ INFO ] Copy firewall sample rules: /etc/default/iptables.
< Question > Restart firewall now (with SSHD port 22)? [y|N]

ここはデフォルトで進めます。

この後、再起動すると、動き出します。https://mail.example.com/iredadmin/に接続して、poastmasterのメールアドレス(postmaster@example.com)およびパスワードを入力すると管理画面にログイン出来ます。

最後にDNS回りの設定を行ってください。通常行うべきDNS設定も全てマニュアルとして提供されています。

でもね・・・実は最近のレンタルサーバ事業者の多くはポート25での外部接続を禁止しているので、メール送れないことにセットアップ後に気がついたりして。

GitBucketを4.23.1にUpgradeしたところjava.lang.NoSuchMethodError~Lscala/Tuple2が発生

しばらくバージョン放置気味だったGitBucketを4.23.1にUpgradeしつつ、きちんとTomcatから起動するように変更。

ところがログインして動かしていると至る所で「java.lang.NoSuchMethodError:gitbucket.core.helper.html.dropdown$.apply$default$5()Lscala/Tuple2;」が発生する。

随分と悩んだが原因は単純なところにあった。GitBucketに管理者でログインした後、SystemAdministrationにあるPuginsに異常なプラグインが。どうもデータベースの変換途中でエラーが発生したらしく「Notifications Plugin」が複数導入されている状態になっていた。重複した「Notifications Plugin」を削除したところ、正常に動作。

apt-get updateに異常に時間がかかる場合の対処法

Ubuntu 16.04LTSのapt-get updateに異常に時間がかかるので調べてみると、どうもIPv6周りの挙動に問題があるらしい。

apt-get -o Acquire::ForceIPv4=true update

上記のようにコマンドラインオプションを指定すると、強制的にIPv4を使用して接続させることが出来る。
私もこれで問題が解決した。

だがしかし、私は忘れっぽい性格なので、毎度コマンドライのプションを指定するというのは頂けない。
その場合は「/etc/apt/apt.conf.d/99force-ipv4」と言うファイルを新規に作成し、ファイルに下記の内容を記載する。

Acquire::ForceIPv4 "true";

これで都度オプションを指定せずとも、毎回IPv4を使用して動作するようになる。

参考:Force Apt-Get to IPv4 or IPv6 on Ubuntu or Debian

nginxでhttp/2(SPDY)を有効にする

サーバーが欧州にある事もあってnginxのキャッシュが機能していても結構遅い。そこでhttp/2に対応することにしました。nginxでhttp/2を有効にするのはとっても簡単。

server {
  listen 443;
  ...
}

上のlistenを、下のように書き換えるだけ。

server {
  listen 443 ssl http2;
  ...
}

その後「sudo service nginx restart」でnginxを再起動すればhttp/2が有効になります。たったこれだけなのに、今までhttp/2を有効にしていなかったとか・・・orz

http/2が有効になったことで、ページの表示完了までにかかる時間が半分以下になりました。

letsencrypt renewでunexpected error: ‘server’が発生する

常時SSLを実現するためにLetsEncryptを使用しています。サーバーの移行に伴い、旧サーバーから/etc/letsencrypt以下のファイルをシンボリックリンクも含めてすべて複製していました。問題なく動作していそうなので安心していたのですが、証明書を更新しようとすると以下のようなエラーが発生してしまいます。

~$ sudo letsencrypt renew
Processing /etc/letsencrypt/renewal/www.example.net.conf
2017-10-25 03:17:34,835:WARNING:letsencrypt.cli:Attempting to renew cert from /etc/letsencrypt/renewal/www.example.net.conf produced an unexpected error: 'server'. Skipping.

設定ファイル(www.example.net.conf)の記述内容が古いバージョンのものであることが原因のようです。/etc/letsencrypt/renewal/www.example.net.confを編集し、serverパラメータを追加します。

# Options and defaults used in the renewal process
[renewalparams]
#・・・
server = https://acme-v01.api.letsencrypt.org/directory
#・・・

[[webroot_map]]
www.example.net = /var/www

その後、あらためて「–force-renew」オプションを付けて証明書の更新をおこないます。

~$ sudo letsencrypt renew --force-renew
Processing /etc/letsencrypt/renewal/www.code-lab.net.conf
new certificate deployed without reload, fullchain is /etc/letsencrypt/live/www.code-lab.net/fullchain.pem

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/www.code-lab.net/fullchain.pem (success)

となり証明書の更新に成功しました。www.example.net.confの内容も、先ほど追記した1行以外にも大幅に書き換えられて、新しいバージョンに適合する物に更新されたようです。

参考:Fix Lets Encrypt renewal error on Ubuntu 16.04

Ubuntu 16.xでUFWを有効にする

ufw(Uncomplicated FireWall)はFiewwallソフトです。ScaleWayのSecurity Groupではデフォルトの設定を接続不可にすることができないので、安全に使用するためには別途irewallを設定することが必須です。Linuxで標準的に使われるiptablesでもよいのですが、iptablesは設定が煩雑なので、シンプルで分かりやすいufwを使うことにします。

sudo apt-get install ufw
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw default DENY
sudo ufw enable

sudo ufw enableを実行するとFirewallが有効になります。sshなどを許可しないままFirewallを有効にすると、完全にハマるので気を付けましょう。

sudo iptables -L

上記コマンドを実行すると、ufwによってiptablesがどのように設定されているか確認できます。

参考:Ubuntuのソフトウェアファイアウォール:UFWの利用