【Terraform】OCI ⇔ オンプレ VPN接続

Cloud

前回作成したOCI環境にDRGを追加し、オンプレとBGPで経路交換を行います。
Azure、AWSと同じ条件で接続してみましたが、OCIはIPSecを無料で使えますが、経路制御は難易度が高い感じがしました。

NW構成図

Azure、AWSの時同様、オンプレ側でVPN接続する機器は家で眠っていたFortigate 50EをVPN接続専用機として利用しました。
検証費用を抑えるため、Object StorageとNAT Gatewayは停止してます。
IPSecでトンネルを2本接続しBGPで経路交換を行い、NAT Gatewayで受信した経路はPrivate Subnetに再配布します。
OCI側はデフォルト値のプライベートAS「31898」を設定しています。

ファイル追加

TFファイル

VCN上はBGPが使えないため、静的経路を追加します。
NAT Gatewayを止めた場合、外部に出る方法がないのでDRGにDFRを向けてオンプレ経由でインターネットに出てもいいです。

network.tf
~~~省略~~~

############################
# Route Table
############################
resource "oci_core_route_table" "private_rt" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.main.id
  display_name   = "private_rt"

  # DRG(BGP経路を設定)
  route_rules {
    destination       = var.onprem_cidr
    destination_type  = "CIDR_BLOCK"
    network_entity_id = oci_core_drg.drg.id
  }
}

DRGを作成し、オンプレとIPSecでトンネルを2本作成します。
Fortigateで実績のあるフェーズ1、フェーズ2の暗号化アルゴリズム、ハッシュ関数、鍵長を指定しています。

drg.tf
########################
# DRG
########################
resource "oci_core_drg" "drg" {
  compartment_id = var.compartment_ocid
  display_name   = "main-drg"
}
resource "oci_core_drg_attachment" "vcn_attach" {
  drg_id = oci_core_drg.drg.id
  vcn_id = oci_core_vcn.main.id
}
data "oci_core_drg_route_distributions" "drg_route_distributions" {
  drg_id = oci_core_drg.drg.id
}

########################
# CPE(Fortigate側)
########################
# OCI側ASNは自動(デフォルト 31898)
resource "oci_core_cpe" "cpe" {
  compartment_id = var.compartment_ocid
  ip_address     = var.onprem_public_ip
  display_name   = "onprem-cpe"
}

########################
# IPSec冗長トンネル(2本)
########################
resource "oci_core_ipsec" "ipsec" {
  compartment_id = var.compartment_ocid
  cpe_id         = oci_core_cpe.cpe.id
  drg_id         = oci_core_drg.drg.id
  display_name   = "onprem-ipsec"

  static_routes = [var.onprem_cidr]
}

data "oci_core_ipsec_connection_tunnels" "tunnels" {
  ipsec_id = oci_core_ipsec.ipsec.id
}

resource "oci_core_ipsec_connection_tunnel_management" "tunnel1" {
  ipsec_id  = oci_core_ipsec.ipsec.id
  tunnel_id =  data.oci_core_ipsec_connection_tunnels.tunnels.ip_sec_connection_tunnels[0].id
  display_name = "ipsectunnel-1"

  shared_secret = var.pre_shared_secret
  ike_version   = "V2"

  routing = "BGP"

  bgp_session_info {
    customer_bgp_asn      = "65001"
    customer_interface_ip = "10.255.0.2/30"
    oracle_interface_ip   = "10.255.0.1/30"
  }

  phase_one_details {
    custom_authentication_algorithm = "SHA2_256"
    custom_encryption_algorithm  = "AES_256_CBC"
    custom_dh_group             = "GROUP14"
  }

  phase_two_details {
    custom_authentication_algorithm = "HMAC_SHA2_256_128"
    custom_encryption_algorithm  = "AES_256_CBC"
    dh_group         = "GROUP14"
  }
}

resource "oci_core_ipsec_connection_tunnel_management" "tunnel2" {
  ipsec_id  = oci_core_ipsec.ipsec.id
  tunnel_id =  data.oci_core_ipsec_connection_tunnels.tunnels.ip_sec_connection_tunnels[1].id
  display_name = "ipsectunnel-2"

  shared_secret = var.pre_shared_secret
  ike_version   = "V2"

  routing = "BGP"

  bgp_session_info {
    customer_bgp_asn      = "65001"
    customer_interface_ip = "10.255.1.2/30"
    oracle_interface_ip   = "10.255.1.1/30"
  }
  phase_one_details {
    custom_authentication_algorithm = "SHA2_256"
    custom_encryption_algorithm  = "AES_256_CBC"
    custom_dh_group             = "GROUP14"
  }

  phase_two_details {
    custom_authentication_algorithm = "HMAC_SHA2_256_128"
    custom_encryption_algorithm  = "AES_256_CBC"
    dh_group         = "GROUP14"
  }
}

output "tunnel_data" {
  value = data.oci_core_ipsec_connection_tunnels.tunnels
}
output "tunnels" {
  value = data.oci_core_ipsec_connection_tunnels.tunnels.ip_sec_connection_tunnels[1]
}
output "drg_dists_debug" {
  value = data.oci_core_drg_route_distributions.drg_route_distributions
}

NSGにオンプレとWebserver間の通信を許可出します。

variables.tf
~~~省略~~~

variable "ingress_rules_private" {
  description = "Ingress rules"
  type = map(object({
    from_port   = number
    to_port     = number
    protocol    = string
    cidr_blocks = list(string)
    description = string
  }))
  default = {}
}
variable "egress_rules_private" {
  description = "Egress rules"
  type = map(object({
    from_port   = number
    to_port     = number
    protocol    = string
    cidr_blocks = list(string)
    description = string
  }))
  default = {}
}

NSGにオンプレセグメントとの許可設定を追加します。

terraform.tfvars
~~~省略~~~

# DRG接続用
onprem_public_ip = "xxx.xxx.xxx.xxx"
onprem_cidr     = "10.200.0.0/16"
pre_shared_secret   = "OCIForti_DRG" #適当

web_ingress_rules = [
  {
    protocol    = "6"
    source_type = "CIDR_BLOCK"
    source      = "10.200.0.0/16"
    min         = 80
    max         = 80
  },
  {
    protocol    = "1"
    source_type = "CIDR_BLOCK"
    source      = "10.200.0.0/16"
    min = 8 #type
    max = 0 #code
  }
]

web_egress_rules = [
  {
    protocol    = "1"
    destination_type = "CIDR_BLOCK"
    destination = "0.0.0.0/0"
    min = 8 #type
    max = 0 #code
  }
]

変数定義を追加します。

variables.tf
# DRG接続用
variable "onprem_public_ip" {}
variable "onprem_cidr" {}
variable "pre_shared_secret" {}

DRGリソースを管理できるようにInstance Principalにポリシーを追加します。

instance_principal.tf
############################
# DRGを利用するためInstance Principalの認証設定(IAMポリシー作成)
############################
resource "oci_identity_policy" "drg_policy" {
  compartment_id = var.tenancy_ocid
  name           = "drg-policy"
  description    = "Allow webserver to access DRG"

  statements = [
    "Allow dynamic-group webserver-dg to manage drgs in compartment id ${var.compartment_ocid}",
    "Allow dynamic-group webserver-dg to manage vcns in compartment id ${var.compartment_ocid}",
    "Allow dynamic-group webserver-dg to manage virtual-network-family in compartment id ${var.compartment_ocid}",
    "Allow dynamic-group webserver-dg to manage ipsec-connections in compartment id ${var.compartment_ocid}",
    "Allow dynamic-group webserver-dg to manage cpes in compartment id ${var.compartment_ocid}",
    "Allow dynamic-group webserver-dg to manage all-resources in compartment id ${var.compartment_ocid}",
  ]
}

Fortigate

トンネル、IPSec、BGP、ポリシーを追加します。
ここで注意しなければいけないのが、通信が発生しないとIPSecを張らないことです。
LAN側からトンネル内を通過する通信を発生させるためにポリシーで許可することで、BGPネイバーの確立ができるようになります。

HCL
config vpn ipsec phase1-interface
  edit oci-vpn-1
    set interface wan
    set ike-version 2
    set peertype any
    set proposal aes256-sha256
    set dhgrp 14
    set remote-gw xxx.xxx.xxx.xxx #OCI側IPアドレス1
    set psksecret OCIForti_DRG
  next
  edit oci-vpn-2
    set interface wan
    set ike-version 2
    set peertype any
    set proposal aes256-sha256
    set dhgrp 14
    set remote-gw xxx.xxx.xxx.xxx #OCI側IPアドレス2
    set psksecret OCIForti_DRG
  next
end

config vpn ipsec phase2-interface
  edit oci-vpn-p2-1
    set phase1name oci-vpn-1
    set proposal aes256-sha256
    set dhgrp 2 14
    set src-subnet 0.0.0.0 0.0.0.0
    set dst-subnet 0.0.0.0 0.0.0.0
  next
  edit oci-vpn-p2-2
    set phase1name oci-vpn-2
    set proposal aes256-sha256
    set dhgrp 2 14
    set src-subnet 0.0.0.0 0.0.0.0
    set dst-subnet 0.0.0.0 0.0.0.0
  next
end

config system interface
  edit "oci-vpn-1"
    set ip 10.255.0.2 255.255.255.255
    set allowaccess ping
    set type tunnel
    set remote-ip 10.255.0.1 255.255.255.252
  next
  edit "oci-vpn-2"
    set ip 10.255.1.2 255.255.255.255
    set allowaccess ping
    set type tunnel
    set remote-ip 10.255.1.1 255.255.255.252
  next
end

config router bgp
  set as 65001
  set router-id 10.201.0.1
  config neighbor
    edit 10.255.0.1
      set remote-as 31898
      set update-source "oci-vpn-1"
    next
    edit 10.255.1.1
      set remote-as 31898
      set update-source "oci-vpn-2"
    next
  end
end
config firewall address
  edit "onprem-real"
    set subnet 10.200.0.0 255.255.0.0
  next
  edit "oci-vpc"
   set subnet 10.0.20.0 255.255.255.0
  next
end

config firewall policy
    edit 1
        set name "lan-to-oci-1"
        set srcintf "lan"
        set dstintf "oci-vpn-1"
        set srcaddr "onprem-real"
        set dstaddr "oci-vpc"
        set action accept
        set schedule "always"
        set service "ALL"
    next
    edit 2
        set name "lan-to-oci-2"
        set srcintf "lan"
        set dstintf "oci-vpn-2"
        set srcaddr "onprem-real"
        set dstaddr "oci-vpc"
        set action accept
        set schedule "always"
        set service "ALL"
    next
end

接続確認

Fortigate確認

IKE SA確認

Tunnel×2本接続するため、SAが2つ作成されていれば問題ありません。

# diagnose vpn ike gateway list
 
vd: root/0
name: oci-vpn-1
version: 2
interface: ppp1 42
addr: x.x.x.x:500 -> y.y.y.y:500
virtual-interface-addr: 10.255.0.2 -> 10.255.0.1
created: 546s ago
PPK: no
IKE SA: created 1/1  established 1/1  time 50/50/50 ms
IPsec SA: created 1/1  established 1/1  time 50/50/50 ms
 
  id/spi: 133401 xxxxxxxxxxxxxx/xxxxxxxxxxxxxx
  direction: initiator
  status: established 546-546s ago = 50ms
  proposal: aes256-sha256
  child: no
  SK_ei: xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx
  SK_er: xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx
  SK_ai: xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx
  SK_ar: xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx
  PPK: no
  message-id sent/recv: 2/19
  lifetime/rekey: 86400/85553
  DPD sent/recv: 00000000/00000000
 
vd: root/0
name: oci-vpn-2
version: 2
interface: ppp1 42
addr: x.x.x.x:500 -> z.z.z.z:500
virtual-interface-addr: 10.255.1.2 -> 10.255.1.1
created: 546s ago
PPK: no
IKE SA: created 1/1  established 1/1  time 50/50/50 ms
IPsec SA: created 1/1  established 1/1  time 50/50/50 ms
 
  id/spi: 133402 xxxxxxxxxxxxx/xxxxxxxxxxxxx
  direction: initiator
  status: established 546-546s ago = 50ms
  proposal: aes256-sha256
  child: no
  SK_ei: xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx
  SK_er: xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx
  SK_ai: xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx
  SK_ar: xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx
  PPK: no
  message-id sent/recv: 2/22
  lifetime/rekey: 86400/85553
  DPD sent/recv: 00000000/00000000

IPSec SA確認

トンネル×2本のため、2つSAが作成されていれば問題ありません。

# diagnose vpn tunnel list
list all ipsec tunnel in vd 0
------------------------------------------------------
name=oci-vpn-1 ver=2 serial=9 x.x.x.x:0->y.y.y.y:0 dst_mtu=1454
bound_if=42 lgwy=static/1 tun=intf/0 mode=auto/1 encap=none/512 options[0200]=frag-rfc  run_state=0 accept_traffic=1 overlay_id=0
 
proxyid_num=1 child_num=0 refcnt=14 ilast=0 olast=0 ad=/0
stat: rxp=122 txp=124 rxb=16136 txb=7811
dpd: mode=on-demand on=1 idle=20000ms retry=3 count=0 seqno=0
natt: mode=none draft=0 interval=0 remote_port=0
proxyid=oci-vpn-p2-1 proto=0 sa=1 ref=2 serial=1
  src: 0:0.0.0.0/0.0.0.0:0
  dst: 0:0.0.0.0/0.0.0.0:0
  SA:  ref=3 options=10202 type=00 soft=0 mtu=1390 expire=42352/0B replaywin=1024
       seqno=7d esn=0 replaywin_lastseq=0000007a itn=0 qat=0 hash_search_len=1
  life: type=01 bytes=0/0 timeout=42899/43200
  dec: spi=1f962ce7 esp=aes key=32 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
       ah=sha256 key=32 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  enc: spi=8a2cade8 esp=aes key=32 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
       ah=sha256 key=32 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  dec:pkts/bytes=122/7525, enc:pkts/bytes=124/16544
run_tally=1
------------------------------------------------------
name=oci-vpn-2 ver=2 serial=a x.x.x.x:0->z.z.z.z:0 dst_mtu=1454
bound_if=42 lgwy=static/1 tun=intf/0 mode=auto/1 encap=none/512 options[0200]=frag-rfc  run_state=0 accept_traffic=1 overlay_id=0
 
proxyid_num=1 child_num=0 refcnt=15 ilast=3 olast=3 ad=/0
stat: rxp=122 txp=124 rxb=16136 txb=7760
dpd: mode=on-demand on=1 idle=20000ms retry=3 count=0 seqno=0
natt: mode=none draft=0 interval=0 remote_port=0
proxyid=oci-vpn-p2-2 proto=0 sa=1 ref=2 serial=1
  src: 0:0.0.0.0/0.0.0.0:0
  dst: 0:0.0.0.0/0.0.0.0:0
  SA:  ref=3 options=10202 type=00 soft=0 mtu=1390 expire=42354/0B replaywin=1024
       seqno=7d esn=0 replaywin_lastseq=0000007a itn=0 qat=0 hash_search_len=1
  life: type=01 bytes=0/0 timeout=42901/43200
  dec: spi=1f962ce8 esp=aes key=32 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
       ah=sha256 key=32 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  enc: spi=96dc664d esp=aes key=32 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
       ah=sha256 key=32 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  dec:pkts/bytes=122/7526, enc:pkts/bytes=124/16496
run_tally=1

BGP受信ルート確認

トンネル経由のネットワークが2経路見えていればOKです。
Private SubnetもPublic SubnetもDRGでアタッチメントされているため経路が広告されてきています。
メトリックもASPathもついていないのでECMP設定を入れてもいいですね。

# get router info bgp network
BGP table version is 6, local router ID is 10.201.0.1
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
              S Stale
Origin codes: i - IGP, e - EGP, ? - incomplete
 
   Network          Next Hop            Metric LocPrf Weight RouteTag Path
*  10.0.10.0/24     10.255.0.1               0             0        0 31898 i <-/->
*>                  10.255.1.1               0             0        0 31898 i <-/1>
*  10.0.20.0/24     10.255.0.1               0             0        0 31898 i <-/->
*>                  10.255.1.1               0             0        0 31898 i <-/1>
*> 10.200.0.0/16    0.0.0.0                       100  32768        0 i <-/1>
 
Total number of prefixes 3

OCI確認

GUI上の接続状態を確認します。
AWSやAzureよりも確認できる項目が多い印象です。

IPSec接続状況

サイト間VPNでIPSec及びBGPステータスが稼働中であれば正常です。

トンネルの名前をクリックすることでステータス詳細を見ることができます。

フェーズ詳細タブからフェーズ1、フェーズ2の詳細を確認できます。

受信したBGPルートタブはオンプレ環境から受信したルートを確認できます。

通知されたBGPルートタブではアタッチメントされたVCN側のルートを確認できます。

オンプレから接続確認

VCNでインターネット宛の経路を設定しておらず、Webサーバ機能をインストールしていないためICMPで疎通確認を行います。
tracerouteを確認しましたが、OCI内の応答がかけてしまいました

>ping 10.0.20.228

10.0.20.228  ping を送信しています 32 バイトのデータ:
10.0.20.228 からの応答: バイト数 =32 時間 =10ms TTL=60
10.0.20.228 からの応答: バイト数 =32 時間 =12ms TTL=60
10.0.20.228 からの応答: バイト数 =32 時間 =11ms TTL=60
10.0.20.228 からの応答: バイト数 =32 時間 =10ms TTL=60

10.0.20.228  ping 統計:
    パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
    最小 = 10ms、最大 = 12ms、平均 = 10ms
    
>tracert -d 10.0.20.228

10.0.20.228 へのルートをトレースしています。経由するホップ数は最大 30 です

  1     4 ms     2 ms     2 ms  10.200.0.254
  2     *        *        *     要求がタイムアウトしました。
  3     *        *        *     要求がタイムアウトしました。
  4    11 ms    10 ms     9 ms  10.0.20.228

トレースを完了しました。

最後に

OCIのVPN接続はAlways Freeリソースに含まれているため気軽に検証ができます。
AWSやAzureと比較すると、経路の再配布ができないことと設定項目の複雑さに目をつぶれば個人利用もできそうな感じがしました。
OCIからの経路はMED値が付与されていないためオンプレ側で簡単に制御できるのもいいですね。
それとGUIは確認できるステータスも多く切り分けもやりやすかったです。