標籤:FreeIPA

使用 cert-manager 透過 ACME 從 FreeIPA 簽發憑證

本篇 TL;DR 因包含腳本及 K8s yaml 內容較長,有興趣直接閱讀全文的讀者可以按此跳到正文

TL;DR

分成 FreeIPA 與 cert-manager 兩個部分設定:

FreeIPA

# ipa-acme-manage enable
# tsig-keygen -a hmac-sha512 cert-manager-acme | tee -a /etc/named/ipa-ext.conf
# systemctl restart named-pkcs11.service || systemctl restart named.service
# ipa dnszone-mod ${IPA_DNS_ZONE} --dynamic-update=True \
    --update-policy='grant cert-manager-acme wildcard * TXT;'

順便記下印出來的 TSIG Key Secret。

cert-manager

---
apiVersion: v1
kind: Secret
type: Opaque
metadata:
  namespace: cert-manager
  name: ipa-tsig
data:
  cert-manager-acme: ${TSIG_SECRET_IN_BASE64}
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: ipa
spec:
  acme:
    caBundle: ${IPA_CA_CERT_IN_BASE64}
    privateKeySecretRef:
      name: ipa-acme-account-key
    server: https://${IPA_CA_SERVER}/acme/directory
    solvers:
    - dns01:
        rfc2136:
          nameserver: ${IPA_NS_IP}
          tsigAlgorithm: HMACSHA512
          tsigKeyName: cert-manager-acme
          tsigSecretSecretRef:
            key: cert-manager-acme
            name: ipa-tsig
      selector:
        dnsZones:
        - ${IPA_DNS_ZONE}

完成。如果要從 Ingress 自動發憑證的話要記得加上 cert-manager.io/cluster-issuer: ipa 的 annotation。

前言

我們在管理 Kubernetes[1] 的時候常常會需要提供一個 TLS Ingress[2],在沒有自動化工具的協助下,我們會需要手動將憑證及金鑰塞進 Cluster 裡。

此時 cert-manager[3] 就能幫我們處理自動簽發憑證的問題,cert-manager 支援透過 ACME[4] 來完成自動化簽發憑證。通常,我們可能會拿來簽發 Let's Encrypt[5] 的憑證,但如何讓 Let's Encrypt 通過驗證就會是個問題,cert-manager 同時支援了 HTTP-01[6] 及 DNS-01[7] 兩種方式。

今天想要來跟大家聊聊,如果想透過 cert-manager 自動簽發憑證的話,我們可以怎麼設定。本篇以想要讓 FreeIPA[8] 作為憑證簽發者,而且透過 DNS-01 通過驗證當作目標來進行(適用於用 FreeIPA 管理 DNS zone 的場景,所以你想用 Let's Encrypt 簽發憑證也可以):

FreeIPA DNS

由於我們打算採用 DNS-01 作為 ACME 的認證手段,我們得先來搞定如何讓 cert-manager 自動來修改 FreeIPA 的 DNS 記錄。

FreeIPA 使用 BIND9[9] 作為其 DNS 的解決方案,由於 BIND 與 cert-manager 都支援[10] RFC-2136[11],因此我們可以透過 UPDATE 指令來更新 RR[12]。而為了讓 FreeIPA 的 BIND 確認這個更新的指令是從被授權的 cert-manager 來的,我們還必須透過 TSIG[13] RR 來進行簽名認證。

TSIG Secret Key

說到簽名,就必須要有一個可以拿來簽名[14]的 Secret Key,於是讓我們從建立這把金鑰開始:

# export TSIG_KEY_NAME=cert-manager-acme
# tsig-keygen -a hmac-sha512 ${TSIG_KEY_NAME} | tee -a /etc/named/ipa-ext.conf
key "cert-manager-acme" {
        algorithm hmac-sha512;
        secret "<...base64 encoded secret here...>";
};
# systemctl restart named.service || systemctl restart named-pkcs11.service
#

請自行將 ${TSIG_KEY_NAME} 替換成想要的金鑰名稱,像筆者是使用容易識別的 cert-manager-acme,表示這把金鑰是給 cert-manager 作為 ACME 用的金鑰。

另外,輸出的 secret 內容請記下來,此為 TSIG Secret Key,後面會用 ${TSIG_SECRET} 表示,在設定 cert-manager 時還會用到。我們在這裡建立了一把使用 HMAC-SHA512[15] 作為簽名演算法的金鑰。

上面的指令除了建立 TSIG Secret Key 以外,還會將這個設定寫入 BIND9 的設定檔中,並重新啓動 BIND9[16],讓 BIND9 認識這把金鑰的存在。

設定更新策略

光是讓 BIND9 認得 TSIG 金鑰是不夠的,我們還必須設定更新策略以讓 BIND9 可以按照允許規則來放行該金鑰的更新指令:

透過 FreeIPA WebUI

我們可以透過 WebUI 來進行更新策略的設定,在登入 WebUI 後點選 "Network Service" 選擇要用來簽發憑證的 DNS Zone 後,來到 Settings 並按照下方截圖說明設定:(如果你的 WebUI 不是英文版本,可參考截圖內的相對位置進行設定)

  • 啓用 "Dynamic update"
  • 在 "BIND update policy"[17] 中加入規則:(截圖中反白內容)
    • grant cert-manager-acme wildcard *.k8s.davy.home TXT;
    • 請將 cert-manager-acme 替換成剛剛設定的 ${TSIG_KEY_NAME}
    • 請將 *.k8s.davy.home 替換成讀者想要簽發憑證的 Domain name,筆者在此想讓 cert-manager 可以簽發 k8s.davy.home. 下的所有 Subdomain[18],因而如此設定

儲存後即可完成更新策略的設定。

透過 ipa CLI

如果不想要再花時間登入 WebUI 的話,也有透過 ipa 指令設定的方式:

# export IPA_DNS_ZONE=davy.home
# ipa dnszone-mod ${IPA_DNS_ZONE} --dynamic-update=True \
    --update-policy='grant cert-manager-acme wildcard *.k8s.davy.home TXT;'
#

請讀者自行依照實際情形替換 ${IPA_DNS_ZONE} 為對應的 DNS Zone,以及將 cert-manager-acme*.k8s.davy.home 替換成對應的內容。

cert-manager ClusterIssuer/Issuer

在 cert-manager 這側,我們需要做的事情就相對簡單了,只需要建立兩個資源 —— SecretClusterIssuer/Issuer

首先我們必須先決定這個 Issuer 是 Cluster-wide 的還是 Namespace-wide[^namespace] 的,如果你打算讓整個 Cluster 內的資源都能使用這個 Issuer 的話就選擇建立 ClusterIssuer,反之,就選擇建立 Issuer

本文將以 ClusterIssuer 作為範例,最大差別在於同一個 Issuer 的相關資源(例如 Secret 等)必須放在同一個 namespace 下;而 ClusterIssuer 則是將相關資源放在 cert-manager 指定的 namespace(預設與 cert-manager 同一個 namespace[19]),自己本身並沒有 namespace 的屬性。

TSIG Secret Key

首先,我們將建立一個存放 TSIG Secret Key 的 Secret 資源:

$ kubectl -n cert-manager create secret generic ipa-tsig \
    --from-literal=secret="${TSIG_SECRET}"
secret/ipa-tsig created
$

此處的 ${TSIG_SECRET} 在前面〈FreeIPA DNS → TSIG Secret Key〉一節中有提到過,如果還沒有取得的讀者請先往前翻閱。

如果讀者想建立的是 Issuer 資源,請在這一步驟中將 Secret 資源建立在對應的 Namespace 中。

建立 ClusterIssuer

有了存放 TSIG Secret Key 的資源後,我們就可以來建立 ClusterIssuer 資源了:

---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: ipa
spec:
  acme:
    caBundle: ${IPA_CA_CERT_IN_BASE64}
    privateKeySecretRef:
      name: ipa-acme-account-key
    server: https://${IPA_CA_SERVER}/acme/directory
    solvers:
    - dns01:
        rfc2136:
          nameserver: ${IPA_NS_IP}
          tsigAlgorithm: HMACSHA512
          tsigKeyName: ${TSIG_KEY_NAME}
          tsigSecretSecretRef:
            name: ipa-tsig
            key: secret
      selector:
        dnsZones:
        - ${IPA_DNS_ZONE}

其中有幾個值需要讀者根據實際情況替換:

  • ${IPA_CA_CERT_IN_BASE64}
    • FreeIPA 的 CA 憑證(以 base64 編碼的 PEM 格式表示)
    • 可以從 FreeIPA 主機的 /etc/ipa/ca.crt 取得
  • ${IPA_CA_SERVER}
    • 通常會是 ipa-ca.<IPA REALM DOMAIN>,以筆者的環境來說就是 ipa-ca.davy.home
  • ${IPA_NS_IP}
    • FreeIPA 的 DNS Primary IP,通常就是 FreeIPA 的 Primary Server IP
  • ${TSIG_KEY_NAME}
  • ${IPA_DNS_ZONE}
    • 你想要簽發的 DNS Zone 或 Subdomain,以筆者的案例來說就是 k8s.davy.home
  • ipa-acme-account-key
    • 這裡會自動建立用於存放 ACME 註冊資訊的 Secret,讀者可自由更換其名稱
  • server: https://${IPA_CA_SERVER}/acme/directory

如果讀者想建立的是 Issuer 資源,請在這一步驟中改建立 Issuer 資源,並將此建立在對應的 Namespace 中。

接下來我們可以透過 kubectl describe 來觀察 ClusterIssuer 與我們 FreeIPA ACME 服務的註冊情形:

$ kubectl describe clusterissuer ipa
...
Status:
  Acme:
    Last Private Key Hash:  BbkPF6ZPgulgisJ80ISEIq10JXHIB19M9I2eYOHIFAQ=
    Uri:                    https://ipa.davy.home/acme/rest/acct/Mt72_fSdmemIOK8cj89x6AZhwujqenQ5MyIxqCAnC1Y
  Conditions:
    Last Transition Time:  2024-07-18T15:18:40Z
    Message:               The ACME account was registered with the ACME server
    Observed Generation:   2
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
$

如果可以看到 Reason: ACMEAccountRegistered,表示我們的 FreeIPA 已經受理我們的 ACME 註冊手續了。但這還並不表示我們設定的 TSIG Secret Key 已經正確被驗證了,這會等到我們真的進行 DNS-01 驗證時,要修改 TXT RR 的時候才會去驗證。

TLS Ingress

到目前為止,我們已經大致上完成了 FreeIPA 及 cert-manager 兩側的串接了,我們可以透過新增一個測試用的 TLS Ingress 來測試是否真的成功了:

---
kind: Namespace
metadata:
  name: cert-manager-acme-test
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: cert-manager-acme-test
  name: hello-deployment
spec:
  selector:
    matchLabels:
      app: hello
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
      - image: nginx:1.14.2
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 80
          protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  namespace: cert-manager-acme-test
  name: hello-service
spec:
  type: ClusterIP
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: hello
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: ipa
  namespace: cert-manager-acme-test
  name: hello-ingress
spec:
  rules:
  - host: hello.k8s.davy.home
    http:
      paths:
      - backend:
          service:
            name: hello-service
            port:
              name: http
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - hello.k8s.davy.home
    secretName: hello-tls

上面的 yaml 會部署一組 DeploymentServiceIngress 到一個新的 Namespace cert-manager-acme-test 中。

注意我們在 Ingress 的地方除了設定 TLS 相關的內容以外,還多了一個 Annotation cert-manager.io/cluster-issuer,cert-manager 會自行過濾帶有此 Annotation 的 Ingress 並以其值對應的 ClusterIssuer 來簽發憑證[20]

如果想使用 Issuer 的話,此處換成 cert-manager.io/issuer 即可。

上方的 Ingress 在設定 TLS 時一併設定了一個 secretName,此為存放 TLS 憑證及金鑰用的 Secret 資源的名稱,如果不存在的話會自動建立。而我們亦可以透過觀察這個 Secret 及其對應的 cert-manager.io/v1/CertificateRequest 資源來看到憑證簽發的狀態如何:

$ kubectl -n cert-manager-acme-test get secret hello-tls -o yaml
apiVersion: v1
data:
  tls.crt: ...
  tls.key: ...
kind: Secret
metadata:
  annotations:
    cert-manager.io/alt-names: hello.k8s.davy.home
    cert-manager.io/certificate-name: hello-tls
    cert-manager.io/common-name: hello.k8s.davy.home
    cert-manager.io/ip-sans: ""
    cert-manager.io/issuer-group: cert-manager.io
    cert-manager.io/issuer-kind: ClusterIssuer
    cert-manager.io/issuer-name: ipa
    cert-manager.io/uri-sans: ""
  labels:
    controller.cert-manager.io/fao: "true"
  name: hello-tls
  namespace: cert-manager-acme-test
type: kubernetes.io/tls
$ kubectl -n cert-manager-acme-test get CertificateRequest \
  -o custom-columns="NAME:.metadata.name,Secret:.metadata.annotations.cert-manager\.io/certificate-name,Issuer:.spec.issuerRef.name,Issued:.status.conditions[?(@.type=='Ready')].status"
NAME          Secret      Issuer   Issued
hello-tls-1   hello-tls   ipa      True
$ kubectl -n cert-manager-acme-test get CertificateRequest hello-tls-1 -o yaml
apiVersion: cert-manager.io/v1
kind: CertificateRequest
metadata:
  annotations:
    cert-manager.io/certificate-name: hello-tls
    cert-manager.io/certificate-revision: "1"
    cert-manager.io/private-key-secret-name: hello-tls-7nrlt
  name: hello-tls-1
  namespace: cert-manager-acme-test
  ownerReferences:
  - apiVersion: cert-manager.io/v1
    blockOwnerDeletion: true
    controller: true
    kind: Certificate
    name: hello-tls
spec:
  issuerRef:
    group: cert-manager.io
    kind: ClusterIssuer
    name: ipa
  request: ...
status:
  certificate: ...
  conditions:
  - lastTransitionTime: "2024-07-18T14:58:21Z"
    message: Certificate request has been approved by cert-manager.io
    reason: cert-manager.io
    status: "True"
    type: Approved
  - lastTransitionTime: "2024-07-18T15:19:48Z"
    message: Certificate fetched from issuer successfully
    reason: Issued
    status: "True"
    type: Ready
$

Secret 及對應的 CertificateRequest 中可以看出來,cert-manager 已經幫我們完成了一條龍 —— 從設定 TLS Ingress 到設定 TXT RR 到通過 DNS-01 驗證再回到設定 Ingress 憑證。

我們也可以打開對應的 Ingress URL 來看到結果:

Certifiacte Information

Certificate Chain

後記

原本以為可以在兩天內連續產出的這篇文章結果又拖了兩天才終於寫完,準備環境明明只花了一個晚上,結果大部分的時間都花在找對應的 Reference。 Orz

總之,這個技巧除了讓 FreeIPA 簽發憑證外,只要是透過 FreeIPA 或甚至純粹地用 BIND9 管理 DNS 的人都能使用這個方式自動化設定 TXT RR 來自動完成 DNS-01 驗證。

或是也可以延伸出其他的用法,其實並不限於 ACME 的場景而已,如果能夠理解背後的設定的意義的話就會有更多有趣的應用可以發掘。 :)


  1. Kubernetes 是用於自動部署、擴展和管理「容器化(containerized)應用程式」的開源系統,詳見其官方網站 ↩︎

  2. 此處指 Kubernetes 中的 networking.k8s.io/v1/Ingress 資源,用於從 Cluster 中暴露 HTTP/HTTPS 服務至外界,其詳細概念請見 Kubernetes 文件《Ingress》一章中的解釋 ↩︎

  3. cert-manager 是用於 Kubernetes 中的 X.509 憑證管理器,詳見其官方網站 https://cert-manager.io/ ↩︎

  4. "Automatic Certificate Management Environment",自動化憑證管理環境,定義於 RFC-8555,或可參考 Let's Encrypt 提供的解釋 ↩︎

  5. Let's Encrypt 是由 ISRG (Internet Security Research Group) 提供的非營利 CA 憑證機構,其透過 ACME 證明網域所有權後可免費提供其所有者短期 TLS 憑證,詳見其官網 https://letsencrypt.org/ ↩︎

  6. HTTP-01 為 ACME 定義的驗證方式之一,透過所有者在指定位置(https://<DOMAIN>/.well-known/acme-challenge/<TOKEN>)放置指定內容的方式驗證其網域所有權 ↩︎

  7. DNS-01 為 ACME 定義的驗證方式之一,透過所有者設定指定 TXT RR (_acme-challenge.<DOMAIN>)來驗證其網域所有權 ↩︎

  8. FreeIPA 是免費的開源身份管理系統,IPA 分別代表 Identity、Policy、Audit,同時也是 Red Hat Identity Manager 的上游開源專案,詳見其官方網站 ↩︎

  9. BIND9 是現今網際網路上常見的 DNS 伺服器軟體,目前由網際網路系統協會(ISC, Internet Systems Consortium)負責開發與維護。詳見維基百科《BIND》條目,或其官方網站 ↩︎

  10. 關於在 cert-mananger 中設定 RFC-2136 的詳細方式及說明請參閱使用手冊〈DNS-01 / RFC-2136〉一節 ↩︎

  11. "Dynamic Updates in the Domain Name System (DNS UPDATE)",旨在使得 DNS 可以在線上動態更新記錄,詳見 RFC-2136 內容 ↩︎

  12. Resource Record,RFC-1034 3.6 ↩︎

  13. Transaction Signature,由 RFC-2845 定義的 RR Type,詳見 RFC-2845 或維基百科上的《TSIG》條目 ↩︎

  14. 此處的「簽名」指的是 MAC (Message Authentication Code),用於認證及確認資料完整性,有關 MAC 的說明請參閱維基百科《Message Authentication Code》條目 ↩︎

  15. 在 RFC-2845 中僅定義 TSIG 可採用 HMAC-MD5 作為簽名演算法,但 MD5 在 2024 的現在已不夠安全,因此我們在此選擇使用 RFC-4635 擴充支援的 HMAC-SHA512 演算法。關於 HMAC-SHA 系列演算法的定義請參閱 RFC-4634;SHA 系列演算法的定義請參閱 FIPS180-4 ↩︎

  16. 依據實際情況的不同,FreeIPA 啓用的 BIND9 可能是 named.servernamed-pkcs11.server,這裡筆者偷懶將兩個都重啓試試看,讀者可以視情況自行調整 ↩︎

  17. BIND9 提供了許多更新策略的設定方式,其他規則寫法可參見筆者常參考的 ZyTrax 網站中關於 update-policy 的說明 ↩︎

  18. Subdomain —— 子域名,為指定域名再多一層的域名,詳見維基百科《Subdomain》條目 ↩︎

  19. 關於 ClusterIssuer/Issuer 相關的 Secret 需放置於何 Namespace 可參見原始碼中的說明,關於如何設定 Cluster resource namespace,如果是使用 Helm 部署 cert-mananger 的讀者可以參閱 clusterResourceNamespace 的設定說明 ↩︎

  20. cert-manager 會透過 webhook 來對 Ingress 做整合,詳見 cert-mananger 文件中〈Requesting Certificates → Ingress〉一節 ↩︎

如何從 NSSDB (certutil) 中取出 pem 格式的 key

本篇提到的 certutil 系指 UNIX 系統下的 Certificate Database 管理工具,與 Windows Server 的 certutil 無關,若想了解 Windows Server 下的 certutil,請造訪: https://docs.microsoft.com/zh-tw/windows-server/administration/windows-commands/certutil

因為最近在玩 FreeIPA 的關係,會需要幫 https 服務簽署憑證,在理解如何使用 FreeIPA 簽署的同時,順便發現到了一個管理憑證與其金鑰的工具 —— certutil[1]

certutil 可以管理 NSS Database 裡面的憑證與金鑰,FreeIPA 中的 PKI 服務 Dogtag 亦使用此一工具來管理憑證,就連 Firefox 自有的憑證庫也是使用這個方式儲存。不過,雖然這個 toolset 很棒,但我的 https 服務只吃 pem 格式的憑證,但從 NSSDB 取出的金鑰則會是 p12[2] 格式,這裡需要再做一些人工的轉換,所以這裡筆記一下該如何使用這些工具順便簡單介紹一下從 FreeIPA 上簽發憑證的流程吧。

建立 NSSDB 並放入 CA

在跟 FreeIPA 請求憑證簽署之前,我們要先有自己的金鑰庫,所以我們要先透過 certutil 來建立金鑰庫並先匯入我們的 CA 憑證,先假設我們要把 NSSDB 存放在 ~/certs

$ mkdir -p ~/certs
$ certutil -N -d ~/certs # 建立新的 NSSDB
$ certutil -A -d ~/certs -n 'IPA CA' -t CT,, -a < ca.crt # 匯入 CA 憑證

certutil 的指令說明可以去參考使用手冊,這裡只會解釋有用到的部分:

  • -d 指定 NSSDB 位置
  • -N 建立新的 NSSDB
  • -A 匯入已存在的憑證
    • -n 給予該憑證一個暱稱(此例為 IPA CA
    • -t 設定該憑證的信任模式,這裡我們採取信任該 CA 簽署的任何 Server/Client SSL
    • -a 採 ASCII 編碼輸入/出

建立金鑰及憑證簽署請求

有了自己的金鑰庫之後,就是要產生金鑰及憑證簽署請求(Certificate Request)了,透過 certutil 我們可以一步同時產生兩者:

$ certutil -R -d ~/certs -a -g 4096 -s CN=web.example.com,O=EXAMPLE.COM > web.csr
  • -R 產生一個憑證簽署請求檔(.csr
    • -g 金鑰長度,預設為 1024(此例為 4096
    • -s 主體名稱(Subject),此處需填寫此憑證的 Common Name(通常是 FQDN),以及 FreeIPA 要求需填寫 Organization

若需要加入 SAN[3],可以再加上 --extSAN 參數,例如 --extSAN dns:web.example.com,dns:web.example.org

從 FreeIPA 簽發憑證

從 FreeIPA 簽發的部分可以從 Web UI 或 CLI 建立,這邊兩者都會介紹,請讀者自己選擇自己喜歡的方式:

via CLI

$ kinit admin # 先登入有權限簽發憑證的 IPA 帳號
Password for [email protected]:
$ ipa cert-request --principal=HTTP/web.example.com web.csr
Issuing CA: ipa
Certificate: ...
Subject: CN=web.example.com,O=EXAMPLE.COM
Issuer: CN=Certificate Authority,O=EXAMPLE.COM
Not Before: Wed Mar 18 00:00:00 2020 UTC
Not After: Thu Mar 17 00:00:00 2022 UTC
Serial number: 11 # <= 記下序號
Serial number (hex): 0xB
$ ipa cert-show 11 --out=web.crt # 填入序號(11)
Issuing CA: ipa
Certificate: ...
Subject: CN=web.example.com,O=EXAMPLE.COM
Issuer: CN=Certificate Authority,O=EXAMPLE.COM
Not Before: Wed Mar 18 00:00:00 2020 UTC
Not After: Sat Mar 19 00:00:00 2022 UTC
Serial number: 11
Serial number (hex): 0xB
Revoked: False
  • ipa cert-request 簽署憑證
    • --principal IPA 中的 Kerberos 主體
  • ipa cert-show 取得對應序號之憑證

取得憑證後,我們接下來就是要準備取得 pem 格式的金鑰了。

via Web UI

首先找到我們要簽署的服務(HTTP/web.example.com),然後按下 Service Certificate 中的 Add,並填入剛剛產生的 csr 內容:


接下來對剛剛產生的 Certificate 按下 Actions -> Get,就可以取得剛剛產生的憑證,我們先複製下來並儲存到 web.crt,然後就可以準備取得 pem 格式的金鑰了。


取得 pem 格式金鑰

終於要進入本篇主題了 —— 取得 NSSDB 中的金鑰,這裡我們還會需要 NSS Security Tools 裡的另一個工具 pk12util[4],他負責將 NSSDB 中的憑證及金鑰以 p12 的格式匯入/匯出,我們稍後要利用他匯出 p12 格式的金鑰並轉成 pem 格式。

匯入簽發憑證

在取得金鑰之前,我們必須先匯入剛剛取得的憑證,讓 NSSDB 知道我們有一個憑證對應到剛剛產生的金鑰,之後我們才可以將金鑰取出:

$ certutil -A -d ~/certs -n web -t u,u,u -i web.crt
  • -A 匯入已存在的憑證
    • -n 給予該憑證一個暱稱(此例為 web
    • -t 設定該憑證的信任模式,這裡我們信任該憑證作為驗證及簽章用
    • -i 憑證檔名

取出 p12 金鑰

成功匯入憑證之後我們就可以使用 pk12util 將金鑰匯出了:

$ pk12util -d ~/certs -n web -o web.p12

pk12util 的參數大致上都跟 certutil 差不多,執行完畢後會得到一個 p12 格式的金鑰 web.p12

轉換成 pem 金鑰

轉換的步驟也很簡單,透過萬能的 OpenSSL 就可以轉換了:

$ openssl pkcs12 -in web.p12 -out web.key -nocerts -nodes

經過轉換後的 pem 格式金鑰會被存放在 web.key 中,如果打開來看的話會發現除了金鑰本體,還會附上一些額外資訊如下,如果是要拿去給服務吃的話可以直接刪掉留下金鑰本體即可:

Tag Attributes
    friendlyName: web
    localKeyID: ...
Key Attributes: <No Attributes>
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----

後記

之前有發過一篇關於使用 OpenSSL 來簽發中介 CA 的方法,這次改嘗試使用不同的工具來操作憑證,畢竟達到相同目的的工具百百種,多用一些抓到自己最順手的方式及工具可以大大的提升工作效率,如果有興趣使用 NSSDB 管理憑證及金鑰的朋友們可以多試試看,真的滿方便的,金鑰再也不會亂丟了 XDDDD


  1. 一個在 UNIX 系統下管理 Certificate Database 的工具,屬於 NSS Security Tools 的一部分,在 Debian/Ubuntu 下套件名為 libnss3-tools;RHEL/Fedora/CentOS 下則為 nss-tools。使用說明可以參考 MDNman 1 certutil↩︎

  2. PKCS #12,存放金鑰及憑證的一種格式,常見副檔名有 .p12.pfx,詳見維基百科上的《PKCS 12》條目 ↩︎

  3. Subject Alternative Name,為 X.509 extension 的一部分,支援單一憑證擴充描述替代用的主體名稱,可用來讓憑證支援多個 domain。 ↩︎

  4. 使用說明可以參考 MDNman 1 pk12util↩︎

如何將 Rancher 串上 FreeIPA 驗證

因工作需求,最近在研究如何使用 Rancher[1] 來管理 Kubernetes[2] 叢集,於是打算在 Homelab 上也建一組,想說前幾個禮拜還在研究 FreeIPA[3] 乾脆把他們湊一堆好了 XD

準備 Service Account

在開始將 FreeIPA 驗證接進 Rancher 之前,Rancher 要求先在 IPA 裡面建立一個 Service Account 來提供 Rancher 查詢 Domain 下的使用者與群組,但這個建立 Service Account 的步驟無法從 FreeIPA Web 界面執行,必須手動將這個 Account 加入 LDAP 中[4],首先到 IPA master 上執行下列步驟:(假設 IPA domain 為 example.com

$ cat - <<-EOF > add-srvacct-rancher.ldif
dn: uid=rancher,cn=sysaccounts,cn=etc,dc=example,dc=com
changetype: add
objectclass: account
objectclass: simplesecurityobject
uid: rancher
userPassword: 2Brq/Ux4rs:2Dnt3#%Rx
nsIdleTimeout: 0
EOF
$ ldapmodify -x -D 'cn=Directory Manager' -W < add-srvacct-rancher.ldif
Enter LDAP Password: # Enter Dircetory Manager password
adding new entry "uid=rancher,cn=sysaccounts,cn=etc,dc=example,dc=com"
$

其中 dc=example,dc=com 記得換成自己的 IPA domain,以及自己產生 userPassword(筆者是使用密碼產生器產生)後記下,待會會在 Rancher 設定用到。

設定 Rancher

首先登入有 administrator 權限的帳號,並依照以下步驟設定 FreeIPA Authenticate:

  1. 在 Global 域中選擇 Security -> Authentication
  2. 選擇 FreeIPA
  3. 填入 FreeIPA 伺服器資訊
  4. 在 Service Account 相關資訊中填入剛剛建立的 Service Account
    • Service Account Distinguished Name(記得換成 IPA domain)
      • uid=rancher,cn=sysaccounts,cn=etc,dc=example,dc=com
    • Service Account Password
      • 剛剛建立的 userPassword
  5. 填入使用者資料庫相關資訊
    • User Search Base(記得換成 IPA domain)
      • cn=users,cn=accounts,dc=example,dc=com
    • Group Search Base(記得換成 IPA domain)
      • cn=groups,cn=accounts,dc=example,dc=com
  6. Customize Schema 的部分我會選擇調整使用者的帳號資訊對應
    • Username Attribute(使用者顯示名稱)
      • displayName
    • Login Attribute(使用者帳號)
      • uid
    • Search Attribute(搜尋欄位,在搜尋使用者時會去尋找的欄位,預設是姓名跟帳號)
      • uid
  7. 在 Test and enable authentication 中填入要與目前帳號綁定的 FreeIPA 帳號資訊
    • 根據 Rancher 的說明[5],在這裡填入的 FreeIPA 帳號(external principal)會自動對應到目前用來設定的管理員帳號(local principal),並會在設定成功之後自動改用 external principal 登入 Rancher
  8. 在設定完成後點擊 Authenticate with FreeIPA 即可完成設定

設定完成後發現會被 Rancher 自動以剛剛 test authentication 的帳號登入,並且同時又是剛剛設定時使用的管理員帳號,從 Preference 頁面可以看到這個現象,但不要緊張,只是因為 Rancher 把這兩個帳號綁定了,共用 local ID。

後記

這次的篇幅稍短,介紹的內容也是偏簡單,僅對於 FreeIPA 如何建立 Service Account 的部分有特別需要撰寫 ldif 來手動加入 LDAP。 不知道大家對於這種篇幅及類型的文章接受度如何,就先當作是嘗試看看,同時也是在回應筆者朋友兼大神 小飛機 提出的看法[6]「資訊太多,透過文字消化資訊和整理思緒」並為自己而寫,因此打算開始嘗試這種廢文體短篇筆記型的內容,同時用比較輕鬆的態度來發文看看 XD


  1. 管理 Kubernetes 叢集的工具,提供多叢集、ACL、自動部署、Web 界面等特點,該公司還有出很多 K8s 相關的其他工具,詳見官方網站 ↩︎

  2. Kubernetes 是用於自動部署、擴展和管理「容器化(containerized)應用程式」的開源系統,詳見其官方網站 ↩︎

  3. FreeIPA 是免費的開源身份管理系統,IPA 分別代表 Identity、Policy、Audit,同時也是 Red Hat Identity Manager 的上游開源專案,詳見其官方網站 ↩︎

  4. FreeIPA 的 HowTo/LDAP 頁面亦有提及如何新增 Service Account/System Account ↩︎

  5. https://rancher.com/docs/rancher/v2.x/en/admin-settings/authentication/#external-authentication-configuration-and-principal-users ↩︎

  6. 請見小飛機大大的文章《成為一名技術留跡者↩︎