【LLM】局域网内为容器服务启用HTTPS

随笔3个月前发布 止疼药
42 0 0

OpenWebUI中,语音输入需要HTTPS才能使用麦克风等硬件资源,在局域网中通过NGINX转发实现HTTPS访问。

具体包含三个部分

容器部署open-webui和nginx生成ssl证书修改nginx配置文件

1、容器部署

基于docker-compose,执行docker-compose up -d即可。
其中主要的就是映射conf文件夹和ssl的路径,不映射手动添加也一样。open-webui的环境变量就看个人需求了。

services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    restart: always
    networks:
    - openwebui
  
  nginx:
    image: nginx
    container_name: nginx
    restart: always
    volumes:
    - /nginx/conf.d:/etc/nginx/conf.d
    - /nginx/ssl:/ssl
    ports:
    - "38080:8080"
    networks:
    - openwebui

# 为空即可
networks:
  openwebui:


1234567891011121314151617181920212223

2、生成ssl证书

这网上教程很多,基本一个命令就搞定,这里找GPT写了一个基于Flask的页面,方便访问(问就是因为懒得敲命令 )。具体代码放最后,有需要的可以复制。生成的文件放到上面配置的ssl文件夹里。

3.修改conf

conf.d文件夹下新建default.conf,写入内容如下,修改server_name以及ssl相关文件。
由于配置了network,localtion中访问地址可以直接用docker-compose中的app名字。
重启nginx容器即可访问。

server {
	listen 8080 ssl;
	server_name yourdomain.com;
	# 配置crt
	ssl_certificate "/ssl/xxx.crt";
	# 配置key
	ssl_certificate_key "/ssl/xxx.key";
	
	location /{
		proxy_pass http://open-webui:8080;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header Host $http_host;
		proxy_set_header X-NginX-Proxy true;
		proxy_redirect default;
	}
}


1234567891011121314151617

附.生成ssl证书代码

pip install cryptography

import os
from flask import Flask, jsonify, send_from_directory, request, render_template_string
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from datetime import datetime, timedelta

app = Flask(__name__)

# SSL 证书文件夹
SSL_FOLDER = "ssl"
if not os.path.exists(SSL_FOLDER):
    os.makedirs(SSL_FOLDER)


# 生成 SSL 证书
def generate_ssl_cert(domain_name):
    key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048,
        backend=default_backend()
    )

    subject = issuer = x509.Name([
        x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
        x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"California"),
        x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"),
        x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Organization"),
        x509.NameAttribute(NameOID.COMMON_NAME, domain_name),
    ])

    cert = x509.CertificateBuilder().subject_name(
        subject
    ).issuer_name(
        issuer
    ).public_key(
        key.public_key()
    ).serial_number(
        x509.random_serial_number()
    ).not_valid_before(
        datetime.utcnow()
    ).not_valid_after(
        datetime.utcnow() + timedelta(days=365)
    ).add_extension(
        x509.SubjectAlternativeName([x509.DNSName(domain_name)]),
        critical=False,
    ).sign(key, hashes.SHA256(), default_backend())

    cert_filename = f"{domain_name}.crt"
    key_filename = f"{domain_name}.key"

    cert_path = os.path.join(SSL_FOLDER, cert_filename)
    key_path = os.path.join(SSL_FOLDER, key_filename)

    with open(key_path, "wb") as f:
        f.write(key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.TraditionalOpenSSL,
            encryption_algorithm=serialization.NoEncryption()
        ))

    with open(cert_path, "wb") as f:
        f.write(cert.public_bytes(serialization.Encoding.PEM))

    return cert_filename, key_filename


# 首页接口,显示 HTML 页面
@app.route("/", methods=["GET"])
def index():
    return render_template_string('''
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SSL Certificate Generator</title>
    <script>
        async function generateCert() {
            const domainName = document.getElementById('domain_name').value;
            const response = await fetch('/generate_cert', {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({ domain_name: domainName })
            });

            if (response.ok) {
                const result = await response.json();
                document.getElementById('cert_link').href = `/download_cert/${result.certificate}`;
                document.getElementById('cert_link').innerText = result.certificate;
                document.getElementById('key_link').href = `/download_cert/${result.key}`;
                document.getElementById('key_link').innerText = result.key;
            } else {
                alert('Error generating certificate');
            }
        }
    </script>
</head>
<body>
    <h1>SSL Certificate Generator</h1>
    <form οnsubmit="event.preventDefault(); generateCert();">
        <label for="domain_name">Domain Name:</label>
        <input type="text" name="domain_name" required>
        <button type="submit">Generate Certificate</button>
    </form>

    <h2>Generated Files</h2>
    <p>Certificate: <a href="#" target="_blank">No certificate generated</a></p>
    <p>Private Key: <a href="#" target="_blank">No key generated</a></p>
</body>
</html>
    ''')


# 生成证书接口
@app.route("/generate_cert", methods=["POST"])
def generate_cert():
    domain_name = request.form.get("domain_name")
    if not domain_name:
        return jsonify({"error": "Domain name is required"}), 400

    cert_filename, key_filename = generate_ssl_cert(domain_name)
    return jsonify({"certificate": cert_filename, "key": key_filename})


# 下载证书接口
@app.route("/download_cert/<filename>", methods=["GET"])
def download_cert(filename):
    return send_from_directory(SSL_FOLDER, filename)


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)



123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...