AWS Network FirewallのアウトバウンドのTLSインスペクションを試してみた

TLSで暗号化されたトラフィックをチェックしたい時に
2024.01.31

Network FirewallでTLSのトラフィックを検査したい

こんにちは、のんピ(@non____97)です。

皆さんはNetwork FirewallでTLSのトラフィックを検査したいなと思ったことはありますか? 私はあります。

通常、Network FirewallではTLSのトラフィックにおいて暗号化された内容を確認することはできません。例えばHTTPSのパスをチェックすることはできません。

2023年の3月と少し前ですがNetwork FirewallでインバウンドのTLSトラフィックの検査ができるようになっています。これによりクライアントとエンドツーエンドでTLSを用いて暗号化している場合もNetwork Firewallで検査できるようになりました。

AWS Blogsでも紹介されていました。

上述のブログで紹介されているとおり、ステートフルルール評価前に復号化して評価後に暗号化するようです。

img2_2-1024x513

インバウンドのTLSインスペクションに遅れること半年、アウトバウンドについてもTLSインスペクションに対応していました。

個人的にはアウトバウンドトラフィックの方が気になります。

実際に試してみたので紹介します。

いきなりまとめ

  • TLSトラフィックで暗号化されている範囲の通常は検査できなかった内容をチェック可能に
    • tlsキーワードではなく、httpなどデコード後のキーワードで検査する
  • Network Firewall作成後にTLSインスペクションを有効/無効にしたい場合はファイアウォールポリシーを新規に作成して、入れ替える必要がある
    • TLSインスペクションを追加する際はトラフィックの中断が発生する
  • TLSインスペクションを利用する際は追加の料金が発生する
  • レイテンシーの増大やCA証明書の配布方法を考慮した上で活用しよう

やってみた

検証環境

検証環境は以下のとおりです。

AWS Network Firewallのルール順序を厳格にして暗黙的な拒否を実装してみた検証環境構成図

検証環境はAWS CDKでデプロイします。使用したコードは以下リポジトリに保存しています。

CA証明書の作成

TLSインスペクションを行うにあたってNetwork Firewallが証明書を動的に発行します。その証明書を発行するためのCA証明書が必要になります。

2024/1/31時点で、ファイアウォールポリシー作成後にTLSインスペクションの設定を追加することはできません。

ファイアウォールポリシー作成後にTLSインスペクションの設定を追加することはできない

検証環境をデプロイする前にCA証明書を発行してACMにインポートしてあげます。

適当にAmazon Linux 2023のEC2インスタンスをデプロイしてCA証明書を発行します。

CA証明書を発行するにあたって/etc/pki/tls/openssl.cnfをできるだけ活用します。デフォルトの/etc/pki/tls/openssl.cnfは以下のとおりです。

$ cat /etc/pki/tls/openssl.cnf
#
# OpenSSL example configuration file.
# See doc/man5/config.pod for more info.
#
# This is mostly being used for generation of certificate requests,
# but may be used for auto loading of providers

# Note that you can include other files from the main configuration
# file using the .include directive.
#.include filename

# This definition stops the following lines choking if HOME isn't
# defined.
HOME                    = .

 # Use this in order to automatically load providers.
openssl_conf = openssl_init

# Comment out the next line to ignore configuration errors
config_diagnostics = 1

# Extra OBJECT IDENTIFIER info:
# oid_file       = $ENV::HOME/.oid
oid_section = new_oids

# To use this configuration file with the "-extfile" option of the
# "openssl x509" utility, name here the section containing the
# X.509v3 extensions to use:
# extensions            =
# (Alternatively, use a configuration file that has only
# X.509v3 extensions in its main [= default] section.)

[ new_oids ]
# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
# Add a simple OID like this:
# testoid1=1.2.3.4
# Or use config file substitution like this:
# testoid2=${testoid1}.5.6

# Policies used by the TSA examples.
tsa_policy1 = 1.2.3.4.1
tsa_policy2 = 1.2.3.4.5.6
tsa_policy3 = 1.2.3.4.5.7

[openssl_init]
providers = provider_sect
# Load default TLS policy configuration
ssl_conf = ssl_module

# Uncomment the sections that start with ## below to enable the legacy provider.
# Loading the legacy provider enables support for the following algorithms:
# Hashing Algorithms / Message Digests: MD2, MD4, MDC2, WHIRLPOOL, RIPEMD160
# Symmetric Ciphers: Blowfish, CAST, DES, IDEA, RC2, RC4,RC5, SEED
# Key Derivation Function (KDF): PBKDF1
# In general it is not recommended to use the above mentioned algorithms for
# security critical operations, as they are cryptographically weak or vulnerable
# to side-channel attacks and as such have been deprecated.

[provider_sect]
default = default_sect
##legacy = legacy_sect
##
[default_sect]
activate = 1

##[legacy_sect]
##activate = 1

[ ssl_module ]

system_default = crypto_policy

[ crypto_policy ]

.include = /etc/crypto-policies/back-ends/opensslcnf.config

####################################################################
[ ca ]
default_ca      = CA_default            # The default ca section

####################################################################
[ CA_default ]

dir             = /etc/pki/CA           # Where everything is kept
certs           = $dir/certs            # Where the issued certs are kept
crl_dir         = $dir/crl              # Where the issued crl are kept
database        = $dir/index.txt        # database index file.
#unique_subject = no                    # Set to 'no' to allow creation of
                                        # several certs with same subject.
new_certs_dir   = $dir/newcerts         # default place for new certs.

certificate     = $dir/cacert.pem       # The CA certificate
serial          = $dir/serial           # The current serial number
crlnumber       = $dir/crlnumber        # the current crl number
                                        # must be commented out to leave a V1 CRL
crl             = $dir/crl.pem          # The current CRL
private_key     = $dir/private/cakey.pem# The private key

x509_extensions = usr_cert              # The extensions to add to the cert

# Comment out the following two lines for the "traditional"
# (and highly broken) format.
name_opt        = ca_default            # Subject Name options
cert_opt        = ca_default            # Certificate field options

# Extension copying option: use with caution.
# copy_extensions = copy

# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
# so this is commented out by default to leave a V1 CRL.
# crlnumber must also be commented out to leave a V1 CRL.
# crl_extensions        = crl_ext

default_days    = 365                   # how long to certify for
default_crl_days= 30                    # how long before next CRL
default_md      = sha256                # use SHA-256 by default
preserve        = no                    # keep passed DN ordering

# A few difference way of specifying how similar the request should look
# For type CA, the listed attributes must be the same, and the optional
# and supplied fields are just that :-)
policy          = policy_match

# For the CA policy
[ policy_match ]
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

# For the 'anything' policy
# At this point in time, you must list all acceptable 'object'
# types.
[ policy_anything ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

####################################################################
[ req ]
default_bits            = 2048
default_md              = sha256
default_keyfile         = privkey.pem
distinguished_name      = req_distinguished_name
attributes              = req_attributes
x509_extensions = v3_ca # The extensions to add to the self signed cert

# Passwords for private keys if not present they will be prompted for
# input_password = secret
# output_password = secret

# This sets a mask for permitted string types. There are several options.
# default: PrintableString, T61String, BMPString.
# pkix   : PrintableString, BMPString (PKIX recommendation before 2004)
# utf8only: only UTF8Strings (PKIX recommendation after 2004).
# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
# MASK:XXXX a literal mask value.
# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
string_mask = utf8only

# req_extensions = v3_req # The extensions to add to a certificate request

[ req_distinguished_name ]
countryName                     = Country Name (2 letter code)
countryName_default             = XX
countryName_min                 = 2
countryName_max                 = 2

stateOrProvinceName             = State or Province Name (full name)
#stateOrProvinceName_default    = Default Province

localityName                    = Locality Name (eg, city)
localityName_default            = Default City

0.organizationName              = Organization Name (eg, company)
0.organizationName_default      = Default Company Ltd

# we can do this but it is not needed normally :-)
#1.organizationName             = Second Organization Name (eg, company)
#1.organizationName_default     = World Wide Web Pty Ltd

organizationalUnitName          = Organizational Unit Name (eg, section)
#organizationalUnitName_default =

commonName                      = Common Name (eg, your name or your server\'s hostname)
commonName_max                  = 64

emailAddress                    = Email Address
emailAddress_max                = 64

# SET-ex3                       = SET extension number 3

[ req_attributes ]
challengePassword               = A challenge password
challengePassword_min           = 4
challengePassword_max           = 20

unstructuredName                = An optional company name

[ usr_cert ]

# These extensions are added when 'ca' signs a request.

# This goes against PKIX guidelines but some CAs do it and some software
# requires this to avoid interpreting an end user certificate as a CA.

basicConstraints=CA:FALSE

# This is typical in keyUsage for a client certificate.
# keyUsage = nonRepudiation, digitalSignature, keyEncipherment

# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer

# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
# subjectAltName=email:copy
# An alternative to produce certificates that aren't
# deprecated according to PKIX.
# subjectAltName=email:move

# Copy subject details
# issuerAltName=issuer:copy

# This is required for TSA certificates.
# extendedKeyUsage = critical,timeStamping

[ v3_req ]

# Extensions to add to a certificate request

basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

[ v3_ca ]


# Extensions for a typical CA


# PKIX recommendation.

subjectKeyIdentifier=hash

authorityKeyIdentifier=keyid:always,issuer

basicConstraints = critical,CA:true

# Key usage: this is typical for a CA certificate. However since it will
# prevent it being used as an test self-signed certificate it is best
# left out by default.
# keyUsage = cRLSign, keyCertSign

# Include email address in subject alt name: another PKIX recommendation
# subjectAltName=email:copy
# Copy issuer details
# issuerAltName=issuer:copy

# DER hex encoding of an extension: beware experts only!
# obj=DER:02:03
# Where 'obj' is a standard or added object
# You can even override a supported extension:
# basicConstraints= critical, DER:30:03:01:01:FF

[ crl_ext ]

# CRL extensions.
# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.

# issuerAltName=issuer:copy
authorityKeyIdentifier=keyid:always

[ proxy_cert_ext ]
# These extensions should be added when creating a proxy certificate

# This goes against PKIX guidelines but some CAs do it and some software
# requires this to avoid interpreting an end user certificate as a CA.

basicConstraints=CA:FALSE

# This is typical in keyUsage for a client certificate.
# keyUsage = nonRepudiation, digitalSignature, keyEncipherment

# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer

# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
# subjectAltName=email:copy
# An alternative to produce certificates that aren't
# deprecated according to PKIX.
# subjectAltName=email:move

# Copy subject details
# issuerAltName=issuer:copy

# This really needs to be in place for it to be a proxy certificate.
proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo

####################################################################
[ tsa ]

default_tsa = tsa_config1       # the default TSA section

[ tsa_config1 ]

# These are used by the TSA reply generation only.
dir             = /etc/pki/CA           # TSA root directory
serial          = $dir/tsaserial        # The current serial number (mandatory)
crypto_device   = builtin               # OpenSSL engine to use for signing
signer_cert     = $dir/tsacert.pem      # The TSA signing certificate
                                        # (optional)
certs           = $dir/cacert.pem       # Certificate chain to include in reply
                                        # (optional)
signer_key      = $dir/private/tsakey.pem # The TSA private key (optional)
signer_digest  = sha256                 # Signing digest to use. (Optional)
default_policy  = tsa_policy1           # Policy if request did not specify it
                                        # (optional)
other_policies  = tsa_policy2, tsa_policy3      # acceptable policies (optional)
digests     = sha1, sha256, sha384, sha512  # Acceptable message digests (mandatory)
accuracy        = secs:1, millisecs:500, microsecs:100  # (optional)
clock_precision_digits  = 0     # number of digits after dot. (optional)
ordering                = yes   # Is ordering defined for timestamps?
                                # (optional, default: no)
tsa_name                = yes   # Must the TSA name be included in the reply?
                                # (optional, default: no)
ess_cert_id_chain       = no    # Must the ESS cert id chain be included?
                                # (optional, default: no)
ess_cert_id_alg         = sha1  # algorithm to compute certificate
                                # identifier (optional, default: sha1)

[insta] # CMP using Insta Demo CA
# Message transfer
server = pki.certificate.fi:8700
# proxy = # set this as far as needed, e.g., http://192.168.1.1:8080
# tls_use = 0
path = pkix/

# Server authentication
recipient = "/C=FI/O=Insta Demo/CN=Insta Demo CA" # or set srvcert or issuer
ignore_keyusage = 1 # potentially needed quirk
unprotected_errors = 1 # potentially needed quirk
extracertsout = insta.extracerts.pem

# Client authentication
ref = 3078 # user identification
secret = pass:insta # can be used for both client and server side

# Generic message options
cmd = ir # default operation, can be overridden on cmd line with, e.g., kur

# Certificate enrollment
subject = "/CN=openssl-cmp-test"
newkey = insta.priv.pem
out_trusted = insta.ca.crt
certout = insta.cert.pem

[pbm] # Password-based protection for Insta CA
# Server and client authentication
ref = $insta::ref # 3078
secret = $insta::secret # pass:insta

[signature] # Signature-based protection for Insta CA
# Server authentication
trusted = insta.ca.crt # does not include keyUsage digitalSignature

# Client authentication
secret = # disable PBM
key = $insta::newkey # insta.priv.pem
cert = $insta::certout # insta.cert.pem

[ir]
cmd = ir

[cr]
cmd = cr

[kur]
# Certificate update
cmd = kur
oldcert = $insta::certout # insta.cert.pem

[rr]
# Certificate revocation
cmd = rr
oldcert = $insta::certout # insta.cert.pem

/etc/pki/tls/openssl.cnfで指定されているとおり、必要なディレクトリやインデックスファイルを作成します。

$ sudo mkdir -p /etc/pki/CA/private
$ sudo mkdir -p /etc/pki/CA/newcerts
$ sudo touch /etc/pki/CA/index.txt

keyUsageとしてcRLSignkeyCertSignを指定してあげます。

$ grep "keyUsage = cRLSign, keyCertSign" /etc/pki/tls/openssl.cnf
# keyUsage = cRLSign, keyCertSign

$ sudo sed -i "s/^# \(keyUsage = cRLSign, keyCertSign\)/\1/g" /etc/pki/tls/openssl.cnf
$ grep "keyUsage = cRLSign, keyCertSign" /etc/pki/tls/openssl.cnf
keyUsage = cRLSign, keyCertSign

こちらの指定がない場合、TLSインスペクションの設定でCA証明書を指定した際にCertificateAuthorityArn is invalid because it references a certificate authority that doesn't comply with RFC 5280 basic constraints. Replace the certificate with a valid certificate, parameter: [ACMの証明書のARN]とエラーになります。

CertificateAuthorityArn is invalid

CSRを発行します。

$ sudo openssl req \
    -new \
    -config /etc/pki/tls/openssl.cnf \
    -out /etc/pki/CA/root-ca-csr.pem \
    -keyout /etc/pki/CA/private/cakey.pem
.......+.....+.........+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*....+..+.+.........+.........+..............+.......+.....+......+....+...+.....+.+.....+.........+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*.......+...+..+.........+.+.....+...+....+..+....+...........+.+...+...........+...+.............+..+.+..+...................+.....+.+........+....+..+................+...+.....+....+.....+...+....+.........+.........+.....+...+...+....+...+..+.+...........+....+..+...+............+.......+..+.....................+....+...+..+......+....+...+.....+...+..........+...+......+.....+..........+.....+.+........+.......+.....+...+....+..+.+...+......+...+..+...+.......+......+...+...........+.+..+...............+...............+......+.......+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
...+..........+...+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*.....+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*.+.....+.+..+.+..+...+.........+............+......+...+......+.+.....+............+.+.....+.+...+......+.........+....................+......+.......+..+.......+.....+....+.....+......+.+..+...+............+.+.....+...............+...............+.......+........+.+......+.........+...+...+...+..+..........+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:JP
State or Province Name (full name) []:Tokyo
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:root-ca.corp.non-97.net
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

CA証明書を発行します。

$ sudo openssl ca \
    -config /etc/pki/tls/openssl.cnf \
    -extensions v3_ca \
    -create_serial \
    -selfsign \
    -in /etc/pki/CA/root-ca-csr.pem
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for /etc/pki/CA/private/cakey.pem:
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number:
            31:14:73:14:f1:25:f7:b9:b0:6c:73:29:35:15:e0:1d:59:99:71:76
        Validity
            Not Before: Jan 30 06:02:45 2024 GMT
            Not After : Jan 29 06:02:45 2025 GMT
        Subject:
            countryName               = JP
            stateOrProvinceName       = Tokyo
            organizationName          = Default Company Ltd
            commonName                = root-ca.corp.non-97.net
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                9E:CC:5C:06:09:80:AB:3F:69:13:94:3F:AA:5F:45:B6:45:EA:07:A4
            X509v3 Authority Key Identifier:
                9E:CC:5C:06:09:80:AB:3F:69:13:94:3F:AA:5F:45:B6:45:EA:07:A4
            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Key Usage:
                Certificate Sign, CRL Sign
Certificate is to be certified until Jan 29 06:02:45 2025 GMT (365 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            31:14:73:14:f1:25:f7:b9:b0:6c:73:29:35:15:e0:1d:59:99:71:76
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=JP, ST=Tokyo, O=Default Company Ltd, CN=root-ca.corp.non-97.net
        Validity
            Not Before: Jan 30 06:02:45 2024 GMT
            Not After : Jan 29 06:02:45 2025 GMT
        Subject: C=JP, ST=Tokyo, O=Default Company Ltd, CN=root-ca.corp.non-97.net
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:b9:53:51:a3:90:02:e8:3d:ba:ed:c9:1b:b3:13:
                    a3:5a:cb:08:51:16:7f:89:6c:4b:55:14:64:0d:a7:
                    f3:3a:35:e1:62:47:c2:ae:dc:01:b4:14:0c:c1:43:
                    50:6e:e1:21:c6:28:49:f2:b9:87:c1:9a:aa:a7:19:
                    15:9e:7f:11:59:62:af:62:c9:af:31:3c:e2:d7:44:
                    c5:2b:de:c4:bd:32:f5:cb:51:f0:88:ce:9b:ed:f6:
                    de:58:a0:fd:2d:0d:c7:d0:0e:ff:52:e7:cb:a1:7e:
                    45:f1:b9:f6:c4:0c:8b:64:8a:fb:b9:14:33:c0:07:
                    5a:0c:9a:91:20:23:b2:c7:ca:29:b1:22:8c:0c:2d:
                    9a:93:d1:0f:98:e4:3f:b1:20:99:d2:c8:ec:14:8e:
                    6c:7c:e2:4a:4f:1f:f3:91:94:6c:75:9d:e0:09:e0:
                    84:ed:48:56:38:32:b1:16:cb:0e:4a:e0:f4:5e:a3:
                    09:f9:2e:4c:8f:6c:8a:74:12:4f:c9:68:72:c4:40:
                    34:a7:ce:27:ab:cb:87:0f:ea:f7:71:2f:8b:ad:40:
                    ce:3d:1d:79:1d:56:7b:49:34:2b:2a:05:b7:9b:e8:
                    96:e2:cc:47:14:e5:4e:dc:e6:cc:4d:2f:9f:ea:cc:
                    3c:b2:24:91:63:55:fd:66:8c:51:c9:4a:06:29:16:
                    20:c3
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                9E:CC:5C:06:09:80:AB:3F:69:13:94:3F:AA:5F:45:B6:45:EA:07:A4
            X509v3 Authority Key Identifier:
                9E:CC:5C:06:09:80:AB:3F:69:13:94:3F:AA:5F:45:B6:45:EA:07:A4
            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Key Usage:
                Certificate Sign, CRL Sign
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        00:bc:b5:fd:9e:15:97:17:ac:53:1a:81:fe:be:6b:0b:b2:98:
        81:03:63:bd:6d:2b:07:93:3f:25:ac:cf:77:bd:4c:1e:a3:c2:
        8b:86:78:1c:9b:43:b7:0a:74:73:d1:c7:82:a2:87:ce:f1:07:
        fc:e4:59:d4:10:05:98:37:6f:19:3a:99:62:5a:f3:61:e7:9c:
        59:49:41:82:38:40:93:4a:b5:f7:3c:2d:e6:03:38:52:03:b5:
        1c:a6:62:c7:70:8e:f7:5e:24:7d:89:a1:3d:b5:fa:a8:87:6e:
        15:4e:98:c0:b2:02:04:63:03:b5:ce:d2:de:66:9c:a4:4d:1b:
        be:ec:ba:6b:89:84:06:21:04:f8:40:00:bf:8d:0d:77:a8:f5:
        45:6a:2e:4d:5c:d3:6c:fe:17:62:cc:9c:64:cb:89:5a:53:da:
        75:ac:c0:40:e5:4e:43:30:a7:5f:b5:44:21:84:95:a7:84:a8:
        f2:62:f6:30:ac:88:a1:14:12:57:4c:9b:44:8c:1d:d2:26:49:
        fd:ab:37:65:3f:0f:98:c7:c1:7f:c2:d1:d6:7b:e6:51:24:84:
        27:6d:a9:53:91:c4:c7:72:d7:75:a7:01:85:79:35:38:38:05:
        18:1f:9c:88:36:fb:88:9f:02:7f:31:af:14:05:cc:ad:1c:5d:
        d2:a4:b4:c8
-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIUMRRzFPEl97mwbHMpNRXgHVmZcXYwDQYJKoZIhvcNAQEL
BQAwXTELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMRwwGgYDVQQKDBNEZWZh
dWx0IENvbXBhbnkgTHRkMSAwHgYDVQQDDBdyb290LWNhLmNvcnAubm9uLTk3Lm5l
dDAeFw0yNDAxMzAwNjAyNDVaFw0yNTAxMjkwNjAyNDVaMF0xCzAJBgNVBAYTAkpQ
MQ4wDAYDVQQIDAVUb2t5bzEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEg
MB4GA1UEAwwXcm9vdC1jYS5jb3JwLm5vbi05Ny5uZXQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQC5U1GjkALoPbrtyRuzE6NaywhRFn+JbEtVFGQNp/M6
NeFiR8Ku3AG0FAzBQ1Bu4SHGKEnyuYfBmqqnGRWefxFZYq9iya8xPOLXRMUr3sS9
MvXLUfCIzpvt9t5YoP0tDcfQDv9S58uhfkXxufbEDItkivu5FDPAB1oMmpEgI7LH
yimxIowMLZqT0Q+Y5D+xIJnSyOwUjmx84kpPH/ORlGx1neAJ4ITtSFY4MrEWyw5K
4PReown5LkyPbIp0Ek/JaHLEQDSnziery4cP6vdxL4utQM49HXkdVntJNCsqBbeb
6JbizEcU5U7c5sxNL5/qzDyyJJFjVf1mjFHJSgYpFiDDAgMBAAGjYDBeMB0GA1Ud
DgQWBBSezFwGCYCrP2kTlD+qX0W2ReoHpDAfBgNVHSMEGDAWgBSezFwGCYCrP2kT
lD+qX0W2ReoHpDAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG
9w0BAQsFAAOCAQEAALy1/Z4VlxesUxqB/r5rC7KYgQNjvW0rB5M/JazPd71MHqPC
i4Z4HJtDtwp0c9HHgqKHzvEH/ORZ1BAFmDdvGTqZYlrzYeecWUlBgjhAk0q19zwt
5gM4UgO1HKZix3CO914kfYmhPbX6qIduFU6YwLICBGMDtc7S3macpE0bvuy6a4mE
BiEE+EAAv40Nd6j1RWouTVzTbP4XYsycZMuJWlPadazAQOVOQzCnX7VEIYSVp4So
8mL2MKyIoRQSV0ybRIwd0iZJ/as3ZT8PmMfBf8LR1nvmUSSEJ22pU5HEx3LXdacB
hXk1ODgFGB+ciDb7iJ8CfzGvFAXMrRxd0qS0yA==
-----END CERTIFICATE-----
Data Base Updated

$ sudo openssl x509 \
    -in /etc/pki/CA/newcerts/31147314F125F7B9B06C73293515E01D59997176.pem \
    -purpose \
    -noout
Certificate purposes:
SSL client : No
SSL client CA : Yes
SSL server : No
SSL server CA : Yes
Netscape SSL server : No
Netscape SSL server CA : Yes
S/MIME signing : No
S/MIME signing CA : Yes
S/MIME encryption : No
S/MIME encryption CA : Yes
CRL signing : Yes
CRL signing CA : Yes
Any Purpose : Yes
Any Purpose CA : Yes
OCSP helper : Yes
OCSP helper CA : Yes
Time Stamp signing : No
Time Stamp signing CA : Yes

CSR発行時に生成された秘密鍵は暗号化されています。ACMにインポートするにあたってデコードしておきます。

$ sudo openssl rsa \
    -in /etc/pki/CA/private/cakey.pem \
    -out /etc/pki/CA/private/cakey_decoded.pem
Enter pass phrase for /etc/pki/CA/private/cakey.pem:
writing RSA key

$ sudo cat /etc/pki/CA/private/cakey_decoded.pem
-----BEGIN PRIVATE KEY-----
.
.
(中略)
.
.
-----END PRIVATE KEY-----

CA証明書をACMにインポートします。

CA証明書のインポート

正常にインポートが完了すると以下のようになります。

証明書のインポート完了

検証環境のデプロイ

先述のAWS CDKを使ってVPCやNetwork Firewallなどのリソースをデプロイします。

デプロイ時に先ほどインポートした証明書のARNを指定します。今回は横着してパラメーターで指定するのではなく、直接定義しました。

./lib/network-firewall-stack.ts

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { Vpc } from "./constructs/vpc";
import { Ec2Instance } from "./constructs/ec2-instance";
import { NetworkFirewall } from "./constructs/network-firewall";

export class NetworkFirewallStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // VPC
    const vpc = new Vpc(this, "Vpc");

    // EC2 Instance
    new Ec2Instance(this, "Ec2InstanceA", {
      vpc: vpc.vpc,
    });

    // Network Firewall
    new NetworkFirewall(this, "NetworkFirewall", {
      vpc: vpc.vpc,
      certificateAuthorityArn:
        "arn:aws:acm:us-east-1:<AWSアカウントID>:certificate/89dcd6b5-e138-4b00-bf6d-af7ff0c92c69",
    });
  }
}

デプロイが完了するとTLSインスペクションの設定は以下のようになっています。

tls-inspection

宛先がTCP/443の時に動作するように設定しています。レスポンスの通信のポートを考慮する必要はありません。Network Firewallで良きように検査してくれます。

In the Scope configuration pane, choose the protocol, source, source port range, destination, and destination port range of the traffic that you want Network Firewall to decrypt. Network Firewall uses the associated certificates to decrypt the SSL/TLS traffic that matches the scope configuration. After Network Firewall decrypts the traffic, the service inspects the traffic according to your firewall policy's stateful rules.

Network Firewall also automatically configures a reverse scope, ensuring that the service inspects the traffic in both directions.

Creating a TLS inspection configuration in Network Firewall - AWS Network Firewall

ちなみにファイアウォールポリシーからTLSインスペクションの設定を削除することはできません。

TLS inspection configuration can't be added or removed when updating your firewall policy

先述のとおり、ファイアウォールポリシー作成後にTLSインスペクション設定を追加することもできません。そのため、TLSインスペクションを無効にしたい場合はファイアウォールポリシーを新規に作成して、入れ替える必要があります。

TLSインスペクションを追加する際はトラフィックの中断が発生するようです。注意しましょう。

Impact on existing flows

When you add a TLS inspection configuration to an existing firewall, Network Firewall interrupts traffic flows that match the criteria defined by the TLS inspection configuration scope configuration. New connections to the firewall begin SSL/TLS decryption and inspection. When you add new TLS inspection configuration to a firewall and there's ongoing TLS traffic that matches the scope criteria, the firewall drops the traffic.

Considerations when working with TLS inspection configurations - AWS Network Firewall

CA証明書をクライアントにインストールしていない場合の通信

動作確認をします。

現在のルールは以下のようになっています。

pass http $HOME_NET any -> $EXTERNAL_NET 80 (http.host; content:"dev.classmethod.jp"; startswith; endswith; http.uri; content:"/author/non____97/"; startswith; endswith; msg:"Pass http://dev.classmethod.jp:80/author/non____97/"; sid:1000001; rev:1;)
pass http $HOME_NET any -> $EXTERNAL_NET 443 (http.host; content:"dev.classmethod.jp"; startswith; endswith; http.uri; content:"/author/non____97/"; startswith; endswith; msg:"Pass http://dev.classmethod.jp:443/author/non____97/"; sid:1000002; rev:1;)
reject http $HOME_NET any -> $EXTERNAL_NET 443 (http.host; content:"dev.classmethod.jp"; startswith; endswith; http.uri; content:"/articles/"; startswith; msg:"Reject http://dev.classmethod.jp:443/articles/*"; sid:1000003; rev:1;)
pass tcp $HOME_NET any <> $EXTERNAL_NET 80 (flow:not_established; sid:100011; rev:1;)
pass tcp $HOME_NET any <> $EXTERNAL_NET 443 (flow:not_established; sid:100012; rev:1;)

また、ルール順序をStrictに設定して、ルールにマッチしないものはドロップするようにしています。ルール順序がStrictである場合の挙動は以下記事をご覧ください。

まずは1000002のルールを試すために、`https://dev.classmethod.jp/author/non____97/`にアクセスします。

$ curl -I https://dev.classmethod.jp/author/non____97/
curl: (60) SSL certificate problem: self-signed certificate in certificate chain
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

$ curl -v https://dev.classmethod.jp/author/non____97/
* Host dev.classmethod.jp:443 was resolved.
* IPv6: 2600:9000:201e:fa00:18:2b7:4cc0:93a1, 2600:9000:201e:3600:18:2b7:4cc0:93a1, 2600:9000:201e:4800:18:2b7:4cc0:93a1, 2600:9000:201e:5000:18:2b7:4cc0:93a1, 2600:9000:201e:ac00:18:2b7:4cc0:93a1, 2600:9000:201e:a000:18:2b7:4cc0:93a1, 2600:9000:201e:8000:18:2b7:4cc0:93a1, 2600:9000:201e:ce00:18:2b7:4cc0:93a1
* IPv4: 52.85.151.38, 52.85.151.107, 52.85.151.123, 52.85.151.5
*   Trying 52.85.151.38:443...
* Connected to dev.classmethod.jp (52.85.151.38) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/pki/tls/certs/ca-bundle.crt
*  CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS alert, unknown CA (560):
* SSL certificate problem: self-signed certificate in certificate chain
* Closing connection
curl: (60) SSL certificate problem: self-signed certificate in certificate chain
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

はい、接続できません。これはACMにインポートしたCA証明書をクライアントにインストールしていないため、Network Firewallが生成する証明書の検証ができなかったためです。

次に1000001のルールを試すために、`http://dev.classmethod.jp/author/non____97/`にアクセスします。

$ curl -I http://dev.classmethod.jp/author/non____97/
HTTP/1.1 301 Moved Permanently
Server: CloudFront
Date: Wed, 31 Jan 2024 01:57:18 GMT
Content-Type: text/html
Content-Length: 167
Connection: keep-alive
Location: https://dev.classmethod.jp/author/non____97/
X-Cache: Redirect from cloudfront
Via: 1.1 c34bd35d24f6df50307d1ac92d0f6110.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: IAD89-C3
Alt-Svc: h3=":443"; ma=86400
X-Amz-Cf-Id: xl6v9hae2n8XgREpy4Us4rXPnysKYO6UpumXk0FJTrXKiA5rDgiHvQ==
Cache-Control: max-age=3600, stale-if-error=86400

$ curl -v http://dev.classmethod.jp/author/non____97/
* Host dev.classmethod.jp:80 was resolved.
* IPv6: 2600:9000:201e:8000:18:2b7:4cc0:93a1, 2600:9000:201e:ce00:18:2b7:4cc0:93a1, 2600:9000:201e:fa00:18:2b7:4cc0:93a1, 2600:9000:201e:3600:18:2b7:4cc0:93a1, 2600:9000:201e:4800:18:2b7:4cc0:93a1, 2600:9000:201e:5000:18:2b7:4cc0:93a1, 2600:9000:201e:ac00:18:2b7:4cc0:93a1, 2600:9000:201e:a000:18:2b7:4cc0:93a1
* IPv4: 52.85.151.123, 52.85.151.5, 52.85.151.38, 52.85.151.107
*   Trying 52.85.151.123:80...
* Connected to dev.classmethod.jp (52.85.151.123) port 80
> GET /author/non____97/ HTTP/1.1
> Host: dev.classmethod.jp
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Server: CloudFront
< Date: Wed, 31 Jan 2024 01:57:26 GMT
< Content-Type: text/html
< Content-Length: 167
< Connection: keep-alive
< Location: https://dev.classmethod.jp/author/non____97/
< X-Cache: Redirect from cloudfront
< Via: 1.1 3dcb635971b5d310e8941cdb963aff70.cloudfront.net (CloudFront)
< X-Amz-Cf-Pop: IAD89-C3
< Alt-Svc: h3=":443"; ma=86400
< X-Amz-Cf-Id: BnNiz_KhvzMIo4ojkySSlp1EHgHbH7IxzB5vTAX3apjly_ro4BicgA==
< Cache-Control: max-age=3600, stale-if-error=86400
<
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>CloudFront</center>
</body>
</html>
* Connection #0 to host dev.classmethod.jp left intact

こちらは正常にアクセスできました。

CA証明書をクライアントにインストール

HTTPSの通信をする際に証明書の検証エラーにならないように、クライアントにCA証明書をインストールします。

# CA証明書が信頼されているルート証明書に含まれていないことを確認
$ openssl crl2pkcs7 \
    -nocrl \
    -certfile /etc/pki/tls/certs/ca-bundle.crt \
    | openssl pkcs7 -print_certs \
    | grep subject \
    | grep root-ca.corp.non-97.net

# CA証明書のファイルを作成
$ sudo vi /etc/pki/ca-trust/source/anchors/root-ca.corp.non-97.net

$ cat /etc/pki/ca-trust/source/anchors/root-ca.corp.non-97.net
# root-ca.corp.non-97.net
-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIUMRRzFPEl97mwbHMpNRXgHVmZcXYwDQYJKoZIhvcNAQEL
BQAwXTELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMRwwGgYDVQQKDBNEZWZh
dWx0IENvbXBhbnkgTHRkMSAwHgYDVQQDDBdyb290LWNhLmNvcnAubm9uLTk3Lm5l
dDAeFw0yNDAxMzAwNjAyNDVaFw0yNTAxMjkwNjAyNDVaMF0xCzAJBgNVBAYTAkpQ
MQ4wDAYDVQQIDAVUb2t5bzEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEg
MB4GA1UEAwwXcm9vdC1jYS5jb3JwLm5vbi05Ny5uZXQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQC5U1GjkALoPbrtyRuzE6NaywhRFn+JbEtVFGQNp/M6
NeFiR8Ku3AG0FAzBQ1Bu4SHGKEnyuYfBmqqnGRWefxFZYq9iya8xPOLXRMUr3sS9
MvXLUfCIzpvt9t5YoP0tDcfQDv9S58uhfkXxufbEDItkivu5FDPAB1oMmpEgI7LH
yimxIowMLZqT0Q+Y5D+xIJnSyOwUjmx84kpPH/ORlGx1neAJ4ITtSFY4MrEWyw5K
4PReown5LkyPbIp0Ek/JaHLEQDSnziery4cP6vdxL4utQM49HXkdVntJNCsqBbeb
6JbizEcU5U7c5sxNL5/qzDyyJJFjVf1mjFHJSgYpFiDDAgMBAAGjYDBeMB0GA1Ud
DgQWBBSezFwGCYCrP2kTlD+qX0W2ReoHpDAfBgNVHSMEGDAWgBSezFwGCYCrP2kT
lD+qX0W2ReoHpDAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG
9w0BAQsFAAOCAQEAALy1/Z4VlxesUxqB/r5rC7KYgQNjvW0rB5M/JazPd71MHqPC
i4Z4HJtDtwp0c9HHgqKHzvEH/ORZ1BAFmDdvGTqZYlrzYeecWUlBgjhAk0q19zwt
5gM4UgO1HKZix3CO914kfYmhPbX6qIduFU6YwLICBGMDtc7S3macpE0bvuy6a4mE
BiEE+EAAv40Nd6j1RWouTVzTbP4XYsycZMuJWlPadazAQOVOQzCnX7VEIYSVp4So
8mL2MKyIoRQSV0ybRIwd0iZJ/as3ZT8PmMfBf8LR1nvmUSSEJ22pU5HEx3LXdacB
hXk1ODgFGB+ciDb7iJ8CfzGvFAXMrRxd0qS0yA==
-----END CERTIFICATE-----

# CA証明書をインストール
$ sudo update-ca-trust

# CA証明書がインストールされたことを確認
$ openssl crl2pkcs7 \
    -nocrl \
    -certfile /etc/pki/tls/certs/ca-bundle.crt \
    | openssl pkcs7 -print_certs \
    | grep subject \
    | grep root-ca.corp.non-97.net
subject=C = JP, ST = Tokyo, O = Default Company Ltd, CN = root-ca.corp.non-97.net

CA証明書インストール後の通信

CA証明書インストール後、再度確認してみます。

まずは`https://dev.classmethod.jp/author/non____97/`にアクセスします。

$ curl -I https://dev.classmethod.jp/author/non____97/
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 182359
Connection: keep-alive
Accept-Ranges: none
Cache-Control: max-age=3600, stale-if-error=86400
Date: Wed, 31 Jan 2024 02:04:07 GMT
ETag: "2c857-UkDRNg7xSiawVQex78V7Lob96vU"
Server: envoy
x-envoy-upstream-service-time: 558
Vary: Accept-Encoding
X-Cache: Miss from cloudfront
Via: 1.1 1fa3f854976309f3d11907ad7125291a.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: IAD89-C3
Alt-Svc: h3=":443"; ma=86400
X-Amz-Cf-Id: R9HTc8uTsdPEQI7jDhnDySH3GFT37Hm8Pek_GKixHYh0Y0wxUjZHAA==

$ curl -v https://dev.classmethod.jp/author/non____97/
* Host dev.classmethod.jp:443 was resolved.
* IPv6: 2600:9000:201e:ee00:18:2b7:4cc0:93a1, 2600:9000:201e:6400:18:2b7:4cc0:93a1, 2600:9000:201e:c200:18:2b7:4cc0:93a1, 2600:9000:201e:8a00:18:2b7:4cc0:93a1, 2600:9000:201e:3e00:18:2b7:4cc0:93a1, 2600:9000:201e:2c00:18:2b7:4cc0:93a1, 2600:9000:201e:f600:18:2b7:4cc0:93a1, 2600:9000:201e:200:18:2b7:4cc0:93a1
* IPv4: 52.85.151.107, 52.85.151.123, 52.85.151.5, 52.85.151.38
*   Trying 52.85.151.107:443...
* Connected to dev.classmethod.jp (52.85.151.107) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/pki/tls/certs/ca-bundle.crt
*  CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / prime256v1 / RSASSA-PSS
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
*  subject: CN=*.classmethod.jp
*  start date: Jan 30 01:08:40 2024 GMT
*  expire date: Jan 29 01:08:40 2025 GMT
*  subjectAltName: host "dev.classmethod.jp" matched cert's "*.classmethod.jp"
*  issuer: C=JP; ST=Tokyo; O=Default Company Ltd; CN=root-ca.corp.non-97.net
*  SSL certificate verify ok.
*   Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* using HTTP/1.x
> GET /author/non____97/ HTTP/1.1
> Host: dev.classmethod.jp
> User-Agent: curl/8.5.0
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Cache-Control: max-age=3600, stale-if-error=86400
< Date: Wed, 31 Jan 2024 02:05:37 GMT
< ETag: W/"2c857-UkDRNg7xSiawVQex78V7Lob96vU"
< Server: envoy
< x-envoy-upstream-service-time: 1357
< Vary: Accept-Encoding
< X-Cache: Miss from cloudfront
< Via: 1.1 2f66aa06710fece8ed203ab0ea81eb56.cloudfront.net (CloudFront)
< X-Amz-Cf-Pop: IAD89-C3
< Alt-Svc: h3=":443"; ma=86400
< X-Amz-Cf-Id: tYGIhDwiyw4LnfFsiIiQuxKu8BiT7tnf5b-nLueO0v5XLMDm5KepMw==
<
<!doctype html>
.
.
(中略)
.
.
* Connection #0 to host dev.classmethod.jp left intact
</html>

先ほどはエラーになりましたが、正常に通信できています。通信できているということはNetwork Firewallのルールにマッチしていると判断できます。

証明書の署名者(issuer)のCNを確認すると、root-ca.corp.non-97.netとインストールしたCA証明書であることが分かります。また、証明書が発行日と期日を確認すると、有効期限が1年間の証明書が生成されていることが分かります。

1000003のルールでRejectされることも確認します。

$ curl -I https://dev.classmethod.jp/articles/amazon-network-firewall-implicit-deny/#toc-15
curl: (52) Empty reply from server

$ curl -v https://dev.classmethod.jp/articles/amazon-network-firewall-implicit-deny/#toc-15
* Host dev.classmethod.jp:443 was resolved.
* IPv6: 2600:9000:201e:1800:18:2b7:4cc0:93a1, 2600:9000:201e:d600:18:2b7:4cc0:93a1, 2600:9000:201e:a00:18:2b7:4cc0:93a1, 2600:9000:201e:8a00:18:2b7:4cc0:93a1, 2600:9000:201e:aa00:18:2b7:4cc0:93a1, 2600:9000:201e:c00:18:2b7:4cc0:93a1, 2600:9000:201e:da00:18:2b7:4cc0:93a1, 2600:9000:201e:5c00:18:2b7:4cc0:93a1
* IPv4: 52.85.151.123, 52.85.151.5, 52.85.151.38, 52.85.151.107
*   Trying 52.85.151.123:443...
* Connected to dev.classmethod.jp (52.85.151.123) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/pki/tls/certs/ca-bundle.crt
*  CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / prime256v1 / RSASSA-PSS
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
*  subject: CN=*.classmethod.jp
*  start date: Jan 30 02:07:22 2024 GMT
*  expire date: Jan 29 02:07:22 2025 GMT
*  subjectAltName: host "dev.classmethod.jp" matched cert's "*.classmethod.jp"
*  issuer: C=JP; ST=Tokyo; O=Default Company Ltd; CN=root-ca.corp.non-97.net
*  SSL certificate verify ok.
*   Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* using HTTP/1.x
> GET /articles/amazon-network-firewall-implicit-deny/ HTTP/1.1
> Host: dev.classmethod.jp
> User-Agent: curl/8.5.0
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* TLSv1.3 (IN), TLS alert, close notify (256):
* Empty reply from server
* Closing connection
* TLSv1.3 (OUT), TLS alert, close notify (256):
curl: (52) Empty reply from server

Rejectされました。

HTTPSだと確認できなかった値に対してチェックできていますね。

CloudWatch Logsに出力されたアラートログは以下のとおりです。

{
    "firewall_name": "network-firewall",
    "availability_zone": "us-east-1a",
    "event_timestamp": "1706667419",
    "event": {
        "tx_id": 0,
        "app_proto": "http",
        "src_ip": "10.1.1.56",
        "src_port": 39572,
        "event_type": "alert",
        "alert": {
            "severity": 3,
            "signature_id": 1000003,
            "rev": 1,
            "signature": "Reject http://dev.classmethod.jp:443/articles/*",
            "action": "blocked",
            "category": ""
        },
        "flow_id": 1202469201483962,
        "dest_ip": "52.85.151.107",
        "proto": "TCP",
        "http": {
            "hostname": "dev.classmethod.jp",
            "url": "/articles/amazon-network-firewall-implicit-deny/",
            "http_user_agent": "curl/8.5.0",
            "http_method": "HEAD",
            "protocol": "HTTP/1.1",
            "length": 0
        },
        "dest_port": 443,
        "timestamp": "2024-01-31T02:16:59.497867+0000"
    }
}

自分で設定したルールでRejectされたことが分かります。

TLSで暗号化されたトラフィックをチェックしたい時に

Network FirewallのアウトバウンドトラフィックのTLSインスペクションを試してみました。

Network Firewallで復号化/暗号化するためレイテンシーは増加しますが、TLSで暗号化されたトラフィックをチェックしたい時に使うと良いでしょう。その他、注意点は以下AWS公式ドキュメントにまとまっています。

使用する場合はクライアントへのCA証明書を配布することを忘れずに行いましょう。EC2インスタンスの場合はCA証明書をS3バケット上に配置しておき、SSM State ManagerやSSM Run CommandでCA証明書をインストールすることで楽できそうです。

ADに参加している環境であればグループポリシーで配布することも可能です。

また、TLSインスペクションを使用する際はNetwork Firewall Advanced Inspection EndpointNetwork Firewall Advanced Inspection Traffic Processingとして通常のNetwork Firewall EndpointNetwork Firewall Traffic Processingとは別に料金が発生します。

TLS 検査機能を使用する場合、Advanced Inspection についての追加の時間料金をお支払いいただきます。一部のリージョンでは、TLS 検査機能を使用する際に、Advanced Inspection のためにファイアウォールエンドポイントによって処理されたトラフィック量 (ギガバイト単位) について追加料金をお支払いいただく場合もあります。また、AWS Network Firewall 経由で転送されるすべてのデータについて、標準の AWS データ転送料金が発生します。

AWS Network Firewall の料金 – ネットワークセキュリティサービス – Amazon Web Services

利用料金には注意しましょう。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!