ぷろじぇくと、みすじら。

ASP.NET CoreでLet's EncryptのChallengeのような拡張子なしのファイルを返す

Created at:

ASP.NET Coreの StaticFiles ミドルウェアは拡張子が既知のMIMEタイプに見つからない場合(不明なMIMEタイプ)、ファイルが見つからないものとして扱います。ところが場合によってこの挙動では困る場合があります。

例えばLet's Encryptは認証のために /.well-known/acme-challenge/Kk5osRC8Q2IKAVgreqBx6lCNgZ-nxzyKje8HJa611_c のようなパスにアクセスを試みますが、標準の挙動のままであればファイルがあっても404が返ってエラーとなります。わかりやすい組み合わせではAzure App Serviceで.NET CoreとLet's Encrypt Site Extensionを利用している場合に発生します。

解決方法1. StaticFiles のオプションで不明な種類も返せるようにする

UseStaticFiles メソッドには StaticFileOptions という StaticFiles ミドルウェアの設定を渡せますが、その際に不明な種類を返せるようにする ServeUnknownFileTypes プロパティがあるのでそれを true にすることで返せるようになります。

app.UseStaticFiles(new StaticFileOptions
{
    ServeUnknownFileTypes = true,
    DefaultContentType = "application/octet-stream"
});

ただしこの設定を行うと、アプリケーション全体に反映されてしまうので意図せずファイルが公開される可能性が増えるのでやらなくていいのであれば設定したくないものでもあります。

解決方法2. ファイルの種類を提供する IContentTypeProvider を実装する

StaticFiles ミドルウェアはファイルの種類を取得するためのプロバイダー IContentTypeProvider を受け取ることもできます。この IContentTypeProvider にはファイルパスも渡ってくるのでパスを見て判断できます。

例えばLet's Encryptが来るのは /.well-known/acme-challenge/ 以下と分かっているのでそこだけ知っているかのように処理をして、それ以外はデフォルトの FileExtensionContentTypeProvider や指定された IContentTypeProvider に任せます。

public class LetsEncryptWellKnownContentTypeProvider : IContentTypeProvider
{
    private IContentTypeProvider _baseProvider;

    public LetsEncryptWellKnownContentTypeProvider()
        : this(new FileExtensionContentTypeProvider())
    { }

    public LetsEncryptWellKnownContentTypeProvider(IContentTypeProvider baseProvider)
    {
        _baseProvider = baseProvider;
    }

    public bool TryGetContentType(string subpath, out string contentType)
    {
        if (subpath.StartsWith("/.well-known/acme-challenge/"))
        {
            contentType = "application/octet-stream";
            return true;
        }

        return _baseProvider.TryGetContentType(subpath, out contentType);
    }
}

IContentTypeProvider を実装したら、StaticFileOptionsContentTypeProvider プロパティにセットすれば完了です。

app.UseStaticFiles(new StaticFileOptions
{
    ContentTypeProvider = new LetsEncryptWellKnownContentTypeProvider()
});