Egress Networkingを考えてから、ROSA（STS+Private Link）をつくる

## 注記

* ROSAとはあんまり関係のない、URLフィルタリング可能なOutboundアクセスの経路をAWS上につくります。
* 作った上で、ROSAのインストールが成功するかを検証してみます。

## ROSA (Red Hat OpenShift on AWS) on Bring Your Own VPC
* この構成をとった場合でもアウトバウンドのアクセスは必要
* しかもこのアクセスはHTTP Proxyを通せない（設定できない）
* よって、ROSAクラスタからL3のルーティングでアウトバウンドの経路を確保しないといけない
* ただ、普通にルーティングすると、ROSAクラスタ上のコンテナからも自由にアウトバウンドできてしまう
* せめて、URLフィルタリングするなり、ログを取るなりしないと、怒られる＞＜

## 構成

Network Firewallの機能で、URLフィルタリングできそうだったので、この構成を目指す。
（ピンクの線は自信ない...Network FirewallはNATしてくれないらしいから、一旦NAT Gatewayに戻ってる、NAT GatewayのEIPで外に出ていっている...はず。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/e72fd7f2-dc13-35c3-117a-dffc8d0ba60a.png)


## 構築

環境変数をセットアップ。

```bash
export ROSA_CLUSTER_NAME=rosa
export REGION=ap-northeast-1
export AZ=ap-northeast-1a
export VPC_CIDR=10.0.0.0/16
export NWFW_CIDR=10.0.10.0/24
export NAT_CIDR=10.0.20.0/24
export ROSA_CIDR=10.0.30.0/24
```

VPC作る

```bash
# === VPC ===
VPC_ID=`aws ec2 create-vpc --cidr-block $VPC_CIDR | jq -r .Vpc.VpcId`
aws ec2 create-tags --resources $VPC_ID --tags Key=Name,Value=$ROSA_CLUSTER_NAME-vpc
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-hostnames
```

Subnetも作る（３つ）

```bash
# === Subnet ===
NWFW_SUBNET=`aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block $NWFW_CIDR --availability-zone $AZ | jq -r .Subnet.SubnetId`
NAT_SUBNET=`aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block $NAT_CIDR | jq -r .Subnet.SubnetId`
ROSA_SUBNET=`aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block $ROSA_CIDR | jq -r .Subnet.SubnetId`
aws ec2 create-tags --resources $NWFW_SUBNET --tags Key=Name,Value=$ROSA_CLUSTER_NAME-nwfw
aws ec2 create-tags --resources $NAT_SUBNET --tags Key=Name,Value=$ROSA_CLUSTER_NAME-nat
aws ec2 create-tags --resources $ROSA_SUBNET --tags Key=Name,Value=$ROSA_CLUSTER_NAME-rosa
```

IGWも作って、VPCにくっつける

```bash
# === IGW ===
I_GW=`aws ec2 create-internet-gateway | jq -r .InternetGateway.InternetGatewayId`
aws ec2 attach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $I_GW
aws ec2 create-tags --resources $I_GW --tags Key=Name,Value=$ROSA_CLUSTER_NAME-igw
```

NAT Gateway 作って、真ん中のSubnetに置く
[このNAT GatewayはPrivateじゃだめで、EIPが必要だった](https://aws.amazon.com/jp/blogs/news/networking-and-content-delivery-deployment-models-for-aws-network-firewall/)。

> この展開モデルでは、Network Firewallを使用して、あらゆるインターネット行きのトラフィックを保護します。ワークロードのサブネットには、対応するAZのファイアウォールエンドポイントへのデフォルトルートがあります。Firewallサブネットには、IGW経由のデフォルトルートがあります。Network FirewallはNATを行わないため、インターネットへの出入りは、ワークロードサブネット内の個々のElastic Network Interface（ENI）に関連付けられたパブリックIPまたはEIPに依存します。IGWではVPC Ingress Routingを利用して戻りのトラフィックを対応するAZのファイアウォールのエンドポイントに誘導します。これにより、トラフィックは正しいファイアウォールエンドポイントに対称的に戻され、ステートフルトラフィックインスペクションを維持することができます。

```
# === NAT GW ===
EIP=`aws ec2 allocate-address --domain vpc | jq -r .AllocationId`
NAT_GW=`aws ec2 create-nat-gateway --subnet-id $NAT_SUBNET --allocation-id $EIP | jq -r .NatGateway.NatGatewayId`
aws ec2 create-tags --resources $EIP --resources $NAT_GW --tags Key=Name,Value=$ROSA_CLUSTER_NAME-natgw
```

Network Firewall作るんだけど、
Rule Groupつくって、Firewall Policyつくってからじゃないと作れないので、順番に作る。
ここで、`example.com`へのアクセスを`ALLOWLIST`に入れている。

```
# === NWFW ===
cat <<EOF > rule-group.json
{
    "RulesSource": {
        "RulesSourceList": {
            "Targets": [
                "example.com"
            ],
            "TargetTypes": [
                "HTTP_HOST",
                "TLS_SNI"
            ],
            "GeneratedRulesType": "ALLOWLIST"
        }
    }
}
EOF
StatefulRuleGroup=`aws network-firewall create-rule-group --rule-group-name $ROSA_CLUSTER_NAME-rule-group --type STATEFUL --capacity 100 --rule-group file://rule-group.json | jq -r .RuleGroupResponse.RuleGroupArn`

cat <<EOF > firewall-policy.json
{
    "StatelessRuleGroupReferences": [],
    "StatelessDefaultActions": [
        "aws:forward_to_sfe"
    ],
    "StatelessFragmentDefaultActions": [
        "aws:forward_to_sfe"
    ],
    "StatelessCustomActions": [],
    "StatefulRuleGroupReferences": [
        {
            "ResourceArn": "$StatefulRuleGroup"
        }
    ]
}
EOF
FirewallPolicyArn=`aws network-firewall create-firewall-policy --firewall-policy-name $ROSA_CLUSTER_NAME-fw-policy --firewall-policy file://firewall-policy.json | jq -r .FirewallPolicyResponse.FirewallPolicyArn`
sleep 120;
FirewallArn=`aws network-firewall create-firewall --firewall-name $ROSA_CLUSTER_NAME-nwfw --firewall-policy-arn $FirewallPolicyArn --vpc-id $VPC_ID --subnet-mappings SubnetId=$NWFW_SUBNET | jq -r .Firewall.FirewallArn`
sleep 120;
VPC_E=`aws network-firewall describe-firewall --firewall-arn $FirewallArn | jq -r .FirewallStatus.SyncStates'["ap-northeast-1a"]'.Attachment.EndpointId`
```

各サブネットからのゲートウェイがそれぞれできたので、（IGW、Network FirewallのVPCe、NAT GW）
Route Tableをつくる。

```
# === ROUTE TABLE ===
NWFW_R_TABLE=`aws ec2 create-route-table --vpc-id $VPC_ID | jq -r .RouteTable.RouteTableId`
aws ec2 create-route --route-table-id $NWFW_R_TABLE --destination-cidr-block 0.0.0.0/0 --gateway-id $I_GW
aws ec2 associate-route-table --subnet-id $NWFW_SUBNET --route-table-id $NWFW_R_TABLE
aws ec2 create-tags --resources $NWFW_R_TABLE --tags Key=Name,Value=$ROSA_CLUSTER_NAME-nwfw-rt

NAT_R_TABLE=`aws ec2 create-route-table --vpc-id $VPC_ID | jq -r .RouteTable.RouteTableId`
aws ec2 create-route --route-table-id $NAT_R_TABLE --destination-cidr-block 0.0.0.0/0 --vpc-endpoint-id $VPC_E
aws ec2 associate-route-table --subnet-id $NAT_SUBNET --route-table-id $NAT_R_TABLE
aws ec2 create-tags --resources $NAT_R_TABLE --tags Key=Name,Value=$ROSA_CLUSTER_NAME-nat-rt

ROSA_R_TABLE=`aws ec2 create-route-table --vpc-id $VPC_ID | jq -r .RouteTable.RouteTableId`
aws ec2 create-route --route-table-id $ROSA_R_TABLE --destination-cidr-block 0.0.0.0/0 --gateway-id $NAT_GW
aws ec2 associate-route-table --subnet-id $ROSA_SUBNET --route-table-id $ROSA_R_TABLE
aws ec2 create-tags --resources $ROSA_R_TABLE --tags Key=Name,Value=$ROSA_CLUSTER_NAME-rosa-rt

IGW_R_TABLE=`aws ec2 create-route-table --vpc-id $VPC_ID | jq -r .RouteTable.RouteTableId`
aws ec2 create-route --route-table-id $IGW_R_TABLE --destination-cidr-block $NAT_CIDR --vpc-endpoint-id $VPC_E
aws ec2 associate-route-table --gateway-id $I_GW --route-table-id $IGW_R_TABLE
aws ec2 create-tags --resources $IGW_R_TABLE --tags Key=Name,Value=$ROSA_CLUSTER_NAME-igw-rt

```

あとは、適当に踏み台のEC2と最奥のEC2を作る。（割愛）

## テスト

cURLで試す。うまく動いてそう。

```bash
$ curl -I https://example.com
HTTP/2 200 
...snip...
$ curl -I https://google.com
(応答なし)
```

## 片付け

非同期でレスが返ってくるので、ちゃんと消えてからじゃないと、次のコマンド打てない。

```bash
# Delete Route
aws ec2 delete-route --route-table-id $ROSA_R_TABLE --destination-cidr-block 0.0.0.0/0
aws ec2 delete-route --route-table-id $NAT_R_TABLE --destination-cidr-block 0.0.0.0/0
aws ec2 delete-route --route-table-id $NWFW_R_TABLE --destination-cidr-block 0.0.0.0/0
aws ec2 delete-route --route-table-id $IGW_R_TABLE --destination-cidr-block $NAT_CIDR

# Delete Network Firewall
aws network-firewall delete-firewall --firewall-name $ROSA_CLUSTER_NAME-nwfw
aws network-firewall delete-firewall-policy --firewall-policy-name $ROSA_CLUSTER_NAME-fw-policy --firewall-policy-arn $FirewallPolicyArn
aws network-firewall delete-rule-group --rule-group-name $ROSA_CLUSTER_NAME-rule-group --type STATEFUL

# Delete NAT Gateway & EIP
aws ec2 delete-nat-gateway --nat-gateway-id $NAT_GW
aws ec2 release-address --allocation-id $EIP

# Delete Internet Gateway
aws ec2 detach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $I_GW
aws ec2 delete-internet-gateway --internet-gateway-id $I_GW

# Delete Subnet
aws ec2 delete-subnet --subnet-id $NWFW_SUBNET
aws ec2 delete-subnet --subnet-id $NAT_SUBNET
aws ec2 delete-subnet --subnet-id $ROSA_SUBNET

# Delete Route Table
aws ec2 delete-route-table --route-table-id $NWFW_R_TABLE
aws ec2 delete-route-table --route-table-id $NAT_R_TABLE
aws ec2 delete-route-table --route-table-id $ROSA_R_TABLE
aws ec2 delete-route-table --route-table-id $IGW_R_TABLE

# Delete VPC
aws ec2 delete-vpc --vpc-id $VPC_ID
```

## ROSAの構築

### 前提

* Network Firewallの制限は切っておく。代わりにロギングをONにしておく
* `rosa`コマンドのインストールが済んでいる
* `rosa login`が済んでいる
* `rosa init`が未済である（実行するとosdCcsAdminというIAM Userが作られてしまうため）

### ROSAのインストール

```bash
# 環境変数をセット
export VERSION=4.8.5 \
         ROSA_CLUSTER_NAME=rosacluster \
         AWS_ACCOUNT_ID=`aws sts get-caller-identity --query Account --output text` \
         REGION=ap-northeast-1 \
         AWS_PAGER=""

# AWSの権限が十分かを確認する
rosa verify permissions

# ROSAに必要な各種RoleやPolicyをつくる
rosa create account-roles --mode auto --version "${VERSION%.*}" -y

# ROSAクラスタのインストールを開始する
rosa create cluster -y --sts --cluster-name ${ROSA_CLUSTER_NAME} \
    --region ${REGION} --version ${VERSION} \
    --subnet-ids=$ROSA_SUBNET \
    --private-link --machine-cidr=$VPC_CIDR \
    --support-role-arn arn:aws:iam::${AWS_ACCOUNT_ID}:role/ManagedOpenShift-Support-Role \
    --role-arn arn:aws:iam::${AWS_ACCOUNT_ID}:role/ManagedOpenShift-Installer-Role \
    --master-iam-role arn:aws:iam::${AWS_ACCOUNT_ID}:role/ManagedOpenShift-ControlPlane-Role \
    --worker-iam-role arn:aws:iam::${AWS_ACCOUNT_ID}:role/ManagedOpenShift-Worker-Role
```

### OIDCの設定（Red Hat SREチーム向け）

```bash
rosa create operator-roles -c $ROSA_CLUSTER_NAME --mode auto --yes
rosa create oidc-provider -c $ROSA_CLUSTER_NAME --mode auto --yes
```

### ROSAのインストールの進捗確認

```bash
watch "rosa describe cluster -c $ROSA_CLUSTER_NAME"
```

## Network Firewall の フローログ

そこそこ取れてるねぇ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/0d1c5a41-9336-d7ba-1ef0-6e5daf144a22.png)

外に飛んでったそれっぽい通信をユニークかけてみた。んんん～？宛先はS3かぁ～？

|dst_ip |dst_port |
|:--|:--|
| 52.119.222.137  | 443             |
| 52.219.1.87     | 443             |
| 52.219.16.250   | 443             |
| 52.219.68.43    | 443             |
| 52.219.8.226    | 443             |
| 93.184.216.34   | 443             |
| 93.184.216.34   | 80              |
|-----------------|-----------------|


## インストールはできた

めでたし、めでたし。

```bash
$ rosa logs install -c $ROSA_CLUSTER_NAME 
I: Cluster 'rosa' has been successfully installed
```

## ROSAのお片付け（IAM RoleやPolicy編）

ただのメモ。きたねぇ花火だ。

```bash
aws iam list-roles | jq -r '.Roles[] | select(.RoleName | startswith("ManagedOpenShift")) | .RoleName' | xargs -I% aws iam delete-role-policy --role-name % --policy-name %-Policy
InstanceProfileName=`aws iam list-instance-profiles | jq -r '.InstanceProfiles[] | select(.Roles[].RoleName | startswith("ManagedOpenShift")) | .InstanceProfileName'`
RoleName=`aws iam list-instance-profiles | jq -r '.InstanceProfiles[].Roles[] | select(.RoleName | startswith("ManagedOpenShift")) | .RoleName'`
aws iam remove-role-from-instance-profile --instance-profile-name $InstanceProfileName --role-name $RoleName
aws iam delete-role --role-name ManagedOpenShift-Installer-Role
aws iam delete-role --role-name ManagedOpenShift-ControlPlane-Role
aws iam delete-role --role-name ManagedOpenShift-Worker-Role
aws iam delete-role --role-name ManagedOpenShift-Support-Role
aws iam list-roles | jq -r ".Roles[] | select(.RoleName | startswith(\"$ROSA_CLUSTER_NAME\")) | .RoleName" | xargs -I% aws iam delete-role --role-name %
for i in `seq 5`                                                      
do
  RoleName=`aws iam list-roles | jq -r ".Roles[] | select(.RoleName | startswith(\"$ROSA_CLUSTER_NAME\")) | .RoleName" | head -n 1`
  PolicyArn=`aws iam list-attached-role-policies --role-name $RoleName | jq -r .AttachedPolicies[].PolicyArn`
  aws iam detach-role-policy --policy-arn $PolicyArn --role-name $RoleName
  aws iam delete-role --role-name $RoleName
done
aws iam delete-policy --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/ManagedOpenShift-openshift-machine-api-aws-cloud-credentials
aws iam delete-policy --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/ManagedOpenShift-openshift-cloud-credential-operator-cloud-crede
aws iam delete-policy --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/ManagedOpenShift-openshift-image-registry-installer-cloud-creden
aws iam delete-policy --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/ManagedOpenShift-openshift-ingress-operator-cloud-credentials
aws iam delete-policy --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/ManagedOpenShift-openshift-cluster-csi-drivers-ebs-cloud-credent
OPENID_PROVIDER_ARN=`aws iam list-open-id-connect-providers | jq -r .OpenIDConnectProviderList[].Arn`
aws iam delete-open-id-connect-provider --open-id-connect-provider-arn $OPENID_PROVIDER_ARN
aws iam list-roles | jq '.Roles[] | select(.RoleName | startswith("rosa")) | .RoleName' | xargs -I% aws iam delete-role --role-name %
```