SnowflakeのAWS PrivateLink設定がセルフサービス対応されたので試してみた

2022.05.17

こんにちは!DA(データアナリティクス)事業本部 サービスソリューション部の大高です。

SnowflakeはSaaSのサービスではありますが、プライベート接続にも対応しており、AWS・Google Cloud、Azureの各クラウドサービスにおけるプライベート接続機能を利用して、Snowflakeとの通信をプライベート化することができます。

この設定については、これまではSnowflakeのサポートに連絡して有効化をしていただく必要がありましたが、なんと6.15のリリースにおいてAWSとAzureでは自分自身で有効化ができるようになりました。

今回は、このAWS PrivateLink設定のセルフサービス設定を実際に試してみたいと思います。

前提と検証範囲について

前提

この機能の前提として、SnowflakeのエディションはBusiness Critical(またはそれ以上)が必要となります。

SnowflakeはAWSのリージョン「ap-northeast-1」(Tokyo)の環境を利用しており、AWSアカウントについては同様に「ap-northeast-1」の環境が既に利用できる環境が整っているものとします。

検証範囲

今回は、以下のドキュメントを参照しながら進めていきます。

一方で、かなり盛り沢山ではあるので以下のポイントに絞って試してみたいと思います。

  • AWS PrivateLink の有効化
  • AWS VPC 環境の構成
    • ステップ1: VPC エンドポイント(VPCE)を作成および構成する
    • ステップ2: VPCネットワークを構成する

ですので、今回は「ステップ3: Amazon S3用 AWS VPC インターフェイスエンドポイントを作成する」については扱いません。

セルフサービス設定をしてみる

AWS PrivateLink の有効化

まずは下記のコマンドを実行し、利用するAWSアカウントの一時的なセキュリティクレデンシャルを取得します。

$ aws sts get-federation-token --name sam
{
    "Credentials": {
        "AccessKeyId": "XXX",
        "SecretAccessKey": "XXX",
        "SessionToken": "XXX",
        "Expiration": "XXX"
    },
    "FederatedUser": {
        "FederatedUserId": "XXXXXXXXXXXX:sam",
        "Arn": "arn:aws:sts::XXXXXXXXXXXX:federated-user/sam"
    },
    "PackedPolicySize": 0
}

取得した情報から、FederatedUser.FederatedUserIdに含まれる「12桁のAWSアカウントID」と、Credentials.SessionTokenの値「Federatedトークン」を利用して、以下のクエリをSnowflakeで実行します。

なお、実行時にはACCOUNTADMINロールを利用します。

USE ROLE ACCOUNTADMIN;
SELECT SYSTEM$AUTHORIZE_PRIVATELINK(
  'XXXXXXXXXXXX',
  '{
    "Credentials": {
        "AccessKeyId": "XXX",
        "SecretAccessKey": "XXX",
        "SessionToken": "XXX",
        "Expiration": "XXX"
    },
    "FederatedUser": {
        "FederatedUserId": "XXXXXXXXXXXX:sam",
        "Arn": "arn:aws:sts::XXXXXXXXXXXX:federated-user/sam"
    },
    "PackedPolicySize": 0
  }'
);

成功すると、以下のように表示されます。

プライベートリンクへのアクセスが許可されました。

確認として、上記と同様にSYSTEM$GET_PRIVATELINK関数を利用して確認してみます。

USE ROLE ACCOUNTADMIN;
SELECT SYSTEM$GET_PRIVATELINK(
  'XXXXXXXXXXXX',
  '{
    "Credentials": {
        "AccessKeyId": "XXX",
        "SecretAccessKey": "XXX",
        "SessionToken": "XXX",
        "Expiration": "XXX"
    },
    "FederatedUser": {
        "FederatedUserId": "XXXXXXXXXXXX:sam",
        "Arn": "arn:aws:sts::XXXXXXXXXXXX:federated-user/sam"
    },
    "PackedPolicySize": 0
  }'
);
プライベートリンクへのアクセスが許可されました。

大丈夫そうですね。

AWS VPC 環境の構成

次にAWS環境上でVPCエンドポイントを作成しますが、その前にVPCとサブネット、および、サブネット上にEC2インスタンスを立てておきます。

今回は以下のようなCloudFormationテンプレートを利用しました。このテンプレートでは、VPCとパブリックサブネットを作成し、その内部にWindows ServerのEC2インスタンスを作成します。また、その際のEC2インスタンスのキーペアはパラメータストアに保存されるようになっています。

なお、プライベートネットワーク経由でのSnowflake接続確認のためには「プライベートサブネット」がよいのですが、検証の利便性のために「パブリックサブネット」としています。

Windows Serverが用意できたらセキュリティグループにRemote Desktop(RDP)用の通信設定を行い、RDP接続をしてサーバーにアクセスしてブラウザが利用できる状態にしておきます。(後でSnowflakeへのブラウザアクセス確認のために利用します)

simple-windows-server-stack.yml (クリックして展開)

simple-windows-server-stack.yml

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Simple Windows Server Template.'

Parameters:
  # ------------------------------------------------------------#
  # Common
  # ------------------------------------------------------------#
  Prefix:
    Type: String
    Default: "prefix"

  # ------------------------------------------------------------#
  # Network
  # ------------------------------------------------------------#
  VpcCidr:
    Type: String
    Default: "10.0.0.0/16"

  PublicSubnetCidr:
    Type: String
    Default: "10.0.0.0/24"

  # ------------------------------------------------------------#
  # EC2
  # ------------------------------------------------------------#
  EC2InstanceName:
    Type: String
    Default: "ec2"
  EC2InstanceAMI:
    Type: AWS::EC2::Image::Id
    Default: "ami-0ec81697f08841aed" # Windows_Server-2019-Japanese-Full-Base-2022.05.11
  EC2InstanceInstanceType:
    Type: String
    Default: "t3.micro"
  EC2InstanceVolumeType:
    Type: String
    Default: "gp2"
  EC2InstanceVolumeSize:
    Type: String
    Default: "30"

Resources:
  # ------------------------------------------------------------#
  #  Network
  # ------------------------------------------------------------#
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCidr
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-vpc

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    DependsOn: Vpc
    Properties:
      Tags:
        -
          Key: Name
          Value: !Sub ${Prefix}-ig

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref Vpc
      InternetGatewayId: !Ref InternetGateway

  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref PublicSubnetCidr
      VpcId: !Ref Vpc
      AvailabilityZone:
        Fn::Select:
          - "0"
          - Fn::GetAZs: ""
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-public-subnet

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref Vpc
      Tags:
        -
          Key: Name
          Value: !Sub ${Prefix}-public-rt

  RouteViaIg:
    Type: AWS::EC2::Route
    DependsOn:
      - AttachGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable

  SecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      VpcId: !Ref Vpc
      GroupName: !Sub "${Prefix}-sg"
      GroupDescription: "-"
      Tags:
        - Key: "Name"
          Value: !Sub "${Prefix}-sg"

  # ------------------------------------------------------------#
  #  Ec2InstanceProfile
  # ------------------------------------------------------------#
  Ec2Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${Prefix}-ec2-role
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

  Ec2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Sub ${Prefix}-ec2-instance-profile
      Roles:
        - !Ref Ec2Role

  # ------------------------------------------------------------#
  #  EC2Instance
  # ------------------------------------------------------------#
  EC2KeyPair:
    Type: 'AWS::EC2::KeyPair'
    Properties:
      KeyName: !Sub ${Prefix}-key-pair

  EC2Instance:
    Type: "AWS::EC2::Instance"
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${Prefix}-${EC2InstanceName}"
      KeyName: !Ref EC2KeyPair
      ImageId: !Ref EC2InstanceAMI
      InstanceType: !Ref EC2InstanceInstanceType
      IamInstanceProfile: !Ref Ec2InstanceProfile
      DisableApiTermination: false
      EbsOptimized: false
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            VolumeType: !Ref EC2InstanceVolumeType
            VolumeSize: !Ref EC2InstanceVolumeSize
      SecurityGroupIds:
        - !Ref SecurityGroup
      SubnetId: !Ref PublicSubnet
      UserData: !Base64 |
        #! /bin/bash
        yum update -y

  EC2EIP:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId:
        Ref: EC2Instance

Outputs:
  VpcId:
    Value: !Ref Vpc

  PublicSubnetId:
    Value: !Ref PublicSubnet

  EC2ElasticIp:
    Description: EC2 Elastic IP
    Value: !Ref EC2EIP

準備ができたら次のステップに進んでいきましょう。

ステップ1: VPC エンドポイント(VPCE)を作成および構成する

Snowflake上でSYSTEM$GET_PRIVATELINK_CONFIGを呼び出して設定値を取得します。

USE ROLE ACCOUNTADMIN;
SELECT SYSTEM$GET_PRIVATELINK_CONFIG();

すると、以下のようなJSONが取得できるのでprivatelink-vpce-idprivatelink-account-urlprivatelink_ocsp-urlの値を控えておきます。

{
  "privatelink-account-name": "XXXXXXX.ap-northeast-1.privatelink",
  "privatelink-vpce-id": "com.amazonaws.vpce.ap-northeast-1.vpce-svc-XXXXXXXXXXXXXXXXX"
  "privatelink-account-url": "XXXXXXX.ap-northeast-1.privatelink.snowflakecomputing.com"
  "regionless-privatelink-account-url": "XXXXXXXXXXXXXXX.privatelink.snowflakecomputing.com"
  "privatelink_ocsp-url": "ocsp.XXXXXXX.ap-northeast-1.privatelink.snowflakecomputing.com"
  "privatelink-connection-urls": "[]"
}

次に、AWSの管理コンソールからVPCエンドポイントを作成します。

「サービスカテゴリ」は「その他のエンドポイントサービス」とし、「サービス名」に先程控えておいたprivatelink-vpce-idの値を入力して「サービスの検証」をします。

無事にサービス名が検証されたら、先程作成したVPC、サブネット、セキュリティグループを指定してエンドポイントを作成します。

また、セキュリティグループは本来であれば別途VPCエンドポイント用のセキュリティグループを作成すべきですが、今回は一旦既存のもの(先程作成したセキュリティグループ)を流用することにします。

このセキュリティグループに対しては、80ポートと443ポートの接続許可を追加します。接続元は「VPCエンドポイント」に接続するEC2インスタンスなどです。今回はCloudFormationでEC2インスタンスを作成したので、該当EC2インスタンスのプライベートIPアドレスを指定しました。

追加のCNAME記録の作成

最後にDNSまわりの設定です。Snowflakeにアクセスする際にはXXXXXXX.ap-northeast-1.privatelink.snowflakecomputing.comのような形式でアクセスしますが、この名前解決のためには権威DNSサーバに該当レコードを登録する必要があります。

今回はEC2インスタンスからのアクセスを行って検証したいと思うので、Route53にプライベートホストゾーンを作成していきます。

ドメイン名は以下のようにap-northeast-1.privatelink.snowflakecomputing.comとし、「プライベートホストゾーン」を選択します。

VPCには先程作成したVPCを指定して「ホストゾーンの作成」を行います。

ホストゾーンが作成できたら、次にレコードを作成していきます。

作成したVPCエンドポイントのDNSの名が、SnowflakeのSYSTEM$GET_PRIVATELINK_CONFIGで取得したprivatelink-account-urlprivatelink_ocsp-urlで解決できるようにCNAMEのレコードを2つ作成します。

また、同様にapp.ap-northeast-1.privatelink.snowflakecomputing.comのレコードも作成します。このレコードはオプションのようなのですが、後述の接続確認時にこのアドレスへのアクセスも行っているでしたので、併せて設定しておきます。(初回はSnowsightへのアクセスがあるようです)

作成したら設定が伝播するのを待って接続確認をしてみましょう。

接続してみる

では、実際に接続をしてみます。

作成したEC2インスタンスのWindowsサーバーにRDP接続をし、ブラウザでアクセスしてみます。

アクセス先はSnowflakeのSYSTEM$GET_PRIVATELINK_CONFIGで取得したhttps://XXXXXXX.ap-northeast-1.privatelink.snowflakecomputing.comのようなprivatelink-account-urlです。

無事にアクセスできました!

まとめ

以上、SnowflakeのAWS PrivateLink設定がセルフサービス対応されたので試してみました。

サポートケースを上げずに自分自身で設定できるようになっているのでとても助かりますね。また、私自身がSnowflakeのPrivateLink設定自体をしたことが無かったので勉強にもなりました。DNS周りの設定が少し難しかったですが、こちらのサイトを参考にさせていただきうまく設定することができました。(ありがとうございます!)

どなたかのお役に立てば幸いです。それでは!