SnowflakeでAWS PrivateLinkを利用する

# Snowflake における AWS PrivateLinkの役割

AWS上のSnowflakeサービス(VPC)に、AWSの閉域からアクセスするための機能です。

自分専用のシングルテナントのSnowflake環境が作成されるものではなく、マルチテナントの環境に閉域でアクセスするための専用の入り口が用意されます。

PrivateLinkを使う場合であっても、インターネットからSnowflakeにアクセスすることができます。
つまりセキュリティ的な観点でいえばPrivateLinkを使ったからといって完全に閉域でSnowflakeを利用できるわけではありません。インターネットからのアクセスを制限したい場合はIPアドレス制限ができるSnowflakeの[ネットワークポリシー]機能を使う必要があります。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/72184251-3c4d-3c0d-b2e4-363cb6486e3b.png)



- AWS PrivateLinkの使用条件は以下の通りです。
  - SnowflakeをBusinessCritical以上のエディションで利用していること
  - AWS VPCのリージョンとSnowflakeのリージョンが一致していること


- 大まかな手順
  1.  Snowflakeへの利用申請(アクセス元AWSアカウントID、SnowflakeアカウントIDを伝える)
  2.  AWSでVPCエンドポイントの作成(Snowflakeから指示のあったエンドポイント名を検索して作成)
  3.  権威DNSにCNAMEレコードの設定
  4.  S3のVPCエンドポイント(ゲートウェイ型)の作成

３番目の「権威DNSにCNAMEレコードの設定」という部分が気になるところです。
というのもVPCエンドポイントを作成すれば、専用のDNS名が作成されるはずです。
VPCエンドポイントのDNS名にアクセスすればいいはずなのに、なぜCNAMEレコードを追加する必要があるのでしょうか。
公式サイトを見ると、以下のように記載されています。


https://docs.snowflake.com/ja/user-guide/admin-security-privatelink.html#step-2-configure-your-vpc-network

> ①AWSPrivateLinkエンドポイントを介してSnowflakeにアクセスするには、DNSにCNAME記録を作成して、 SYSTEM$GET_PRIVATELINK_CONFIG 関数からの privatelink-account-url 値と privatelink-ocsp-url 値を許可する必要があります。

https://docs.snowflake.com/ja/user-guide/admin-security-privatelink.html#creating-additional-cname-records


> ②一部のSnowflake機能では、AWSPrivateLinkで機能を使用するために、DNSに追加のCNAME記録が必要です。
> Snowflake Data MarketplaceまたはSnowsight：app.<地域ID>.privatelink.snowflakecomputing.com
> 組織：<組織名>-<アカウント名>.privatelink.snowflakecomputing.com

公式の説明が少し分かりづらいため、番号は私が振りました。
①が必須であり、②は任意という理解であっているようです。

①の必須レコードを使わず、VPCエンドポイント名でSnowflakeにアクセスしたらどうなるのでしょうか。そのへんも検証してみます。
②のSnowsightはクエリ結果の単純な可視化ができて便利なので登録してみます。組織機能はまだ使っていないので登録は見合わせます。

手順の詳細は公式の以下ページもご確認ください。

https://docs.snowflake.com/ja/user-guide/admin-security-privatelink.html

# 具体的な手順

では検証していきます。
※この手順はAWSもSnowflakeも東京リージョンにあることを前提としたものです。

まず、SnowflakeのアカウントURLについて少し説明します。

https://docs.snowflake.com/ja/user-guide/organizations-connect.html#your-snowflake-account-url

Snowflakeのアクセス用URLは上記リンクにあるとおり、(「組織」機能を使っていない場合は)以下のような形式になっており、このアカウントロケータがユーザ固有の名前になっています。
`https://<account_locator>.<region_name>.snowflakecomputing.com`



## ①Snowflakeへの利用申請

Snowflake Communityアカウントにログインし、サポートケースを起票します。
サポートケースの起票の仕方は以下のページを参考にしてください。

https://qiita.com/jnit/items/2ff1c5be934f4399557c

- サポートケースのDescriptionでは以下の情報を記載します。
  - SnowflakeアカウントのURL
  - AWSのアカウントID(12桁)
  - AWSのアカウントのリージョン

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/4d29a061-7e0a-8696-9235-9ddadcaf8c2c.png)

申請後、最大2営業日でサポートケースからのメールが来ます。(私の場合は翌営業日にメールが来ました)
メッセージには以下のようにselect文で情報を取得してね、と記載されています。

`You can get the VPCE ID by running the system function SYSTEM$GET_PRIVATELINK_CONFIG.`

Snowflakeのワークシートにてクエリを実行します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/9aa2e497-e60a-a0ee-d2ca-65cf45d29346.png)

以下のようなJSONデータが出力されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/e5ef65bf-6cc1-1887-dc83-e7f368299ccd.png)

上記のデータを分解すると、以下のようなデータになります。

|#|Key|Value|備考|
|:-----------:|------------|------------|------------|
|1|regionless-privatelink_ocsp-url|ocsp.ocsp-xxxxxxx-xxxxxxx.privatelink.snowflakecomputing.com||
|2|privatelink-account-name|xx12345.ap-northeast-1.privatelink||
|3|privatelink-vpce-id|com.amazonaws.vpce.ap-northeast-1.vpce-svc-xxxxxxxxxxx|VPCエンドポイントで利用|
|4|privatelink-account-url|xx12345.ap-northeast-1.privatelink.snowflakecomputing.com|DNSにCNAME登録必須①|
|5|regionless-privatelink-account-url|xxxxxxx-xxxxxxx.privatelink.snowflakecomputing.com||
|6|privatelink_ocsp-url|ocsp.xx12345.ap-northeast-1.privatelink.snowflakecomputing.com|DNSにCNAME登録必須②|
|7|privatelink-connection-urls|[]||

このあとのVPCエンドポイントの検索で利用するため、3番の[privatelink-vpce-id]の値をメモしておきます。
また、のちほどCNAMEに登録するため4番、6番もメモしておきます。

## ②AWSでVPCエンドポイントの作成

Snowflakeに申請したAWSのアカウントにログインします。マネジメントコンソールから[VPC]->[エンドポイント]を選択し、[エンドポイントの作成]をクリックすると、以下の画面が表示されます。
[サービスを名前で検索]を選択し、[サービス名]に先程メモしておいたprivatelink-vpce-idの値を入力すると、正常に申請が完了していればサービス名が発見できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/6d038e1d-29ae-8e6a-ab3b-cc2cf18ac8f3.png)

紐付けたいVPCとサブネットを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/9b2ea1bd-a7be-6f28-f9a9-d6bbfa5d0571.png)


VPCエンドポイントのセキュリティグループについては適切に変更してください。
https://docs.snowflake.com/ja/user-guide/admin-security-privatelink.html#step-1-create-and-configure-a-vpc-endpoint-vpce 

> 443: 一般的なSnowflakeのトラフィックに必要です。
> 80 ：このポートですべてのSnowflakeのクライアント通信をリッスンする、Snowflake OCSP キャッシュサーバーで必要です。Snowflakeの顧客データが port 80 に流出することはありません。

VPCエンドポイントのステータスが「保留中」から「使用可能」になったことを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/57694495-6c45-08f7-23d4-00977dd03050.png)

使用可能となったあと、対象のVPCエンドポイントを選択した際に右下に表示されるDNS名をメモします。
VPC単位のDNS名、サブネット単位のDNS名等が表示されます。ここでは一番上に表示されるVPC単位のDNS名をメモします。
このDNS名をのちほどDNSのCNAMEレコードの値として登録します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/ddfc0dd2-493e-ed1e-b22d-0cb2fdb43f3b.png)


## ③権威DNSにCNAMEレコードの設定

今回のテストではSnowflakeにPrivateLinkでアクセスするクライアントはAWS上にあるEC2を想定しています。
この環境ではWindowsServerをシンプルに立てただけですので、EC2のDNSリゾルバはAmazonProvidedDNS(最近の呼称でいうとRoute 53 Resolver )を指定しています。

AmazonProvidedDNSはRoute53を参照してくれます。
そのためこれからCNAMEレコードを追加する権威DNSは、Route53を利用します。

ユーザの利用環境によってはクライアントが参照するDNSリゾルバはRoute53ではない場合もありますので、その場合は手順を読み替えてください。

Route53でゾーンを新規作成します。先程のJSONデータのアカウントURLの以下のドメイン部分のプライベートホストゾーンを作成します。
`ap-northeast-1.privatelink.snowflakecomputing.com`
これはパブリックゾーンではないため、snowflakecomputing.comからのDNS委任は必要ありません。あくまでプライベートネットワークのみで通用するゾーンになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/d908f0c0-f2d0-c236-9568-34bcbcc15682.png)

ホストゾーンを紐付けるVPCは、SnowflakeのPrivateLinkを展開したVPCを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/384f84fc-bb37-575c-4e5c-2f166a7f9e8c.png)

プライベートホストゾーンが作成されました。
繰り返しますがプライベートゾーンであるためゾーン委任は必要なく、SOAレコードもNSレコードも変更する必要はありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/2b37e9ca-5f23-c718-0c50-e8831f6f5565.png)

ではレコードを登録していきます。
全体として下表のようなものを登録します。

|#|レコード名|レコードタイプ|値|備考|
|:-----------:|------------|------------|------------|------------|
|1|(アカウントロケータ)|CNAME|(VPCエンドポイントのDNS名)|必須レコード①|
|2|ocsp.(アカウントロケータ)|CNAME|(VPCエンドポイントのDNS名)|必須レコード②|
|3|app|CNAME|(VPCエンドポイントのDNS名)|Snowflake Data MarketplaceまたはSnowsightで利用|
|4|(org_name)-(account_name) |CNAME|(VPCエンドポイントのDNS名)|「組織」機能。今回有効化していないため設定しない|

:::note info
4番はSnowflakeの「組織」機能を使う方は、事前に「組織」機能を申請・有効化し組織名・アカウント名を確認してから設定してください。今回の私の検証では組織機能を有効化していないため、設定しません。
:::

1つ目のレコードを登録します。

|レコード名|レコードタイプ|値|備考|
|--|--|--|--|
|(アカウントロケータ)|CNAME|(VPCエンドポイントのDNS名)|必須レコード①|

アカウントロケータとは先に説明したとおり、アカウントURLのうち、以下の太字の部分を指します。
**xx12345**.ap-northeast-1.privatelink.snowflakecomputing.com
このアカウントロケータは例ですので、値は環境毎で読み替えてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/c0b075ec-fae1-f587-20bd-4645aa2c6a4a.png)

2つ目のレコードを登録します。

|レコード名|レコードタイプ|値|備考|
|--|--|--|--|
|ocsp.(アカウントロケータ)|CNAME|(VPCエンドポイントのDNS名)|必須レコード②|

こちらはJSONで取得した[privatelink_ocsp-url]の値の以下の太字の部分をレコードで指定します。
**ocsp.xx12345**.ap-northeast-1.privatelink.snowflakecomputing.com
このアカウントロケータは例ですので、値は環境毎で読み替えてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/f9464cbe-4082-f8ef-45a5-d2cb9f2013b6.png)

3つ目のレコードを登録します。

|レコード名|レコードタイプ|値|備考|
|--|--|--|--|
|app|CNAME|(VPCエンドポイントのDNS名)|Snowflake Data MarketplaceまたはSnowsightで利用|

最終的にこのようなレコードになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/5550a19e-9d42-9a34-54e5-e4292c03d6ba.png)



## ④S3のVPCエンドポイント(ゲートウェイ型)の作成

こちらは省略します。
公式には以下のように記載されています。

https://docs.snowflake.com/ja/user-guide/admin-security-privatelink.html#step-3-create-gateway-endpoints-for-amazon-s3

> Snowflakeクライアント（SnowSQL、 JDBC ドライバー、 ODBC ドライバーなど）は、さまざまなランタイム操作を実行するためにS3へのアクセスを必要とします。 PrivateLink VPC ネットワークはインターネット経由のS3アクセスを許可しないため、Snowflakeクライアントに必要なS3ホスト名に1つ以上のゲートウェイエンドポイントを構成する必要があります。
> このステップは、SnowflakeクライアントからのS3トラフィックが AWS バックボーンにとどまるために必要です。

SnowflakeクライアントがS3にアクセスすることがあるようです。
その経路も閉域網化したい場合は、Snowflakeクライアントが存在するVPC上にS3のゲートウェイ型VPCエンドポイントを置くようです。
ただしゲートウェイ型のエンドポイントはVPC外からアクセスすることができないため、あくまでSnowflakeクライアントがAWSのVPC内にあることを想定した手順のようです。

今回検証したEC2のサブネットにはすでにVPCエンドポイントのS3ゲートウェイ型が設置済みでした。
もしVPC外からアクセスした場合、S3の経路だけインターネットで接続できればいいということかもしれませんが、そこまでは検証していません。


# 動作確認

## CLIによるテスト

まず、今回SnowflakeクライアントとしているEC2で、VPCエンドポイントのDNS名がプライベートIPで名前解決できるか確かめます。

```
$ nslookup vpce-xxxxxxxxxx-xxxxxxxx.vpce-svc-xxxxxxxxxxxxxxxxx.ap-northeast-1.vpce.amazonaws.com
```

CNAMEで登録したレコードも名前解決でプライベートIPが返ってくるか試します。

```
$ nslookup xxxxxxxx.ap-northeast-1.privatelink.snowflakecomputing.com
```

問題なければ、まずはVPCエンドポイントのDNS名で、ワークシートのURLにたいしてcurlコマンドでHTTPアクセスを試してみます。

```
$ curl https://vpce-xxxxxxxxxx-xxxxxxxx.vpce-svc-xxxxxxxxxxxxxxxxx.ap-northeast-1.vpce.amazonaws.com/console#/internal/worksheet
curl: (51) SSL: no alternative certificate subject name matches target host name 'vpce-xxxxxxxxxx-xxxxxxxx.vpce-svc-xxxxxxxxxxxxxxxxx.ap-northeast-1.vpce.amazonaws.com'
```

SSL/TLS証明書エラーで怒られてしまいましたね。
ホスト名が一致せず、このようなエラーが出る場合、証明書を自分で持っている場合は証明書側のSAN(Subject Alternative Name)に代替ホスト名を登録するような対応をしますが、証明書管理はSnowflake側なのでどうにもしようがないです。
苦し紛れに、警告を無視するオプション -k をつけて試してみます。

```
$ curl -k https://vpce-xxxxxxxxxx-xxxxxxxx.vpce-svc-xxxxxxxxxxxxxxxxx.ap-northeast-1.vpce.amazonaws.com/console#/internal/worksheet
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
</body>
</html>
```

404 Not Foundになってしまいました。

ではCNAME登録した名前でアクセスしてみます。

```
$ curl https://xxxxxxxx.ap-northeast-1.privatelink.snowflakecomputing.com/console#/internal/worksheet
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta charset="UTF-8" />
    <meta
(snip)
```

コンテンツが正常に取得できました。

## ブラウザによるテスト

今度は同じEC2でブラウザからのアクセスを試してみます。

### VPCエンドポイントのDNS名によるテスト

まずはVPCエンドポイントのDNS名でアクセスしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/60332f7b-07cd-ef72-f500-9d6374ca7d00.png)

証明書不一致警告が出たため、不一致を無視してアクセスしてみると、、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/12fa96d2-f9c1-7ec1-bc19-35bfde0178a9.png)

404 Not Foundでアクセスに失敗しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/c9dff7fc-9e55-2dc3-1de7-748b246a6f78.png)

***この結果から推測すると、SnowflakeのWebサーバは(apacheでいう)VirtualHostのような機能をつかってアクセス時のドメイン名をチェックし、それでユーザ毎のテナントに振り分けていると考えられます。***

そう考えると当初の疑問、「なぜCNAMEレコードを追加する必要があるのか」は、「SnowflakeのPrivateLinkを使う上で必須だから」といえそうです。

:::note info
DNSに作成するゾーンは企業の独自ドメイン名ではなく、(東京リージョンの場合は)「ap-northeast-1.privatelink.snowflakecomputing.com」のようなsnowflake固有のドメイン名である必要があります。
ためしに独自ドメインでもテストを行いましたが、"403 Forbidden"が返ってきてエラーとなりました。
:::

### CNAMEを設定したDNS名によるテスト

#### Snowflakeコンソール

以下のようなURLにアクセスすると、問題なくログイン画面が表示され、ログインもできました。
`https://xx12345.ap-northeast-1.privatelink.snowflakecomputing.com/console#/internal/worksheet`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/9dcc58ca-3369-1c5e-66a4-03d6bc09f109.png)

#### Snowsightコンソール

Snowsightも試してみます。
Snowflakeコンソールから「アプリをプレビュー」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/9e61c9a3-c451-eaa8-1853-f0347fbb96af.png)

CNAMEに登録したドメイン名にリダイレクトされます。
`https://app.ap-northeast-1.privatelink.snowflakecomputing.com/ap-northeast-1.aws/(アカウントロケータ)`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/63225425-cf8b-7783-553a-c77e316fee77.png)

サインインすると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/fe739bca-9c58-ad30-8912-acabc34c6b72.png)

Snowsightに正常にアクセスできました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/b4518ee6-1b1f-f464-6a06-02de4e0dba99.png)

Snowsightのチャートも正常に表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/15f92d05-64df-9a43-81e6-2b94b7d49c93.png)


## システム連携テスト(Tableau)

AWS内のWindowsServerからテストするため、SnowflakeのODBCドライバをダウンロードしインストールします。
インストール後、Windowsの[ODBC データソースアドミニストレーター]を起動し、セットアップします。
(後ほどTableauからの接続でも同じような設定をするので、もしかしたらセットアップは不要で、ODBCドライバのインストールだけをすればよいのかもしれません)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/aa1f5d9d-ff62-2829-2afe-fab7345950b5.png)

### CNAMEを設定したDNS名によるテスト

ODBCドライバのセットアップで指定するServerはCNAME登録済みのドメイン（以下例）です。

`xx12345.ap-northeast-1.privatelink.snowflakecomputing.com`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/a44d9fe5-7d68-0a6e-f35b-39a69740fefe.png)

ODBCドライバをセットアップしたあとは、TableauのコネクタでSnowflakeを選択すればSnowflake専用の接続画面が出てきます。
Tableauの[サーバー]欄は先程ODBCドライバに入力したものと同じものを指定します。

`xx12345.ap-northeast-1.privatelink.snowflakecomputing.com`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/7f0dff31-9003-56ca-2b16-dc5fc280b1c6.png)

無事プライベートリンクのドメイン名で接続ができました。
接続時の[役割]でACCOUNT ADMINを指定したため、SNOWFLAKEデータベースも見えています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/b6e88502-d0c7-9f7e-2e1c-6e15025ef0a8.png)

# まとめ

- 以下の検証を行い、正常にアクセスできることを確認しました。
  - CNAMEを設定したDNS名でSnowflakeコンソールにブラウザから閉域接続できること
  - CNAMEを設定したDNS名でSnowsightコンソールにブラウザから閉域接続できること
  - CNAMEを設定したDNS名でTableauからSnowflakeにシステム的な閉域接続できること
  - CNAMEの登録せずにVPCエンドポイントのDNS名を使うと正常にアクセスができないこと(404 NotFoundになる)

- 今回の例では AWS(EC2) -> AWS(Snowflake)の通信でしたので、以下の公式FAQにあるとおり、仮にグローバルIPでアクセスしたとしてもAWS構内経路で完結するはずです(試してはいません)。しかしアクセス元がDirectConnectを通したオンプレにあるような場合では、やはりこのPrivteLinkの機能で閉域を通すのは有効な方法ではあります。

https://aws.amazon.com/jp/vpc/faqs/

> Q:2 つのインスタンスがパブリック IP アドレスを使用して通信する場合、またはインスタンスが AWS のサービスのパブリックエンドポイントと通信する場合、トラフィックはインターネットを経由しますか?

> いいえ。パブリックアドレススペースを使用する場合、AWS でホストされているインスタンスとサービス間のすべての通信は AWS のプライベートネットワークを使用します。AWS ネットワークから発信され、AWS ネットワーク上の送信先を持つパケットは、AWS 中国リージョンとの間のトラフィックを除いて、AWS グローバルネットワークにとどまります。

以上です。