This repository has been archived on 2025-11-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
test/tls-sync/job.py
merlin 3df0658949
Some checks failed
Docker Image CI / build (push) Failing after 21s
ci test
2025-10-20 15:09:42 +08:00

214 lines
7.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
import os
import time
import base64
import logging
import subprocess
from kubernetes import client, config, watch
from kubernetes.client.rest import ApiException
from Crypto.PublicKey import RSA
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# 配置 Kubernetes 客户端
try:
config.load_incluster_config() # 如果在 Pod 内运行
except Exception as e:
logging.error("Failed to load kube config: %s", e)
exit(1)
v1 = client.CoreV1Api()
# 配置 Nginx 主机和目录
CLUSTER_HOST = "10.0.0.4"
NGINX_HOST = "10.0.0.1"
NGINX_USER = "nginx"
NGINX_CERT_DIR = "/etc/nginx/certs"
NGINX_CONF_DIR = "/etc/nginx/conf.d"
NAMESPACE = "basic"
MAX_RETRIES = 10 # 最大重试次数
RETRY_DELAY = 20 # 每次重试的时间间隔(秒)
# 创建目录用于存放临时证书和密钥
TEMP_DIR = "/tmp/nginx_certs"
RSA_DIR = "/root/.ssh" # 新增的文件夹,用于存放 SSH 密钥对
PUBLIC_KEY_COMMENT = "generated-key"
os.makedirs(TEMP_DIR, exist_ok=True)
os.makedirs(RSA_DIR, exist_ok=True)
def generate_ssh_key_pair():
"""
生成 SSH 密钥对(私钥为 PEM公钥为 OpenSSH 单行格式),并保存到 RSA_DIR。
私钥权限设为 600公钥权限设为 644。
"""
try:
logging.info("Generating SSH key pair...")
os.makedirs(RSA_DIR, exist_ok=True)
# 生成 RSA 私钥
key = RSA.generate(2048)
# 私钥PEM多行
private_key_bytes = key.export_key(format='PEM')
# 公钥OpenSSH 单行格式,例如: b"ssh-rsa AAAAB3NzaC1yc2E..."}
public_key_bytes = key.publickey().export_key(format='OpenSSH')
# 给公钥添加注释(可选),并确保以换行结尾
if PUBLIC_KEY_COMMENT:
public_key_bytes = public_key_bytes + b' ' + PUBLIC_KEY_COMMENT.encode('utf-8')
public_key_bytes = public_key_bytes + b'\n'
private_key_path = os.path.join(RSA_DIR, "id_rsa")
public_key_path = os.path.join(RSA_DIR, "id_rsa.pub")
# 写入私钥与公钥
with open(private_key_path, "wb") as private_file:
private_file.write(private_key_bytes)
with open(public_key_path, "wb") as public_file:
public_file.write(public_key_bytes)
# 设置合理的文件权限
try:
os.chmod(private_key_path, 0o600)
os.chmod(public_key_path, 0o644)
except PermissionError:
logging.warning("No permission to chmod the key files; please set permissions manually if needed.")
logging.info(f"SSH key pair saved to {private_key_path} and {public_key_path}")
except Exception as e:
logging.error("Failed to generate SSH key pair: %s", e)
def upload_cert_with_retry(tmp_crt, tmp_key, domain):
"""
尝试多次上传证书文件到 Nginx 服务器,直到成功或达到最大重试次数
"""
retries = 0
while retries < MAX_RETRIES:
try:
logging.info(f"Attempting to upload certificate for {domain}, attempt {retries + 1}...")
# 上传证书和私钥,使用 ssh + sudo tee
for local_file, remote_file in [
(tmp_crt, f"{NGINX_CERT_DIR}/{domain}.pem"),
(tmp_key, f"{NGINX_CERT_DIR}/{domain}.key")
]:
ssh_command = [
"ssh", f"{NGINX_USER}@{NGINX_HOST}",
f"sudo tee {remote_file} > /dev/null"
]
with open(local_file, "rb") as f:
subprocess.run(ssh_command, stdin=f, check=True)
logging.info(f"Uploaded certificate and key for {domain} to Nginx server.")
return # 上传成功,退出循环
except subprocess.CalledProcessError as e:
retries += 1
logging.error(f"Attempt {retries} failed: {e}. Retrying in {RETRY_DELAY} seconds...")
time.sleep(RETRY_DELAY)
logging.error(f"Failed to upload certificate for {domain} after {MAX_RETRIES} attempts.")
def upload_cert(domain, crt_data, key_data):
"""
上传证书和私钥到 Nginx 中转服务器,并生成 Nginx 配置文件
"""
try:
# 临时保存证书和私钥
tmp_crt = os.path.join(TEMP_DIR, f"{domain}.crt")
tmp_key = os.path.join(TEMP_DIR, f"{domain}.key")
with open(tmp_crt, "wb") as f:
f.write(base64.b64decode(crt_data))
with open(tmp_key, "wb") as f:
f.write(base64.b64decode(key_data))
logging.info(f"Certificate and key for {domain} saved locally.")
# 上传证书和私钥
upload_cert_with_retry(tmp_crt, tmp_key, domain)
# 生成 Nginx 配置文件
conf_content = f"""
server {{
listen 443 ssl http2;
server_name {domain};
ssl_certificate /etc/nginx/certs/{domain}.pem;
ssl_certificate_key /etc/nginx/certs/{domain}.key;
location / {{
proxy_pass https://{CLUSTER_HOST};
proxy_ssl_server_name on;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}}
}}
"""
tmp_conf = os.path.join(TEMP_DIR, f"{domain}.conf")
with open(tmp_conf, "w") as f:
f.write(conf_content)
# 上传 Nginx 配置文件
ssh_command = [
"ssh", f"{NGINX_USER}@{NGINX_HOST}",
f"sudo tee {NGINX_CONF_DIR}/{domain}.conf > /dev/null"
]
logging.info(f"Uploading Nginx config for {domain} using sudo tee...")
with open(tmp_conf, "rb") as f:
subprocess.run(ssh_command, stdin=f, check=True)
logging.info(f"Uploaded Nginx config for {domain} to Nginx server via sudo tee.")
# 重载 Nginx
reload_command = ["ssh", f"{NGINX_USER}@{NGINX_HOST}", "sudo nginx -s reload"]
subprocess.run(reload_command, check=True)
logging.info(f"Nginx reloaded successfully for {domain}.")
except subprocess.CalledProcessError as e:
logging.error("Error during SSH commands: %s", e)
except Exception as e:
logging.error("Unexpected error: %s", e)
def watch_secrets_all_ns():
"""
监听所有命名空间中的 Secrets当发现以 -tls 结尾的 cert-manager 生成证书时上传到 Nginx
"""
logging.info("Starting to watch all Kubernetes secrets across namespaces.")
w = watch.Watch()
try:
# 监听所有命名空间
for event in w.stream(v1.list_secret_for_all_namespaces):
secret = event['object']
secret_name = secret.metadata.name
annotations = secret.metadata.annotations or {}
# 仅处理 cert-manager 生成的 TLS Secret
if secret_name.endswith("-tls") and annotations.get("cert-manager.io/issuer-name"):
if "tls.crt" in secret.data and "tls.key" in secret.data:
domain = secret_name[:-4] # 去掉 -tls 后缀
logging.info(f"Found TLS secret {secret_name} for domain {domain}, uploading...")
upload_cert(domain, secret.data["tls.crt"], secret.data["tls.key"])
except ApiException as e:
logging.error("Kubernetes API exception: %s", e)
except Exception as e:
logging.error("Unexpected error: %s", e)
exit(1)
if __name__ == "__main__":
# 生成 SSH 密钥对
generate_ssh_key_pair()
# 开始监听 Secrets 变化
watch_secrets_all_ns()