windows スリープ させない

Google Play Music に全部アップロードした

Windowsがスリープしてしまう

PowerShellでスリープ抑止(Win32APIの利用)

SetThreadExecutionState function (Windows)

$SystemRequired = [uint32]1
$Continuous = [uint32]"0x80000000"
$signature = @"
[DllImport("kernel32.dll")]
public extern static uint SetThreadExecutionState(uint esFlags);
"@

$func = Add-Type -memberDefinition $signature -namespace "Win32Functions" -name "_SetThreadExecutionState" -passThru

$func::SetThreadExecutionState($SystemRequired -bor $Continuous);
Read-Host -Prompt "Sleep 抑止しています 終了するにはEnterキーを押してください . . ."

Amazon EC2インスタンスのログをCloudWatch Logsに送る方法が2017年12月から新しくなってた

いままではSSMエージェントのCloudWatchプラグインで送信していた。2017年末にSystems Manager がEC2から独立したのを機にCloudWatchエージェントというのが登場したようだ。

新発表 – Amazon CloudWatch AgentとAWS Systems Managerとの連携 – 統一されたメトリクスとログの収集をLinuxとWindowsに | Amazon Web Services ブログ

SSMエージェントが古くなければ、Systems Manager の RunCommand の AmazonCloudWatch-MigrateCloudWatchAgentでいままでのSSMエージェントでのログ収集を停止して、新しいCloudWatchエージェントでの収集を開始できる。

CloudWatch Logs へのログの送信 (CloudWatch エージェント) - AWS Systems Manager

設定ファイルもスキーマが以前とは変っている。

CloudWatch エージェント設定ファイルを手動で作成または編集する - Amazon CloudWatch

CloudWatchエージェントの設定変更はjsonをあらかじめ Parameter Storeに追加しておいて、Run CommandのAmazonCloudWatch-ManageCloudWatchドキュメントConfigアクションでEC2インスタンスに適用する。 EC2インスタンスのIAMロールにParameter Storeの読み取り許可が必要になる。

at_hashのバリデーションに失敗するから対処した

azechi-n.hatenadiary.com

久しぶりに実行してみたら、アマゾン側のログインUIから戻ってきたときにエラーが出るようになってた。 こんなやつ

OpenIdConnectProtocolInvalidAtHashException: IDX10348: Validating the 'at_hash' failed, see inner exception.

id_tokenに含まれるclaimのat_hashってやつの検証に失敗している。

結論から言うと、アマゾンが送ってくる値にはパディングの"="が付いているが、検証のときに計算して作った値にはパディングが無いので一致せずに失敗する。仕様的にはパディングを付けないっぽいので、アマゾンから送られた値から"="を取り除いて検証コードに渡すようにした。

github.com

options.Events.OnTokenValidated = (context) =>
{
    var atHash = context.SecurityToken.Payload["at_hash"] as string;
    var atHash2 = atHash.Split("=")[0];
    context.SecurityToken.Payload["at_hash"] = atHash2;

    return Task.CompletedTask;
};

docs.aws.amazon.com response_type = code だとコード付与フローとなって、id_tokenにat_hashが付いてくる。

at_hashは、AccessTokenのハッシュ値の半分をBase64UrlEncodeしたもの。 Base64UrlEncode自体はパディングはなしともありとも決まってないが、JWTではBase64UrlEncodeした値にはパディングを付けないと決まってる。なので、at_hashに"="付けて送ってくるアマゾンがいかん。

ASP.NET Core のWebサイトに組み込みのOpenID ConnectスキームでAmazon Cognito User PoolsのログインUIを統合する

Amazon Cognito User Poolsのアプリ統合で、OpenID ConnectっぽいエンドポイントとログインUIが利用できる。 ASP.NET Core Authentication のOpenID Connectスキームは、ASP.NET Core メタパッケージに含まれているビルトインの機能。 Webサイトでajaxを使ったAPIアクセスではなくでブラウジングコンテキストでUser PoolsのIDにログインする。

services
  .AddAuthentication(options =>
  {
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
  })
  .AddCookie()
  .AddOpenIdConnect(options =>
  {
    options.ClientId = "client id";
    options.ClientSecret = "client secret";
    options.MetadataAddress = "https://cognito-idp.{Region}.amazonaws.com/{Pool ID}/.well-known/openid-configuration";
    options.Scope.Clear();
    options.Scope.Add("openid");
    options.ResponseType = "code";

    // Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectPostConfigureOptions
    options.Backchannel = new HttpClient(options.BackchannelHttpHandler ?? new HttpClientHandler());
    options.Backchannel.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft ASP.NET Core OpenIdConnect handler");
    options.Backchannel.Timeout = options.BackchannelTimeout;
    options.Backchannel.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB

    var byteArray = System.Text.Encoding.ASCII.GetBytes(options.ClientId + ":" + options.ClientSecret);
    options.Backchannel.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));

    options.ProtocolValidator.RequireNonce = false;
    options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.MetadataAddress, new AmazonUserPools_OpenIdConnectConfigurationRetriever(),
         new HttpDocumentRetriever(options.Backchannel) { RequireHttps = options.RequireHttpsMetadata });
    
    options.Events.OnRedirectToIdentityProviderForSignOut = (context) =>
    {
      context.ProtocolMessage.SetParameter("logout_uri", context.ProtocolMessage.PostLogoutRedirectUri);
      context.ProtocolMessage.SetParameter("post_logout_redirect_uri", null);
      context.ProtocolMessage.SetParameter("client_id", context.Options.ClientId);
      return Task.CompletedTask;
    };
  });
public sealed class AmazonUserPools_OpenIdConnectConfigurationRetriever : IConfigurationRetriever<OpenIdConnectConfiguration>
{
  Task<OpenIdConnectConfiguration> IConfigurationRetriever<OpenIdConnectConfiguration>.GetConfigurationAsync(string address, IDocumentRetriever retriever, CancellationToken cancel)
  {
    return GetAsync(address, retriever, cancel);
  }
  static async Task<OpenIdConnectConfiguration> GetAsync(string address, IDocumentRetriever retriever, CancellationToken cancel)
  {
    var configuration = await OpenIdConnectConfigurationRetriever.GetAsync(address, retriever, cancel);
    var app_url = "https://<domain_prefix>.auth.<region>.amazoncognito.com";
    configuration.TokenEndpoint = app_url = "/oauth2/token";
    configuration.AuthorizationEndpoint = app_url + "/login";
    configuration.EndSessionEndpoint = app_url +  "/logout";
    return configuration;
  }
}

OpenID Provider Configuration

.well-known/openid-configuration で得られる情報が足りないので、IConfigurationRetrieverの実装で取得したOpenIdConnectConfigurationに不足するプロパティの値を設定した。

Backchannel HttpClient

エンドポイントへのアクセスはベーシック認証。Authorization ヘッダーでClient Secretを渡す必要がある。OpenIDConnectionOptionsのBackchannelのHttpClientで設定する。Backchannelは既定ではOpenIdConnectPostConfigureOptionsで構築される。既定のHttpClientにヘッダーを追加することはできなかったので、同じように作ってヘッダーを追加したHttpClientのインスタンスをOptionsに設定した。

おまけ

Razor PagesのPageModelではメソッドレベルのAuthorizeAttributeは効かない。

iPhoneからLANのPCへ名前でアクセスするために一時的にDNSサーバーを動かす

iPhoneのWebアプリでも作って勉強しようと思った。開発には短い間隔で書いて確認の反復が大事。同じネットワークにいるデスクトップPCのWebサーバーへiPhoneからアクセスしたい。カメラ使いたいのでHTTPSでアクセスする必要がある。自己署名証明書やオレオレ認証局だとiPhone側の設定とか面倒くさそう。Let's EncryptならiPhoneで信頼済みの証明書が簡単に手に入る。これを使いたい。

hostsファイルがあればいい。iPhoneにhostsはないらしい。iPhoneのネットワーク設定を見るとipv4DNSサーバーはルーターのアドレスになってる。ipv6も設定されてるけどよくわかってないから今回は無視する。ルーターはBUFFALO WXR-1900DHP3。ルーターでhostsみたいなことできるやつある。うちのルーターの設定画面にそれっぽい項目がない。

作業中だけDockerでDNSサーバー動かせば解決できる。dnsmasqというのが目的に適っていそう。

dnsmasqをインストールしたdockerイメージを作る

Dockerfile
FROM alpine

RUN apk update \
    && apk upgrade \
    && apk add --no-cache dnsmasq

ENTRYPOINT ["dnsmasq", "-k" ,"-u", "root"]

alpine linuxにdnsmasqをインストールするだけ。dnsmasqのオプションの-kは"keep-in-foreground"。これがないとコンテナがすぐ終了してしまう。dockerはENTRYPOINTのプロセスが終了したらコンテナを終了させる。

docker build
docker build -t dnsmasq - < Dockerfile 

何も持っていかないから'-'を指定する。Dockerfileだけ。今回は"dnsmasq"というイメージ名を付けた。

dnsmasqを起動する

docker run -d -p 53:53/tcp -p 53:53/udp --name dns dnsmasq -A /www.example.com/192.168.11.4

tcp:53とudp:53をDockerホストとつなぐ。dnsmasqの-Aオプションで解決したい名前とipを指定する。ここでの"dnsmasq"の文字はイメージの名前。ENTRYPOINTのコマンドの追加の引数として"-A"から後ろの文字が渡される。

Dockerホストのファイアウォール

tcp:53とudp:53の受信を許可する。

iPhoneの設定

  • 設定 - Wi-Fi - 接続しているネットワークをタップ
  • DNSを構成 - 手動を選択する
    • いまあるDNSサーバーを全部削除
    • DockerホストのIPを追加

あとかたづけ

docker rm -f dns

iphoneWi-Fi設定は「DNSを構成」を自動にして「リースを更新」で元に戻る。

docker build のWARNING

SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.

Docker for WindowsLinuxコンテナのbuildをすると必ず表示されるらしい。よくないことが起きたって意味ではなくて、よく確認してねって意味らしい。そもそも今回はpathに-を指定してるから関係ない。

Dockerで動かすASP.NET CoreとリバースプロキシをUnixドメインソケットでつないでみた

ASP.NET Coreの組み込みWebサーバーのKestrelはUNIXドメインソケット(UDS)で待ち受けができる、と知ったのでやってみた。

UNIXドメインソケットは同一ホストでプロセス間通信をするもの。リバースプロキシ(Webサーバー)とASP.NET Coreアプリケーションを同じホストで実行する。両者をDockerコンテナとして実行し、UNIXドメインソケットをDockerのボリューム機能で共有する。Webサーバーは触ってみたかったのでH2Oにした。

やってみた環境

> chcp 437
> systeminfo | findstr /B /C:"OS Name" /C:"OS Version"
OS Name:                   Microsoft Windows 10 Pro
OS Version:                10.0.16299 N/A Build 16299
> dotnet --version
2.1.2
> docker --version
Docker version 17.09.1-ce build 19e2cf6

Docker ImageはDocker Hubにある公式イメージを使った。(2017/12/13でのlatest)

ASP.NET Coreアプリケーション

素のASP.NET Coreアプリケーションを用意する

dotnet newコマンドの「web」テンプレートでHello Worldと返すだけのアプリケーションが作れる。アプリケーションの名前は「myapp」とする。

> mkdir myapp
> cd myapp
> dotnet new web

UNIXドメインソケットでの待ち受けを構成する

Program.csのBuildWebHostメソッドでASP.NET CoreのWebサーバー(Kestrel)がセットアップされる。WebHost.CreateDefaultBuilderの中でUseKestrel、UseIISIntegrationが呼ばれ、Kestrelの待ち受けが構成される。今回はCreateDefaultBuilderの後でさらに追加でUseKestrelを呼んでUNIXドメインソケットでのListenを追加することにした。UNIXドメインソケットのパスは「/tmp/myapp/kestrel.sock」にした。

// Program.cs
public class Program
{
    public static void Main(string[] args)
    {
        BuildWebHost(args).Run();
    }

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseKestrel(options =>
            {
                options.ListenUnixSocket("/tmp/myapp/kestrel.sock");
            })
            .UseStartup<Startup>()
            .Build();
}

公式ドキュメント: ASP.NET Core でのホスティング
WebHost.CreateDefaultBuilderのソースコード: MetaPackages/WebHost.cs at rel/2.0.0 · aspnet/MetaPackages · GitHub

ASP.NET Coreアプリケーションを実行する

dotnet publishコマンドでビルドする。docker runでアプリケーションを実行する。このときコンテナに2種類のボリュームをマウントする。一つはアプリケーションのdllを含んだホストディレクトリと対応したボリューム。もう一つはUNIXドメインソケットをコンテナ間で共有するためのホストディレクトリと対応しないボリューム。

> dotnet publish
> cd bin\Debug\netcoreapp2.0\publish
> docker run -d --rm -v /tmp/myapp -v %CD%:/app -w /app --name app microsoft/aspnetcore dotnet myapp.dll
docker run
--name app コンテナの名前をappとする
-v /tmp/myapp UNIXドメインソケットを作るディレクトリをボリュームとしてマウントする
-v %CD%:/app ビルド結果をフォルダごとDockerコンテナの/appディレクトリとしてマウントする
-w /app コンテナの作業ディレクトリを/appにする
dotnet myapp.dll コンテナ実行時のコマンドと引数

Docker run リファレンス docs.docker.jp
コンテナでデータを管理する

待ち受けを確認する

アプリケーションがUNIXドメインソケットで待ち受けしているか確認する。コンテナの中でcurlを使う。Hello Worldが返ってくればOK。

> docker exec -it app bash
# curl --unix-socket /tmp/myapp/kestrel.sock http://localhost/

curlでunix domain socket経由アクセスする
Can cURL send requests to sockets? - Super User
curl 7.50以降ではURLにホスト名を含めてよくなったらしい

リバースプロキシ

H2Oの設定ファイルを作る

yamlの設定ファイルを作る。ファイル名はh2o.confとする。接続を試したいだけなのでuser:rootとする。

hosts:
  default:
    listen:
      port: 8080
    paths:
      /:
        proxy.reverse.url: "http://[unix:/tmp/myapp/kestrel.sock]/"

access-log: /dev/stdout
error-log: /dev/stderr
user: root

公式ドキュメント: Reverse Proxy · h2o/h2o WikiProxy Directives - Configure - H2O

今回のやりかたでは、ASP.NET CoreはrootユーザーでUNIXドメインソケットを作る。H2Oもrootで実行しないとUNIXドメインソケットが読み書きできない。ちゃんと使おうと思ったらきちんとユーザーを作って設定しなければならないだろう。

H2Oを実行する

--volumes-fromで「app」コンテナのボリュームを共有する。 これで「/tmp/myapp」ディレクトリが2つのコンテナ間で共有される。 h2o.confファイルをコンテナの「/etc/h2o/」ディレクトリに置く。ファイル1つでもボリュームマウントできるが、今回はdocker cpを使ってファイルをコンテナにコピーした。

>docker create --rm --volumes-from app -p 8080:8080 --name h2o lkwg82/h2o-http2-server
>docker cp h2o.conf app:/etc/h2o/h2o.conf
>docker start h2o

できあがり

Dockerホスト1のブラウザでhttp://localhost:8080にアクセスするとアプリケーションからの応答が返ってくる。

手軽にできるようにDocker HubのイメージのDockerfileを見てdocker runのオプションを決めた。docker build のコンテキストと、DockerfileでのCOPYがこのイメージの肝。DockerfileはDockerデーモンが実行するので、docker buildしたクライアントとは見えるファイルが違う。そのためのコンテキスト。

参考にした: Dockerfileを書く時の注意とかコツとかハックとか | kim hirokuni

おまけ1

H2Oのコンテナの中でUNIXドメインソケットへアクセスできているか確認する。このイメージはalpine linuxがベースになっている。bashがないので、コマンドashで対話する。パッケージマネージャーはapkupdateしてaddでインストール。

> docker exec -it h2o ash
# apk update
# apk add curl
# curl --unix-socket /tmp/myapp/kestrel.sock http://localhost/

おまけ2

Kestrelの既定の待ち受けポートはTCP:5000。UseIISIntegration(IIS統合)が有効になるとKestrelの待ち受けポートが動的に決定され、IIS経由のリクエスト以外は拒否される。しかし、後で追加するUnixドメインソケットでの待ち受けには影響がないっぽい。

実行中のASP.NET CoreアプリケーションでWebサーバーの待ち受けがどうなっているのかIServerAddressesFeatureでわかる。

Kestrel web server implementation in ASP.NET Core | Microsoft Docs

 var serverAddressesFeature = app.ServerFeatures.Get<IServerAddressesFeature>();

片付け

> docker rm -v -f h2o app

  1. 今回はDocker for WindowsなのでWindowsからブラウザでhttp://localhost:8080でDockerで動くH2Oにアクセスできる