本文摘要:本文详细介绍了Harbor的双向认证配置及实践。首先,文章解释了单向认证与双向认证的概念及其应用场景,对比了二者的认证流程。接着,通过具体步骤展示了如何在Harbor中配置双向认证,包括证书生成、配置Harbor及Nginx,并提供了详细的验证测试方法,涵盖命令行、浏览器、Docker及Containerd等多种场景。通过本文,帮助读者进一步深入了解和掌握Harbor双向认证的配置与验证过程。
目录
1 概念
1.1 什么是单向认证
单向认证是指只有服务器需要提供 SSL 证书给客户端,以确认服务器的身份。而客户端则无需提供证书。这种模式下,客户端通过信任服务器证书颁发机构(CA)来建立安全连接。
1.1.1 单向认证应用场景
单向认证最常用于以下场景:
- 网页浏览:客户端(如浏览器)与服务器之间建立 HTTPS 连接时,通常是单向认证。
- API 调用:客户端调用某些公开的 API 服务时,服务器通过提供 SSL 证书来保证通信安全,但客户端无需提供任何认证信息。
- Web 应用:典型的用户登录操作,通过 HTTPS 保证用户输入信息的安全传输。
1.1.2 单向认证流程
❔ 流程说明:
- Client Hello:客户端请求服务器建立 SSL 连接。
- Server Hello:服务器响应客户端请求并发送 SSL 证书。
- 证书验证:客户端验证服务器的证书是否合法(由受信任的 CA 签发)。
- SSL 加密连接建立:客户端与服务器之间建立加密通信隧道,确保数据传输安全。
1.2 什么是双向认证
双向认证(Mutual SSL Authentication)是在单向认证的基础上,要求客户端也必须提供 SSL 证书。服务器和客户端双方都要验证对方的身份,只有双方验证通过后,才能建立安全连接。
1.2.1 双向认证应用场景
双向认证适用于安全要求较高的场景,如:
- 金融服务:银行和金融机构在内部服务之间的通信中经常使用SSL双向认证,以确保只有经过身份验证的客户端和服务器才能进行通信,防止中间人攻击和数据泄露。
- 政府和军事系统:这些系统需要对所有参与通信的实体进行严格的身份验证,以确保信息的机密性和完整性。SSL双向认证确保了通信双方的身份都经过验证。
- 医疗保健:在处理敏感的患者数据(如电子病历)时,SSL双向认证可以确保只有经过认证的应用程序和设备可以访问和传输这些信息,符合HIPAA等法规要求。
- 物联网(IoT):在IoT设备和云平台之间的通信中,SSL双向认证可以防止未经授权的设备接入网络,保护设备之间的数据传输安全,特别是在工业自动化、智能城市等应用中。
- 企业内部微服务通信:在微服务架构中,服务之间的通信通常需要高度安全性。SSL双向认证确保了服务间的相互信任和身份验证,防止未经授权的服务访问。
1.2.2 双向认证流程
❔ 流程说明:
- Client Hello:客户端请求服务器建立 SSL 连接。
- Server Hello:服务器响应并发送 SSL 证书。
- 验证服务器证书:客户端验证服务器的 SSL 证书。
- 请求客户端证书:服务器请求客户端提供证书。
- 发送客户端证书:客户端提供其 SSL 证书给服务器。
- 验证客户端证书:服务器验证客户端证书的有效性。
- SSL 加密连接建立:双方成功认证后,建立加密通信通道。
2 Harbor配置双向认证
2.1 证书要求说明
上文《一文读懂Harbor以及部署实践攻略》已经说明了ssl证书生成以及实现harbor单向认证的流程,并已经生成的ca证书和服务器证书。
如果要把整个双向认证的流程跑通,最终需要6个证书文件:
- 自签名CA证书: ca.crt
- 服务器端公钥证书:server.crt
- 服务器端私钥文件:server.key
- 客户端公钥证书:client.crt
- 客户端私钥文件:client.key
- 客户端集成证书(包括公钥和私钥,用于浏览器访问场景):client.p12
以下是6个文件关系图:
2.2 生成客户端证书
切换到证书目录
cd /harbor/pki
现在生成客户端私钥, 执行以下命令
openssl genrsa -out client.key 4096
生成客户端证书签名请求文件
openssl req -new -key client.key -out client.csr -subj "/C=CN/ST=Guangdong/L=Foshan/O=example/OU=Personal/CN=Registry client"
使用CA证书签名生成客户端证书,执行以下命令
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 3650
生成客户端 P12 格式证书,后面浏览器访问用到
# 生成时需要证书加密密码,后面导入浏览器会使用到
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
2.3 配置harbor
❔ 说明:harbor的组件中有一个nginx,作为portal和apiserver, 通过配置harbor的nginx.config 实现双向认证。
nginx文件一般在harbor.yml配置文件下的common目录,详细路径:./common/config/nginx/nginx.conf
修改nginx.conf
vim /harbor/common/config/nginx/nginx.conf
server {
listen 8443 ssl;
# server_name harbordomain.com;
server_tokens off;
# SSL
# 配置harbor.yml自动生成的配置
ssl_certificate /etc/cert/server.crt;
ssl_certificate_key /etc/cert/server.key;
# 下面两行是手动添加
# 添加client的ca证书
ssl_client_certificate /etc/cert/ca.crt;
# 开启客户端的ssl校验
ssl_verify_client on;
...
将ca证书复制到nginx的cert目录
cp ../pki/ca.crt /data/secret/cert/
重启nginx方法有两种:
- 进入nginx容器重新加载配置
docker exec -it nginx nginx -s reload
- 或者,重启harbor应用
docker-compose restart
2.4 验证测试
2.4.1 命令行验证
通过curl工具验证, 执行以下命令验证
root@harbor:~/harbor/pki# curl --cert ./client.crt --key ./client.key https://harbor.zx -k -v
❔ 参数说明:
参数 | 说明 |
---|---|
-E,–cert | 客户端证书文件和密码 (SSL) |
—key | 客户端私钥文件名 (SSL) |
-k,–insecure | 允许不使用证书到SSL站点 |
-v | 输出调试日志 |
结果输出
root@harbor:~/harbor/pki# curl --cert ./client.crt --key ./client.key https://harbor.zx -k -v
* Trying 127.0.1.1:443...
* Connected to harbor.zx (127.0.1.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.2 (OUT), TLS header, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.3 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.3 (OUT), TLS handshake, CERT verify (15):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
* subject: C=CN; ST=GuangDong; L=Foshan; O=example; OU=Personal; CN=harbor.zx
* start date: Sep 2 13:54:09 2024 GMT
* expire date: Aug 31 13:54:09 2034 GMT
* issuer: C=CN; ST=GuangDong; L=Foshan; O=example; OU=Personal; CN=harbor.zx
* SSL certificate verify result: authority and subject key identifier mismatch (30), continuing anyway.
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/1.1
> Host: harbor.zx
> User-Agent: curl/7.81.0
> Accept: */*
>
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx
< Date: Fri, 06 Sep 2024 15:49:49 GMT
< Content-Type: text/html
< Content-Length: 785
< Connection: keep-alive
< Last-Modified: Tue, 02 Jul 2024 07:36:34 GMT
< ETag: "6683ae02-311"
< Cache-Control: no-store, no-cache, must-revalidate
< Accept-Ranges: bytes
< Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
< X-Frame-Options: DENY
< Content-Security-Policy: frame-ancestors 'none'
<
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Harbor</title>
<base href="/"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="icon" type="image/x-icon" href="favicon.ico?v=2"/>
<link rel="stylesheet" href="styles.75cb4562f0127450.css"></head>
<body>
<harbor-app>
<div class="spinner spinner-lg app-loading app-loading-fixed">
Loading...
</div>
</harbor-app>
<script src="runtime.2a3bcf80a84bec2d.js" type="module"></script><script src="polyfills.67cfd31b2752cc11.js" type="module"></script><script src="scripts.3846d86d42cdb753.js" defer></script><script src="main.ce93c109f9fc4943.js" type="module"></script></body>
</html>
* Connection #0 to host harbor.zx left intact
2.4.2 浏览器验证
❔ 说明: 这里使用Chrome访问进行验证
如果没有导入证书, 访问harbor 页面,可能出现400 Bad Request
报错
先下载client.pl12到客户端电脑
第一次导入客户端证书,可以直接在浏览器输出:chrome://settings/security
按以下步骤导入
新建窗口访问harbor页面,访问时选择刚导入的证书
登录harbor
2.4.3 Docker验证
尝试无客户端证书登录harbor,执行以下命令
root@jenkins:~# docker login harbor.zx
Username: admin
Password:
Error response from daemon: login attempt to https://harbor.zx/v2/ failed with status: 400 Bad Request
❔ 说明:出现400报错代码,证明是身份验证错误
将客户端的证书远程拷贝到docker主机的证书目录harbor.zx
上:
scp root@192.168.3.201:/harbor/pki/client.crt /etc/docker/certs.d/harbor.zx/client.cert
scp root@192.168.3.201:/harbor/pki/client.key /etc/docker/certs.d/harbor.zx/client.key
❔ 说明:docker对证书的后缀有规定,守护程序将
.crt
文件解释为 CA 证书,将.cert
文件解释为客户端证书。所以如果配置出了可能出现以下报错:
Missing key KEY_NAME for client certificate CERT_NAME. CA certificates should use the extension .crt.
查看docker证书目录
/etc/docker/certs.d/ <-- 证书目录
└── harbor.zx <-- 镜像仓库域名
├── harbor.zx.cert <-- 服务器证书
├── harbor.zx.key <-- 服务器私钥
├── client.cert <-- 客户端证书
├── client.key <-- 客户端私钥
└── ca.crt <-- 根CA证书
再次尝试登录harbor
root@jenkins:~# docker login harbor.zx
Username: admin
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credential-stores
Login Succeeded
❔ 说明: docker成功登录harbor
尝试拉取镜像stress:latest
root@jenkins:~# docker pull harbor.zx/library/stress:latest
latest: Pulling from library/stress
Digest: sha256:b6144f84f9c15dac80deb48d3a646b55c7043ab1d83ea0a697c09097aaad21aa
Status: Downloaded newer image for harbor.zx/library/stress:latest
harbor.zx/library/stress:latest
❔ 说明: 也是可以成功拉取镜像
2.4.4 Containerd
如果没有配置双向认证,尝试拉取harbor仓库的镜像
[root@k8s-master1 containerd]# crictl pull harbor.zx/library/stress:latest
E0908 14:50:36.398371 2840 remote_image.go:238] "PullImage from image service failed" err="rpc error: code = Unknown desc = failed to pull and unpack image \"harbor.zx/library/stress:latest\": failed to resolve reference \"harbor.zx/library/stress:latest\": pulling from host harbor.zx failed with status code [manifests latest]: 400 Bad Request" image="harbor.zx/library/stress:latest"
FATA[0000] pulling image: rpc error: code = Unknown desc = failed to pull and unpack image "harbor.zx/library/stress:latest": failed to resolve reference "harbor.zx/library/stress:latest": pulling from host harbor.zx failed with status code [manifests latest]: 400 Bad Request
❔ 说明: 同样也是400 身份认证报错。
将客户端的证书上传到k8s-master节点
scp root@192.168.3.201:/harbor/pki/client.crt /etc/containerd/certs.d/harbor.zx/client.cert
scp root@192.168.3.201:/harbor/pki/client.key /etc/containerd/certs.d/harbor.zx/client.key
配置containerd,修改config.toml
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = ""
[plugins."io.containerd.grpc.v1.cri".registry.auths]
[plugins."io.containerd.grpc.v1.cri".registry.configs]
[plugins."io.containerd.grpc.v1.cri".registry.configs."harbor.zx".auth]
username = "admin"
password = "admin12345"
[plugins."io.containerd.grpc.v1.cri".registry.configs."harbor.zx".tls]
insecure_skip_verify = false
ca_file = "/etc/containerd/certs.d/harbor.zx/ca.crt"
# 添加客户端的证书和私钥
cert_file = "/etc/containerd/certs.d/harbor.zx/client.cert"
key_file = "/etc/containerd/certs.d/harbor.zx/client.key"
[plugins."io.containerd.grpc.v1.cri".registry.headers]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."harbor.zx"]
endpoint = ["https://harbor.zx"]
重启Containerd服务
systemctl restart containerd
再次尝试拉取镜像
[root@k8s-master1 ~]# crictl pull harbor.zx/library/stress:latest
Image is up to date for sha256:df58d15b053d1ac1378fa685cbc1f71a0a1716a4c6f6f105df9c4d7b7f1ee5bb
❔ 说明: 这证明可以正常从harbor拉取镜像
3 独立nginx代理Harbor
❔ 说明: 以下案例使用 Nginx 代理 Harbor应用,独立Nginx开启双向认证。 Nginx代理与Harbor应用部署在同一主机。
3.1 部署Nginx
实验环境: Ubuntu22.04
安装nginx
apt-get -y install nginx
3.2 配置Nginx
❔ 说明: Nginx需要使用443端口,提前将Harbor的https端口改为8443, 方法参考前文《一文读懂Harbor以及部署实践攻略》
增加Harbor代理配置,Harbor地址为 127.0.0.1:8443
vim /etc/nginx/conf.d/harbor.conf
详细配置如下:
# 添加上游服务器,指向harbor应用
upstream harbor {
server 127.0.0.1:8443;
}
server {
# 使用https端口
listen 443 ssl;
# 域名自定义
server_name harbor.zx;
server_tokens off;
# 设置服务器端证书和密钥
ssl_certificate /data/ca/server.crt;
ssl_certificate_key /data/ca/server.key;
# 设置客户端ca证书
ssl_client_certificate /data/ca/ca.crt;
# 开启客户端证书校验
ssl_verify_client on;
# Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers '!aNULL:kECDH+AESGCM:ECDH+AESGCM:RSA+AESGCM:kECDH+AES:ECDH+AES:RSA+AES:';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
# disable any limits to avoid HTTP 413 for large image uploads
client_max_body_size 0;
# required to avoid HTTP 411: see Issue #1486 (https://github.com/docker/docker/issues/1486)
chunked_transfer_encoding on;
# Add extra headers
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";
add_header X-Frame-Options DENY;
add_header Content-Security-Policy "frame-ancestors 'none'";
location / {
# 指向上游服务器
proxy_pass https://harbor/;
proxy_set_header Host $http_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 $x_forwarded_proto;
proxy_cookie_path / "/; HttpOnly; Secure";
proxy_buffering off;
proxy_request_buffering off;
}
}
启动nginx
systemctl enable --now nginx
- 验证步骤参考: 2.4验证测试,不重复说明