[実践]AWS Private Certificate Authorityで発行したプライベート証明書でEC2をSSL化してみよう

![001samune.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/1d2be273-0971-463a-b819-77af3efb4b3f.png)

# 概要
`PCA (AWS Private Certificate Authority)`から発行したプライベート証明書を使って、プライベートサブネットに配置されているWEBサーバーをSSL化したいと思います。

# AWS Private Certificate Authorityとは
プライベートCA（`ルートCA` / `下位CA`）の作成及びプライベート証明書を発行・管理できるマネージドサービスです。
`ACM (AWS Certificate Manager)`の一機能として提供されているため、ACMと統合されているサービスに関連付けることが可能です。

https://docs.aws.amazon.com/acm/latest/userguide/acm-services.html

ユースケースとしては以下が想定されます。(一部)
* 内部向けWebサーバー、VPN認証
* IoTデバイス認証
* AD連携による端末及びユーザー認証


# パブリック証明書とプライベート証明書の違い
違いはざっくり以下の通りです。
プライベート証明書だからといって外部公開するサーバーに使えないわけではありませんが、デフォルトで信頼されていないため基本的には社内ネットワークシステムなどの特定ユーザー向けシステムで利用するものです。

|項目|パブリック証明書|プライベート証明書|
|:-|:-|:-|
|発行元|公開認証局<br>(公に認められた認証局)|独自の認証局|
|信頼性|端末にデフォルトで認証局の<br>ルート証明書がインストールされている|発行元認証局のルート証明書を<br>別途インストールする必要あり|
|安全性|差異なし|差異なし|
|用途|● 外部公開サーバー<br>● 一般ユーザー向けサービス|● 内部システム<br>● 社内NWシステム<br>● 特定顧客向けサービス|
|適用例|● 外部向けサービス|● 社内システム<br>● VPN<br>● mTLS<br>● イントラネット|


# 実践
### 構成図
構成図は以下の通りです。
* 踏み台サーバーに`AWS Systems Manager Session Manager`経由でログインし、各WEBサーバーにアクセス
* 各サーバーでの作業も`AWS Systems Manager Session Manager`経由でログインし実施
* WEBサーバーのドメインは`Amazon Route 53`の`Private Hosted Zone`で管理
* ルートCAからのルート証明書取得及び各サーバー証明書の取得は`AWS CloudShell`にて作業を実施

その他環境の詳細はGitHubにて公開していますので、良かったら参照してみてください。

![画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/f33a9ed7-5784-4336-92c2-af9b277d21ce.png)

https://github.com/tyskJ/aws-pca-ec2-ssl

### 前提条件
* 作業PCに`AWS CLI`をインストールしていること
* 作業PCに`Session Manager Plugin`をインストールしていること
* `AdministratorAccess`がアタッチされたIAMユーザーを作成していること
* IAMユーザーの`アクセスキーID/シークレットアクセスキー`を発行していること
* AWS CLIのプロファイルを作成していること<br>(プロファイル名は任意ですが今回は`admin`とします)

:::note warn
権限が強いIAMユーザーの`アクセスキーID/シークレットアクセスキー`を発行しますので取り扱いには十分ご注意ください
:::

### 作業環境
* 64bit版 Windows 11 Pro
* Visual Studio Code 1.96.2 (Default Terminal: Git Bash)
* Git 2.47.1.windows.2
* Session Manager Plugin 1.2.553.0
* AWS CLI 2.22.19.0

### Linux編
##### Session Manager接続(Port Forwarding)
`Session Manager Port Forwarding`を使用し、`SSH Over Session Manager`で繋ぎます。
(`Start Session`でも良いのですがせっかくなので。)

まずターミナルを起動し以下コマンドを実行して、任意の`エフェメラルポート`をSSH用システムポート(`22`)に紐づけます。
実行後、`Waiting for connections...`と出力されればOKです。
これで作業PCから`エフェメラルポート`に対して`SSH`接続すると、`Session Manager Plugin`がその通信をカプセル化し`Session Managerエンドポイント`に投げます。
その通信をEC2内の`SSM Agent`がポーリングして取得し、非カプセル化してEC2内のSSHサービスで受け取ります。

:::note info
`--target`に指定するインスタンスIDはマネジメントコンソールで確認し修正してください
:::

```shell-session
$ aws ssm start-session \
--target linux-instance-id \
--document-name AWS-StartPortForwardingSession \
--parameters portNumber="22",localPortNumber="10022" --profile admin

Starting session with SessionId: IAMユーザー名-セッションID
Port 10022 opened for sessionId IAMユーザー名-セッションID.
Waiting for connections...
```

上記コマンドを実行したターミナルは閉じずにそのままにしてください。別のターミナルを開いて以下コマンドを実行します。

:::note info
EC2作成時に指定したキーペアの秘密鍵(PEMファイル)は、デプロイ後`envs`フォルダ配下の`.keypair`フォルダに作成されますのでそちらを使用してください
:::

```shell-session
$ ssh -i 秘密鍵PEMファイル ec2-user@localhost -p 10022
The authenticity of host '[localhost]:10022 ([127.0.0.1]:10022)' can't be established.
ED25519 key fingerprint is SHA256:Oh168/Y9D19IC+mJ2vSxUkVBRGgv+Shzerg40oFGUd0.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[localhost]:10022' (ED25519) to the list of known hosts.
   ,     #_
   ~\_  ####_        Amazon Linux 2023
  ~~  \_#####\
  ~~     \###|
  ~~       \#/ ___   https://aws.amazon.com/linux/amazon-linux-2023
   ~~       V~' '->
    ~~~         /
      ~~._.   _/
         _/ _/
       _/m/'
[ec2-user@ip-10-0-3-113 ~]$
```

接続ができました。以降は`root`ユーザーで作業を実施していきます。

##### Nginxインストール
`Nginx`をインストールします。
```shell-session
# dnf install -y nginx
~省略~
Complete!
```

`Nginx`の起動&自動起動設定を入れておきます。
```shell-session
# systemctl enable nginx --now
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.
```

以下コマンドでバックアップを取得後、`Route 53 Private Hosted Zone`に作成したレコードのホスト名に設定ファイルを修正します。
```shell-session
# cp -p /etc/nginx/nginx.conf{,.org}
```

```diff_conf:/etc/nginx/nginx.conf
# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80;
        listen       [::]:80;
-        server_name  _; ←全て受け入れになっている
+        server_name  linux.pcassl.com;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        error_page 404 /404.html;
        location = /404.html {
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        }
    }

# Settings for a TLS enabled server.
#
#    server {
#        listen       443 ssl;
#        listen       [::]:443 ssl;
#        http2        on;
#        server_name  _;
#        root         /usr/share/nginx/html;
#
#        ssl_certificate "/etc/pki/nginx/server.crt";
#        ssl_certificate_key "/etc/pki/nginx/private/server.key";
#        ssl_session_cache shared:SSL:1m;
#        ssl_session_timeout  10m;
#        ssl_ciphers PROFILE=SYSTEM;
#        ssl_prefer_server_ciphers on;
#
#        # Load configuration files for the default server block.
#        include /etc/nginx/default.d/*.conf;
#
#        error_page 404 /404.html;
#        location = /404.html {
#        }
#
#        error_page 500 502 503 504 /50x.html;
#        location = /50x.html {
#        }
#    }
}
```

編集後、サービスをリロードして設定ファイルの変更を読み込ませます。
```shell-session
# systemctl reload nginx
```
これで踏み台サーバーから`http://linux.pcassl.com`で`Nginx`のデフォルトページが表示されます。

##### サーバー証明書取得
この作業は`CloudShell`で実施していきます。
以下禁止フレーズ以外の文字4~128文字を記載したファイルを作成します。
今回は`passphrase.txt`とします。

>パスフレーズを作成する際は、#、$、% 以外の任意の ASCII 文字を使用できます。

https://docs.aws.amazon.com/ja_jp/acm/latest/userguide/export-private.html#export-cli

:::note warn
パスフレーズのファイルには改行が含まれていてはいけません。
Linuxで作成したファイルの場合は`LF`を、Windowsで作成したファイルの場合は`CRLF`を取り除きます。

```shell-session
# Linux
$ cat 除去前ファイル名.txt | tr -d '\n' > 除去後ファイル名.txt

# Windows
$ cat 除去前ファイル名.txt | tr -d '\r\n' > 除去後ファイル名.txt
```

改行が入っているかの確認は以下コマンドを実行し、`with no line terminators`と出力されればOKです
```shell-session
$ file -k passphrase.txt 
passphrase.txt: ASCII text, with no line terminators
```

:::


以下コマンドを実行し「証明書本文」「証明書チェーン」「証明書のプライベートキー」をファイルに出力します。
証明書本文を`cer.txt`、証明書チェーンを`cert_chain.txt`、証明書プライベートキーを`private_key.txt`として保存します。

:::note info
`--certificate-arn`はエクスポート対象の証明書ARNに変更してください
:::

```shell-session
$ aws acm export-certificate \
--certificate-arn arn:aws:acm:Region:444455556666:certificate/certificate_ID \
--passphrase fileb://passphrase.txt \
--output text \
--query Certificate > cert.txt

$ aws acm export-certificate \
--certificate-arn arn:aws:acm:Region:444455556666:certificate/certificate_ID \
--passphrase fileb://passphrase.txt \
--output text \
--query CertificateChain > cert_chain.txt

$ aws acm export-certificate \
--certificate-arn arn:aws:acm:Region:444455556666:certificate/certificate_ID \
--passphrase fileb://passphrase.txt \
--output text \
--query PrivateKey > private_key.txt

```

作成後、まずNginxの場合は証明書と証明書チェーンは連結しないといけないので以下コマンドを実行し連結後のファイルを作成します。

```shell-session
$ cat cert.txt cert_chain.txt | tee fullchain.pem
```

続いて証明書プライベートキーのパスフレーズを解除します。

```shell-session
$ openssl rsa -in private_key.txt -out private_key.pem
Enter pass phrase for private_key.txt:
writing RSA key
```

##### サーバー証明書設定
ここからは`Linuxサーバー`で作業していきます。
まずサーバー証明書とプライベートキーを格納するディレクトリを作成します。
```shell-session
# mkdir /etc/nginx/ssl
```

作成したディレクトリ内に、`fullchain.pem`と`private_key.pem`ファイルを作成し、それぞれのファイルに先ほど`CloudShell`で作成したファイル内の値を張り付け保存します。


続いて設定ファイルを以下の通り修正します。

```diff_conf:/etc/nginx/nginx.conf
# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80;
        listen       [::]:80;
        server_name  linux.pcassl.com;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        error_page 404 /404.html;
        location = /404.html {
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        }
    }

# Settings for a TLS enabled server.
-#
-#    server {
-#        listen       443 ssl;
-#        listen       [::]:443 ssl;
-#        http2        on;
-#        server_name  _;
-#        root         /usr/share/nginx/html;
-#
-#        ssl_certificate "/etc/pki/nginx/server.crt";
-#        ssl_certificate_key "/etc/pki/nginx/private/server.key";
-#        ssl_session_cache shared:SSL:1m;
-#        ssl_session_timeout  10m;
-#        ssl_ciphers PROFILE=SYSTEM;
-#        ssl_prefer_server_ciphers on;
-#
-#        # Load configuration files for the default server block.
-#        include /etc/nginx/default.d/*.conf;
-#
-#        error_page 404 /404.html;
-#        location = /404.html {
-#        }
-#
-#        error_page 500 502 503 504 /50x.html;
-#        location = /50x.html {
-#        }
-#    }
+    server {
+        listen       443 ssl;
+        listen       [::]:443 ssl;
+        http2        on;
+        server_name  linux.pcassl.com;
+        root         /usr/share/nginx/html;
+
+        ssl_certificate "/etc/nginx/ssl/fullchain.pem";
+        ssl_certificate_key "/etc/nginx/ssl/private_key.pem";
+        ssl_session_cache shared:SSL:1m;
+        ssl_session_timeout  10m;
+        ssl_ciphers PROFILE=SYSTEM;
+        ssl_prefer_server_ciphers on;
+
+        # Load configuration files for the default server block.
+        include /etc/nginx/default.d/*.conf;
+
+        error_page 404 /404.html;
+        location = /404.html {
+        }
+
+        error_page 500 502 503 504 /50x.html;
+        location = /50x.html {
+        }
+    }
}
```

編集後、サービスをリロードして設定ファイルの変更を読み込ませます。
```shell-session
# systemctl reload nginx
```
これで踏み台サーバーから`https://linux.pcassl.com`で`Nginx`のデフォルトページが表示されます。
ただし現状では、踏み台サーバー側にサーバー証明書を検証するルート証明書がないため、証明書エラーとなります。(後述で対応)


### Windows Server編
##### Session Manager接続(Port Forwarding)
`Session Manager Port Forwarding`を使用し、`RDP Over Session Manager`で繋ぎます。
(`Start Session`でも良いのですが操作性を加味してこちらの方法で実施します。)

まずターミナルを起動し以下コマンドを実行して、任意の`エフェメラルポート`をRDP用システムポート(`3389`)に紐づけます。
実行後、`Waiting for connections...`と出力されればOKです。
これで作業PCから`エフェメラルポート`に対して`RDP`接続すると、`Session Manager Plugin`がその通信をカプセル化し`Session Managerエンドポイント`に投げます。
その通信をEC2内の`SSM Agent`がポーリングして取得し、非カプセル化してEC2内のRDPサービスで受け取ります。

:::note info
`--target`に指定するインスタンスIDはマネジメントコンソールで確認し修正してください
:::

```shell-session
$ aws ssm start-session \
--target windows-instance-id \
--document-name AWS-StartPortForwardingSession \
--parameters portNumber="3389",localPortNumber="33389" --profile admin

Starting session with SessionId: IAMユーザー名-セッションID
Port 33389 opened for sessionId IAMユーザー名-セッションID.
Waiting for connections...
```

上記コマンドを実行したターミナルは閉じずにそのままにしてください。
リモートデスクトップを起動し接続します。

![画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/af81a6e5-605d-49dd-b145-b9a845c5080e.png)


:::note info
EC2作成時に指定したキーペアの秘密鍵(PEMファイル)が、デプロイ後`envs`フォルダ配下の`.keypair`フォルダに作成されます。
そちらを使用し、事前に`Administrator`ユーザーのパスワードを取得しておいてください。

![画像1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/1217689a-6147-4eb5-baf7-78e4366fe0c4.png)
:::

##### Nginxインストール
[こちら](https://nginx.org/en/download.html)から`Stable version`の`nginx/Windows-1.26.3`(バージョンはインストール時のもので大丈夫です)をサーバーにダウンロードします。
zipファイルをダウンロードしたら任意の場所に解凍します。今回は`Documents`に解凍します。

![画像3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/d0b28bc5-be3c-4316-8a4c-6a844e38f404.png)

`Route 53 Private Hosted Zone`に作成したレコードのホスト名に設定ファイルを修正します。
`$Env:USERPROFILE\Documents\nginx-1.26.3\conf`フォルダ配下にある`nginx.conf`を以下の通り修正します。

```diff_conf:$Env:USERPROFILE\Documents\nginx-1.26.3\conf\nginx.conf

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
-        server_name  localhost;
+        server_name  windows.pcassl.com;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}

```

`Nginx`を起動します。
`$Env:USERPROFILE\Documents\nginx-1.26.3`フォルダに遷移し、パスタブに`powershell`と入力しEnterを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/1d9560e8-d56d-4cf8-a398-09e18543d063.png)

Powershell上で以下コマンドを実行します。
```powershell
PS C:\Users\Administrator\Documents\nginx-1.26.3> start nginx
```

警告がでた場合は、`実行`を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/5841e0b1-f8be-4566-8e85-cbb655c834d3.png)

これで踏み台サーバーから`http://windows.pcassl.com`でNginxのデフォルトページが表示されます。

:::note warn
* もしつながらない場合は、`$Env:USERPROFILE\Documents\nginx-1.26.3`フォルダ上で`.\nginx -s reload`コマンドで設定を再読み込みするか、<br>`taskkill /F /IM nginx.exe`で一度プロセスを削除して再度起動してみてください
* それでもできない場合は、`Windows Defenderファイアウォール`を無効化してください
:::

:::note info
せっかくなので[サービス化](#おまけサービス化)についてまとめてますのでよかったら参考にしてみてください
:::


##### サーバー証明書設定
この作業は`CloudShell`で実施していきます。
以下禁止フレーズ以外の文字4~128文字を記載したファイルを作成します。
今回は`passphrase.txt`とします。

>パスフレーズを作成する際は、#、$、% 以外の任意の ASCII 文字を使用できます。

https://docs.aws.amazon.com/ja_jp/acm/latest/userguide/export-private.html#export-cli

:::note warn
パスフレーズのファイルには改行が含まれていてはいけません。
Linuxで作成したファイルの場合は`LF`を、Windowsで作成したファイルの場合は`CRLF`を取り除きます。

```shell-session
# Linux
$ cat 除去前ファイル名.txt | tr -d '\n' > 除去後ファイル名.txt

# Windows
$ cat 除去前ファイル名.txt | tr -d '\r\n' > 除去後ファイル名.txt
```

改行が入っているかの確認は以下コマンドを実行し、`with no line terminators`と出力されればOKです
```shell-session
$ file -k passphrase.txt 
passphrase.txt: ASCII text, with no line terminators
```

:::


以下コマンドを実行し「証明書本文」「証明書チェーン」「証明書のプライベートキー」をファイルに出力します。
証明書本文を`cer.txt`、証明書チェーンを`cert_chain.txt`、証明書プライベートキーを`private_key.txt`として保存します。

:::note info
`--certificate-arn`はエクスポート対象の証明書ARNに変更してください
:::

```shell-session
$ aws acm export-certificate \
--certificate-arn arn:aws:acm:Region:444455556666:certificate/certificate_ID \
--passphrase fileb://passphrase.txt \
--output text \
--query Certificate > cert.txt

$ aws acm export-certificate \
--certificate-arn arn:aws:acm:Region:444455556666:certificate/certificate_ID \
--passphrase fileb://passphrase.txt \
--output text \
--query CertificateChain > cert_chain.txt

$ aws acm export-certificate \
--certificate-arn arn:aws:acm:Region:444455556666:certificate/certificate_ID \
--passphrase fileb://passphrase.txt \
--output text \
--query PrivateKey > private_key.txt

```

作成後、まずNginxの場合は証明書と証明書チェーンは連結しないといけないので以下コマンドを実行し連結後のファイルを作成します。

```shell-session
$ cat cert.txt cert_chain.txt | tee fullchain.pem
```

続いて証明書プライベートキーのパスフレーズを解除します。

```shell-session
$ openssl rsa -in private_key.txt -out private_key.pem
Enter pass phrase for private_key.txt:
writing RSA key
```

##### サーバー証明書設定
ここからは`Windowsサーバー`で作業していきます。
まずサーバー証明書とプライベートキーを格納するディレクトリを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/69cdfc03-489c-4288-8da9-837472bf701e.png)

作成したディレクトリ内に、`fullchain.pem`と`private_key.pem`を作成し、`CloudShell`で作成したファイル内の値をそれぞれ張り付けて保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/dc4c32bb-d7c4-407f-a990-0ffbd793b07d.png)



続いて設定ファイルを以下の通り修正します。

```diff_conf:$Env:USERPROFILE\Documents\nginx-1.26.3\conf\nginx.conf
#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  windows.pcassl.com;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
-    #
-    #server {
-    #    listen       443 ssl;
-    #    server_name  localhost;
-
-    #    ssl_certificate      cert.pem;
-    #    ssl_certificate_key  cert.key;
-
-    #    ssl_session_cache    shared:SSL:1m;
-    #    ssl_session_timeout  5m;
-
-    #    ssl_ciphers  HIGH:!aNULL:!MD5;
-    #    ssl_prefer_server_ciphers  on;
-
-    #    location / {
-    #        root   html;
-    #        index  index.html index.htm;
-    #    }
-    #}
+    server {
+        listen       443 ssl;
+        server_name  windows.pcassl.com;
+
+        ssl_certificate      "C:\\Users\\Administrator\\Documents\\nginx-1.26.3\\ssl\\fullchain.pem";
+        ssl_certificate_key  "C:\\Users\\Administrator\\Documents\\nginx-1.26.3\\ssl\\private_key.pem";
+
+        ssl_session_cache    shared:SSL:1m;
+        ssl_session_timeout  5m;
+
+        ssl_ciphers  HIGH:!aNULL:!MD5;
+        ssl_prefer_server_ciphers  on;
+
+        location / {
+            root   html;
+            index  index.html index.htm;
+        }
+    }
}

```

設定ファイルの変更を読み込ませるため、Powershell上で以下コマンドを実行します。
```powershell
PS C:\Users\Administrator\Documents\nginx-1.26.3> nginx -s reload
```

これで踏み台サーバーから`https://windows.pcassl.com`で`Nginx`のデフォルトページが表示されます。
ただし現状では、踏み台サーバー側にサーバー証明書を検証するルート証明書がないため、証明書エラーとなります。(後述で対応)


### ルート証明書インストール
##### ルート証明書取得
`CloudShell`で作業をします。
以下コマンドを実行し、`PCA`のルートCAの証明書を取得します。

:::note info
`--certificate-authority-arn`はルートCAのARNに変更してください
:::

```shell-session
$ aws acm-pca get-certificate-authority-certificate \
--certificate-authority-arn arn:aws:acm-pca:us-east-1:111122223333:certificate-authority/11223344-1234-1122-2233-112233445566 \
--output text \
--query Certificate > root_cert.txt
```

##### Session Manager接続(Port Forwarding)
[Windows Server編](#windows-server編)と同じ方法で`踏み台サーバー`に`RDP Over Session Manager`で繋ぎます。(割愛)

:::note warn
`エフェメラルポート`は未使用の値を使用してください
:::

##### ルート証明書インストール
任意のフォルダに`pem形式`のファイルを作成し、先ほど取得した`PCA`のルートCAの証明書の値を張り付け保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/1b5fbbe3-ba8f-4fa9-9153-c0c7cd99fe16.png)

「Windows キー」と「R キー」を同時に押して「certmgr.msc」と入力し「OK」を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/b96b14da-b973-4bc1-bc14-2649910dee09.png)


「信頼されたルート証明書」→「証明書」→「すべてのタスク」→「インポート」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/92e814b7-8026-42a9-9908-940035e28b8c.png)

そのまま「次へ」を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/51083f9a-88f8-41a5-9330-ec8e04468d46.png)

インポートするファイルとして先ほど任意の場所に保存した`pem形式`のファイルを選択し「次へ」を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/9da9a8bf-89b8-4447-88d8-8b82381ff6bd.png)

そのまま「次へ」を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/a5148446-39b3-430e-a585-ec9613f749f5.png)

「完了」を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/5c67dfd1-226d-424d-ba55-a1329fa54bc5.png)

警告が出ますが「はい」を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/31496dbb-b40c-4ff1-a45c-4f46d031d41f.png)

インポートが完了します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/2389d1fd-3060-48fa-8f56-92f627337f2b.png)

証明書一覧にインストールされているのが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/bac267e8-fcb8-44f2-9550-e0069b72c654.png)

この状態で各サーバーにhttpsでアクセスすると、サーバー証明書の検証に成功するため証明書エラーが発生しなくなります。

### 動作確認
まずLinuxサーバーにアクセスしてみます。問題ないですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/a936bd02-24f2-4c4f-b062-4d9c9adf6509.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/6ec861ec-6d75-4c18-b6a1-d6cf8edec641.png)

続いてWindowsサーバーにアクセスしてみます。こちらも問題ないですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/07813550-6c73-4df9-84c0-d94b3929a44c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/f1af96f8-8dea-4e7f-89e8-97083aff9efd.png)


# まとめ
今回は`PCA (AWS Private Certificate Authority)`から発行したプライベート証明書を使って、プライベートサブネットに配置されているWEBサーバーをSSL化してみました。
余談ですが、今回の構成では「`PCA`を認証局として`ACM`で発行」としているので、<br>サーバー証明書の有効期限は`ACMの仕様上13ヵ月`となり変更はできません。
より長い有効期限としたい場合は、`PCAのルート証明書の有効期限未満`とはなりますが、<br>`AWS CLI`の`Issue Certificate API`を使って`PCA`に対して直接証明書を発行することになります。
こちらの記事が誰かの役に立てば幸いです。

:::note warn
* 「`PCA`を認証局として`ACM`で発行」とする際、`PCA`の証明書の有効期限が`13ヵ月未満`の場合、発行はできるものの証明書のStatusは`Failed（失効状態）`になりますので注意してください
* また「`短期間証明書モード（short-lived certificate mode）`」の場合、`PCA`で発行できる証明書の有効期限は`最大で7日`のため、発行はできるものの証明書のStatusは`Failed`になりますので注意してください。`PCA`の証明書の有効期限が`13ヵ月以上`だとしてもStatusは`Failed`になります
:::

# おまけ(サービス化)
せっかくなのでWindows Serverにてサービス化してみましょう。
サービス化プログラム(`winsw.exe`)を使います。

:::note warn
同様のプログラムとして[NSSM - the Non-Sucking Service Manager](https://nssm.cc/download)もありますが最新版が2017年のため見送りました
:::

以下リポジトリから`WinSW-x64.exe`をダウンロードし、任意のフォルダに配置します。

https://github.com/winsw/winsw/releases

今回は`$Env:USERPROFILE\Documents\nginx-1.26.3\service`フォルダに配置しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/ac5c95cb-0329-429d-ac9c-babd82899fb8.png)

任意ではありますが、わかりやすいように`WinSv-nginx.exe`とリネームしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/b3a8c5bc-976a-47fd-a94b-03629e0080f1.png)

続いて、`WinSW設定ファイル(XMLファイル)`を作成します。
こちらの名称は実行ファイルと同一名称にする必要がありますので`WinSv-nginx.xml`とします。
記載内容は以下の通りです。

```xml:WinSW-nginx.xml
<service>
    <id>nginx</id>
    <name>nginx</name>
    <description>HTTP Server</description>
    <workingdirectory>C:\Users\Administrator\Documents\nginx-1.26.3</workingdirectory>
    <logpath>C:\Users\Administrator\Documents\nginx-1.26.3\logs</logpath>
    <logmode>roll</logmode>
    <depend></depend>
    <executable>C:\Users\Administrator\Documents\nginx-1.26.3\nginx.exe</executable>
    <startarguments></startarguments>
    <stoparguments>-s stop</stoparguments>
</service>
```

インストールします。
`$Env:USERPROFILE\Documents\nginx-1.26.3\service`フォルダに遷移し、パスタブに`powershell`と入力しEnterを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/3dbbf511-1b19-4493-920f-98e96968b129.png)

以下コマンドを実行しインストールします。
`successfully.`となれば成功です。

```powershell
PS C:\Users\Administrator\Documents\nginx-1.26.3\service> .\WinSW-nginx.exe install
2025-04-01 16:09:26,805 INFO  - Installing service 'nginx (nginx)'...
2025-04-01 16:09:26,881 INFO  - Service 'nginx (nginx)' was installed successfully.
```

サービス一覧で確認すると問題なくサービス化されていますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/609bbec9-e8d5-455e-815f-da249eb8255e.png)


# 参考資料
### リファレンス
* [AWS Private CAとは - AWS Private Certificate Authority ユーザーガイド](https://docs.aws.amazon.com/ja_jp/privateca/latest/userguide/PcaWelcome.html)
* [パブリック証明書とプライベート証明書 - NRA-PKI サポート情報](https://www.nrapki.jp/support/?p=1071)

### ブログ
* [AWS System Manager Sessions Manager を使用した新しい機能 – Port Forwarding - Amazon Web Services ブログ](https://aws.amazon.com/jp/blogs/news/new-port-forwarding-using-aws-system-manager-sessions-manager/)
* [WinSWで任意のexeをWindowsサービス化する方法について](https://zenn.dev/0_0/articles/08a57797e62da4)
* [手順：Windows版nginx・PHP8のインストール・サービス化(2024年9月版）- SE_BOKUのまとめノート的ブログ](https://arakan-pgm-ai.hatenablog.com/entry/2024/09/07/233000)
* [ACMでプライベートCAを作成して証明書を発行してみた - Serverworks ENGINEER BLOG](https://blog.serverworks.co.jp/tech/2020/02/05/privateca-certificate/)