八只猪

为内网 Homelab 创建 CA 证书和通配符证书

家里 Homelab 使用了 Let’s Encrypt 的通配符证书提供外网的 HTTPS 访问,内网则是使用 HTTP 或者 IP 直接访问。最近把 Homelab 的外网完全关闭了,通过 VPN 回家以提高安全性。

当全切回内网后,Bitwarden iOS 客户端无法正常使用,在登录时会提示错误。大概原因是 Apple 会禁止使用不安全的连接

其实应该使用 HTTPS 的,毕竟是一个存储密码的服务,安全性还是很重要的。本文记录了给内网 Homelab 绑定自定与域名以及启用 HTTPS 证书的相关步骤,大致为:

  1. 创建一个 CA 证书
  2. 创建一个 通配符证书
  3. 服务使用通配符证书
  4. 设备导入 CA 证书
  5. Enjoy

1. 创建证书

我将创建证书的逻辑整理成了以下脚本,执行根据提示输入即可。

#!/bin/bash

function check_file() {
    local file_path="$1"
    if [ -e "${file_path}" ]; then
        read -p "${file_path} 已存在,是否覆盖?(y/n):" yn
        if [[ "${yn}" != "y" ]]; then
            echo "操作已取消。"
            exit 1
        fi
    fi
}

# 询问用户 CA 存储的目录
read -p "请输入 CA 存储的目录(例如:/root/my_ca):" ca_dir
mkdir -p "${ca_dir}"
cd "${ca_dir}"

# 询问用户 CA 文件名(默认为 ca.key 和 ca.crt)
read -p "请输入 CA 私钥文件名(默认:ca.key):" ca_key
ca_key=${ca_key:-ca.key}
read -p "请输入 CA 证书文件名(默认:ca.crt):" ca_crt
ca_crt=${ca_crt:-ca.crt}
read -p "请输入 组织名(默认:Homelab.LAB):" org_name
org_name=${org_name:-Homelab.LAB}

check_file "${ca_key}"
check_file "${ca_crt}"

# 询问用户要创建通配符证书的域名(默认为 xxxx.lab)
read -p "请输入要创建通配符证书的域名(默认:xxxx.lab):" domain
domain=${domain:-xxxx.lab}

wildcard_key="wildcard.${domain}.key"
wildcard_csr="wildcard.${domain}.csr"
wildcard_crt="wildcard.${domain}.crt"
wildcard_ext="wildcard.ext"

check_file "${wildcard_key}"
check_file "${wildcard_csr}"
check_file "${wildcard_crt}"
check_file "${wildcard_ext}"

ca_openssl_cnf="ca_openssl.cnf"
check_file "${ca_openssl_cnf}"

cat > "${ca_openssl_cnf}" << EOL
[req]
default_bits = 4096
prompt = no
default_md = sha256
distinguished_name = dn

[ dn ]
C = ZZ
ST = Utopia
L = The Peach Garden
O = ${org_name}
OU = ${org_name}
CN = CA.${domain}
EOL

# 创建 CA
openssl genrsa -out "${ca_key}" 4096
openssl req -x509 -new -nodes -key "${ca_key}" -sha256 -days 3650 -out "${ca_crt}" -config "${ca_openssl_cnf}"

wildcard_openssl_cnf="wildcard_openssl.cnf"
check_file "${wildcard_openssl_cnf}"

cat > "${wildcard_openssl_cnf}" << EOL
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext

[ dn ]
C = ZZ
ST = Utopia
L = The Peach Garden
O = ${org_name}
OU = ${org_name}
CN = *.${domain}

[ req_ext ]
subjectAltName = @alt_names

[alt_names]
DNS.1 = *.${domain}
DNS.2 = ${domain}
EOL

# 为通配符证书创建私钥
openssl genrsa -out "${wildcard_key}" 2048

# 为通配符证书创建证书签名请求(CSR)
openssl req -new -key "${wildcard_key}" -out "${wildcard_csr}" -config "${wildcard_openssl_cnf}"

# 创建通配符证书的扩展文件
# 其中需要注意参数:extendedKeyUsage = serverAuth 需要添加,
cat > "${wildcard_ext}" << EOL
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
extendedKeyUsage = serverAuth

[alt_names]
DNS.1 = *.${domain}
DNS.2 = ${domain}
EOL

# 使用 CA 对 CSR 文件进行签名,生成通配符证书
openssl x509 -req -in "${wildcard_csr}" -CA "${ca_crt}" -CAkey "${ca_key}" -CAcreateserial -out "${wildcard_crt}" -days 820 -sha256 -extfile "${wildcard_ext}"

echo "创建完成,证书和私钥位于 ${ca_dir} 目录。"

代码执行完成后,会在第一步指定的目录生成几个证书文件:

以上代码中,需要注意两个地方(Apple - Requirements for trusted certificates in iOS 13 and macOS 10.15):

2. Kubernates 配置

创建 secret

在 Kubernates 中创建一个名为 helloworld-lab-tls 的通配符证书,需要注意下是使用了通配符的证书文件,而不是 CA 文件。

kubectl create secret tls helloword-lab-tls \
  --cert=wildcard.helloword.lab.crt \
  --key=wildcard.helloword.lab.key

同步 Secert 到其他命名空间

由于我是区分了 Namespace:

所以需要将证书同步到多个命名空间。我使用了 kuberops/config-syncer 来做多命名空间的同步。

安装 kuberops/config-syncer 比较简单,跟着这 #installation 部分操作就行。

使用也比较简单,步骤是:

  1. 给需要同步的内容打注解
  2. 给需要使用的命名空间添加 labels
# 1. 给证书打注解,支持同步到其他命名空间
kubectl annotate secret \
  helloword-lab-tls \
  kubed.appscode.com/sync="tls-certs=helloword.lab" \
  -n default
# 2. 给需要同步证书的命名空间添加 label,使能将证书同步到当前命名空间
apiVersion: v1
kind: Namespace
metadata:
  name: infra
  labels:
    tls-certs: helloword.lab
# 3. 查看证书是否已经同步到其他命名空间
~/Workspace/kube [main] → kubectl get secret -A | grep helloword-lab-tls
default           helloword-lab-tls                                                 kubernetes.io/tls                     2      44m
selfhosts         helloword-lab-tls                                                 kubernetes.io/tls                     2      22m
infra             helloword-lab-tls                                                 kubernetes.io/tls                     2      22m

tls secrets in kubernetes

服务使用通配符证书

部署的相关服务需要使用证书也可以直接在 Ignress 配置上配置,如下:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: memos-ingress
  namespace: selfhosts
  annotations:
    kubernetes.io/ingress.class: "traefik"
spec:
  tls:
    - hosts:
      - memo.helloword.lab
      secretName: helloword-lab-tls
  rules:
    - host: memo.helloword.lab
      http:
        paths:
        - path: /
          pathType: ImplementationSpecific
          backend:
            service: 
              name: memos-svc
              port:
                name: memos-web

添加好配置记得应到 pod,完成以上步骤后,可以在浏览器访问下服务,应该会提示证书错误,接下来就需要给设备添加自签名的通配符证书。

3. 导入证书

需要注意下,上面在绑定证书时是使用的通配符证书,而在设备上使用时是需要导入 CA 证书。

MacOS

  1. MacOS - Keychain Access - 左侧选择系统 / System keychain_access_import 导入过程中请选择信任证书 keychain_access_import_trust

导入并信任之后为: keychain_access_imported

Linux (Debian)

sudo cp wildcard.helloword.lab.crt /usr/local/share/ca-certificates
sudo update-ca-certificates

然后 curl 看一下是否有证书错误即可。

浏览器证书导入

Firefox 访问依旧提示证书错误,Firefox 没有使用系统的证书存储,需要自己单独导入一次。 firefox_warning.png firefox_importing_1.png firefox_importing.png

之后再访问就正常了。

手机导入证书

4. 内网 DNS 配置

我家庭网络的核心是 OpenWrt,所以这里贴出 OpenWrt 配置 DNS 的部分。

思路是将指定域名指定到 K3s 上(我的 Kubernates 是 K3s 版本),K3s 的 IP 为: 10.0.0.2

OpenWrt - Network - DHCP and DNS - Addresses 配置,格式如下:

/git.helloworld.lab/10.0.0.2

也可以直接通过登录到 OpenWrt 用命令行修改:

  1. 编辑 /etc/config/dhcp 文件
  2. config dnsmasq 部分的底部添加:list address '/git.helloworld.lab/10.0.0.2'
  3. 重启 /etc/init.d/dnsmasq restart

给各个设备、路由器都配置好后,就可以完全无障碍访问自签名的 HTTPS 域名了。