9
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ubuntuで作るiOS/AndroidでIKEv2接続できるVPN環境のつくりかた

Last updated at Posted at 2019-08-13

この記事は

常時接続(Always-on)VPNの環境を試してみたかった事と、自宅アクセスをSoftetherによるL2TP/IPSecから置き換えるためのメモ

として書いたが、途中で飽きて放置していたところ、 ふと再構築が必要になったので調べなおしつつ書き直したメモ

目標

  • iOSとWindowsはOSの標準機能で、Androidはそれに加えて公式クライアントで接続できるようにする
  • iOSは構成プロファイルを使わなくても接続できるようにする
    • 認証方式はMSCHAPv2によるID/PW認証と、EAP-TLSによるクライアント証明書認証
  • サーバ証明書とクライアント証明書は商用のものでも利用できるようにする
    • 前者はとりあえずLet's Encryptで代用、後者は記事中では具体名は伏せ。

環境

  • さくらのVPS(プラン1G)
    • おうちサーバも使いながら書いているので、時折インタフェース名が混じる場合があり
  • Ubuntu Server 22.04 LTS (Jammy Jellyfish)
    • Linux strongSwan U5.9.5/K5.15.0-33-generic

クライアント

iOS15系(メイン)

以下おまけ
macOS12系
Android+strongSwan公式クライアント
Android12+OS標準実装
Windows10

ネットワーク

  • ens3 : WAN(グローバルアドレス直振り)
  • ens4 : LAN(192.168.200.0/24、あまり深い意味はない)
  • ens5 : 未使用

下準備

割とどうでもいいインターフェース設定

netplanからsystemd-networkdへ切り替え
LLMNRを明示的に切りたかったのと、そのうちmDNSを使いたいから(/etc/systemd/resolved.confを触っていないので今は無意味)
DNSはとりあえず「1.1.1.1 for Families」

10-ens3.network
[Match]
Name=ens3

[Network]
LinkLocalAddressing=ipv6
Address=[WANアドレス]/23
Address=[WANアドレス(IPv6)]/64
Gateway=[ゲートウェイアドレス]
Gateway=[ゲートウェイアドレス(IPv6)]

DNS=2606:4700:4700::1112
DNS=2606:4700:4700::1002
DNS=1.1.1.2
DNS=1.0.0.2

LLMNR=no
20-ens4.network
[Match]
Name=ens4

[Network]
LinkLocalAddressing=ipv6
Address=192.168.200.1/24

MulticastDNS=yes
/etc/netplan/00-installer-config.yaml
network:
  version: 2

ここまでやってから netplan apply

設定確認

名前解決ができて、ens3のLLMNRが無効(-)になっていて、ens4のmDNSが有効(+)になっていることの確認
ただし、前述のとおりGlobalを触っていないので今は使えない。今回は使わない。使える時は「Current Scopes: DNS mDNS/IPv4 mDNS/IPv6」のように出る。

DNS周りの確認
# host dns.google
dns.google has address 8.8.8.8
dns.google has address 8.8.4.4
dns.google has IPv6 address 2001:4860:4860::8888
dns.google has IPv6 address 2001:4860:4860::8844

# resolvectl
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub

Link 2 (ens3)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 2606:4700:4700::1112
       DNS Servers: 2606:4700:4700::1112 2606:4700:4700::1002 1.1.1.2 1.0.0.2

Link 3 (ens4)
Current Scopes: none
     Protocols: -DefaultRoute +LLMNR +mDNS -DNSOverTLS DNSSEC=no/unsupported

Link 4 (ens5)
Current Scopes: none
     Protocols: -DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
割とどうでもいいおまけ
# cat /etc/systemd/resolvd.conf
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it under the
#  terms of the GNU Lesser General Public License as published by the Free
#  Software Foundation; either version 2.1 of the License, or (at your option)
#  any later version.
#
# Entries in this file show the compile time defaults. Local configuration
# should be created by either modifying this file, or by creating "drop-ins" in
# the resolved.conf.d/ subdirectory. The latter is generally recommended.
# Defaults can be restored by simply deleting this file and all drop-ins.
#
# Use 'systemd-analyze cat-config systemd/resolved.conf' to display the full config.
#
# See resolved.conf(5) for details.

[Resolve]
# Some examples of DNS servers which may be used for DNS= and FallbackDNS=:
# Cloudflare: 1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com 2606:4700:4700::1111#cloudflare-dns.com 2606:4700:4700::1001#cloudflare-dns.com
# Google:     8.8.8.8#dns.google 8.8.4.4#dns.google 2001:4860:4860::8888#dns.google 2001:4860:4860::8844#dns.google
# Quad9:      9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net 2620:fe::fe#dns.quad9.net 2620:fe::9#dns.quad9.net
#DNS=
#FallbackDNS=
#Domains=
#DNSSEC=no
#DNSOverTLS=no
MulticastDNS=yes
LLMNR=yes
#Cache=no-negative
#CacheFromLocalhost=no
#DNSStubListener=yes
#DNSStubListenerExtra=
#ReadEtcHosts=yes
#ResolveUnicastSingleLabel=no

# resolvectl
Global
       Protocols: +LLMNR +mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub

Link 2 (ens18)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=yes/supported
Current DNS Server: 2606:4700:4700::1112
       DNS Servers: 2606:4700:4700::1112 2606:4700:4700::1002 1.1.1.2 1.0.0.2

Link 3 (ens19)
Current Scopes: LLMNR/IPv4 LLMNR/IPv6 mDNS/IPv4 mDNS/IPv6
     Protocols: -DefaultRoute +LLMNR +mDNS -DNSOverTLS DNSSEC=no/unsupported

ネットワーク設定

ufwでIP転送やフィルタリングの設定を行う

ポートを指定してもいいけどアプリルールを作る

/etc/ufw/applications.d/IKE-NAT-T
[IKE-NAT-T]
title=IKE NAT-T
description=IKE NAT-T (500,4500/udp)
ports=500,4500/udp

net/ipv4/ip_forward=1 のコメントアウトを外してIP転送を有効化する。直接sysctlをいじってもいいかもしれない
IPv6もコメントアウトを外して、poolsでULAとかを割り当てて、before6.ruleを同じように設定すれば使えた。

/etc/ufw/sysctl.conf
(前略)

# Uncomment this to allow this host to route packets between interfaces
net/ipv4/ip_forward=1
#net/ipv6/conf/default/forwarding=1
#net/ipv6/conf/all/forwarding=1

(以下省略)

ufwコマンドでできないマスカレード設定と(要るか知らんけど)IPSecの通信を許可する設定を、ファイルの末尾に入れる
後述の「updown = /usr/lib/ipsec/_updown iptables」を使う場合はマスカレードの設定のみでよい
どっちがいいのかは判らないがスクリプトに寄せたほうがいいんだろうなあ・・・

必要なら内向き(ens4)に対してもマスカレードを入れる
ウチの環境では内向きにマスカレードを掛けておらず、LANには別のGWになるルータがあるので、そこに戻りのルーティングを入れている。
strongSwanのホストがGWを兼ねているのであればマスカレードもルーティング設定もたぶん不要。たぶん。

/etc/ufw/before.rules
(前略)
# don't delete the 'COMMIT' line or these rules won't be processed
COMMIT

# ここから追記
*nat
-F
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -m policy --dir out --pol ipsec -j ACCEPT
-A POSTROUTING -o ens3 -j MASQUERADE  #WAN側へ出ていく際のマスカレード設定
#-A POSTROUTING -o ens4 -j MASQUERADE #LAN側へ出ていく際のマスカレード設定(必要なら入れる)

COMMIT

500,4500/udpの通信を許可する
ついでにルーティングも有効化する

# ufw allow IKE-NAT-T
Rule added
Rule added (v6)

# ufw default allow routed
Default routed policy changed to 'allow'
(be sure to update your rules accordingly)

確認(する前に、必要ならsshの許可設定入れたり、ufw enableをやっておく)

# ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), allow (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
500,4500/udp (IKE-NAT-T)   ALLOW IN    Anywhere
500,4500/udp (IKE-NAT-T (v6)) ALLOW IN    Anywhere (v6)

パッケージインストール

慣例的に

apt update && apt -y full-upgrade

それから

apt install strongswan strongswan-swanctl strongswan-pki

strongswanは本体、strongswan-swanctlはswanctl形式での設定を行えるように、strongswan-pkiはいずれオレオレ証明書を使うためにとりあえず入れておく
でもstrongswan-pkiは正直無くてもいい。opensslとかXCAとか使うので。

ひとまず

apt install strongswan libcharon-extra-plugins libcharon-extauth-plugins libstrongswan-extra-plugins

libcharonは証明書認証(EAP-TLS)に必要、たぶん
libstrongswan-extra-pluginsは「curve25519 (support for Diffie-Hellman group 31 using Curve25519 and support for the Ed25519 digital signature algorithm for IKEv2)」とあるので、入れておいた方が良さげだが、なくても使えているっぽい。なにゆえ。

おまけに

apt install certbot python3-certbot-dns-cloudflare

今回Let's Encryptの証明書を使ったので入れる
オレオレや他のサーバ証明書を使う場合はいらない

swanctlの自動ロード設定

起動直後とかサービス再起動を際に、swanctlの設定を再ロードする設定
ググるいろいろやり方が見つかるが、とりあえずこんな感じに

/etc/strongswan.conf
# strongswan.conf - strongSwan configuration file
#
# Refer to the strongswan.conf(5) manpage for details
#
# Configuration changes should be made in the included files

charon {
        load_modular = yes
        plugins {
                include strongswan.d/charon/*.conf
        }

        start-scripts {
                load-all = /usr/sbin/swanctl --load-all --noprompt
        }

}

include strongswan.d/*.conf

「/etc/strongswan.d/」にある「charon.conf」にstart-scriptのセクションがあるのでそこに突っ込んでもいいし、「swanctl.conf」とか「starter.conf」に突っ込んでもいいのかもしれない。

certbot周り

割とどうでもいい鍵設定

ECDSAを使うと、iOS手動設定で接続した際に認証エラーが出るので、証明書の鍵はRSAの4096bitに。プロファイルで明示しないとECDSAは使えないのか。そいやほかのクライアントだと大丈夫か試してないわ。Apple信者だし。

/etc/letsencrypt/cli.ini
# Because we are using logrotate for greater flexibility, disable the
# internal certbot logrotation.
max-log-backups = 0
# Adjust interactive output regarding automated renewal
preconfigured-renewal = True

key-type = rsa
rsa-key-size = 4096

#いつか使いたい
#key-type = ecdsa
#elliptic-curve = secp384r1

#preferred_chain = ISRG Root X1

サーバ証明書発行

Cloudflareを使っているので、APIトークンを使ってDNS認証。ここら辺は環境次第。

/etc/letsencrypt/cf_credentials.ini
dns_cloudflare_api_token = [CloudflareのAPIトークン、DNS編集権限があればいい]
# chmod 600 /etc/letsencrypt/cf_credentials.ini
証明書の取得
# certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cf_credentials.ini -d [ホスト名]

メアドとagree-tosを入れ忘れたのでポチポチ

証明書更新時のリロード設定

適当に

/etc/letsencrypt/renewal-hooks/deploy
#!/usr/bin/env bash

/usr/sbin/swanctl --load-creds

証明書へのアクセス権付与

過去に詰まっていた、証明書への直アクセスができなかった件、apparmorが邪魔していたくさい。
なので、charonとswanctlにディレクトリへのアクセス権を付与する

/etc/apparmor.d/local/usr.lib.ipsec.charon
#include <abstractions/ssl_keys>
/etc/apparmor.d/local/usr.sbin.swanctl
#include <abstractions/ssl_keys>

反映

# systemctl restart apparmor.service

swanctlへの証明書配置

シンボリックリンクで

ln -s /etc/letsencrypt/live/[ホスト名]/privkey.pem /etc/swanctl/private/certbot.key
ln -s /etc/letsencrypt/live/[ホスト名]/fullchain.pem /etc/swanctl/x509/certbot.cer

AndroidからMSCHAPv2繋ぐ場合は以下も必要だったが、果たしてこれが正しい方法なのかが不明。

ln -s /etc/letsencrypt/live/[ホスト名]/chain.pem /etc/swanctl/x509ca/certbot_chain.cer

EAP-TLSだとこれでもダメでLets's Encryptの中間証明書をAndroid側にインポートしないとIssueエラーを吐くことも。わからん。

証明書の読み込みと確認

証明書と秘密鍵が読めているか確認。apparmor設定が漏れていたり、反映していなかったりすると、keyのところでエラーが出る

# swanctl -q
no files found matching '/etc/swanctl/conf.d/*.conf'
loaded certificate from '/etc/swanctl/x509/certbot.cer'
loaded RSA key from '/etc/swanctl/private/certbot.key'
no authorities found, 0 unloaded
no pools found, 0 unloaded
no connections found, 0 unloaded

# swanctl -x

ずらっと出る

設定(サーバ認証:証明書、ユーザ認証:ID/PASS)

設定は過去設定の移植とネット上からのかき集めたものと、swanctl.confとリファレンスのごった煮。
たぶん不要なモノとか調整した方がいいのも混じっている。
コメントアウトされている行はデフォルト値。
secretsがあるので丸ごと600にするか、別ファイルにしてそっちを600にするか

長いので折り畳み
/etc/swanctl/conf.d/sample.conf
# Section defining IKE connection configurations.
connections {

    # Section for an IKE connection named <conn>.
    TestIKEv2-RemoteAccess {

        # IKE major version to use for connection.
        # version = 0
        version = 2

        # Local address(es) to use for IKE communication, comma separated.
        # local_addrs = %any

        # Remote address(es) to use for IKE communication, comma separated.
        # remote_addrs = %any

        # Local UDP port for IKE communication.
        # local_port = 500

        # Remote UDP port for IKE communication.
        # remote_port = 500

        # Comma separated proposals to accept for IKE.
        # proposals = default

        # Virtual IPs to request in configuration payload / Mode Config.
        # vips =

        # Use Aggressive Mode in IKEv1.
        # aggressive = no

        # Set the Mode Config mode to use.
        # pull = yes

        # Differentiated Services Field Codepoint to set on outgoing IKE packets
        # (six binary digits).
        # dscp = 000000

        # Enforce UDP encapsulation by faking NAT-D payloads.
        # encap = no
        encap = yes

        # Enables MOBIKE on IKEv2 connections.
        # mobike = yes

        # Interval of liveness checks (DPD).
        # dpd_delay = 0s
        dpd_delay = 60s

        # Timeout for DPD checks (IKEV1 only).
        # dpd_timeout = 0s

        # Use IKE UDP datagram fragmentation (yes, accept, no or force).
        # fragmentation = yes

        # Use childless IKE_SA initiation (allow, force or never).
        # childless = allow

        # Send certificate requests payloads (yes or no).
        # send_certreq = yes

        # Send certificate payloads (always, never or ifasked).
        # send_cert = ifasked
        send_cert = always

        # String identifying the Postquantum Preshared Key (PPK) to be used.
        # ppk_id =

        # Whether a Postquantum Preshared Key (PPK) is required for this
        # connection.
        # ppk_required = no

        # Number of retransmission sequences to perform during initial connect.
        # keyingtries = 1

        # Connection uniqueness policy (never, no, keep or replace).
        # unique = no
        unique = never

        # Time to schedule IKE reauthentication.
        # reauth_time = 0s

        # Time to schedule IKE rekeying.
        # rekey_time = 4h
        rekey_time = 1h

        # Hard IKE_SA lifetime if rekey/reauth does not complete, as time.
        # over_time = 10% of rekey_time/reauth_time

        # Range of random time to subtract from rekey/reauth times.
        # rand_time = over_time

        # Comma separated list of named IP pools.
        # pools =
        pools = ipv4_pool

        # Default inbound XFRM interface ID for children.
        # if_id_in = 0

        # Default outbound XFRM interface ID for children.
        # if_id_out = 0

        # Whether this connection is a mediation connection.
        # mediation = no

        # The name of the connection to mediate this connection through.
        # mediated_by =

        # Identity under which the peer is registered at the mediation server.
        # mediation_peer =

        # Section for a local authentication round.
        local-pubkey-certbot {

            # Optional numeric identifier by which authentication rounds are
            # sorted.  If not specified rounds are ordered by their position in
            # the config file/VICI message.
            # round = 0

            # Comma separated list of certificate candidates to use for
            # authentication.
            # certs =
            certs = certbot.cer

            # Section for a certificate candidate to use for authentication.
            # cert<suffix> =

            # Comma separated list of raw public key candidates to use for
            # authentication.
            # pubkeys =

            # Authentication to perform locally (pubkey, psk, xauth[-backend] or
            # eap[-method]).
            # auth = pubkey

            # IKE identity to use for authentication round.
            # id =
            id = @[↑で指定した証明書のCNかSANに入っているホスト名]

            # Client EAP-Identity to use in EAP-Identity exchange and the EAP
            # method.
            # eap_id = id

            # Server side EAP-Identity to expect in the EAP method.
            # aaa_id = remote-id

            # Client XAuth username used in the XAuth exchange.
            # xauth_id = id

            # cert<suffix> {

                # Absolute path to the certificate to load.
                # file =

                # Hex-encoded CKA_ID of the certificate on a token.
                # handle =

                # Optional slot number of the token that stores the certificate.
                # slot =

                # Optional PKCS#11 module name.
                # module =

            # }

        }

        # Section for a remote authentication round.
        remote-eap-mschapv2 {

            # Optional numeric identifier by which authentication rounds are
            # sorted.  If not specified rounds are ordered by their position in
            # the config file/VICI message.
            # round = 0

            # IKE identity to expect for authentication round.
            # id = %any

            # Identity to use as peer identity during EAP authentication.
            # eap_id = id

            # Authorization group memberships to require.
            # groups =

            # Certificate policy OIDs the peer's certificate must have.
            # cert_policy =

            # Comma separated list of certificate to accept for authentication.
            # certs =

            # Section for a certificate to accept for authentication.
            # cert<suffix> =

            # Comma separated list of CA certificates to accept for
            # authentication.
            # cacerts =

            # Section for a CA certificate to accept for authentication.
            # cacert<suffix> =

            # Identity in CA certificate to accept for authentication.
            # ca_id =

            # Comma separated list of raw public keys to accept for
            # authentication.
            # pubkeys =

            # Certificate revocation policy, (strict, ifuri or relaxed).
            # revocation = relaxed

            # Authentication to expect from remote (pubkey, psk, xauth[-backend]
            # or eap[-method]).
            auth = eap-mschapv2

            # cert<suffix> {

                # Absolute path to the certificate to load.
                # file =

                # Hex-encoded CKA_ID of the certificate on a token.
                # handle =

                # Optional slot number of the token that stores the certificate.
                # slot =

                # Optional PKCS#11 module name.
                # module =

            # }

            # cacert<suffix> {

                # Absolute path to the certificate to load.
                # file =

                # Hex-encoded CKA_ID of the CA certificate on a token.
                # handle =

                # Optional slot number of the token that stores the CA
                # certificate.
                # slot =

                # Optional PKCS#11 module name.
                # module =

            # }

        }

        children {

            # CHILD_SA configuration sub-section.
            testikev2_child {

                # AH proposals to offer for the CHILD_SA.
                # ah_proposals =

                # ESP proposals to offer for the CHILD_SA.
                # esp_proposals = default
                #esp_proposals = aes256-sha256

                # Use incorrect 96-bit truncation for HMAC-SHA-256.
                # sha256_96 = no

                # Local traffic selectors to include in CHILD_SA.
                # local_ts = dynamic
                local_ts = 0.0.0.0/0, ::/0

                # Remote selectors to include in CHILD_SA.
                # remote_ts = dynamic

                # Time to schedule CHILD_SA rekeying.
                # rekey_time = 1h
                rekey_time = 30m

                # Maximum lifetime before CHILD_SA gets closed, as time.
                # life_time = rekey_time + 10%

                # Range of random time to subtract from rekey_time.
                # rand_time = life_time - rekey_time

                # Number of bytes processed before initiating CHILD_SA rekeying.
                # rekey_bytes = 0

                # Maximum bytes processed before CHILD_SA gets closed.
                # life_bytes = rekey_bytes + 10%

                # Range of random bytes to subtract from rekey_bytes.
                # rand_bytes = life_bytes - rekey_bytes

                # Number of packets processed before initiating CHILD_SA
                # rekeying.
                # rekey_packets = 0

                # Maximum number of packets processed before CHILD_SA gets
                # closed.
                # life_packets = rekey_packets + 10%

                # Range of random packets to subtract from packets_bytes.
                # rand_packets = life_packets - rekey_packets

                # Updown script to invoke on CHILD_SA up and down events.
                # updown =

                # Hostaccess variable to pass to updown script.
                # hostaccess = no

                # IPsec Mode to establish (tunnel, transport, transport_proxy,
                # beet, pass or drop).
                # mode = tunnel

                # Whether to install IPsec policies or not.
                # policies = yes

                # Whether to install outbound FWD IPsec policies or not.
                # policies_fwd_out = no

                # Action to perform on DPD timeout (clear, trap or restart).
                # dpd_action = clear
                dpd_action = restart

                # Enable IPComp compression before encryption.
                # ipcomp = no

                # Timeout before closing CHILD_SA after inactivity.
                # inactivity = 0s

                # Fixed reqid to use for this CHILD_SA.
                # reqid = 0

                # Optional fixed priority for IPsec policies.
                # priority = 0

                # Optional interface name to restrict IPsec policies.
                # interface =

                # Netfilter mark and mask for input traffic.
                # mark_in = 0/0x00000000

                # Whether to set *mark_in* on the inbound SA.
                # mark_in_sa = no

                # Netfilter mark and mask for output traffic.
                # mark_out = 0/0x00000000

                # Netfilter mark applied to packets after the inbound IPsec SA
                # processed them.
                # set_mark_in = 0/0x00000000

                # Netfilter mark applied to packets after the outbound IPsec SA
                # processed them.
                # set_mark_out = 0/0x00000000

                # Inbound XFRM interface ID.
                # if_id_in = 0

                # Outbound XFRM interface ID.
                # if_id_out = 0

                # Traffic Flow Confidentiality padding.
                # tfc_padding = 0

                # IPsec replay window to configure for this CHILD_SA.
                # replay_window = 32

                # Enable hardware offload for this CHILD_SA, if supported by the
                # IPsec implementation.
                # hw_offload = no

                # Whether to copy the DF bit to the outer IPv4 header in tunnel
                # mode.
                # copy_df = yes

                # Whether to copy the ECN header field to/from the outer IP
                # header in tunnel mode.
                # copy_ecn = yes

                # Whether to copy the DSCP header field to/from the outer IP
                # header in tunnel mode.
                # copy_dscp = out

                # Action to perform after loading the configuration (none, trap,
                # start).
                # start_action = none

                # Action to perform after a CHILD_SA gets closed (none, trap,
                # start).
                # close_action = none

            }

        }

    }

}

# Section defining secrets for IKE/EAP/XAuth authentication and private key
# decryption.
secrets {

    # EAP secret section for a specific secret.
    # eap<suffix> {

        # Value of the EAP/XAuth secret.
        # secret =

        # Identity the EAP/XAuth secret belongs to.
        # id<suffix> =

    # }

    eap-username {
        secret = "[パスワード]"
        id = [ユーザー名]
    }

    # XAuth secret section for a specific secret.
    # xauth<suffix> {

    # }

    # NTLM secret section for a specific secret.
    # ntlm<suffix> {

        # Value of the NTLM secret.
        # secret =

        # Identity the NTLM secret belongs to.
        # id<suffix> =

    # }

    # IKE preshared secret section for a specific secret.
    # ike<suffix> {

        # Value of the IKE preshared secret.
        # secret =

        # IKE identity the IKE preshared secret belongs to.
        # id<suffix> =

    # }

    # Postquantum Preshared Key (PPK) section for a specific secret.
    # ppk<suffix> {

        # Value of the PPK.
        # secret =

        # PPK identity the PPK belongs to.
        # id<suffix> =

    # }

    # Private key decryption passphrase for a key in the private folder.
    # private<suffix> {

        # File name in the private folder for which this passphrase should be
        # used.
        # file =

        # Value of decryption passphrase for private key.
        # secret =

    # }

    # Private key decryption passphrase for a key in the rsa folder.
    # rsa<suffix> {

        # File name in the rsa folder for which this passphrase should be used.
        # file =

        # Value of decryption passphrase for RSA key.
        # secret =

    # }

    # Private key decryption passphrase for a key in the ecdsa folder.
    # ecdsa<suffix> {

        # File name in the ecdsa folder for which this passphrase should be
        # used.
        # file =

        # Value of decryption passphrase for ECDSA key.
        # secret =

    # }

    # Private key decryption passphrase for a key in the pkcs8 folder.
    # pkcs8<suffix> {

        # File name in the pkcs8 folder for which this passphrase should be
        # used.
        # file =

        # Value of decryption passphrase for PKCS#8 key.
        # secret =

    # }

    # PKCS#12 decryption passphrase for a container in the pkcs12 folder.
    # pkcs12<suffix> {

        # File name in the pkcs12 folder for which this passphrase should be
        # used.
        # file =

        # Value of decryption passphrase for PKCS#12 container.
        # secret =

    # }

    # Definition for a private key that's stored on a token/smartcard.
    # token<suffix> {

        # Hex-encoded CKA_ID of the private key on the token.
        # handle =

        # Optional slot number to access the token.
        # slot =

        # Optional PKCS#11 module name to access the token.
        # module =

        # Optional PIN required to access the key on the token. If none is
        # provided the user is prompted during an interactive --load-creds call.
        # pin =

    # }

}

# Section defining named pools.
pools {

    # Section defining a single pool with a unique name.
    ipv4_pool {

        # Addresses allocated in pool.
        addrs = 10.0.1.1 - 10.0.1.100

        # Comma separated list of additional attributes from type <attr>.
        # <attr> =
        dns = 1.1.1.2, 1.0.0.2

    }

}

# Section defining attributes of certification authorities.
# authorities {

    # Section defining a certification authority with a unique name.
    # <name> {

        # CA certificate belonging to the certification authority.
        # cacert =

        # Absolute path to the certificate to load.
        # file =

        # Hex-encoded CKA_ID of the CA certificate on a token.
        # handle =

        # Optional slot number of the token that stores the CA certificate.
        # slot =

        # Optional PKCS#11 module name.
        # module =

        # Comma-separated list of CRL distribution points.
        # crl_uris =

        # Comma-separated list of OCSP URIs.
        # ocsp_uris =

        # Defines the base URI for the Hash and URL feature supported by IKEv2.
        # cert_uri_base =

    # }

# }

すっきり版

sample.conf
connections {
    TestIKEv2-RemoteAccess {
        version = 2
        encap = yes
        dpd_delay = 60s
        send_cert = always
        unique = never
        rekey_time = 1h
        pools = ipv4_pool

        local-pubkey-certbot {
            certs = certbot.cer
            id = @[↑で指定した証明書のCNかSANに入っているホスト名]
        }

        remote-eap-mschapv2 {
            auth = eap-mschapv2
        }

        children {
            testikev2_child {
                local_ts = 0.0.0.0/0, ::/0
                rekey_time = 30m
                dpd_action = restart
                #updown = /usr/lib/ipsec/_updown iptables
            }
        }
    }
}

secrets {
    eap-username {
        secret = "[パスワード]"
        id = [ユーザー名]
    }
}

pools {
    ipv4_pool {
        addrs = 10.0.1.1 - 10.0.1.100
        dns = 1.1.1.2, 1.0.0.2
    }
}

「.updown」を設定すれば、前述のufw設定のうち、マスカレード以外を接続時にピンポイントで投入してくれる
ubuntu 22.04の場合、スクリプトは「/usr/lib/ipsec/_updown」にあるので「updown = /usr/lib/ipsec/_updown iptables」をコメントアウトするのもアリ

反映と確認
最後にsuccessfully loaded 1 connectionsが出ていれば良い。

# swanctl -q
loaded certificate from '/etc/swanctl/x509/certbot.cer'
loaded RSA key from '/etc/swanctl/private/certbot.key'
loaded eap secret 'eap-username'
no authorities found, 0 unloaded
loaded pool 'ipv4_pool'
successfully loaded 1 pools, 0 unloaded
loaded connection 'TestIKEv2-RemoteAccess'
successfully loaded 1 connections, 0 unloaded

ここまでで、一旦iOSでの手動設定にてVPN接続と、インターネット側へのアクセス、ローカル側(192.168.200.0/24)へのアクセスができる。

設定(サーバ認証:証明書、ユーザ認証:クライアント証明書)

  • 公的クライアント証明書(セコムとかグローバルサインとかで買うやつ)での接続 → 半分できた

    • 証明書の内容による振り分け(OUがxxxだったら許可みたいな) → 試行中
      • certs = vpn.example.com.crt { ou = vpn } 的な書き方をするらしいが → ガセ
    • これができないと、同じサービスを使っているほかの無関係なユーザー(≒CA証明書が一緒)も認証通っちゃう?
  • オレオレ証明書 → できた

  • オレオレ証明書の失効判定処理 → 省略

  • Android

    • 12標準 △
    • strongSwanクライアント △
    • 中間証明書の取り扱いがわからん。アプリかシステムに突っ込めばとりあえず通る
      • Let's Encryptの期限切れルート証明書の問題もありそう → なさそう
  • Windows -

オレオレ証明書の作成方法はいろいろと試してみたが、結局XCAに落ち着いた。公式にも記載あるし

CA証明書の配置

「/etc/swanctl/x509ca」にクライアント証明書のCA証明書を置く(ひとまず「ca.cer」の名前)

設定ファイルの変更箇所

上記「sample.conf」ファイルのうち、「remote-eap-mschapv2{~}」の箇所を置き換える

sample.conf
remote-pki {
            auth = eap-tls
            cacerts = ca.cer
        }

設定(EAP-MSCHAPv2, EAP-TLS(ID/PW、証明書認証)併用)

ここで言う「併用」は、「ID/PWでも証明書でもどっちでも使える」の意
単一の接続設定内で別の認証方式を併用するのは できないくさい。 eap-dynamicを指定する
上記「sample.conf」ファイルのうち、「remote-eap-mschapv2{~}」の箇所を置き換える

sample.conf
remote-pki {
            auth = eap-dynamic
            cacerts = ca.cer
        }

proposalの候補

環境ごとのデフォルト値をテキトーに調べた

proposals = aes256gcm16-prfsha256-ecp521, aes256gcm16-prfsha256-ecp256, aes256-sha256-ecp256, aes256-sha256-modp2048
esp_proposals = aes256gcm16, aes256-sha256

Windowsからは滅多に繋がないのでこの辺りに落ち着きそう。環境によっては「CHACHA20_POLY1305」を入れてもいいかもしれない。

strongSwan

Ubuntu 22.04 + strongSwan U5.9.5/K5.15.0-33-generic でのデフォルト値

IKE:
    AES_CBC_128/
    AES_CBC_192/
    AES_CBC_256/
    AES_CTR_128/
    AES_CTR_192/
    AES_CTR_256/
    CAMELLIA_CBC_128/
    CAMELLIA_CBC_192/
    CAMELLIA_CBC_256/
    CAMELLIA_CTR_128/
    CAMELLIA_CTR_192/
    CAMELLIA_CTR_256/
    3DES_CBC/
    
    HMAC_SHA2_256_128/
    HMAC_SHA2_384_192/
    HMAC_SHA2_512_256/
    AES_XCBC_96/
    AES_CMAC_96/
    HMAC_SHA1_96/
    PRF_AES128_XCBC/
    PRF_AES128_CMAC/
    PRF_HMAC_SHA2_256/
    PRF_HMAC_SHA2_384/
    PRF_HMAC_SHA2_512/
    PRF_HMAC_SHA1/
    
    CURVE_25519/
    CURVE_448/
    ECP_256/
    ECP_384/
    ECP_521/
    ECP_256_BP/
    ECP_384_BP/
    ECP_512_BP/
    NTRU_128/
    NTRU_192/
    NTRU_256/
    MODP_3072/
    MODP_4096/
    MODP_6144/
    MODP_8192/
    MODP_2048, 

IKE:
    AES_CCM_16_128/
    AES_CCM_16_192/
    AES_CCM_16_256/
    AES_GCM_16_128/
    AES_GCM_16_192/
    AES_GCM_16_256/
    CHACHA20_POLY1305/
    CAMELLIA_CCM_16_128/
    CAMELLIA_CCM_16_192/
    CAMELLIA_CCM_16_256/
    AES_CCM_8_128/
    AES_CCM_8_192/
    AES_CCM_8_256/
    AES_CCM_12_128/
    AES_CCM_12_192/
    AES_CCM_12_256/
    AES_GCM_8_128/
    AES_GCM_8_192/
    AES_GCM_8_256/
    AES_GCM_12_128/
    AES_GCM_12_192/
    AES_GCM_12_256/
    CAMELLIA_CCM_8_128/
    CAMELLIA_CCM_8_192/
    CAMELLIA_CCM_8_256/
    CAMELLIA_CCM_12_128/
    CAMELLIA_CCM_12_192/
    CAMELLIA_CCM_12_256/
    PRF_AES128_XCBC/
    PRF_AES128_CMAC/

    PRF_HMAC_SHA2_256/
    PRF_HMAC_SHA2_384/
    PRF_HMAC_SHA2_512/
    PRF_HMAC_SHA1/
    
    CURVE_25519/
    CURVE_448/
    ECP_256/
    ECP_384/
    ECP_521/
    ECP_256_BP/
    ECP_384_BP/
    ECP_512_BP/
    NTRU_128/
    NTRU_192/
    NTRU_256/
    MODP_3072/
    MODP_4096/
    MODP_6144/
    MODP_8192/
    MODP_2048

ESP:
    AES_GCM_16_128/
    AES_GCM_16_192/
    AES_GCM_16_256, 

ESP:
    AES_CBC_128/
    AES_CBC_192/
    AES_CBC_256/
    
    HMAC_SHA2_256_128/
    HMAC_SHA2_384_192/
    HMAC_SHA2_512_256/
    HMAC_SHA1_96/
    AES_XCBC_96/
    NO_EXT_SEQ

iOS(macOSもだいたい同じと想定)

手動設定時候補

IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_2048, 
IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_256, 
IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1536, 
IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024, 
IKE:3DES_CBC/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024

ESP:AES_CBC_256/HMAC_SHA2_256_128/NO_EXT_SEQ, 
ESP:AES_CBC_256/HMAC_SHA2_256_128/NO_EXT_SEQ, 
ESP:AES_CBC_256/HMAC_SHA2_256_128/NO_EXT_SEQ, 
ESP:AES_CBC_128/HMAC_SHA1_96/NO_EXT_SEQ, 
ESP:3DES_CBC/HMAC_SHA1_96/NO_EXT_SEQ

iOS構成プロファイル

  • VPN.IKEv2.IKESecurityAssociationParameters
    • EncryptionAlgorithm
      • Default: AES-256
      • Possible values: DES, 3DES, AES-128, AES-256, AES-128-GCM, AES-256-GCM, ChaCha20Poly1305
    • IntegrityAlgorithm
      • Default: SHA2-256
      • Possible values: SHA1-96, SHA1-160, SHA2-256, SHA2-384, SHA2-512
    • DiffieHellmanGroup
      • Default: 14(modp2048)
      • Possible values: 1, 2, 5, 14, 15, 16, 17, 18, 19, 20, 21, 31
  • VPN.IKEv2.ChildSecurityAssociationParameters
    • EncryptionAlgorithm
      • Default: AES-256
      • Possible values: DES, 3DES, AES-128, AES-256, AES-128-GCM, AES-256-GCM, ChaCha20Poly1305
    • IntegrityAlgorithm
      • Default: SHA2-256
      • Possible values: SHA1-96, SHA1-160, SHA2-256, SHA2-384, SHA2-512
    • DiffieHellmanGroup
      • Default: 14
      • Possible values: 1, 2, 5, 14, 15, 16, 17, 18, 19, 20, 21, 31

VPN.IKEv2 @ Apple Developer Documantaition

Android

strongSwan client 2.3.3

IKE:
    AES_CBC_128/
    AES_CBC_192/
    AES_CBC_256/
    3DES_CBC/
    
    HMAC_SHA2_256_128/
    HMAC_SHA2_384_192/
    HMAC_SHA2_512_256/
    HMAC_SHA1_96/
    AES_XCBC_96/
    PRF_HMAC_SHA2_256/
    PRF_HMAC_SHA2_384/
    PRF_HMAC_SHA2_512/
    PRF_AES128_XCBC/
    PRF_HMAC_SHA1/
    
    ECP_256/
    ECP_384/
    ECP_521/
    ECP_256_BP/
    ECP_384_BP/
    ECP_512_BP/
    CURVE_25519/
    MODP_3072/
    MODP_4096/
    MODP_6144/
    MODP_8192/
    MODP_2048, 
    
IKE:
    AES_GCM_16_128/
    AES_GCM_16_192/
    AES_GCM_16_256/
    CHACHA20_POLY1305/
    AES_GCM_12_128/
    AES_GCM_12_192/
    AES_GCM_12_256/
    AES_GCM_8_128/
    AES_GCM_8_192/
    AES_GCM_8_256/

    PRF_HMAC_SHA2_256/
    PRF_HMAC_SHA2_384/
    PRF_HMAC_SHA2_512/
    PRF_AES128_XCBC/
    PRF_HMAC_SHA1/
    
    ECP_256/
    ECP_384/
    ECP_521/
    ECP_256_BP/
    ECP_384_BP/
    ECP_512_BP/
    CURVE_25519/
    MODP_3072/
    MODP_4096/
    MODP_6144/
    MODP_8192/
    MODP_2048

ESP:
    AES_GCM_16_256/
    AES_GCM_16_128/
    CHACHA20_POLY1305/
    NO_EXT_SEQ, 

ESP:
    AES_CBC_256/
    AES_CBC_192/
    AES_CBC_128/

    HMAC_SHA2_384_192/
    HMAC_SHA2_256_128/
    HMAC_SHA2_512_256/
    HMAC_SHA1_96/
    NO_EXT_SEQ

Android12

IKE:
    AES_CTR_256/
    AES_CBC_256/
    AES_CTR_192/
    AES_CBC_192/
    AES_CTR_128/
    AES_CBC_128/
    HMAC_SHA2_512_256/
    HMAC_SHA2_384_192/
    HMAC_SHA2_256_128/
    AES_XCBC_96/
    AES_CMAC_96/

    PRF_HMAC_SHA1/
    PRF_AES128_XCBC/
    PRF_HMAC_SHA2_256/
    PRF_HMAC_SHA2_384/
    PRF_HMAC_SHA2_512/
    PRF_AES128_CMAC/
    
    MODP_4096/
    CURVE_25519/
    MODP_3072/
    MODP_2048, 

IKE:
    CHACHA20_POLY1305/
    AES_GCM_16_256/
    AES_GCM_12_256/
    AES_GCM_8_256/
    AES_GCM_16_192/
    AES_GCM_12_192/
    AES_GCM_8_192/
    AES_GCM_16_128/
    AES_GCM_12_128/
    AES_GCM_8_128/
    PRF_HMAC_SHA1/
    PRF_AES128_XCBC/
    PRF_HMAC_SHA2_256/
    PRF_HMAC_SHA2_384/
    PRF_HMAC_SHA2_512/
    PRF_AES128_CMAC/
    
    MODP_4096/
    CURVE_25519/
    MODP_3072/
    MODP_2048

ESP:
    AES_CBC_256/
    AES_CBC_192/
    AES_CBC_128/

    HMAC_SHA2_512_256/
    HMAC_SHA2_384_192/
    HMAC_SHA2_256_128/
    NO_EXT_SEQ,
    
ESP:
    AES_GCM_16_256/
    AES_GCM_12_256/
    AES_GCM_8_256/
    AES_GCM_16_192/
    AES_GCM_12_192/
    AES_GCM_8_192/
    AES_GCM_16_128/
    AES_GCM_12_128/
    AES_GCM_8_128/
    NO_EXT_SEQ

Windows10

初期状態では「proposal = default」では許容されていない「modp1024」しか候補にないので、Windows10から接続したい場合はPowerShellコマンドで許容されるものに設定するか、レジストリをいじって強度を上げるか、strongSwan側で初期状態のものを許容するか。

PowerShellでの設定方法

レジストリ設定方法

IKE

標準状態
IKE:3DES_CBC/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024, 
IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024, 
IKE:3DES_CBC/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1024, 
IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1024, 
IKE:3DES_CBC/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_1024, 
IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_1024
標準+「暗号化は省略可能」以下
IKE:3DES_CBC/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024, 
IKE:3DES_CBC/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1024, 
IKE:3DES_CBC/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_1024, 
IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024, 
IKE:AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1024, 
IKE:AES_CBC_128/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_1024, 
IKE:AES_CBC_192/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024, 
IKE:AES_CBC_192/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1024, 
IKE:AES_CBC_192/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_1024, 
IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024, 
IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1024, 
IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_1024, 
IKE:AES_GCM_16_128/PRF_HMAC_SHA1/MODP_1024, 
IKE:AES_GCM_16_128/PRF_HMAC_SHA2_256/MODP_1024, 
IKE:AES_GCM_16_128/PRF_HMAC_SHA2_384/MODP_1024, 
IKE:AES_GCM_16_256/PRF_HMAC_SHA1/MODP_1024, 
IKE:AES_GCM_16_256/PRF_HMAC_SHA2_256/MODP_1024, 
IKE:AES_GCM_16_256/PRF_HMAC_SHA2_384/MODP_1024
NegotiateDH2048_AES256 = 2
IKE:AES_CBC_256/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_2048, 
IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_2048, 
IKE:AES_CBC_256/HMAC_SHA2_384_192/PRF_HMAC_SHA2_384/MODP_2048

ESP

「暗号化は省略可能」以下
ESP:AES_CBC_256/HMAC_SHA1_96/NO_EXT_SEQ, 
ESP:AES_CBC_128/HMAC_SHA1_96/NO_EXT_SEQ, 
ESP:3DES_CBC/HMAC_SHA1_96/NO_EXT_SEQ, 
ESP:DES_CBC/HMAC_SHA1_96/NO_EXT_SEQ, 
ESP:NULL/HMAC_SHA1_96/NO_EXT_SEQ ※「暗号化を許可しない」の場合はこれのみ
「暗号化が必要」以上
ESP:AES_CBC_256/HMAC_SHA1_96/NO_EXT_SEQ, 
ESP:3DES_CBC/HMAC_SHA1_96/NO_EXT_SEQ

VPNクライアントになんちゃってDoT(DNS over TLS)を提供する

ホスト側systemd-resolvedでDoT(ついでにDNSSEC)を有効化

DNSアドレスの後ろに「#ホスト名」をいれる。たぶんTLSに用いられている証明書のSANでいい。
Googleの場合だと「8.8.8.8#dns.google 8.8.4.4#dns.google 2001:4860:4860::8888#dns.google 2001:4860:4860::8844#dns.google」てな感じに

/etc/systemd/network/10-ens3.network
[Match]
Name=ens3

[Network]
LinkLocalAddressing=ipv6
Address=[WANアドレス]/23
Address=[WANアドレス(IPv6)]/64
Gateway=[ゲートウェイアドレス]
Gateway=[ゲートウェイアドレス(IPv6)]

DNS=2606:4700:4700::1112#security.cloudflare-dns.com
DNS=2606:4700:4700::1002#security.cloudflare-dns.com
DNS=1.1.1.2#security.cloudflare-dns.com
DNS=1.0.0.2#security.cloudflare-dns.com

DNSSEC=yes
DNSOverTLS=yes

LLMNR=no

resolvectlを実行して、Protocolsで有効化(+)を確認

Protocols: +DefaultRoute -LLMNR -mDNS +DNSOverTLS DNSSEC=yes/supported
Current DNS Server: 1.1.1.2#security.cloudflare-dns.com

クエリを叩くと、authenticated(たぶんこれがDNSSEC)とencrypted transport(たぶんDoT)が「yes」となる

# resolvectl query dns.google
dns.google: 2001:4860:4860::8844               -- link: ens3
            2001:4860:4860::8888               -- link: ens3
            8.8.4.4                            -- link: ens3
            8.8.8.8                            -- link: ens3

-- Information acquired via protocol DNS in 33.1ms.
-- Data is authenticated: yes; Data was acquired via local or encrypted transport: yes
-- Data from: network

dnsmasqのインストールと設定

# apt install dnsmasq

systemd-resolvedが127.0.0.53を押さえているので起動エラーとなる
dnsmasq.confに「bind-interface」のオプションを入れて起動エラーを回避する
「interface」オプションで物理インターフェースを指定する
ufwでVPNクライアントからの53/udpを許可する

クライアントからテストサイトにアクセス

swanctlの設定ファイルにて、poolでcloudflareを指定している箇所を、ここで立てたDNSサーバに向ける
設定再読み込み、再接続語にDNSサーバがそっちに向いていることを確認し、クライアントから https://1.1.1.1/help へアクセスする
「Using DNS over TLS(DoT)」が「Yes」となっていれば良しとする
それにしてもどうやって判定してんだろコレ

こんな感じ

dnsmasqはデフォルトでOS設定のDNS(/etc/resolve.conf)を上位DNSとして用いる
Ubuntuの場合はsystemd-resolvedが動いており、OS設定のDNSは先ず自身(127.0.0.53)を参照する
systemd-resolvedはsystemd-networkdの設定ファイルを参照して問い合わせを行う

なので、systemd-networkdでDoTを利用できるようにしておくと、

  • VPNクライアントからdnsmasqまではIPSecで保護された通信でDNSプロトコルを用いる
  • dnsmasqとsystemd-resolvedの間はDNSプロトコルを用いるがホスト内で完結なのでまあ保護されているとみなす
  • systemd-resolvedから先はDoTを用いTLSで保護される

そこまでガチで保護する必要を感じるかどうかは人それぞれ
クライアント側で別途DoH・DoTの設定をしている場合のVPNへの影響は見ていない

                  ===[IPSec]=== =============[ホスト内で完結]============== ====[TLS]====
[VPNクライアント]   ---(DNS)---   [dnsmasq] ---(DNS)--- [systemd-resolved]   ---(DoT)---   [security.cloudflare-dns.com]
                  ============= ========================================== =============

こんなことをやるメリットの一つが「MulticastDNS」が使えること
systemd-resolbedでMulticastDNSの利用を許可しておくと、「*.local」のクエリを投げたときに、sytemd-resolvedがDNSレコードとして返してくれる
ただし、あくまでDNSレコードとして返してくるだけなのでMulticastDNSをフル活用するようなものが使えるわけではない
もともとは外からプリンタを使いたかっただけではあったが、構成プロファイルでセグメント越えのAirPrint利用ができるので、もはやどうでもいい

Device Management Profile (AirPrint) @ Apple Developer Documantaition
というか、これAirPrint非対応でもIPP対応ならいけそうなのでCUPS-PDFってのもアリかもしれない。

がっつりDoTとかDoHとか使いたいなら、AdGuard Homeを使えばいい。その時の証明書はSANにIPアドレスを持つやつ推奨。VPNの時はそもそも昇格するか知らんが、Wi-Fi環境下ではSANにIPアドレスがないとDoHの自動昇格をしてくれない謎。

クライアント側のネタ

iOS

サーバ証明書がRSA以外だとか、Always-onとかPer-app、On-demandを使おうとおもったら構成プロファイルが必須
iOS単体だと純正メールAppの添付か純正ファイルApp、Safariからでないとプロファイルを読まないのが辛い。ストレージ系アプリから直接いかせて。
Always-onは証明書認証必須だし、かつPer-appに至っては対応したMDMが必要そうなので当分スルー

16.4から構成プロファイルの項目が増えている。
ExcludeAPNs、ExcludeCellularServicesは明示的に有効化しておいた方がいいかもしれない。

iOSの怪しい挙動

iOS 15.4.1で手動設定を行う場合、ローカルIDが空欄の状態でユーザー認証を証明書で設定すると設定が逝って繋がんない。適当なローカルIDを指定。指定した後も一回逝くことがあるので、証明書を選択しなおし。
15.5は問題ない。上げたらでなくなった。15.4.1未満は知らん。

Apple Configurator 2の罠

2.15.1でIKEv2のVPNペイロードの作った場合、DNS周りの設定をしていない場合でも空欄の設定が吐き出されるので、テキストエディットで開いてDNSの項を消さないとプロファイルインストールでコケる
AC2ではまれによくあることなのでたぶんそのうちなおる

クライアント証明書の暗号化方式

Ubuntu22.04(というかOpenSSL 3系)の「openssl pkcs12 -export」で出したものや、や、Windows10とかのエクスポートの際に「AES256-SHA256」を選ぶと、iOSが対応していないためパスワードが正しくても「パスワードが違います」とインストールできない。
OpenSSL 3系の場合は「openssl pkcs12 -export -inkey user.key -in user.cer -out user.p12 -legacy」のように、「-legacy」オプションをつけたり、Windowsでエクスポートする場合は「TripleDES-SHA1」を選ぶ必要がある。

ってかAppleさんや、脆弱なんちゃう?知らんけど。

脱線
VyOSで秘密鍵が読めない → 新方式にしないとダメ
-----BEGIN XXX PRIVATE KEY----- でなく -----BEGIN PRIVATE KEY-----な方で
今のcertbotなら大丈夫。openssl ecparamだと古いので openssl pkeyを通す。ほかに方法無いのか。

Android

Android12でIKEv2に対応したけど、設定の手軽さと細かさを考えると公式クライアントアプリの方が良さげ
設定のインポートができるのでGoogleドライブにでも置いとけば復旧容易
プロポーザルやスプリット、per-appとかもクライアント側で設定できる

Windows

上記の通り、初期設定のままでは繋がらない。たぶんWin11も。知らんけど。
おま環と思うが、認証情報を覚えてくれない。必ずパスワードを聞いてくる。しかもダイアログをキャンセルで閉じるとsvchostがポートを食ったままになるので詰む。

課題

  • IPv4 over IKEv2 over IPv6?(現状、VPNの接続は確立するけど疎通が取れん。ip6tablesの設定っぽいけども)

    • Site to Siteなら問題ないのに何でだらう
  • 高速化

    • スループットがローカル環境でも500Mbps辺りが頭打ち。仮想環境の限界か。AES-NIが使えていないのか。こんなものなのか。
  • 【済】 証明書認証

    • 【済】 ID/PASS(eap-mschapv2)と証明書の2つの認証方式の併用
  • 【概ね安定のため済】 Always-ON VPNの安定化

  • 【済】 常時接続+グローバルHTTPプロキシ

  • 【済】 per-app vpn

    • 【挫折】iOSはMDMがないと無理なので放置
    • 【済】 AndroidはstrongSwanクライアントならば設定ありでポチポチするだけ
  • 【半ば挫折】 IPv6のGlobalをクライアントに振れないか

    • ULAは不慣れでどうも扱い難い、振っても優先度が低いのでほぼ意味がない
    • 【放置】 たださくらVPSじゃ無理だろうで一旦スルー
    • 【放置】 /56が振ってくる環境で空いているセグメントを使えば亀が踊る。でもマスカレード必須?
  • 【放置】 on-demand vpn

  • 【済】 参考サイトの整理

参考にさせていただいたページ/サイト

公式情報(主にサーバ側に関わるもの)

swanctl リファレンス

ipsec.confからswanctlへの設定読み替え表

IKEv2で利用できる暗号スイート

コンフィグ例

ルートベースVPN

スプリットトンネル

eap-tls

eap-dynamic

ディストリビューター系情報

strongSwanでIKEv2リモートアクセスをやる系記事

ipsec-pkiによる証明書作成法あり(サーバ、クライアント)(ipsec.conf)

証明書作成スクリプトあり(サーバ)(ipsec.conf)

証明書作成スクリプトあり(サーバ、クライアント)(swanctl)

rekey時間に関する記事

ChaCha20-Poly1305設定例

構築に伴う細かい情報とか

apparmor対策
Re: [strongSwan] Let's Encrypt CA Expiry & related StrongSWAN trouble@たぶん公式MLの過去ログ

iOS/macOSクライアント

iOS用IKEv2プロファイルサンプル(古い。一つ下のリンク先に新しいサンプルあり)

iOS/macOSに関する公式ドキュメント

構成プロファイルリファレンス

オンデマンド

Windowsクライアント

Windowsのプロポーザル指定方法

上記で用いられている指定コマンドのリファレンス

Windowsクライアントに関する公式情報と、レジストリによるデフォルトプロポーザル変更方法

Androidクライアント

strongSwan公式クライアント(Play Store)

strongSwan公式クライアント(公式のAPK配布)

ツール

証明書作成ツール XCA

iOS/macOS 構成プロファイル作成に

IKEv2のVPNプロファイル作れるか知らんけど。気が向いたら試す。

追々のネタ用

ext-auth

bypass-lan

vyosでやるときのネタ

1.5-rolling-202406020021

set nat source rule 100 outbound-interface name 'eth0'
set nat source rule 100 translation address 'masquerade'

set pki ca CA certificate 'MIIxxx...'
set pki certificate vyos certificate 'MII...'
set pki certificate vyos private key 'MII...'
# xcaでエクスポートした秘密鍵はそのままでは読めない。
# openssl pkey -in key.pem で変換してから読ませる
# というかこの違いが何かわからなくて悶々とする

set vpn ipsec esp-group ESP lifetime '1800'
set vpn ipsec esp-group ESP pfs 'disable'
set vpn ipsec esp-group ESP proposal 1 encryption 'aes256'
set vpn ipsec esp-group ESP proposal 1 hash 'sha256'
set vpn ipsec esp-group ESP proposal 10 encryption 'aes128gcm128'
set vpn ipsec esp-group ESP proposal 10 hash 'sha256'
set vpn ipsec ike-group IKE dead-peer-detection action 'restart'
set vpn ipsec ike-group IKE dead-peer-detection interval '60'
set vpn ipsec ike-group IKE key-exchange 'ikev2'
set vpn ipsec ike-group IKE lifetime '3600'
set vpn ipsec ike-group IKE proposal 1 dh-group '19'
set vpn ipsec ike-group IKE proposal 1 encryption 'aes256'
set vpn ipsec ike-group IKE proposal 1 hash 'sha256'
set vpn ipsec ike-group IKE proposal 1 prf 'prfsha256'
set vpn ipsec options flexvpn
set vpn ipsec remote-access connection IKEv2_Remote authentication client-mode 'eap-tls'
set vpn ipsec remote-access connection IKEv2_Remote authentication local-id 'vyos.garupon.com'
set vpn ipsec remote-access connection IKEv2_Remote authentication local-users username hogehoge password 'piyopiyo'
set vpn ipsec remote-access connection IKEv2_Remote authentication server-mode 'x509'
set vpn ipsec remote-access connection IKEv2_Remote authentication x509 ca-certificate 'CA'
set vpn ipsec remote-access connection IKEv2_Remote authentication x509 certificate 'vyos'
set vpn ipsec remote-access connection IKEv2_Remote esp-group 'ESP'
set vpn ipsec remote-access connection IKEv2_Remote ike-group 'IKE'
set vpn ipsec remote-access connection IKEv2_Remote local prefix '0.0.0.0/0'
set vpn ipsec remote-access connection IKEv2_Remote local-address 'any'
set vpn ipsec remote-access connection IKEv2_Remote pool 'pool'
set vpn ipsec remote-access connection IKEv2_Remote timeout '0'
set vpn ipsec remote-access connection IKEv2_Remote unique 'never'
set vpn ipsec remote-access pool pool name-server '8.8.8.8'
set vpn ipsec remote-access pool pool name-server '8.8.4.4'
set vpn ipsec remote-access pool pool prefix '172.16.100.0/24'

commit, saveした後に、設定ファイルに追記しないとiOSから繋がらない

sudo nano /etc/swanctl/swanctl.conf

以下の二行を追加

encap = yes
send_cert = always

追加後に sudo swanctl -q を実行。

なお、設定を更新したり再起動したりする度に飛ぶ。

それにしても、忘れたころにIKEv2が必要になる時が来る。つらい。

9
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?