図とかにするブログ

監査系コンサル会社にて、IT エンジニアとして勤務している人のブログです。 AWS・Azure などのクラウドインフラと、バックエンド関連の技術を触っています。 なるべく図解と結論ファーストを意識した、読み手にとってタイパの良い技術発信を心がけます。

CORS を実験的にテストできるアプリ

概要

CORS を何度学んでみても、しばらく経つと全く内容を理解していないことが多かったので、ローカルで動作する CORS を試すことのできるアプリケーションを作って触ってみました

github.com

CORS の概要

CORS とは javascript が本来禁止されているクロスオリジンのリソースにアクセスするための制限緩和のためのブラウザの仕組みです

本来、javascript は同一オリジンポリシー(SOP)というセキュリティ上の制限があり、異なるオリジンのリソースにアクセスすることができませんが、 現代ではアプリケーションがオリジンを跨いでアクセスすることが一般化しているので、CORS での制御が必要となります

あくまで「JavaScriptによるデータの読み取り」に対する制限緩和の仕組みであ離、iframe や img タグなどによる「HTMLの埋め込み表示」 には適用されません

オリジンとは

スキーム + ホスト + ポート の組み合わせのことです 以下は全て異なるオリジンとなります

  • http://localhost:3000
  • http://localhost:8080(ポートが違う)
  • https://localhost:3000(スキームが違う)

そもそも何で同一オリジンポリシーがあるのか?

そもそも何でこの SOP が「Javascript」にあるのかは、 「Javascript」と「HTMLの埋め込み表示」を比較すると理解しやすいです

  • Javascript: 動的にデータを操作できるため、他サイトの秘密データを読み取り、それを別のサーバーへ転送できてしまう(読み取り&持ち出しが可能)

  • HTMLの埋め込み: 静的な表示機能であるため、単に画面に表示するだけで、その中身のデータを解析して別のサーバーへ転送することはできない

つまり、js というプログラムからデータを読み取れてしまうと、 悪意のあるサーバーへ別オリジンの個人情報などを転送できてしまうので、これを防ぐためにあるわけです

CORS の仕様

CORS はAccess-Control-Allow-*といったヘッダーを利用して、アクセスを許可するオリジン・操作・ヘッダーなどを許可を見た上でリクエストを送信できるかを決定します

そのヘッダーをまずもらう必要がありますが、許可の確認をするための CORS のリクエストには、以下2種類のリクエストが存在します

  • プリフライトリクエスト
  • シンプルリクエスト(単純リクエスト)

プリフライトリクエスト

1つ目の「プリフライトリクエスト」の方式では、ヘッダーを確認するためのリクエストをブラウザが事前に勝手に送信してくれます

このリクエストで、別サーバーがクロスオリジンでのアクセスを許可しているオリジンや対象メソッド・ヘッダーをブラウザが確認し、安全にリクエストが送信できるのか確かめた上で送信をしてくれる仕様となっています

プリフライトリクエスト

シンプルリクエスト(単純リクエスト)

一定の条件を満たすリクエストは、事前確認をせずに直接クロスオリジンのリクエストを送信することができます(GET,POSTなど該当するのでこちらの方がよく使われるはず)

ただし、事前確認(プリフライトリクエスト)をしないとはいっても、返却されたヘッダーで CORS の許可の確認はしています

許可されていないメソッドやオリジンであれば、javascript から読めないようにブラウザがレスポンスを破棄しているだけで、リクエスト自体は成功してレスポンスは返ってきています(開発者ツールなどを使うとレスポンスが見える)

シンプルリクエスト

シンプルリクエストが発生する詳細な条件は以下参照

オリジン間リソース共有 (CORS) - HTTP | MDN

なぜシンプルリクエストでは、プリフライトリクエストでの事前確認をしないのか?

これは、昔からある HTML の<form>で送信できるGETPOSTなどのリクエストでは、同一オリジン外からの悪意あるリクエストの対策(CSRFの対策)をしていることが前提であり、「事前にクロスオリジンの通信を防ぐように対策をしてるよね?だから Javascript の CORS で面倒は見ないっすよ」的な話らしいです

アプリケーションの概要

ここからアプリケーションを用いて、実際に挙動を見てみます

アプリケーション自体は大体AIに書かせて細々とした修正だけしています

  • バックエンド/フロントエンドともに Typescript
  • バックエンド
    • itemsというテスト用データをメモリ上に持ち、フロントエンドから各種メソッドを用いて操作可能
      • デフォルトでid:1,2のデータが用意されている
    • 別途、フロントエンドに返却する CORS 設定値を操作するAPIを持つ
    • Cache-Control: no-storeとしており、ブラウザがキャッシュを持たないようにしている
  • フロントエンド
    • バックエンドに対して各種API操作が可能
    • CORSの設定も変更可能

デフォルトのitemsデータ

[
    {"id":1,"name":"item1","value":"foo"},
    {"id":2,"name":"item2","value":"bar"}
]

CORS 許可設定なしの挙動

シンプルリクエスト

GET メソッドを実行すると、CORS エラーが返ってきます

Response もFailed to fetchとエラーとなっています

中身を見てみると、レスポンス自体は正常に 200 となっているため、ブラウザがレスポンスを捨てたことがわかります

POST も同様に、エラーが発生します

ただし、このエラーの中身を見てみると、POST 自体は実行されてid:3の新しい item が追加されています

先述のシンプルリクエストの項の通り、あくまでブラウザが結果を破棄しているだけなので、 CORS を制限をしていてもリクエスト自体は実行されてしまいます

CSRF は、CORS を制限するだけでは防げないということです

プリフライトリクエスト

PUT を実行してみると、以下の2つのリクエストが飛んでいることが分かります

  • preflightリクエスト(CORSエラーとなっている)
  • CORS エラーのリクエスト

PUT はプリフライトリクエストが飛ぶメソッドですので、挙動が正常なことが分かります

PUT 自体がでは実行されたのかというと、itemsはデフォルト値のまま残っていて変更されていません

つまり、プリフライトリクエストが発生する場合には、メソッドの実行自体が制限されていることがわかります

中身を見てみると、ステータスコードが返ってきていないので、リクエストが送信されていなさそうですね

まとめ

以上より、CORS はリクエストの実行自体を防ぐというよりは、あくまで別オリジンのデータを javascript が読みとって悪意のあるユーザーの元へ送るのを防ぐような役割を持っていることがわかります

悪意のあるリクエストの実行自体を防ぐものではない、ということは念頭に入れておく必要がありそうです(一部の CSRF を防止してくれるものであり、XSS は防げない)

参考文献

NLB 設定値メモ(Terraform)

概要

Terraform で NLB を構成する必要があった(UDP要件)が、いくつかの設定値がドキュメントを見ただけではすんなり理解ができなかったため、多少深掘りが必要となった値をメモ📝

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb#desync_mitigation_mode-1

zenn にも投稿済みの記事

設定値

desync_mitigation_mode

デフォルトdefensive、特に厳しいセキュリティ要件がなければデフォルトのままでよい

HTTP リクエストスマグリングの対策のためのオプション 値はmonitor, defensive, strictestの3つで、HTTPリクエストの安全性の分類レベルをどこまで許可するかが異なる

詳細はこちらの表が詳しい

参考:HTTP リクエストスマグリング

HTTPリクエストスマグリングは、現在の RFC で定義されている以下2つのヘッダが同時に存在する場合の解釈の優先順位を正しく実装していない脆弱性のあるサーバーがプロキシなどに存在する際に起きる脆弱性

  • Content-Length:HTTP のボディの長さを指定する(固定長)ヘッダ
  • Transfer-Encoding:レスポンスをリアルタイムに生成するなどで HTTP のボディの長さが不明な場合にこの値をchunckedとして可変長として送信するためのヘッダ

Transfer-Encoding : chunckedがある場合、本来連続で送られるリクエストなどを待たなければならず、Content-Lengthより優先される が、脆弱性のあるサーバーではContetn-Lengthを優先した結果、バックエンドに対して本来可変長のリクエストを固定長の完了したリクエストとしてバックエンドに送信し、一連のHTTP通信を一旦完了する バックエンド側が正常な実装をしている場合、Transfer-Encoding : chunckedを正しく解釈するので、レスポンスを返した後に次のパケットを持つ

ここで、別のHTTPパケットが届きプロキシがバックエンドへ送信すると、バックエンドはこのパケットを前の続きと解釈してしまう、といった問題が起こる💥

HTTP リクエストスマグリング入門から最新研究まで - FFRIエンジニアブログ

dns_record_client_routing_policy

特に要件がなければデフォルトで良い、いまいち情報が少なめ

大半がクロスゾーン負荷分散をONにする気もするので触ることが少なさそう(クロスゾーン負荷分散との併用は不可)

設定値としては、Route 53 Resolver を利用した名前解決を行うクライアントに対して、NLB の AZ へのルーティングのポリシーを制御するもの

  • 全て同じ AZ にルーティング(100%)
  • 85% 同じ AZ にルーティングし残りは分散
  • 全てランダムにルーティング

The possible values are availability_zone_affinity with 100 percent zonal affinity, partial_availability_zone_affinity with 85 percent zonal affinity, and any_availability_zone with 0 percent zonal affinity.

Network Load Balancers - Elastic Load Balancing

NLBのAvailability Zonal DNS affinityを試してみた

enable_cross_zone_load_balancing

NLB ではデフォルト OFF、ALB はデフォルト ON

AWS の LoadBalancer は、AZ 単位でノードが存在しており、本来 AZ をまたがってトラフィックを分散しないが、クロスゾーン孵化分散を ON にすると AZ を跨いで分散してくれる

それぞれのノードは NIC で静的 IP アドレスを持つ

ロードバランサー用のアベイラビリティーゾーンを有効にすると、Elastic Load Balancing はアベイラビリティーゾーンにロードバランサーノードを作成します。デフォルトでは、各ロードバランサーノードは、アベイラビリティーゾーン内の登録済みターゲット間でのみトラフィックを分散します。クロスゾーン負荷分散を有効にすると、各ロードバランサーノードは、有効なすべてのアベイラビリティーゾーンの登録済みターゲットにトラフィックを分散します。 https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html

以下の記述の通り、冗長性の確保のために基本的には有効化推奨だが、 NLB の場合、クロスゾーン負荷分散の結果発生した AZ をまたぐ通信については GB 単位での課金が発生するので料金が気になる人は注意が必要💰

アプリケーションの耐障害性を向上させる目的で、複数のアベイラビリティーゾーンをロードバランサーに対して有効にすることができます。各ターゲットグループで、有効にした各アベイラビリティーゾーンに 1 つ以上のターゲットがあることを確認してください。たとえば、1 つ以上のターゲットグループで 1 つのアベイラビリティーゾーン内に正常なターゲットがない場合、DNS から該当するサブネットの IP アドレスを削除しますが、他のアベイラビリティーゾーンのロードバランサーノードは、引き続きトラフィックをルーティングできます。

NLB の場合、クロスゾーンで GB 単位の料金が発生するが、 ALB の場合はクロスゾーン負荷分散が常時有効であるため、課金が発生しないらしい (これが ALB はデフォルト有効、NLB はデフォルト無効な理由か)

Application Load Balancer でクロスゾーンロードバランシングを有効にすると、リージョンの AWS データ転送について料金が発生しますか? いいえ。クロスゾーン負荷分散は Application Load Balancer では常に有効であるため、この種類のリージョンデータ転送には料金はかかりません。 Network Load Balancer でクロスゾーン負荷分散を有効にすると、リージョンの AWS データ転送に対して料金が発生しますか? はい、クロスゾーン負荷分散を有効にすると、Network Load Balancer でのアベイラビリティーゾーン間のリージョンデータ転送には料金が発生します。料金は、Amazon EC2 オンデマンド料金表のページのデータ転送セクションを参照してください。 https://aws.amazon.com/jp/elasticloadbalancing/faqs/

参考:フローハッシュアルゴリズム

NLB はラウンドロビンではなく、フローハッシュアルゴリズムに基づいてパケットを分散する

TCP, UDP ともに、送信元ポートや送信先アドレスなどの要素をベースに通信を1つの「フロー」とみなして、フローが変わらなければ同じ宛先へ振り分けを行う (TCP はそもそもコネクションを保持するので、コネクションが切れないと常に同じ対象に保存するけれど)

Network Load Balancer とは? - エラスティックロードバランシング

この「フロー」はNW界隈の用語で、同じ送信元と送信先を共有するパケットのことを言うらしい📝

In networking terms, a flow represents a unidirectional sequence of packets sharing the same source and destination IP addresses, source and destination ports, and protocol type. https://www.linkedin.com/pulse/hash-flow-algorithm-aws-network-load-balancer-nlb-in-depth-mishra

余談も余談だけれど、AWS が独自に作ったプロトコルらしい

NLB は UDP もフローをコネクションとして保持する

NLB が TCP コネクション を保持するのは特に違和感なく入ってくるが、 本来コネクションレスである UDP も上述のフローを一つのコネクションとして、120秒間コネクションを保持する

UDPコネクションレスですが、ロードバランサーは送信元と宛先のIPアドレスとポートに基づいて UDP フロー状態を維持します。これにより、同じフローに属するパケットが一貫して同じターゲットに一貫して同じターゲットに送信されます。アイドルタイムアウト期間が経過した後、ロードバランサーは着信 UDP パケットを新しいフローとみなし、それを新しいターゲットにルーティングします。Elastic Load Balancing は、UDP フローのアイドルタイムアウト値を 120 秒に設定します。これは変更できません。 https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/network/network-load-balancers.html#connection-idle-timeout

プライベートリンク経由できたパケットに対して、Security Group のインバウンドルールを適用するかどうか これはお好みに応じて

[Enhancement]: aws_lb support enforce_security_group_inbound_rules_on_private_link_traffic · Issue #33766 · hashicorp/terraform-provider-aws

余談

NLB の設定ではないが調べてしまったものがあり、もったいないのでそのまま載せておく

drop_invalid_header_fields

ALB のみの設定値 デフォルトはOFF(false)、AWS Config のルールの確認項目でもあるので、基本は ON(true)にしてしまってもいいはず

ELB では、正規表現[-A-Za-z0-9]+の準拠を推奨しているので、これに準拠しないHTTPヘッダをドロップしてくれる機能

Elastic Load Balancing では、登録されているすべてのメッセージヘッダー名が正規表現 [-A-Za-z0-9]+ に準拠している必要があります。 https://repost.aws/ja/knowledge-center/elb-alb-drop-not-valid-headers

有効化することでセキュリティ的には向上が見込めるが、一方でアンダースコアを使ったヘッダ名がNGとなるので、既存システムなどでアンダースコアを用いたカスタムヘッダーなどを使用している場合は注意が必要(AWSがデフォルトでは無効としているのはこのためと思われる)

ALBの無効なリクエストヘッダ削除(Drop Invalid Header Fields)設定を試してみた | DevelopersIO

SSM Agent のエンドポイントを上書きする方法

概要

諸事情で SSM Agent を利用する際に、元々 SSM Agent が利用するエンドポイント名ではなく、 vpc エンドポイントのエンドポイント名(vpce-hogehogeみたいなやつ)を利用したかった。

ただ、ssm agent の設定のいじり方が調べてもあまり出てこなかったのでブログとして記録。

結論

実はきっちり github に SSM Agent のエンドポイントの上書きに関する記載がある

AWSのドキュメントでの記載は見つけられなかったが、SSM Agent には、諸々の設定用ファイル(amazon-ssm-agent.json)がある

(CloudWatch Agent とかは普通にドキュメントに記載あるのに、、、)

このamazon-ssm-agent.jsonの中にエンドポイントのオーバーライド設定があるので、ここを上書きして、起動前に Windows ならC:\Program Files\Amazon\SSM配下に、Linux なら/etc/amazon/ssm/に置いてあげればok👌

To set up your own custom configuration for the agent:

Navigate to /etc/amazon/ssm/ (or C:\Program Files\Amazon\SSM for windows)

Copy the contents of amazon-ssm-agent.json.template to a new file amazon-ssm-agent.json

Restart agent

github.com

詳細

概要と結論に書いた通りと言えば通りだけれど、もう少し詳細に記載する。

vpc エンドポイントは普通に使う場合、本来普通に AWS 関連のサービスが利用してくれるエンドポイント名を上書きするDNSを登録してくれるので、デフォルトのエンドポイント名を気にせず利用することができる。

※ cloudwatch logs の場合
logs.<リージョン>.amazonaws.com

仕組みとしては、VPCエンドポイントを作成した VPCが利用するDNSに勝手にプライベートなレコードを追加してくれるようなイメージ。(きっちりとした裏はとっていないので注意)

ただ、今回諸事情によってデフォルトのエンドポイント名の利用ができなくなったため、VPCエンドポイント自体が固有で持つエンドポイント名を設定する必要があり、DNSの上書きを行った。

意識したことがない人からすれば、VPCエンドポイントが固有で持つエンドポイントってなんだよ、って感じだけれど、コンソール↓から確認できる。

VPCエンドポイントは作成すると、以下2種類のエンドポイント名が確認できるようになる。

  • リージョンエンドポイント:vpce-****.<サービス名>.<リージョン名>.vpce.amazonaws.com
  • AZエンドポイント:vpce-****-<AZ名>.<サービス名>.<リージョン名>.vpce.amazonaws.com

AZ側のエンドポイントは、VPCエンドポイントを設置したAZごとにおそらく作成される。

今回画像のエンドポイントは、ap-northeast-1a, ap-northeast-1cの2つに作成している。

VPCエンドポイント

amazon-ssm-agent.json を利用したエンドポイントの上書き設定は結論に記載した通りなので、あまり詳細に解説する部分はないかも。

SSM Agent は現在、2つのエンドポイント(ssmssmmessages)を利用しているので、フルで機能を利用したい場合はどちらも上書きしてやる必要がある。

イメージとしては、ssm の通信自体はssmのエンドポイントで、諸々のスクリプトの実行(Run Command) 的なのはmessages側ぽい

※ちゃんと調べてなくて不正確なので注意

昔は3つのエンドポイントが必要だったが、今は2つでいいらしい。

余談1

ちなみにエンドポイントを適当なドメインに上書きするのは、AWSのサービス側のサーバー証明書がカバーしているドメインとズレるのでできない。(当たり前だけど)

ssm では検証していないけれど、アホだったから、CloudWatch Agent でエンドポイントをプライベートなドメインにしようとしてこけた。

余談2

vpce-***DNSはプライベートなIPが返ってくるので勘違いしやすいけれど、普通にパブリックに名前解決できる、特にプライベートなレコードではない。 (パッと試してみたければ、VPCエンドポイントを作って、ローカル端末から解決してみるとわかる)

別にパブリックにプライベートIPアドレスの名前解決ができたからなんだって話ではあるけれど、検証の時にこれを認識してないと地味にめんどい。

仮想的に閉域網とかを作成して検証する際に、雑にDNSは一部パブリックに到達できたりして検証OKにしてたら、本番環境ではパブリックなDNSが使えずにおしまい!な事故につながりそうな気がする。

Vscode の拡張「C# Dev kit」の補完や Intellisense がうまく動作しない問題 on Mac

問題

Mac 上で、C#の開発を行っていると、Intellisenseが動作しないことがある。

本来、メソッドとかにマウスをホバーした時にオブジェクトやメソッドに対するいい感じの説明をしてくれるけれど、それが動作しないケース。

これは、GUI からクリックなどで VScode などを起動すると起こる。

This happens when opening VSCode from the Dock in MacOS. As I understand it, VSCode does not necessarily provide the correct path unless spawned from a terminal process which carries that PATH, i.e from the code command. Opening my project this way resolves the issue, however it is not the solution in my opinion.

github.com

↓ output を確認すると、dotnetがそもそも見つからないと言われており、Intellisense 用の C# Dev kit のサーバーが起動していないことがわかる。

Starting Spawn .NET server...
Using legacy dotnet resolution.
Starting opening a solution...
Starting processing the solution file "/Users/tak/MMM/SportsDistractor/SportsDistractor.sln" in Dev Kit server...
Error running "dotnet --info". Either the dotnet installation cannot be found from PATH, or it might be corrupted.
Error: Error: spawn dotnet ENOENT
STDOUT:

STDERR:

Failed to find dotnet from path with "which dotnet".
Error: Error: Command failed: which dotnet

結論

これはGUI のアプリケーションを GUI から起動した場合、シェルの子プロセスとして処理されないために、環境変数が異なるために生じてるらしい。

superuser.com

↓とりあえずの迂回策として、こちらのコメントにあるUse Legacy Dotnet Resolutionを、OFFにすると環境変数以外の方法でdotnetのパスを見つけてくれるっぽい

github.com

OFFにして、エディタを再起動すると、ちゃんと Intellisense が機能するようになった。

【図解】レガシーシステム通知のためにSMTP サーバーを立てずに smtp4dev でいい感じにやりくりする

悩んだこと

少しレガシーなシステムを触って、なんらかの通知システムを実装しようとすると、SMTPしか対応してないことが割とあります。

メール通知じゃなくて、Slack とかに通知したいんだよとか、

クラウド環境だと SMTP サーバーを建てるのはそもそも色々制約があって面倒とか、

Amazon SES のようなマネージドサービスもパブリックなドメインが必要で手間でやりたくないとか、SMTP 通知のみは結構面倒臭い。

要件ガチガチじゃないちょっとしたシステム通知くらいならわざわざドメインを取らずにいい感じにやってしまいたい感があります。

結論

smtp4dev というツールを使うといい感じに解決できます。

github.com

smtp4dev は簡易な smtp サーバーを起動することができるツールです。

smtp サーバーを利用する通知システムの中にこいつを仕込んでしまうことで、レガシーシステムからとりあえず通知を発することができます。(もちろん別サーバーを建ててもOKです)

ローカルで受けてどうするんだ?ってところですが、smtp4dev には API も実装されており、簡単にメールを引っ張り出すことが可能です。

なので Windows であれば、API を叩く PowerShell スクリプトを適当に書いてタスクスケジューラーなどで回せば、Amazon SNS などの外部サービスに簡単に連携できます。

実装例

簡単にですが、実装を紹介します。

今回レガシーシステムとして、Windows Server の「File Server Resource Manager(FSRM)」 を利用します。

ユーザーがごとのディレクトリの使用量の監視などもできる便利な機能ですが、通知には SMTP サーバーが必要です。

通知のテストメールも送信できるのでちょうど良いです。

アーキテクチャイメージは以下です。

アーキテクチャ

FSRMのインストール

FSRM は、デフォルトでは入っていないので、Server Manager を使って GUI から、または、PowerShell で以下のコマンドでインストールします。

Install-WindowsFeature FS-Resource-Manager -IncludeManagementTools

smtp4dev のインストール

次に smtp4dev のインストールです。

環境は Windows Server 2022 で、smtp4dev は、Windows x64 binary standalone - Server editionを利用します。

↓こちらで最新のリリースをDLできます。

github.com

DLしたファイルを解凍して、exeファイルを起動すると smtp サーバーが起動してくれるので、こいつをサービス登録して常時起動するように設定します。

Windows x64 binary standalone - Server edition

# サービスを登録
New-Service -Name "smtp4dev" `
  -BinaryPathName "<パス>\Rnwood.Smtp4dev.exe" `
  -StartupType Automatic

# サービスを起動
Start-Service -Name "smtp4dev"

FSRM 通知設定

smtp4dev は、localhost で起動してくれるので、FSRM のメール通知設定からlocalhostに対して送信します

FSMR 通知設定

smtp4dev はメールボックス GUI を、デフォでlocalhost:5000にて、起動してくれているので、そちらを確認するとメールが届いています。

localhost:5000

メールが届くのが確認できたら、次に API を叩いて smtp4dev からメールを取得します。

API の利用方法は、http://localhost:5000/api にて、swagger の説明を用意してくれているので、そちらで確認ができます。

AWS CLI のインストール

API を叩いていきたいところですが、SNSAPI を叩くために AWS CLI が必要になるので、以下を参考にインストールしておきます。

インストール方法は、↓のとおりです。

AWS CLI の最新バージョンのインストールまたは更新 - AWS Command Line Interface

PowerShell スクリプトの準備

API を利用して定期的にメールを取得して、SNS へ送信するため以下のようなスクリプトを用意します。

SNS にメールを実際に送る場合は、もう少しメールの中身を確認して整形した方がいいですが、今回は送信できればよしとしてこのまま送信します。

InvoekWebRequestにて、UseBasicParsingを利用しているのは、IE を利用しないようにするためです。(場合によってはエラーが出ます)

 Write-Output "スクリプトが実行されました"

$res = Invoke-WebRequest http://localhost:5000/api/Messages -UseBasicParsing
$content = ConvertFrom-Json $res.Content

foreach($item in $content.results){
    Write-Output "$($item.id)のメールを処理します"

    aws sns publish --topic-arn <SNSのARN> --message $item

    Invoke-WebRequest -Method DELETE "http://localhost:5000/api/Messages/$($item.id)" -UseBasicParsing
}

Write-Output "スクリプトが終了しました" 

タスクスケジューラへの登録

最後に、先ほどのスクリプトをタスクスケジューラへ登録します。

今回5分置きでの実行をしますが、タスクスケジューラは、結構クセがあり、定期間隔での時間設定がなかなかわかりづらい...

タスクスケジューラの5分置きの実行には、↓の設定が必要です。「毎日」などの設定がありますが、それは罠なので注意してください。

1 回 + 繰り返し間隔の継続時間「無期限」

jpwinsup.github.io

5分置きの実行はこれでOKですが、実はこれだけだと再起動した際に、タスクスケジューラが停止してしまいます。

なので、StartWhenAvailableオプションを有効にして、過去の日付をタスク開始日として指定することで、再起動後も常に実行できるようにします。

そのほかの細かな設定値の詳細説明は省きますが、↓のコマンドを叩けばOKです。(ExecutionPolicyなども雑に付けているので本番運用の際には注意してください)

$Action = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-NoProfile -ExecutionPolicy Bypass -File `"C:\Program Files\smtp4dev\MailPasser.ps1`""

$Trigger = New-ScheduledTaskTrigger -Once -At "01/01/2025 00:00:00" `
    -RepetitionInterval (New-TimeSpan -Minutes 5)

$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries `
    -DontStopIfGoingOnBatteries `
    -StartWhenAvailable `
    -ExecutionTimeLimit (New-TimeSpan -Minutes 5)

Register-ScheduledTask -TaskName "MailPasser" `
    -Action $Action `
    -Trigger $Trigger `
    -Settings $Settings `
    -User "SYSTEM" `
    -RunLevel Highest

以上で、完成です。

EC2 の IAM ロールなど省いてますが、もちろん SNS の権限が必要なので、付け忘れには注意してください。

実際に本番で、開発用のツールを導入するのはどうなんだ、とか、

API の実行がうまくいくのか?リトライが必要ではないか?とか、

諸々考慮事項はあるとは思うんですが、一つの解決策として持っておくのは悪くないと思います。

VPC Lambda が使用している ENI を確認する方法

困ったこと

VPC に Lambda を統合する際に対象の ENI を確認したくなったが、色々デプロイされている場合 ENI の量が半端じゃなかったのでどうやって確認していいかわからなかった

結論

describe-network-interfaces の中で、interface-typeという要素が返却されるので、以下の CLI で拾うことができる

CLI の実行にはec2:DescribeNetworkInterfaces が必要なので注意

aws ec2 describe-network-interfaces --filters Name=interface-type,Values=lambda

解説

interface-typeには、以下のような種類があってそれをフィルタリングすることで検索できる

interface-type - The type of network interface (api_gateway_managed | aws_codestar_connections_managed | branch | ec2_instance_connect_endpoint | efa | efa-only | efs | gateway_load_balancer | gateway_load_balancer_endpoint | global_accelerator_managed | interface | iot_rules_managed | lambda | load_balancer | nat_gateway | network_load_balancer | quicksight | transit_gateway | trunk | vpc_endpoint).

マネジメントコンソールからも確認できる

interface-typeは、普通にマネジメントコンソールからも確認ができるっぽい、全然知らなかった

マネジメントコンソール

コード

動作検証のためにシンプルな VPC lambda を準備するだけの IaC を作った

コード自体は VPCVPC Lambda を用意するだけの CDK コード

github.com

WSUS(IIS) の HTTPERR ログの出し方

困ったこと

WSUS(IIS)を利用するプロジェクトにて、%SystemRoot%\system32\LogFiles\HTTPERRにエラーログがきっちりと出力されるかテストをしたかったけれど、 適当なパスを指定して 404 エラーを起こしたりしても、エラーはでないし、サーバー周りの設定値をいじりたくもなくて、少し悩んだ。

jpdsi.github.io

結論

そもそも HTTPERR ログは、不正なHTTPリクエストのようなエラーを記録するもので、IIS上のアプリケーションで判断するエラーログじゃなかった。

色々とやり方はあるけれど、以下のように、HTTPヘッダの最大長を超えるようなHTTPリクエストを作って送りつけると簡単にエラーログが出せる。

$Headers = @{ "Large-Header" = ("A" * 100000) }
Invoke-WebRequest -Uri "http://<WSUSサーバー>:8530/" -Headers $Headers

HTTP のヘッダサイズの制限はRFCでは特に定められてなくて、各 Web サーバーの仕様によりけり。

IIS では(厳密にはHTTP.sysでは)、レジストリキーによって既定されていて、デフォルトではリクエストラインとヘッダの合計値が 16 KB。

learn.microsoft.com

ちょっと深掘り

HTTPERR ログとは

こちらがとてもわかりやすかった。

HTTPERRログは、要は IIS 上で動いているアプリケーション(ワーカー)まで辿り着かず、その前段階で不正なHTTPリクエスト形式などの理由によって落ちたエラーを記録するためのログ。

こいつ自身はIISのログというよりは、IISのフロントとして機能してくれるHTTP.sysと呼ばれるシステムのログで、そのため、HTTPの不正な形式エラーが記録される。

さっきも書いた通り、いわゆる HTTP 404 のようなIISで動くアプリケーションまで届いた上で、HTTPプロトコルとしては正常な動作をした場合のエラーはここには記録されないっぽい。

This log records all errors that are not handed off to a valid worker process, typically responses to clients, connection time-outs, and orphaned requests.

www.oreilly.com

HTTP.sys とは

HTTP.sysとは、飛んできたリクエストをIISで動くアプリケーション(ワーカー)に対して橋渡しをしてくれるカーネルモードで動くWindows OSの機能。

OSの機能だから、設定値もレジストリキーなんかで決まっているのかも。

By default, IIS provides HTTP.sys as the protocol listener that listens for HTTP and HTTPS requests. HTTP.sys was introduced in IIS 6.0 as an HTTP-specific protocol listener for HTTP requests. HTTP.sys remains the HTTP listener in IIS 7 and later, but includes support for Secure Sockets Layer (SSL).

learn.microsoft.com

IIS 6.0 から導入されたようで、それ以降のバージョンでも基本的な構造は変わっていないよう。以下の図が非常にわかりやすい。

IIS 6.0 includes a new HTTP listener (HTTP.Sys) that is implemented in the kernel. Requests are routed to one of the multiple worker process instances (W3wp.exe) that host ASP.NET applications and Web services.

IIS6.0のアーキテクチャ

単に、橋渡しをしてくれるだけではなく、IISのフロントとして、キャッシュ機能やリクエストのキューイング、不正なリクエストのフィルタリングなどの色々便利機能を持ってるらしい。