セキュリティグループとネットワークACLの違い—戻り通信のハマりどころ

オフィスのIPアドレスからEC2にSSHを通したいだけなのに、セキュリティグループなら1行、ネットワークACLなら2行のルールが必要になります。この「1行と2行」の差が、両者を分けている一番大きな性質そのものです。

AWSのVPCまわりを触っていると、この2つは「どっちもファイアウォールでしょ」で片付けたくなります。ただ、通信が落ちたときの切り分けはこの差を理解しているかどうかで所要時間がまるで変わります。手を動かしながら公式ドキュメントを読み直したので、両者の違い・デフォルト値・エフェメラルポートの決め方・遮断されたときにどちらを疑うかまでを、Amazon VPC User Guide の記述に沿って整理しておきます。CLIの実行例は AWS CLI 2.35.11 で確認したものです。

目次

同じSSH許可でも、片方は1行・片方は2行いる

まずセキュリティグループ側。オフィスの固定IP 203.0.113.12/32 から22番を開けるなら、インバウンドに1行足すだけです。

aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp --port 22 --cidr 203.0.113.12/32
{
    "Return": true,
    "SecurityGroupRules": [
        {
            "SecurityGroupRuleId": "sgr-0a1b2c3d4e5f67890",
            "GroupId": "sg-0123456789abcdef0",
            "IsEgress": false,
            "IpProtocol": "tcp",
            "FromPort": 22,
            "ToPort": 22,
            "CidrIpv4": "203.0.113.12/32"
        }
    ]
}

アウトバウンドには何も足していません。それでもSSHのレスポンスはクライアントに返ります。

戻りの通信を誰が許可しているのか

Amazon VPC User Guide の Security group basics にこう書かれています。

Security groups are stateful. For example, if you send a request from an instance, the response traffic for that request is allowed to reach the instance regardless of the inbound security group rules.(セキュリティグループはステートフルです。たとえばインスタンスからリクエストを送った場合、そのレスポンスはインバウンドルールに関係なくインスタンスに届きます)

ステートフルとは、通した通信を覚えていて、その戻りを自動的に通すという意味です。行きを1行書けば、帰りは書かなくていい。セキュリティグループがアウトバウンド全開放のまま運用されがちなのは、そもそも戻りのために書く必要がないからです。

ネットワークACLは行きと帰りが別勘定

同じことをネットワークACLでやると、インバウンドとアウトバウンドの2本が要ります。

# 行き: 22番をオフィスIPから許可
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0a1b2c3d4e5f67890 \
  --rule-number 100 --protocol 6 --rule-action allow --ingress \
  --cidr-block 203.0.113.12/32 --port-range From=22,To=22

# 帰り: 戻り先のエフェメラルポートを許可
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0a1b2c3d4e5f67890 \
  --rule-number 100 --protocol 6 --rule-action allow --egress \
  --cidr-block 203.0.113.12/32 --port-range From=1024,To=65535

このコマンドは成功しても何も出力しません。入ったかどうかは describe-network-acls で確認します。

aws ec2 describe-network-acls --network-acl-ids acl-0a1b2c3d4e5f67890 \
  --query 'NetworkAcls[0].Entries[?Egress==`false`]'
[
    {
        "CidrBlock": "203.0.113.12/32",
        "Egress": false,
        "PortRange": {
            "From": 22,
            "To": 22
        },
        "Protocol": "6",
        "RuleAction": "allow",
        "RuleNumber": 100
    },
    {
        "CidrBlock": "0.0.0.0/0",
        "Egress": false,
        "Protocol": "-1",
        "RuleAction": "deny",
        "RuleNumber": 32767
    }
]

2件目に注目してください。マネジメントコンソールでルール番号が * と表示される最後のdenyルールは、APIでは RuleNumber: 32767 という数字で返ってきます。CLIヘルプの --rule-number にも「Constraints: Positive integer from 1 to 32766. The range 32767 to 65535 is reserved for internal use.」とあり、自分で使えるのは32766までです。コンソールの表示だけ見ていると気づかない部分だと思います。

行きと戻りで通る関門の順番 行きの通信はネットワークACLのインバウンド、次にセキュリティグループのインバウンドの順に審査される。戻りの通信はセキュリティグループでは自動で許可されるが、ネットワークACLではアウトバウンドルールで改めて審査される。 クライアント 行き 戻り ネットワークACL(サブネット境界) 行きと戻りを別々に審査する 再審査 セキュリティグループ(ENI) 戻りは審査せず自動で許可する EC2インスタンス 戻りで落ちるならACL側を疑う

公式の比較表は5行しかない

Amazon VPC User Guide には Compare security groups and network ACLs という節があり、そこに載っている比較表はたった5行です。両者の差は、突き詰めるとこれだけです。

観点セキュリティグループネットワークACL
動作するレベルインスタンス(ENI)単位サブネット単位
適用範囲そのSGを付けた全リソース関連付けたサブネット内の全リソース
ルールの型allowのみallowとdeny
ルールの評価全ルールを評価してから判断番号の昇順に評価し、一致した時点で確定
戻りの通信自動的に許可(ステートフル)明示的な許可が必要(ステートレス)

ENI単位かサブネット単位か

セキュリティグループはENI(ネットワークインターフェイス)に紐づきます。同じサブネットに10台並べても、SGが違えば挙動は台ごとに変わります。ネットワークACLはサブネットの出入口に置かれるので、そのサブネットに置いた全リソースへ一律に効きます。

この差は「後から付けたインスタンスに設定を忘れたとき」に出ます。公式の Control network traffic でも、ネットワークACLの使いどころとして「because network ACLs apply to an entire subnet, they can be used as defense-in-depth in case an instance is ever launched without the correct security group」(サブネット全体に効くので、正しいSGなしでインスタンスが起動された場合の多層防御になる)と書かれています。SGの付け忘れを受け止める最後の網、という位置づけですね。

allowだけか、denyも書けるか

セキュリティグループに書けるのは許可ルールだけです。「この1つのIPだけブロックしたい」という要件は、SGでは表現できません。ネットワークACLならdenyが書けるので、特定IPの遮断はACL側の仕事になります。

全部見るか、番号順で打ち切るか

評価方法の違いは、denyを書くときに効いてきます。

ルールの評価方法の違い ネットワークACLはルール番号の小さい順に評価し、最初に一致したルールで結果が確定する。セキュリティグループは全ルールを評価し、どれか1つでも一致すれば許可する。 ネットワークACL:番号の小さい順に評価 100 allow tcp 22 自社IP 一致で確定 110 deny tcp 22 0.0.0.0/0 * deny すべて 以降は 読まない 順番を間違えると、後ろのdenyが一生効かない セキュリティグループ:全ルールを評価 allow tcp 22 自社IP allow tcp 443 0.0.0.0/0 allow all sg-0123abcd 1つでも 一致すれば 通す

ネットワークACLは番号の小さい順に評価し、一致した瞬間に結果が決まってそれ以降は読まれません。公式の Network ACL rules にも「As soon as a rule matches traffic, it’s applied regardless of any higher-numbered rule that might contradict it」とあります。広いレンジを許可するルールより前(小さい番号)にdenyを置かないと、そのdenyは永久に評価されないままになります。

ルール番号を100、110、120と10刻みで振るのが定石なのは、あとから間に差し込めるようにするためです。公式も「We recommend that you start by creating rules in increments (for example, increments of 10 or 100)」と推奨しています。

デフォルト状態は正反対を向いている

新しくVPCを作ると、デフォルトのセキュリティグループとデフォルトのネットワークACLが1つずつ付いてきます。この2つの初期値は、方向性がちょうど逆です。

デフォルトSGは「同じSGの仲間」だけ通す

デフォルトセキュリティグループのインバウンドは、送信元が自分自身のセキュリティグループIDという1行だけです。つまり同じSGを付けたリソース同士は全ポートで通信できますが、外からは何も入れません。アウトバウンドは 0.0.0.0/0 全許可です。

ちなみにデフォルトSGは削除できません。削除しようとすると Client.CannotDelete というエラーコードが返ります。

デフォルトNACLはルール100で全開放

一方のデフォルトネットワークACLは、インバウンド・アウトバウンドともにルール番号100で 0.0.0.0/0 をALLOWしています。作った直後のACLは何も止めていないということです。VPC作成直後の通信制御が実質SGだけで成り立っているのは、ここが素通しになっているからですね。

自分で作ったACLは全部denyから始まる

ここがアンチパターンになりやすいところです。create-network-acl で新規作成したカスタムACLには、* のdenyルールしか入っていません。

# NG: 中身が空のカスタムACLをいきなりサブネットに関連付ける
aws ec2 create-network-acl --vpc-id vpc-0abc123def4567890
aws ec2 replace-network-acl-association \
  --association-id aclassoc-0123456789abcdef0 \
  --network-acl-id acl-0fedcba9876543210
既存のSSHセッションを含め、そのサブネットへの通信がすべて落ちる

デフォルトACLは全開放、カスタムACLは全遮断。同じ「ネットワークACL」でも初期状態が真逆なので、先にルールを入れてから関連付ける順番を守ります。

# OK: ルールを入れてから関連付ける
aws ec2 create-network-acl-entry --network-acl-id acl-0fedcba9876543210 \
  --rule-number 100 --protocol 6 --rule-action allow --ingress \
  --cidr-block 203.0.113.12/32 --port-range From=22,To=22
aws ec2 create-network-acl-entry --network-acl-id acl-0fedcba9876543210 \
  --rule-number 100 --protocol 6 --rule-action allow --egress \
  --cidr-block 0.0.0.0/0 --port-range From=1024,To=65535
aws ec2 replace-network-acl-association \
  --association-id aclassoc-0123456789abcdef0 \
  --network-acl-id acl-0fedcba9876543210
{
    "NewAssociationId": "aclassoc-0fedcba9876543210"
}

関連付けの制約も押さえておきます。1つのACLは複数サブネットに関連付けられますが、1つのサブネットが同時に持てるACLは1つだけです。新しく関連付けると前の関連付けは外れます。サブネットとルートテーブルの関係を整理したAWS VPCの構成要素—パブリック/プライベートサブネットの見分け方とNATの置き場所でも触れましたが、「サブネットに1つ」という制約はルートテーブルと同じ形です。

エフェメラルポートをどこまで開けるか

ネットワークACLで最初に踏むのがこれです。戻りの通信は、クライアントが動的に選んだエフェメラルポート宛に返ります。行きが22番でも、帰りの宛先は22番ではありません。

クライアントの種類でレンジが違う

どのレンジを開ければいいかは、通信相手のOSやサービスによって変わります。公式の Ephemeral ports に列挙されている値がこれです。

リクエスト元使用するポートレンジ
多くのLinuxカーネル(Amazon Linuxを含む)32768-61000
Windows Server 2003まで1025-5000
Windows Server 2008以降49152-65535
Elastic Load Balancing1024-65535
NATゲートウェイ1024-65535
AWS Lambda1024-65535

公式ドキュメントの例では 32768-65535 が使われていますが、これはLinuxクライアントを想定した値です。ALB経由のトラフィックやLambdaからの呼び出しが混ざるサブネットでこのレンジだけを開けると、1024-32767を使う通信が戻れずに詰まります。

1024-65535を開ける妥協と、denyの置き場所

公式も現実解を書いています。

In practice, to cover the different types of clients that might initiate traffic to public-facing instances in your VPC, you can open ephemeral ports 1024-65535. However, you can also add rules to the ACL to deny traffic on any malicious ports within that range. Ensure that you place the deny rules earlier in the table than the allow rules.(実際には1024-65535を開けて構いません。ただしその範囲内の危険なポートを拒否するルールを足すこともできます。その場合、denyルールは広い許可ルールより前に置いてください)

「denyを前に置く」がここでも出てきます。番号順に打ち切る評価方式である以上、denyルールを後ろに置いた設定は書いた本人以外には効いているように見えて、実際には一度も評価されません。

ACLで止められないもの、SGで書けないもの

ここからは比較表の5行に載っていない部分です。上位の解説記事でもあまり触れられていませんが、設計を左右します。

ネットワークACLを素通りする通信がある

「ACLで全部deny すれば完全に閉じられる」は成り立ちません。公式の Limitations に、ACLでフィルタできない通信が明記されています。

  • Route 53 Resolver(VPC+2のアドレス、AmazonProvidedDNS)へのDNSクエリ
  • インスタンスメタデータサービス(IMDS)宛の通信
  • Amazon DHCP
  • Amazon ECSのタスクメタデータエンドポイント
  • Windowsインスタンスのライセンス認証
  • Amazon Time Sync Service
  • デフォルトVPCルーターが使う予約IPアドレス

DNSを絞りたいなら Route 53 Resolver DNS Firewall、IMDSを塞ぎたいならインスタンスのメタデータオプション、と別の機能に投げる必要があります。ネットワークACLは万能の関門ではない、というのは覚えておいて損がないところです。

SGはセキュリティグループIDを送信元に書ける

逆にネットワークACLにできないことが、送信元にセキュリティグループIDを指定する書き方です。

# ALBのSGからのみ、アプリのSGに8080を許可する
aws ec2 authorize-security-group-ingress \
  --group-id sg-0app0000000000000 \
  --protocol tcp --port 8080 \
  --source-group sg-0alb0000000000000
{
    "Return": true,
    "SecurityGroupRules": [
        {
            "SecurityGroupRuleId": "sgr-0987654321fedcba0",
            "GroupId": "sg-0app0000000000000",
            "IsEgress": false,
            "IpProtocol": "tcp",
            "FromPort": 8080,
            "ToPort": 8080,
            "ReferencedGroupInfo": {
                "GroupId": "sg-0alb0000000000000"
            }
        }
    ]
}

出力の ReferencedGroupInfo が、CIDRではなくSGを参照していることを示しています。ネットワークACLの送信元・宛先はCIDRしか受け付けないので、Auto Scalingでインスタンスが入れ替わる構成をACLだけで縛ろうとすると、IPが変わるたびに破綻します。動的にIPが変わるものはSG、固定のCIDRで語れるものはACL、と考えると住み分けが決めやすいです。

ルール数の上限が設計を縛る

見落としがちなのがクォータです。Amazon VPC quotas の値を並べるとこうなります。

項目デフォルト引き上げ
ネットワークACLあたりのルール数20インバウンド40・アウトバウンド40まで(性能に影響の可能性あり)
VPCあたりのネットワークACL数200
SGあたりのインバウンド/アウトバウンドルール数60可(ENIあたりのSG数との積が1,000を超えられない)
ENIあたりのセキュリティグループ数5最大16まで
リージョンあたりのVPCセキュリティグループ数2,500

ACLはデフォルトで20ルール、増やしても40が上限です。IPv4とIPv6は別々に評価されるため、両対応のサブネットではルールが倍に増えます。許可したいIPを列挙していくとすぐ埋まるので、ACLを細かいアクセス制御に使う設計そのものが上限と噛み合いません。SG側は60×5で最大300ルールまで積めますが、こちらも積の上限1,000という制約が別にあります。

ELBのヘルスチェックを落とす事故

もう1つ、公式が名指しで警告しているケースです。バックエンドのサブネットに 0.0.0.0/0 あるいはサブネットのCIDRを送信元とするdenyルールを入れると、ロードバランサーがヘルスチェックを実行できなくなります。ヘルスチェックが落ちればターゲットは全台unhealthyになり、アプリは正常なのに503が返る状態になります。

遮断されたとき、どちらを疑うか

本題です。通信が通らないとき、SGとACLのどちらが落としたのかを効率よく絞り込みます。

フローログのREJECTが1本か2本かで分かる

VPCフローログの action フィールドはACCEPTかREJECTしか持たず、レコード単体では「SGが落とした」「ACLが落とした」を区別できません。公式の説明でも「the traffic was not allowed by the security groups or network ACLs」と両方が並記されています。

ただしレコードの本数を見ると絞り込めます。公式の Security group and network ACL rules にpingの例が載っています。SGはICMPのインバウンドを許可、ACLはインバウンドだけ許可してアウトバウンドを許可していない状態です。

2 123456789010 eni-1235b8ca123456789 203.0.113.12 172.31.16.139 0 0 1 4 336 1432917027 1432917142 ACCEPT OK
2 123456789010 eni-1235b8ca123456789 172.31.16.139 203.0.113.12 0 0 1 4 336 1432917094 1432917142 REJECT OK

1行目が行きのping(ACCEPT)、2行目が戻りのping(REJECT)です。行きは通っているのに戻りが落ちている状態は、ステートレスなネットワークACLでしか起こりません。SGなら戻りは自動許可されるので、この形にはならないからです。

逆に、SGのインバウンドが拒否している場合はレコードがREJECT1本だけになります。公式の記述もこうです。

If your security group denies inbound ICMP traffic, the flow log displays a single REJECT record, because the traffic was not permitted to reach your instance.(セキュリティグループがインバウンドのICMPを拒否している場合、トラフィックがインスタンスに到達できないため、フローログにはREJECTレコードが1本だけ表示されます)

切り分けの手順

1
フローログを action = REJECT で絞り、同じ5-tupleのレコードが1本か2本かを見る。2本(行きACCEPT・戻りREJECT)ならネットワークACLのアウトバウンドが原因。
2
1本だけなら、まず aws ec2 describe-network-acls でそのサブネットのインバウンドルールを番号順に確認する。番号の若いdenyに引っかかっていないかを見る。
3
ACLが素通しなら aws ec2 describe-security-groups --group-ids sg-xxxxIpPermissions を確認する。SGは全ルールを評価するので、順番ではなく「該当する許可が1つもない」ことを確認する。
4
設定に問題がなさそうなら Reachability Analyzer で経路を静的に解析する。ルートテーブル・ACL・SGのどこで止まっているかをホップ単位で返してくれる。

変更を当てる前に空撃ちする

ACLのルール変更は、間違えると同じサブネットの全リソースを巻き込みます。当てる前に --dry-run で権限とパラメータだけ検証しておくと事故が減ります。

aws ec2 create-network-acl-entry --dry-run \
  --network-acl-id acl-0a1b2c3d4e5f67890 \
  --rule-number 90 --protocol 6 --rule-action deny --ingress \
  --cidr-block 198.51.100.7/32 --port-range From=22,To=22
An error occurred (DryRunOperation) when calling the CreateNetworkAclEntry operation: Request would have succeeded, but DryRun flag is set.

DryRunOperation が返れば、権限もパラメータも問題なしという意味です。権限が足りなければ UnauthorizedOperation が返るので、この段階で気づけます。

なお --protocol-1(全プロトコル)や 6・17・1 以外の番号を指定すると、--port-range の指定は無視されて全ポートが対象になります。ポートを絞ったつもりで全開放していた、という事故はここから生まれます。

まとめ

  • セキュリティグループはステートフルで戻りが自動許可、ネットワークACLはステートレスで戻りも明示的に許可が必要。SSHを通すのに必要な行数が1行と2行に分かれるのはこのため
  • SGはENI単位・allowのみ・全ルール評価。ACLはサブネット単位・denyも書ける・番号昇順で一致した時点で確定。denyは広い許可ルールより若い番号に置く
  • デフォルトACLはルール100で全開放、自分で作ったカスタムACLは * のdenyだけ。ルールを入れてから関連付ける
  • コンソールで * と表示されるルールは、APIでは RuleNumber: 32767 として返る。自分で使えるのは1〜32766
  • エフェメラルポートはクライアント依存。Linuxは32768-61000、Windows Server 2008以降は49152-65535、ELB・NATゲートウェイ・Lambdaは1024-65535
  • ACLはRoute 53 ResolverへのDNS・IMDS・DHCP・Time Syncを止められない。ルール数もデフォルト20(最大40)と少なく、細かいアクセス制御には向かない
  • 切り分けはフローログのREJECTが1本(インバウンド拒否)か2本(ACLの戻り拒否)かで見当をつけ、Reachability Analyzerで確定させる

基本はSGで制御し、ACLはサブネット全体のガードレールとして粗く使う。公式の Control network traffic が推している構成も、結局はこの形です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次