S3にaptリポジトリを作成してプライベートサブネットから参照する

S3にaptリポジトリを作成してプライベートサブネットから参照する

プライベートサブネット内のUbuntuから、S3上の自作aptリポジトリにアクセスする

以下のページで、S3上にyumリポジトリを作成する方法をご紹介しました。

あわせて読みたい
S3にyumリポジトリを作成してプライベートサブネットから参照する 【プライベートサブネット内のAmazon Linux  2から、S3上の自作yumリポジトリにアクセスする】 Amazon Linux  (2)の場合、プライベートサブネットからでも、S...

今回はS3上でaptリポジトリを構築し、プライベートサブネット上に配置されたUbuntuからアクセスします。

構築する環境

Diagram of create apt Repository in S3 and Access from Private Subnet.

2つのVPCを作成します。

一方のVPCはaptリポジトリ作成用です。
最新のUbuntuインスタンスを配置し、aptlyを使用してリポジトリを作成します。

https://www.aptly.info/

今回はプライベートサブネットからApacheをインストールするためのリポジトリを作成します。

もう一方のVPCに、自作リポジトリの検証インスタンスを配置します。
こちらのVPCにはインターネットゲートウェイやNATゲートウェイを配置せず、S3用VPCエンドポイントを経由して、S3バケットにアクセスします。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置してます。

https://github.com/awstut-an-r/awstut-fa/tree/main/032

テンプレートファイルのポイント解説

静的Webサイトホスティング機能を有効化する

S3バケットを確認します。
ポイントは静的Webサイトホスティングに関する設定です。

Resources:
  Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref Prefix
      AccessControl: Private
      WebsiteConfiguration:
        IndexDocument: index.html
Code language: YAML (yaml)

WebsiteConfigurationプロパティで静的Webサイトホスティング機能を設定します。
本機能を有効化することで、aptクライアントからのHTTP通信を受け付けることができます。

注意点はIndexDocumentプロパティです。
本プロパティに「index.html」を設定します。
これは本プロパティを設定しなければ、ホスティング機能が有効化できないためです。
そのため実際にインデックスファイルを設置するわけではありませんが、形式的に設定します。

バケットポリシーでアクセス制限する

作成したバケットへのアクセスを、バケットポリシーを使って制限します。
ポイントはアクセスを許可する条件です。
今回は以下の方針でアクセス制限します。

  • 送信元のIPアドレスが、NATゲートウェイに付与したElastic IPアドレスの場合は、アクセスを許可する ※ VPC1内のインスタンス用
  • 送信元のVPCがVPC2の場合は、アクセスを許可する ※ VPC2内のインスタンス用
Resources:
  BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref Bucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - s3:*
            Effect: Allow
            Resource:
              - !Sub "arn:aws:s3:::${Bucket}"
              - !Sub "arn:aws:s3:::${Bucket}/*"
            Condition:
              IpAddress:
                "aws:SourceIp":
                  - !Ref EIP
            Principal: "*"
          - Action:
              - s3:*
            Effect: Allow
            Resource:
              - !Sub "arn:aws:s3:::${Bucket}"
              - !Sub "arn:aws:s3:::${Bucket}/*"
            Condition:
              StringEquals:
                "aws:SourceVpc":
                  - !Ref VPC
            Principal: "*"
Code language: YAML (yaml)

2つのポリシーを定義します。

1つ目のポリシーは、インスタンス1からのアクセスを許可するためのものです。
Conditionプロパティがポイントです。
IpAddressプロパティに「aws:SourceIp」およびNATゲートウェイに付与したElastic IPアドレスを設定することで、同アドレスからの通信を許可するポリシーとなります。
インスタンス1がS3バケットにアクセスする際は、NATゲートウェイを経由し、送信元アドレスがElastic IPアドレスに付け替わるためです。

2つ目のポリシーは、インスタンス2からのアクセスを許可するためのものです。
こちらもConditionプロパティで条件を設定します。
StringEqualsプロパティに「aws:SourceVpc」およびVPC2のIDを設定することで、同VPC内からの通信を許可するポリシーとなります。

S3用VPCエンドポイント

VPC2はインターネットとの接点を作成せず、VPCエンドポイントを通じて、S3バケットへアクセスします。

Resources:
  S3Endpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds:
        - !Ref PrivateRouteTable
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
      VpcId: !Ref VPC
Code language: YAML (yaml)

特別な設定は不要です。
ServiceNameプロパティにS3を設定します。
S3用のVPCエンドポイントはゲートウェイタイプですから、VPC2のIDおよび同VPC内のサブネットに関連づいたルートテーブルを設定します。

aptlyでリポジトリを作成する

aptリポジトリを作成するために、インスタンス1で実行する内容を確認します。

Resources:
  RunShellScriptAssociation1:
    Type: AWS::SSM::Association
    Properties:
      AssociationName: !Sub "${Prefix}-runshellscript-association1"
      Name: AWS-RunShellScript
      Parameters:
        commands:
          - "sudo apt update"
          - "sudo apt install -y aptly"
          - "sudo apt install -y unzip"

          - "curl 'https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip' -o 'awscliv2.zip'"
          - "unzip awscliv2.zip"
          - "sudo ./aws/install"


          - !Sub |
              sudo cat << EOF > ./gpg.txt
              Key-Type: RSA
              Subkey-Type: RSA
              Key-Length: 2048
              Subkey-Length: 2048
              Expire-Date: 0
              Name-Real: ${GPGName}
              Name-Email: ${GPGEmail}
              Passphrase: ${GPGPassphrase}
              EOF
          - "sudo gpg1 --gen-key --batch ./gpg.txt"

          - "sudo gpg1 --no-default-keyring --keyring trustedkeys.gpg --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5 3B4FE6ACC0B21F32"
          - !Sub "sudo aptly mirror create -architectures=${Arch} -filter='apache2' -filter-with-deps myrepo http://ap-northeast-1a.clouds.ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe multiverse"
          - !Sub "sudo aptly mirror update ${Repository}"
          - !Sub "sudo aptly snapshot create ${Repository}-${SnapshotSuffix} from mirror ${Repository}"

          - !Sub |
              sudo cat << EOF > /root/.aptly.conf
              {
                "rootDir": "/root/.aptly",
                "downloadConcurrency": 4,
                "downloadSpeedLimit": 0,
                "architectures": [],
                "dependencyFollowSuggests": false,
                "dependencyFollowRecommends": false,
                "dependencyFollowAllVariants": false,
                "dependencyFollowSource": false,
                "dependencyVerboseResolve": false,
                "gpgDisableSign": false,
                "gpgDisableVerify": false,
                "gpgProvider": "gpg",
                "downloadSourcePackages": false,
                "skipLegacyPool": true,
                "ppaDistributorID": "ubuntu",
                "ppaCodename": "",
                "skipContentsPublishing": false,
                "FileSystemPublishEndpoints": {},
                "S3PublishEndpoints": {
                  "fa-032": {
                    "region": "${AWS::Region}",
                    "bucket": "${Bucket}"
                  }
                },
                "SwiftPublishEndpoints": {}
              }
              EOF

          - !Sub "sudo aptly publish snapshot -batch -passphrase='${GPGPassphrase}' ${Repository}-${SnapshotSuffix} s3:${Bucket}:"

          - "sudo gpg1 --export --armor > ./gpg.pub"
          - !Sub "aws s3 cp ./gpg.pub s3://${Bucket}/"
      Targets:
        - Key: InstanceIds
          Values:
            - !Ref Instance1
      WaitForSuccessTimeoutSeconds: !Ref WaitForSuccessTimeoutSeconds
Code language: YAML (yaml)

今回はインスタンス初期化処理の一環として、SSMドキュメントAWS-RunShellScriptを実行し、リポジトリ作成を行います。
同ドキュメントを使用した初期化処理に関しては、以下のページをご確認ください。

あわせて読みたい
Linuxインスタンスの初期化方法4選 【Linuxインスタンスを初期化する4つの方法】 EC2インスタンスの起動時に初期化処理を実行する方法を考えます。 EC2インスタンスを構築時に初期化する以下の4つの手法を...

冒頭でも述べた通り、aptlyを使用してリポジトリを作成し、S3バケットに公開します。

commandsプロパティで、実際に実行するコマンドを定義します。
コマンドの内容は以下の通りです。

  • 事前準備(パッケージ更新、aptlyインストール、AWS CLIインストール)
  • バッチモードのGnuPGで公開鍵を作成する
  • 元リポジトリ(http://ap-northeast-1a.clouds.ports.ubuntu.com/ubuntu-ports/)用の公開鍵を取得して、ミラーリポジトリを作成し、スナップショットを作成する
  • aptlyのコンフィグファイルにS3バケットの情報を記載し、バッチモードでS3バケットにスナップショットを公開する。
  • リポジトリ署名時に使用した鍵の公開鍵をエクスポートし、AWS CLIを使用してS3バケットにアップロードする。

注意点はaptlyが対応しているGnuPGのバージョンです。

Aptly at this time only supports GNUPG 1.x for server-side use. On newer Debian systems you’ll want to make sure that the gnupg1 and gpgv1 packages are installed.

PGP PROVIDERS

上記の通り、1系にのみ対応しているとされていますので、2系を使用しないように注意してください。
ですから今回は「gpg」コマンドではなく、「gpg1」コマンドを使用することで、明示的に1系を使用しています。

もう1つの注意点は、aptlyからS3バケットにアクセスするための権限についてです。
公式サイトでは、必要な権限を持つIAMユーザーのアクセスキーを使用する方法が紹介されています。

awsAccessKeyID, awsSecretAccessKey: (optional) Amazon credentials to access S3 bucket. If not supplied, environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are used.

PUBLISHING TO AMAZON S3

ただし今回は上記で記されているようなパラメータ設定や、環境変数の指定は不要です。
これは先述のS3バケットポリシー側で、両インスタンスからのアクセスを許可しているためです。

なおaptlyの使用方法については、以下のサイトを参照しました。

https://gihyo.jp/admin/serial/01/ubuntu-recipe/0485

https://qiita.com/roki18d/items/0f63a85292df1ef511c5

aptに自作リポジトリを登録する

インスタンス2で実行する内容を確認します。
ポイントは自作したaptリポジトリを参照する方法です。

Resources:
  RunShellScriptAssociation2:
    Type: AWS::SSM::Association
    DependsOn:
      - RunShellScriptAssociation1
    Properties:
      AssociationName: !Sub "${Prefix}-runshellscript-association2"
      Name: AWS-RunShellScript
      Parameters:
        commands:
          - !Sub "curl ${BucketWebsiteURL}/gpg.pub | sudo apt-key add -"
          - !Sub |
              sudo cat << EOF > /etc/apt/sources.list
              deb ${BucketWebsiteURL}/ xenial main
              EOF
      Targets:
        - Key: InstanceIds
          Values:
            - !Ref Instance2
      WaitForSuccessTimeoutSeconds: !Ref WaitForSuccessTimeoutSeconds
Code language: YAML (yaml)

こちらもSSMドキュメントAWS-RunShellScriptを実行して、自作リポジトリ参照に必要な処理を行います。
コマンドの内容は以下の通りです。

  • S3バケットに配置した公開鍵をインポートする
  • 自作リポジトリを記載した/etc/apt/sources.listを配置する

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • S3バケット:fa-032
  • S3バケットのWebサイトエンドポイント:http://fa-032.s3-website-ap-northeast-1.amazonaws.com
  • インスタンス1:i-09467a9a4e6bb9bc4
  • インスタンス2:i-063843a7185abe446

AWS Management Consoleからも、リソースの作成状況を確認します。
まずインスタンスの作成状況です。

Two Ubuntu instances are created.

確かに2つのインスタンスが作成されています。

次に両インスタンスにおけるSSMドキュメントの実行結果を確認します。

SSM Document Execution Result 1.
SSM Document Execution Result 2.

Outputから実行結果を確認することができます。

S3バケットを確認します。

Snapshots are published in S3 aptly.

aptlyでアップロードされたリポジトリファイルと、このリポジトリの公開鍵が配置されていることがわかります。

S3 website hosting is enabled.

静的Webサイトホスティング機能が有効化され、HTTPリクエストを受け付ける準備が整っていることも確認できます。

S3 bucket policy allows access from both instances.

バケットポリシーによってNATゲートウェイのアドレスと、VPC2からのアクセスが許可されています。
これで両インスタンスがS3バケットにアクセス可能ということです。

動作確認

準備が整いましたので、インスタンス2にアクセスします。
インスタンスへのアクセスは、SSM Session Managerを使用します。

% aws ssm start-session --target i-063843a7185abe446

Starting session with SessionId: root-05d2fb1be2c79590b

$Code language: Bash (bash)

詳細は以下のページをご確認ください。

あわせて読みたい
LinuxインスタンスにSSM Session Manager経由でアクセスする 【LinuxインスタンスにSSM Session Manager経由でアクセスする】 EC2インスタンスにSSM Session Manager経由でアクセスする構成を確認します。 Session Manager は完全...

公開鍵の登録状況を確認します。

$ sudo apt-key list
/etc/apt/trusted.gpg
--------------------
pub   rsa2048 2022-06-19 [SCEA]
      1133 C594 73B1 E2D9 5E32  09A2 BD71 F30D 3250 8A8D
uid           [ unknown] fa-032 <fa-032@example.com>
sub   rsa2048 2022-06-19 [SEA]

...
Code language: Bash (bash)

自作リポジトリの検証用鍵が登録されています。

自作リポジトリの登録用ファイルを確認します。

$ sudo cat /etc/apt/sources.list
deb http://fa-032.s3-website-ap-northeast-1.amazonaws.com/ xenial main
Code language: Bash (bash)

確かに自作リポジトリが登録されています。

パッケージ一覧を更新します。

$ sudo apt update
Get:1 http://fa-032.s3-website-ap-northeast-1.amazonaws.com xenial InRelease [3170 B]
Get:2 http://fa-032.s3-website-ap-northeast-1.amazonaws.com xenial/main arm64 Packages [31.8 kB]
Fetched 34.9 kB in 0s (151 kB/s)
Reading package lists... Done
Building dependency tree
Reading state information... Done
All packages are up to date.
Code language: Bash (bash)

自作リポジトリにアクセスし、正常に更新されました。
先述の鍵を使って検証が行われたということです。

Apacheをインストールします。

$ sudo apt install -y apache2
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
  apache2-bin apache2-data apache2-utils libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap liblua5.1-0 libssl1.0.0
Suggested packages:
  www-browser apache2-doc apache2-suexec-pristine | apache2-suexec-custom
Recommended packages:
  ssl-cert
The following NEW packages will be installed:
  apache2 apache2-bin apache2-data apache2-utils libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap liblua5.1-0 libssl1.0.0
0 upgraded, 10 newly installed, 0 to remove and 0 not upgraded.
Need to get 2061 kB of archives.
After this operation, 8762 kB of additional disk space will be used.
Get:1 http://fa-032.s3-website-ap-northeast-1.amazonaws.com xenial/main arm64 libapr1 arm64 1.5.2-3 [71.1 kB]
Get:2 http://fa-032.s3-website-ap-northeast-1.amazonaws.com xenial/main arm64 libssl1.0.0 arm64 1.0.2g-1ubuntu4 [726 kB]
Get:3 http://fa-032.s3-website-ap-northeast-1.amazonaws.com xenial/main arm64 libaprutil1 arm64 1.5.4-1build1 [67.4 kB]
Get:4 http://fa-032.s3-website-ap-northeast-1.amazonaws.com xenial/main arm64 libaprutil1-dbd-sqlite3 arm64 1.5.4-1build1 [9656 B]
Get:5 http://fa-032.s3-website-ap-northeast-1.amazonaws.com xenial/main arm64 libaprutil1-ldap arm64 1.5.4-1build1 [8358 B]
Get:6 http://fa-032.s3-website-ap-northeast-1.amazonaws.com xenial/main arm64 liblua5.1-0 arm64 5.1.5-8ubuntu1 [88.7 kB]
Get:7 http://fa-032.s3-website-ap-northeast-1.amazonaws.com xenial/main arm64 apache2-bin arm64 2.4.18-2ubuntu3 [764 kB]
Get:8 http://fa-032.s3-website-ap-northeast-1.amazonaws.com xenial/main arm64 apache2-utils arm64 2.4.18-2ubuntu3 [77.5 kB]
Get:9 http://fa-032.s3-website-ap-northeast-1.amazonaws.com xenial/main arm64 apache2-data all 2.4.18-2ubuntu3 [162 kB]
Get:10 http://fa-032.s3-website-ap-northeast-1.amazonaws.com xenial/main arm64 apache2 arm64 2.4.18-2ubuntu3 [86.7 kB]
...
Code language: Bash (bash)

正常に自作リポジトリからインストールが完了しました。

最後にApacheの動作状況を確認します。

$ sudo systemctl status apache2
● apache2.service - LSB: Apache2 web server
     Loaded: loaded (/etc/init.d/apache2; generated)
    Drop-In: /usr/lib/systemd/system/apache2.service.d
             └─apache2-systemd.conf
     Active: active (running) since Sun 2022-06-19 03:58:31 UTC; 1min 54s ago
       Docs: man:systemd-sysv-generator(8)
      Tasks: 55 (limit: 1061)
     Memory: 4.6M
     CGroup: /system.slice/apache2.service
             ├─2221 /usr/sbin/apache2 -k start
             ├─2224 /usr/sbin/apache2 -k start
             └─2225 /usr/sbin/apache2 -k start

Jun 19 03:58:30 ip-10-0-1-205 systemd[1]: Starting LSB: Apache2 web server...
Jun 19 03:58:30 ip-10-0-1-205 apache2[2199]:  * Starting Apache httpd web server apache2
Jun 19 03:58:31 ip-10-0-1-205 apache2[2199]:  *
Jun 19 03:58:31 ip-10-0-1-205 systemd[1]: Started LSB: Apache2 web server.
Code language: Bash (bash)

正常に動作しています。

まとめ

S3上に、自作のaptリポジトリを構築する方法を確認しました。
自作リポジトリを作成し、参照することで、プライベートサブネットに配置されたUbuntuインスタンスでも、インターネットにアクセスせずとも、任意のパッケージをインストールできることできました。