EP27 - 建立 VPN 连线,直接连线到 AWS

今天是要来填之前未补之坑,
那就是建立 VPN 连线,
以小公司来说,
其实能够快速加快产品上市比较重要,
因此能够内部 CI/CD 服务能够使用就好,
真的要卡控就卡公司内的 IP,
有需要的话就使用远端遥控工具到公司的电脑做操作。

当组织规模持续过大,
甚至采用不同的开发方法(如:成对开发),
我们会发现的确还是需要一台 VPN,
小公司也许没办法花费太多预算在基础设施上,
这时候使用 AWS 托管的 VPN 服务就是个不错的选择,
我们可以建立一个 VPN 连线,
让我们直接连到 AWS 的 Endpoint,
这样我们就可以直接存取 private subnet 的资源,
不用在 public subnet 另外架设负载平衡器,
还要消耗多个 public ip 和 private ip。

回顾负载平衡器

中间穿插一个与 VPN 无关的元件做为暖身
会先回顾负载平衡器
只是为了要在建立 VPN 以後
再次调整 AWS 上的架构

程序码的角度

从程序码的角度来看
我们可以看到 internal 是 false
因此外网可以读取得到
在 subents 中填入所有 public 的 subnets
可以理解成我们可能会随机从一个 public 的 subnets 中连进去
并且这个接口可视为 DMZ 网段
因此也可以直接连到我们放在 aws 上 private subnet 的其他资源

resource "aws_lb" "jenkins" {
    name               = "jenkins"
    internal           = false
    load_balancer_type = "application"
    security_groups    = [ aws_security_group.elb_internal.id ]
    subnets            = data.aws_subnet_ids.public_subnet_ids.ids
    
    enable_deletion_protection = false
    
    tags = {
        Creator = "Terraform"
    }
}

从 AWS Cloud Console 理解

从 AWS Cloud Console 登入後
在 EC2 的画面中左侧选择网路介面
可以看到我们为 Jenkins 建立的负载平衡器
实际上 subnets 会关系到我们建立的负载平衡器
会在每个 subnet 上各配置一个专属的 private ip
并且每个 subnet 也都会配置一个 public ip
这样在其他可用区域挂掉之後
可由其他可用区域接手继续运作
因此 AWS 负载平衡器贵虽贵
但还是贵的有其道理
可以在多个可用区域作用
可以挂 Web ACLs(虽然只有 ALB 可以用)
可以挂 SSL 凭证
https://ithelp.ithome.com.tw/upload/images/20211009/20141518y26qcLo6pc.png

理解之後呢?

负载平衡器老实说是个不小的花费
但是如果都架设在内网
可以想要的话也可以将 Gitlab 和 Jenkins 的负载平衡器拔除
Gitlab 有整合 Let's Encrypt
Jenkins 则可以考虑在上面架设 Nginx
并且自签凭证然後设定於 Nginx 上
最後再将 DNS 设定直接指向 Jenkins 和 Gitlab 的 private ip 上

刚刚说的只是其中一个做法
真心觉得 AWS 可以帮你申请套用凭证真的太方便了
因此我们不考虑把凭证改回设定在 EC2 上
但是负载平衡器会改成内部使用
不可公开存取

建立 VPN 连线

以下建立 VPN 连线的做法
是参考自这个 Youtube 影片
觉得字数太多有点烦躁的人
建议可以直接观看影片
照着影片一步一步实作

Clone easy-rsa 专案

easy-rsa 是一个可以凭证产生工具
可以使用 cli 的方式去产生及管理这些产生的 CA 和 PKI

cd /vagrant_data/project
git clone https://github.com/OpenVPN/easy-rsa.git

https://ithelp.ithome.com.tw/upload/images/20211009/20141518xirg3u81y2.png

初始化及产生 CA 凭证

cd easy-rsa/easyrsa3/
./easyrsa init-pki
./easyrsa build-ca nopass

https://ithelp.ithome.com.tw/upload/images/20211009/20141518aIvPfdmh8W.png

产生 Server 用凭证

./easyrsa build-server-full ithome-ironman.com nopass

https://ithelp.ithome.com.tw/upload/images/20211009/20141518imMGpFVDyQ.png

产生 Clien 用凭证

./easyrsa build-client-full markmewmew.ithome-ironman.com nopass

https://ithelp.ithome.com.tw/upload/images/20211009/20141518DU6P8l4XvV.png

整理凭证

刚刚根据 CA 产生的 crt 会放在 issued 底下
而 private key 则会放在 private 底下

mkdir acm
cp pki/ca.crt acm
cp pki/issued/ithome-ironman.com.crt acm
cp pki/issued/markmewmew.ithome-ironman.com.crt acm
cp pki/private/ithome-ironman.com.key acm
cp pki/private/markmewmew.ithome-ironman.com.key acm

将凭证上传到 AWS

分别将 Server Side 和 Client Side 的 凭证使用 aws cli 汇入到 aws
汇入後可以会出现凭证的 ARN

Server 凭证

aws acm import-certificate --certificate fileb://ithome-ironman.com.crt --private-key fileb://ithome-ironman.com.key --certificate-chain fileb://ca.crt --region ap-northeast-1

https://ithelp.ithome.com.tw/upload/images/20211009/20141518Gz9egQ6L4i.png

Client 凭证

aws acm import-certificate --certificate fileb://markmewmew.ithome-ironman.com.crt --private-key fileb://markmewmew.ithome-ironman.com.key --certificate-chain fileb://ca.crt --region ap-northeast-1

https://ithelp.ithome.com.tw/upload/images/20211009/20141518yVTPX1TtUI.png

汇入後我们就可以在 AWS Cloud Console 的 Cert Manager
看到我们目前使用的凭证
https://ithelp.ithome.com.tw/upload/images/20211009/201415184yXmksetB4.png

建立 Client VPN EndPoint

首先我们用 data 将汇入的凭证查询出来
让我们在建立 clien vpn endpoint 时
可以直接拿来使用

stage/main.tf

data "aws_acm_certificate" "vpn_server_cert" {
    domain   = "ithome-ironman.com"
    statuses = ["ISSUED"]
}

data "aws_acm_certificate" "vpn_client_cert" {
    domain   = "markmewmew.ithome-ironman.com"
    statuses = ["ISSUED"]
}

stage/main.tf

resource "aws_ec2_client_vpn_endpoint" "markmew_ithome_ironman" {
    description            = "markmew-ithome-ironman"
    server_certificate_arn = data.aws_acm_certificate.vpn_server_cert.arn
    client_cidr_block      = "172.31.0.0/20"
    
    authentication_options {
        type                       = "certificate-authentication"
        root_certificate_chain_arn = data.aws_acm_certificate.vpn_client_cert.arn
    }
    
    connection_log_options {
        enabled               = false
    }
}

在建立 vpn 网路的关联需要花费不少时间(大概七到十分钟)
建议这里可以先去喝杯咖啡稍待一下
网路的部分要设定 public 或 private 都没差
因为在连上之後
会因为路由的关系
只能连到 AWS 上的资源
但是外网都连不到

main.tf

resource "aws_ec2_client_vpn_network_association" "public_a" {
    client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.markmew_ithome_ironman.id
    subnet_id              = aws_subnet.public_a.id
}

resource "aws_ec2_client_vpn_network_association" "public_c" {
    client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.markmew_ithome_ironman.id
    subnet_id              = aws_subnet.public_c.id
}

resource "aws_ec2_client_vpn_network_association" "public_d" {
    client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.markmew_ithome_ironman.id
    subnet_id              = aws_subnet.public_d.id
}
terraform apply

下载 Configuration

登入 AWS Cloud Console
在 VPC 页面的侧栏中 VIRTUAL PRIVATE NETWORK (VPN) 的区块
点按 Client VPN Endpoints 进入 VPN 列表页
选择我们刚刚建立的 VPN 後
点按上方的 Download Client Configuration 即可下载设定档
https://ithelp.ithome.com.tw/upload/images/20211009/20141518Baad0MGT6E.png

修改设定档

如果有仔细前一个步骤 VPN 的资讯
会发现它给的 DNS 是 Wild Card
因此网址我们可以任意加前缀字

使用文字编辑器打开下载的 ovpn 档

remote 後方的 vpn 网址
在网址的前方加上任意网址
让其变成 sub domain

remote ithome-ironma.ncvpn-endpoint-03a3157c3d2edd90f.prod.clientvpn.ap-northeast-1.amazonaws.com 443

设定档末
加上 client 的 cert 和 key 的位置

cert /Users/mark/vagrant/data/project/easy-rsa/easyrsa3/acm/markmewmew.ithome-ironman.com.crt
key /Users/mark/vagrant/data/project/easy-rsa/easyrsa3/acm/markmewmew.ithome-ironman.com.key

https://ithelp.ithome.com.tw/upload/images/20211009/20141518R4JZbGL1er.png

安装 VPN Client

我们到这个网址 https://aws.amazon.com/tw/vpn/client-vpn-download/
下载 AWS 的 VPN Client 工具并安装

载入设定档

打开 VPN Client
选择管理描述档
将刚刚修改完後的描述档加入

https://ithelp.ithome.com.tw/upload/images/20211009/20141518c8bj4QBxZ1.png

https://ithelp.ithome.com.tw/upload/images/20211009/20141518nRSFYzlVWz.png

https://ithelp.ithome.com.tw/upload/images/20211009/20141518YWu44IGoJd.png

连上 VPN 以後
就可以直接从 web 打内网 IP 连到 Jenkins
https://ithelp.ithome.com.tw/upload/images/20211009/20141518GVNSqkpabJ.png

架构调整

VPC 设定调整

在 VPC 页面中
确认 DNS hostnamesDNS resolutions 都是 enabled
https://ithelp.ithome.com.tw/upload/images/20211009/20141518GDSSCaAyB9.png

VPN 挂载 DNS

在 AWS 上的 DNS Server
预设是 VPC 的 CIDR 固定保留给 AmazonProvidedDNS
而 AmazonProvidedDNS 可以解析 Route53 上的私有托管区域

说是这样说
但是我建立好 VPN 连线以後
SSH 到 Jenkins Server 上
查看 DHCP 套用的设定 cat /etc/resolv.conf
发现 nameserver 是 127.0.0.53
因此挂载 DNS 的部分可能会因人而异

修改刚刚设定的 aws_ec2_client_vpn_endpoint
stage/main.tf

resource "aws_ec2_client_vpn_endpoint" "markmew_ithome_ironman" {
    description            = "markmew-ithome-ironman"
    server_certificate_arn = data.aws_acm_certificate.vpn_server_cert.arn
    client_cidr_block      = "172.31.0.0/20"
    dns_servers            = ["172.31.16.2", "127.0.0.53"]
    
    authentication_options {
        type                       = "certificate-authentication"
        root_certificate_chain_arn = data.aws_acm_certificate.vpn_client_cert.arn
    }
    
    connection_log_options {
        enabled               = false
    }
}

负载平衡器调整

将 jenkins 的负载平衡器 internal 改为 true
subnets 改为 private

stage/main.tf

resource "aws_lb" "jenkins" {
    name               = "jenkins"
    internal           = true
    load_balancer_type = "application"
    security_groups    = [ aws_security_group.elb_internal.id ]
    subnets            = data.aws_subnet_ids.private_subnet_ids.ids
    
    enable_deletion_protection = false
    
    tags = {
        Creator = "Terraform"
    }
}
terraform apply

建立私有托管的网域

我们建立一个私有托管网域
跟我在 GoDaddy 上的网域名称是相同的
如此就可以营造出外部 DNS 和内部 DNS 的情境

stage/main.tf

resource "aws_route53_zone" "private" {
    name = "markmewmew.com"
    
    vpc {
        vpc_id = aws_vpc.stage.id
    }
}

resource "aws_route53_record" "jenkins" {
    zone_id = aws_route53_zone.private.zone_id
    name    = "jenkins.markmewmew.com"
    type    = "A"
    
    alias {
        name                   = aws_lb.jenkins.dns_name
        zone_id                = aws_lb.jenkins.zone_id
        evaluate_target_health = true
    }
}

VPN 连线後确认网址

连上 VPN 以後
我们打开网页确认网址是可用的

https://ithelp.ithome.com.tw/upload/images/20211009/20141518K0PP7WRkdj.png

并且试着在 vagrant 的 console 中
确认网址会解析到内部 IP

https://ithelp.ithome.com.tw/upload/images/20211009/20141518WgTJOfKqiG.png

剩下交给各位了

目前还需要调整 Gitlab 的 load balance 设定

EP13 - 灾难演练,重建你的 VPC
有教各位如何重建 EC2
而重建 Octopuse Deploy 的工作也就交给各位了

之前我们在设定 Jenkins Pipeline 时
有些还是使用 private ip
如果拔掉 public 网段中的 alb 改用 NLB
就可以同时支援 22、80、443 port 的转发
既然已经要 VPN 才能连到
其实有没有使用到 Web ACLs 的保护就是其次了

同场加印

这部分就不太重要了

如果公司规模日益增长
或是目前服务的公司规模很大
那就建议改用其他方式

Customer Gateways + Virtual Private Gateways + Site-to-Site VPN Connections
就适用於大型规模的架构
让我们可以根据 VPN 的终端设备
直接设定连到 AWS 上
并且预设支援的设备也有提供相对应的组态档

设定上就需要与公司的网管合作
并讨论如何规划目前公司内部网路
及安全性的问题

参考资料:

  1. Setup AWS Client VPN & Access Private AWS Resources Across VPCs
  2. Client VPN 入门
  3. VPC 的 DNS 支援
  4. 适用於 VPC 的 DHCP 选项集
  5. AWS Site-to-Site VPN 的运作方式

<<:  {DAY 27} Matplotlib 绘图

>>:  [从0到1] C#小乳牛 练成基础程序逻辑 Day 24 - String操纵术I 遍历综合技 Trim()

Day 26 Quantum Protocols and Quantum Algorithms

Quantum Counting Quantum search + Quantum phase es...

[Day 11] SRE - 事後检讨,拜托拜托让我吸个经验值

从历史中学习 我们最讨厌事件历史重演QQ 在每次遇到问题後,我们全员都会一起开个检讨会议,当中会提到...

Day29-实作(地图) (part1)

进入到尾声,范例的最後一片拼图,马上开始吧!!! 地图的部分使用了leaflet JS和OpenSt...

JavaScript Day01 - 说明

前言 这次主要是更新我之前的笔记,那时候刚学习 JavaScript,对於一些结果可能不是很懂,刚好...

Day-11 priority queue

Priority queue Priority queue和queue一样也有两种形式 : max ...