NGINX SSL HTTPS 配置

前言

以Alpine Linux 的Docker 镜像为基础,创建一个SSL 证书配置测试环境,用于熟悉Nginx 的SSL 配置。

准备工作

Dockerfile GitHub

FROM alpine:3.6
RUN apk add --no-cache python3 nginx curl openssl socat bash openrc &&\
    mkdir /run/nginx &&\
    touch /root/.bashrc &&\
    echo "export PS1='\h:\w\\\$ '" >> /root/.bashrc &&\
    echo "alias r='fc -e -'" >> /root/.bashrc &&\
    echo "set -o vi" >> /root/.bashrc &&\
    echo 'rc_provide="loopback net"' >> /etc/rc.conf &&\
    rc-update add nginx default
VOLUME ["/tmp/sys/fs/cgroup","/sys/fs/cgroup"]
WORKDIR /root
CMD ["/sbin/init"]

创建Docker 镜像alssltest

# 获取Dockerfile 并创建镜像
git clone [email protected]:suzhi82/Alpine-SSLTEST.git &&\
cd Alpine-SSLTEST &&\
docker build -t alssltest .

# 查看Docker 镜像列表
docker image ls
# 或者
docker images

运行Docker 镜像alssltest

# 宿主机的8888 就当成80 来用,8001、8002 及8443 测试不同应用时用
docker run --name alssltest -itd \
  -p 8888:80 -p 8443:8443 -p 8001:8001 -p 8002:8002 \
  alssltest
参数 解释
–name 给容器命名,以便后面使用,否则只能用容器的ID
-i 获取标准输入STDIN,容器会一直等待用户输入而不退出
-t 给docker attach 准备,表用户连上来的是一个tty,否则连输密码都显示明文
-d detach,分离,即让容器后台运行
-p 端口映射,宿主机端口:容器端口,该参数可多次使用来映射多个端口

连接Docker 容器alssltest

docker exec -it alssltest /bin/bash

容器alssltest 内部调整

cat /etc/nginx/nginx.conf
http 内部最后可看到include /etc/nginx/conf.d/*.conf;
所以,我们可以将测试用的.conf 配置文件都写在/etc/nginx/conf.d/ 下面,然后

# 将默认的配置备份起来不启用
mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak

OpenSSL 自签SSL 证书

SSL 自签证书生成

# 创建保存SSL 文件的目录
mkdir -p /etc/nginx/ssl_keys/openssl
cd /etc/nginx/ssl_keys/openssl

# 生成服务器端的私钥 (key 文件)
openssl genrsa -out server.key 1024

# 生成服务器端证书签名请求文件 (csr 文件),certificate signing request,自定义区域详见下表
openssl req -new -key server.key -out server.csr

# 生成证书文件 (crt 文件),csr 文件至此就没有用了
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

csr 文件属性

自定义区域
Country Name (2 letter code) [XX]:CN--------------------------------- 证书持有者所在国家
State or Province Name (full name) []:BJ----------------------------- 证书持有者所在州或省份(可省略不填)
Locality Name (eg, city) []:BJ--------------------------------------- 证书持有者所在城市(可省略不填)
Organization Name (eg, company) []:NH-------------------------------- 证书持有者所属组织或公司
Organizational Unit Name (eg, section) []:.-------------------------- 证书持有者所属部门(可省略不填)
Common Name (eg, your name or your server's hostname) []:ceshi.com--- 域名
Email Address []:---------------------------------------------------- 邮箱(可省略不填)
challenge password:-------------------------------------------------- 自定义密码
An optional company name:-------------------------------------------- 可选公司名称

Nginx SSL 配置

# Nginx 配置文件
cat > /etc/nginx/conf.d/openssl.conf << EOF
server {
  listen 8443 ssl;
  server_name ceshi.com;
  ssl on;
  ssl_certificate     /etc/nginx/ssl_keys/openssl/server.crt;
  ssl_certificate_key /etc/nginx/ssl_keys/openssl/server.key;

  index index.html index.htm;
  location / {
    root  /home/app/code;
  }
}
EOF

访问测试文件

mkdir -p /home/app/code/
cat > /home/app/code/index.html << EOF
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Index</title>
</head>
<body>
  <h1>What a good day!</h1>
</body>
</html>
EOF

# 测试Nginx 配置
nginx -t

# 重启Nginx 服务
rc-service nginx restart
# 或者
nginx -s reload

重启完毕后就可以在浏览器上访问https://服务器IP:8443,出现Your connection is not privateHelp me understand,再点击下方的Proceed to 服务器IP (unsafe),出现What a good day! 表示访问成功,但不被浏览器所信任。

OpenSSL 使用补充

# 去除key 文件中的密钥
openssl rsa -in jesonc.key -out jesonc_nopwd.key

# 符合苹果标准的签名文件,直接生成私钥key 和公钥crt,由于是自签所以省略了请求文件csr 的生成
openssl req -days 3650 -x509 -sha256 -nodes -newkey rsa:2048 -keyout new_server.key -out new_server.crt
# rsa:2048 是服务器私钥的算法,-sha256 是发给浏览器证书即公钥的算法,可用以下命令查看
openssl x509 -noout -text -in new_server.crt

Nginx 排错补充

Nginx 启动的用户须拥有页面文件所在文件夹的读取权限,否则会报403 错误
cat /etc/nginx/nginx.conf

# 启动nginx 所用的用户
user nginx;

# 错误日志的存放路径
error_log /var/log/nginx/error.log warn;

OpenSSL 使用结论

由于是自签,证书需手工导入浏览器才会被信任,在生成证书申请的时候所有自定义信息都可以直接回车略过,只要浏览器输入的是服务器的IP 或指向该IP 的域名及对应端口即可访问到网站内容,用户的安全性可想而知。?

acme.sh 获取SSL 证书

获取正规渠道CA 颁发的SSL 证书

Freenom 域名转Cloudflare 管理

虽然根据cme.sh 提示在freenom 里添加txt 类型的DNS 记录也可申请到证书,但是每次续期都得操作一次,相较没有使用DNS 的API 自动化来得方便。

先从https://www.freenom.com 获取到免费域名,注意要挂美国IP 的VPN。
无非就是注册然后输入想要的域名 -->Check Availability,一般tk/ml/gq 等结尾的都是免费的。

注册登录https://www.cloudflare.com,Home 下面点击Add a Site,输入签名获得的域名例如maybook.tk,再按Add site,Select a plan,选择Free $0/month,一路continue,直至Change your nameservers 界面。

登录到https://www.freenom.com,Services -->My Domains,选中maybook.tk 的Manage Domain,–>Management Tools -->Nameservers,选择Use custom nameservers,写入如下两个服务器。

amit.ns.cloudflare.com
amy.ns.cloudflare.com

最后点击Change Nameservers 即可。

回到Cloudflare 的Change your nameservers 页面,点击Done, check nameservers,如果页面跳转的话则点击Re-check now 按钮。可能要等待几个小时Freenom 的域名才能交给Cloudflare 接管。

Cloudflare 接管之后会有Email 通知。登录Cloudflare 之后就会出现状态为Active 的maybook.tk 了,点击它,
Overview 中的Get your API key 的API Tokens 的Global API Key 可以授权第三方程序管理dns 的设置,例如用acme.sh 自动申请、续期SSL 证书。

设置域名记录

Cloudflare -->Home 选中maybook.tk -->DNS 依次添加以下Records 以便后面做试验用?

Type Name Content TTL Proxy status
A maybook.tk 139.180.167.52 Auto DNS only
A app1.maybook.tk 139.180.167.52 Auto DNS only
A app2.maybook.tk 139.180.167.52 Auto DNS only

acme.sh 安装及使用

acme.sh 使用说明

# 安装acme.sh
curl  https://get.acme.sh | sh

# 使acme.sh 生效,重新进入容器也行
source ~/.bashrc

DNS API

# 导入Cloudflare Token,即Global API Key,让acme.sh 控制DNS 记录以自动申请证书
export CF_Key="ba1a821xxxxxx42b603064xxxxxxx3443"
export CF_Email="[email protected]"

# 将非泛型域名放在第一位方便以后部署,因为* 是通配符。
# 用以下命令申请的SSL 证书会包含maybook.tk 及泛型*.maybook.tk 域名
acme.sh --issue --dns dns_cf -d maybook.tk -d *.maybook.tk

执行过acme.sh 后DNS 的API 信息会保存到~/.acme.sh/account.conf,如果写错了去该文件里删除相关内容,然后unset 环境变量后重新export 环境变量即可。

# 安装部署SSL 证书
acme.sh --installcert -d maybook.tk \
        --key-file        /etc/nginx/ssl_keys/maybook.tk.key.pem \
        --fullchain-file  /etc/nginx/ssl_keys/maybook.tk.cert.pem \
        --reloadcmd "service nginx restart"

每运行acme.sh --installcert 安装的路径及其他配置会被保存到~/.acme.sh/<domain>/<domain>.conf 里面。
其他发行版可能要用--reloadcmd "service nginx force-reload" 才能让Nginx 重新读取配置文件。

Nginx SSL 配置

# 新建一个Nginx 配置acme.sh.conf
cat > /etc/nginx/conf.d/acme.sh.conf << EOF
server {
  listen 8001 ssl;
  server_name maybook.tk;
  ssl on;
  ssl_certificate     /etc/nginx/ssl_keys/maybook.tk.cert.pem;
  ssl_certificate_key /etc/nginx/ssl_keys/maybook.tk.key.pem;

  index index.html index.htm;
  location / {
    root  /home/app/code;
  }
}
EOF

# 让Nginx 重新读取配置
nginx -s reload

新证书访问验证

还是使用之前创建的页面,配置上CA 颁发的新SSL 证书。
浏览器中输入https://maybook.tk:8001,出现What a good day!且浏览器显示绿色锁表示SSL 证书被信任了。

验证泛型域名

Nginx 配置文件

# 创建两个server 都监听80 端口,根据不同的请求host 临时重定向302 到对应的https 链接
cat > /etc/nginx/conf.d/acme.sh.x.conf << EOF 
server {
  listen 80;
  server_name app1.maybook.tk;
  # 不使用301 永久重定向,因为如果后期地址改变需要清理浏览器端的Cookie 才能生效
  return 302 https://$host:8001$request_uri;
}

server {
  listen 80;
  server_name app2.maybook.tk;
  return 302 https://$host:8002$request_uri;
}

# 创建两个开启了SSL 的server,并将请求代理转发到对应的后台应用
server {
  listen 8001 ssl;
  server_name app1.maybook.tk;
  ssl_certificate     /etc/nginx/ssl_keys/maybook.tk.cert.pem;
  ssl_certificate_key /etc/nginx/ssl_keys/maybook.tk.key.pem;

  location / {
    proxy_pass http://127.0.0.1:8801;
  }
}

server {
  listen 8002 ssl;
  server_name app1.maybook.tk;
  ssl_certificate     /etc/nginx/ssl_keys/maybook.tk.cert.pem;
  ssl_certificate_key /etc/nginx/ssl_keys/maybook.tk.key.pem;

  location / {
    proxy_pass http://127.0.0.1:8802;
  }
}
EOF

访问测试文件

# 模拟应用1
mkdir /home/web1/
cd /home/web1/

cat > index.html << EOF
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Index</title>
</head>
<body>
  <h1>
    <a href="/a1.html">a1.html</a><br/>
    <a href="/a2.html">a2.html</a>
  </h1>
</body>
</html>
EOF

cat > a1.html << EOF
<!DOCTYPE html>
<html lang="en">
<head>
  <title>A1</title>
</head>
<body>
  <a href="/index.html">index.html</a>
  <h1>A1A1A1</h1>
</body>
</html>
EOF

cat > a2.html << EOF
<!DOCTYPE html>
<html lang="en">
<head>
  <title>A2</title>
</head>
<body>
  <a href="/index.html">index.html</a>
  <h1>A2A2A2</h1>
</body>
</html>
EOF

# 模拟应用2
mkdir /home/web2/
cd /home/web2/

cat > index.html << EOF
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Index</title>
</head>
<body>
  <h1>
    <a href="/b1.html">b1.html</a><br/>
    <a href="/b2.html">b2.html</a>
  </h1>
</body>
</html>
EOF

cat > b1.html << EOF
<!DOCTYPE html>
<html lang="en">
<head>
  <title>B1</title>
</head>
<body>
  <a href="/index.html">index.html</a>
  <h1>B1B1B1</h1>
</body>
</html>
EOF

cat > b2.html << EOF
<!DOCTYPE html>
<html lang="en">
<head>
  <title>B2</title>
</head>
<body>
  <a href="/index.html">index.html</a>
  <h1>B2B2B2</h1>
</body>
</html>
EOF

模拟后台API

nohup python3 -m http.server 8801 > /dev/null 2>&1 &
nohup python3 -m http.server 8802 > /dev/null 2>&1 &

测试结果及结论

URL 内容
http://maybook.tk:8888 https 形式的“What a good day!” 页面
http://app1.maybook.tk:8888 https 形式含有a1、a2 的index 页面
http://app2.maybook.tk:8888 https 形式含有b1、b2 的index 页面

Nginx 中可以多个server 同时监听同一个端口,再用server_name 即域名或子域名的方式来区分不同的业务。
监听容器的80 端口(映射到宿主机8888),然后将http 的请求转到https(8001-8002),再根据server_name 转到后台应用(8801-8802)。
浏览器会对比地址栏输入的域名是否跟SSL 证书里的域名相同,如果相同且证书是受信任的则允许访问,否则出现自签证书那样的安全提示。

SSL 双向认证简单介绍

SSL 双向认证和SSL 单向认证的区别

双向认证 SSL 协议要求服务器和用户双方都有证书。单向认证 SSL 协议不需要客户拥有CA证书,具体的过程相对于上面的步骤,只需将服务器端验证客户证书的过程去掉,以及在协商对称密码方案,对称通话密钥时,服务器发送给客户的是没有加过密的(这并不影响 SSL 过程的安全性)密码方案。这样,双方具体的通讯内容,就是加过密的数据,如果有第三方攻击,获得的只是加密的数据,第三方要获得有用的信息,就需要对加密的数据进行解密,这时候的安全就依赖于密码方案的安全。而幸运的是,目前所用的密码方案,只要通讯密钥长度足够的长,就足够的安全。这也是我们强调要求使用128位加密通讯的原因。
一般Web应用都是采用SSL单向认证的,原因很简单,用户数目广泛,且无需在通讯层对用户身份进行验证,一般都在应用逻辑层来保证用户的合法登入。但如果是企业应用对接,情况就不一样,可能会要求对客户端(相对而言)做身份验证。这时就需要做SSL双向认证。

清理工作

停止并删除Docker 容器

docker stop alssltest && docker rm alssltest

删除Docker 镜像alssltest

docker rmi alssltest:latest

参考文档

Nginx 入门实战
使用 acme.sh 安装 Let’ s Encrypt 提供的免费 SSL 证书
线上服务器部署(前后端)

发布了27 篇原创文章 · 获赞 4 · 访问量 9695

猜你喜欢

转载自blog.csdn.net/yoshubom/article/details/99675743