Oracle Functions で Let's Encrypt を使用した、自動SSL証明書更新をつくってみた

# はじめに

Oracle Functions を使用して、自動的にSSL証明書を発行する自動化(oci-lego-sslupdate)を作りました。
Let's Encrypt からSSL証明書を発行し、OCIのLoadBalancerに設定し、ObjectStorageへ保存することが出来ます。
Oracle Functions を使用しているので、ComputeInstanceを必要としないサーバレスなアーキテクチャとなっています。

この記事では、使い方と、技術的な解説を書きます。
作成した自動化(oci-lego-sslupdate)は、GItHubにアップロードしているので、適宜ご利用ください。
https://github.com/Sugi275/oci-lego-sslupdate



# 概要

![architecture.png](https://qiita-image-store.s3.amazonaws.com/0/104247/fe628972-0e88-f50c-a115-8d3fe0afaf69.png)



- OCIR (Docker Registry) にアップロードしている、自動化用の DockerImage を Oracle Functions に Pull
- Fn 上でDockerImageを実行
- Let's Encryptから新たなSSL証明書を取得
- OCIのLoadBalancerに、新たなSSL証明書を設定
- OCIのObjectStorageに、新たなSSL証明書と、SSL証明書に紐づく秘密鍵を保存



# 使い方

## 前提条件

- 取得したいSSL証明書のドメインを、OCIのDNSにあるZoneで管理していること。設定方法例は[こちら](https://qiita.com/sugimount/items/7d14ca7185d87c9763ff) を参照 (Let's Encryptの認証方式をDNS-01としているため)

- OCI上にLoadBalancerを作成して、SSL用のListenerを作成していること

- Oracle Functions を使用可能なこと。設定方法は[こちら](https://qiita.com/sugimount/items/018e08f575ecefb1546c)を参照

  

## oci-lego-sslupdateを Oracle Functions に設定

Gitからoci-lego-sslupdateをclone

```
git clone https://github.com/Sugi275/oci-lego-sslupdate.git
```



Oracle Functions 用の設定ファイルを、必要に応じて変更します

```
> cat func.yaml
schema_version: 20180708
name: oci-lego-sslupdate     <=== Functionの名前を変更したい場合は変更する
version: 0.0.20              <=== バージョンも変更したい場合は変更する
runtime: docker
entrypoint: ./func
format: http-stream
timeout: 120
```



Oracle Functions のコンソール画面で、適当なアプリケーション(枠組み)を作成します。今回の例では、`env-app` という名前を使用しています。
アプリケーションを作成後、fn deploy コマンドで、Oracle Functions にデプロイします

```
fn --verbose deploy --app env-app
```



function 一覧を確認します。oci-lego-sslupdate が存在していることを確認します

```
> fn ls functions env-app
NAME                    IMAGE                                                                   ID
custom_runtime          phx.ocir.io/<tenancy>/oracle-function/custom_runtime:0.0.2             ocid1.fnfunc.oc1.phx.aaaaaaaaadizxuar3gv6ubeoztffm5l2nfnujna6vbnkc67g7td2c722jg2a
env-print               phx.ocir.io/<tenancy>/oracle-function/env-print:0.0.7                  ocid1.fnfunc.oc1.phx.aaaaaaaaadu6qtn6zyn754zbiqi3boavtunsdbkwfllggixg7hkwjihfo5ma
oci-lb-list             phx.ocir.io/<tenancy>/oracle-function/oci-lb-list:0.0.24               ocid1.fnfunc.oc1.phx.aaaaaaaaaaialw3wgcnm2ytn7cveaaqmlmtaniilu3qy7k2bmxkbn2tgnpiq
oci-lego-sslupdate      phx.ocir.io/<tenancy>/oracle-function/oci-lego-sslupdate:0.0.21        ocid1.fnfunc.oc1.phx.aaaaaaaaaaz4bvn4oz2sp4d3zzfuaefkui2be7b5vp4cgayrdmltj6f7yeuq <=== 表示されていればおｋ
test                    phx.ocir.io/<tenancy>/oracle-function/test:0.0.6                       ocid1.fnfunc.oc1.phx.aaaaaaaaaatajmkauwajgxbjjm2p23kiu2do7nz76psxcldwv5w2palfatpq
```



function に環境変数を設定します。以下に例を記載します。適宜環境に合わせて変更をする必要があります
bashの場合

```
# oci
export OCI_PRIVKEY_BASE64=`base64 $HOME/.oci/oci_api_key.pem`
fn config func env-app oci-lego-sslupdate OCI_PRIVKEY_BASE64 $OCI_PRIVKEY_BASE64
# fnでは値が空白だと、反映されない。パスフレーズを指定する場合はコメントアウトを外して指定
# fn config func env-app oci-lego-sslupdate OCI_PRIVKEY_PASS ""
fn config func env-app oci-lego-sslupdate OCI_TENANCY_OCID 'ocid1.tenancy.oc1..secret'
fn config func env-app oci-lego-sslupdate OCI_USER_OCID 'ocid1.user.oc1..secret'
fn config func env-app oci-lego-sslupdate OCI_PUBKEY_FINGERPRINT '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00'
fn config func env-app oci-lego-sslupdate OCI_REGION 'us-phoenix-1'
fn config func env-app oci-lego-sslupdate OCI_COMPARTMENT_OCID 'ocid1.tenancy.oc1..secret'

# lego (let's encrypt client)
fn config func env-app oci-lego-sslupdate OCI_TTL 1
fn config func env-app oci-lego-sslupdate OCI_PROPAGATION_TIMEOUT 120
fn config func env-app oci-lego-sslupdate OCI_POLLING_INTERVAL 10
fn config func env-app oci-lego-sslupdate LETSENCRYPT_DOMAINS 'sugi.tokyo,*.sugi.tokyo'
fn config func env-app oci-lego-sslupdate LETSENCRYPT_MY_MAILADDRESS 'myaddress@mail.com'
# fn config func env-app oci-lego-sslupdate LETSENCRYPT_CA_URL 'https://acme-staging-v02.api.letsencrypt.org/directory'

# oci lb 
fn config func env-app oci-lego-sslupdate OCI_LB_OCID 'ocid1.loadbalancer.oc1.phx.secret'
fn config func env-app oci-lego-sslupdate OCI_LISTENERS 'listener1,listener2'

# oci ObjectStorage
fn config func env-app oci-lego-sslupdate OCI_OS_NAMESPACE '<tenancy>'
fn config func env-app oci-lego-sslupdate OCI_OS_BUCKETNAME 'lego-certificate4'
```





fishの場合は、若干書式を変更する必要があります

```
# oci
set IFS
set -x OCI_PRIVKEY_BASE64 (base64 $HOME/.oci/oci_api_key.pem)
set IFS \n" "\t
# fnでは値が空白だと、反映されない。パスフレーズを指定する場合はコメントアウトを外して指定
# fn config func env-app oci-lego-sslupdate OCI_PRIVKEY_PASS ""
fn config func env-app oci-lego-sslupdate OCI_PRIVKEY_BASE64 $OCI_PRIVKEY_BASE64
fn config func env-app oci-lego-sslupdate OCI_TENANCY_OCID 'ocid1.tenancy.oc1..secret'
fn config func env-app oci-lego-sslupdate OCI_USER_OCID 'ocid1.user.oc1..secret'
fn config func env-app oci-lego-sslupdate OCI_PUBKEY_FINGERPRINT '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00'
fn config func env-app oci-lego-sslupdate OCI_REGION 'us-phoenix-1'
fn config func env-app oci-lego-sslupdate OCI_COMPARTMENT_OCID 'ocid1.tenancy.oc1..secret'

# lego (let's encrypt client)
fn config func env-app oci-lego-sslupdate OCI_TTL 1
fn config func env-app oci-lego-sslupdate OCI_PROPAGATION_TIMEOUT 120
fn config func env-app oci-lego-sslupdate OCI_POLLING_INTERVAL 10
fn config func env-app oci-lego-sslupdate LETSENCRYPT_DOMAINS 'sugi.tokyo,*.sugi.tokyo'
fn config func env-app oci-lego-sslupdate LETSENCRYPT_MY_MAILADDRESS 'myaddress@mail.com'
# fn config func env-app oci-lego-sslupdate LETSENCRYPT_CA_URL 'https://acme-staging-v02.api.letsencrypt.org/directory'

# oci lb 
fn config func env-app oci-lego-sslupdate OCI_LB_OCID 'ocid1.loadbalancer.oc1.phx.secret'
fn config func env-app oci-lego-sslupdate OCI_LISTENERS 'listener1,listener2'

# oci ObjectStorage
fn config func env-app oci-lego-sslupdate OCI_OS_NAMESPACE '<tenancy>'
fn config func env-app oci-lego-sslupdate OCI_OS_BUCKETNAME 'lego-certificate4'
```



## 環境変数について

各環境変数の説明を記載します

| Key                        | 内容                                                         | 必須 or 任意 | Default値                                      |
| -------------------------- | ------------------------------------------------------------ | ------------------ | ---------------------------------------------- |
| OCI_PRIVKEY_BASE64         | OCIに接続するための秘密鍵をbase64でエンコードした文字列      | 必須               | なし                                           |
| OCI_PRIVKEY_PASS           | 秘密鍵のパスフレーズ                                         | 必須               | なし                                           |
| OCI_TENANCY_OCID           | 操作するテナンシーのOCID                                     | 必須               | なし                                           |
| OCI_USER_OCID              | OCIで公開鍵を設定した、ユーザのOCID                          | 必須               | なし                                           |
| OCI_PUBKEY_FINGERPRINT     | 公開鍵のフィンガープリント                                   | 必須               | なし                                           |
| OCI_REGION                 | リージョン名                                                 | 必須               | なし                                           |
| OCI_COMPARTMENT_OCID       | コンパートメントのOCID                                       | 必須               | なし                                           |
| OCI_TTL                    | Let's Encryptでdns-01認証をする際に、一時的に作成するレコードのTTL | 任意               | 30                                             |
| OCI_PROPAGATION_TIMEOUT    | Let's Encryptでdns-01認証をするときの、タイムアウト          | 任意               | 60                                             |
| OCI_POLLING_INTERVAL       | Let's Encryptでdns-01認証をするときの、インターバル          | 任意               | 2                                              |
| LETSENCRYPT_DOMAINS        | SSL証明書を取得したいドメイン名。カンマ区切りで複数指定可能  | 必須               | なし                                           |
| LETSENCRYPT_MY_MAILADDRESS | SSL証明書に紐づくメールアドレス                              | 必須               | なし                                           |
| LETSENCRYPT_CA_URL         | Let's Encrypt を始めとしたCA URLを指定。動作確認をするときには、stagingを指定すると便利 | 任意               | https://acme-v02.api.letsencrypt.org/directory |
| OCI_LB_OCID                | SSL証明書を設定したいLoadBalancerのOCID                      | 必須               | なし                                           |
| OCI_LISTENERS              | SSL証明書を設定したいLoadBanancerに所属している、ListenerのOCID | 必須               | なし                                           |
| OCI_OS_NAMESPACE           | ObjectStorageのネームスペース名                              | 必須               | なし                                           |
| OCI_OS_BUCKETNAME          | ObjectStorageにSSL証明書や秘密鍵を保存するときのバケット名。指定したバケットが存在しない場合は、自動的にPrivateバケットを作成 | 任意               | lego-cert                                      |



## loggingの設定 (任意)

現在、Oracle Functions は Limited Available となっており、logを確認したい場合は、logging用のsyslogを用意する必要があります。
Papertrail というサービスを使うことで、簡単に無料でsyslogを表示することが出来ます。
設定方法などは、[こちら](https://qiita.com/sugimount/items/590796840338170f62d8)を参照してください。

今回の自動SSL設定は、2分くらい実行時間が必要なので、loggingしておくと進捗がわかって便利です。



## Functionの実行

fn invoke コマンドで、Functionを実行できます。実行してから2分弱ほど経過すると、成功した旨のメッセージが表示されます。
また、実行中の経過はloggingを確認するとよいです。

```
fn invoke env-app oci-lego-sslupdate
```



なお、現在のOracle Functions では、定期実行することは制限されていて出来ませんが、GAされれば定期実行できるため、非常にお手軽にSSL証明書を自動的に更新し続けることが出来ます。