4月7日土曜日にNagoya.cloud.first #2へ参加した

来栖川電算さんにピザとスシを食わせてもらった、ごちそうさまでした。

"見に来てみる枠"という成果発表が必須ではない参加者として行ってきた。 本当に皆さんもくもくとやっていた。

クラウド縛りの指すものがよくわからずに参加した。どうもawsとかgcpとかのクラウドサービスに触れればOKっぽい空気を嗅ぎ取った。なので、自分は、Amazon Route 53で管理するドメインの証明書を、Google Cloud Shell で Certbotを実行してLet's Encryptに発行してもらって、AWS Systems Manager Parameter Store に格納する、って作業をやった。

Secrets Manager はParameter Storeの高機能版で、バージョン管理、ローテーションのトリガー、監査ログ、などが利用できるよになったやつと理解してる、有料。自分の用途では、開発時に手元のWebサーバーで使う証明書と鍵の置き場所としてだけ考えている。s3や、dynamo db、google cloud shellのHOMEドライブ、なんだったらgmailにメールとして送信しておいてもいいかもしれないけど。どこに置いても、使おうと思ったときにはどこにあるか忘れるし、とり出し方も忘れがち。当面はAWSを触ってみようと思ってるので、Parameter Storeにした。

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に-を指定してるから関係ない。