wmproxy

wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 负载均衡, 静态文件服务器,websocket代理,四层TCP/UDP转发,内网穿透等,会将实现过程分享出来,感兴趣的可以一起造个轮子

项目地址

国内: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

设计目标

让系统拥有acme的能力,即可以领取Let's Encrypt的证书签发,快速实现上线部署。

acme是什么?

ACME(Automated Certificate Management Environment)是一个用于自动化管理SSL/TLS证书的协议。它通过自动获取、自动更新和自动拒绝等功能,可以大大提高SSL证书的管理和更新效率,降低错误风险,提高网站的安全性和稳定性。

当ACME服务器发布不安全的SSL证书时,可以通过ACME协议自动拒绝证书,确保网站始终使用安全的SSL证书。此外,ACME协议还支持自动续期功能,这意味着在证书到期之前,系统可以自动申请并获取新的证书,从而避免了因证书过期而导致的网站访问中断或安全风险。

acme的定义

acme是一个可以自动获取 TLS证书的协议,acmev1已经被正式弃用,现行的acme在rfc8555定义。其中定义了SSL如何获取的整个过程,包括其中最重要的权限鉴定。

以下是两种acme判定权限拥有者的鉴权方式,以下是wmproxy.net做为域名来举例。

HTTP-01 方式鉴定

HTTP-01 的校验原理是访问给你域名指向的 HTTP 服务增加一个临时 location,Let’s Encrypt 会发送 http 请求到 http://wmproxy.net/.well-known/acme-challenge/wmproxy.net 就是被校验的域名,TOKEN 是 ACME 协议的客户端负责放置的文件,在这里 ACME 客户端就是 acme-lib。Let’s Encrypt 会对比 TOKEN 是否符合预期,校验成功后就会颁发证书。不支持泛域名证书。成功后我们就可以拥有TLS证书了。

  • 优点

    配置简单通用

    任何DNS服务商均可

  • 缺点

    需要依赖HTTP服务器

    集群会无法申请的可能

    不支持泛域名

DNS-01 方式鉴定

在 ACME DNS 质询验证的自动化中,以下是一些关键步骤:

  1. 生成一个 DNS TXT 记录,如_acme-challenge
  2. 将 TXT 记录添加到 DNS 区域中。
  3. 通知 Let's Encrypt 验证 DNS 记录。
  4. 等待 Let's Encrypt 验证完成。
  5. 如果验证成功,则生成证书。
  6. 删除 DNS TXT 记录。

此方法不需要你的服务使用 Http 服务,并且支持泛域名证书。

  • 优点

    不需要HTTP服务器

    支持泛域名

  • 缺点

    各DNS服务商均不一致

acme在保证安全的情况下缩短了TLS证书的申请流程,可以自动化的进行部署,极大的缓解因证书过期带来的麻烦。

代码实现

依赖:acme-lib

改造:之前是确定配置证书及密钥后直接生成完整的TLS信息TlsAcceptor,那么现在在未申请到证书前,不能确定完整的TlsAcceptor,需要对初始化对象进行重新改造处理。

源码:wrap_tls_accepter

定义:

/// 为了适应acme, 重新改造Acceptor进行封装处理
#[derive(Clone)]
pub struct WrapTlsAccepter {
pub last: Instant,
pub domain: Option<String>,
pub accepter: Option<TlsAcceptor>,
}

同样添加accept方法

#[inline]
pub fn accept<IO>(&self, stream: IO) -> io::Result<Accept<IO>>
where
IO: AsyncRead + AsyncWrite + Unpin,
{
self.accept_with(stream, |_| ())
} pub fn accept_with<IO, F>(&self, stream: IO, f: F) -> io::Result<Accept<IO>>
where
IO: AsyncRead + AsyncWrite + Unpin,
F: FnOnce(&mut ServerConnection),
{
if let Some(a) = &self.accepter {
Ok(a.accept_with(stream, f))
} else {
self.check_and_request_cert()
.map_err(|_| io::Error::new(io::ErrorKind::Other, "load https error"))?;
Err(io::Error::new(io::ErrorKind::Other, "try next https error"))
}
}

accepter未初始化时,我们将会试图检查证书,查看是否能签发证书。

此处我们为了避免并发中,重复多次请求导致请求数过多导致的服务不可用,我们此处定义了全局静态变量。

lazy_static! {
static ref CACHE_REQUEST: Mutex<HashMap<String, Instant>> = Mutex::new(HashMap::new());
}

在检查的时候,我们只允许一段时间内仅有一个请求进入申请证书的流程,其它的请求全部返回错误:

let mut map = CACHE_REQUEST
.lock()
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Fail get Lock"))?;
if let Some(last) = map.get(self.domain.as_ref().unwrap()) {
if last.elapsed() < Duration::from_secs(30) {
return Err(io::Error::new(io::ErrorKind::Other, "等待上次请求结束").into());
}
}
map.insert(self.domain.clone().unwrap(), Instant::now());

然后我们对该域名发起证书签名请求,此处我们会循环卡住整个线程,而非异步的请求,所以我们这里用了thread::spawn而非tokio::spawn

let obj = self.clone();
thread::spawn(move || {
let _ = obj.request_cert();
});

以下是请求证书的函数:

fn request_cert(&self) -> Result<(), Error> {
// 使用let's encrypt签发证书
let url = DirectoryUrl::LetsEncrypt;
let path = Path::new(".well-known/acme-challenge");
if !path.exists() {
let _ = std::fs::create_dir_all(path);
} // 使用内存的存储结构,存储自己做处理
let persist = MemoryPersist::new(); // 创建目录节点
let dir = Directory::from_url(persist, url)?; // 设置请求的email信息
let acc = dir.account("wmproxy@wmproxy.net")?; // 请求签发的域名
let mut ord_new = acc.new_order(&self.domain.clone().unwrap_or_default(), &[])?; let start = Instant::now();
// 以下域名的鉴权,需要等待let's encrypt确认信息
let ord_csr = loop {
// 成功签发,跳出循环
if let Some(ord_csr) = ord_new.confirm_validations() {
break ord_csr;
} // 超时30秒,认为失败了
if start.elapsed() > Duration::from_secs(30) {
println!("获取证书超时");
return Ok(());
} // 获取鉴权方式
let auths = ord_new.authorizations()?; // 以下是HTTP的请求方法,本质上是请求token的url,然后返回正确的值
// 此处我们用的是临时服务器
//
// /var/www/.well-known/acme-challenge/<token>
//
// http://mydomain.io/.well-known/acme-challenge/<token>
let chall = auths[0].http_challenge(); // 将token存储在目录下
let token = chall.http_token();
let path = format!(".well-known/acme-challenge/{}", token); // 获取token的内容
let proof = chall.http_proof(); Helper::write_to_file(&path, proof.as_bytes())?; // 等待acme检测时间,以ms计
chall.validate(5000)?; // 再尝试刷新acme请求
ord_new.refresh()?; }; // 创建rsa的密钥对
let pkey_pri = create_rsa_key(2048); // 提交CSR获取最终的签名
let ord_cert = ord_csr.finalize_pkey(pkey_pri, 5000)?; // 下载签名及证书,此时下载下来的为pkcs#8证书格式
let cert = ord_cert.download_and_save_cert()?;
Helper::write_to_file(
&self.get_cert_path().unwrap(),
cert.certificate().as_bytes(),
)?;
Helper::write_to_file(&self.get_key_path().unwrap(), cert.private_key().as_bytes())?;
Ok(())
}

在其中,我们跟acme服务器的时候我们需要架设临时文件服务器以使acme访问我们http服务器的时候http://mydomain.io/.well-known/acme-challenge/<token>能正确的返回正常的请求,我们将在绑定tls的时候,如果没有该证书信息时,我们将自动添加一个.well-known/acme-challenge的location以启用https的验证:

pub async fn bind(
&mut self,
) -> ProxyResult<(Vec<Option<WrapTlsAccepter>>, Vec<bool>, Vec<TcpListener>)> {
// ...
for value in &mut self.server {
// ...
if has_acme {
let mut location = LocationConfig::new();
let file_server = FileServer::new(
".well-known/acme-challenge".to_string(),
"/.well-known/acme-challenge".to_string(),
);
location.rule = Matcher::from_str("/.well-known/acme-challenge/").expect("matcher error");
location.file_server = Some(file_server);
value.location.insert(0, location);
}
}
Ok((accepters, tlss, listeners))
}

以启用远程acme能访问该链接的能力,也就意味着我们不能将敏感信息放置在".well-known/acme-challenge"目录下面,也就是我们使用MemoryPersist的原因。

测试是否可行

因为http-01的方式必须使acme能访问我们的服务器,所以此时测试需要公网环境下进行测试:

我们配置如下文件,reverse.toml:

# 反向代理相关,七层协议为http及https
[http] # 反向代理中的具体服务,可配置多个多组
[[http.server]]
bind_addr = "0.0.0.0:80"
bind_ssl = "0.0.0.0:443"
up_name = "auto1.wmproxy.net"
root = "" [[http.server.location]]
rule = "/"
static_response = "I'm Ok {client_ip}"

此时布置在我们的auto1.wmproxy.net的服务器上,我们运行

wmproxy run -c reverse.toml

此时当我们访问https://auto1.wmproxy.net的请求的时候,将会触发证书申请,成功后证书将放置在".well-known"下面,下次启动服务器的时候我们将自动加载已请求的tls证书以提供https服务。

频繁限制问题

在let's encrypt中,如果有早过5次成功后,需要2天后才能继续申请,他将无限返回429,得注意控制申请证书的频率。

总结

TLS证书在当今互联网中处于最重要的一环,他保护着我们的隐私数据的安全,也是最流行的加密方式之一。所以TLS证书的快速部署对于小而美的应用能让其快速的落地使用。

点击 [关注][在看][点赞] 是对作者最大的支持

50从零开始用Rust编写nginx,原来TLS证书还可以这么申请的更多相关文章

  1. 使用vault pki 为nginx 生成tls 证书文件

    关于vault pki 管理的使用的可以参考官方文档或者docker-vault 以下演示一个简单的基于vault pki 为nginx 提供tls 证书 项目环境配置 nginx 配置文件   wo ...

  2. bloom-server 基于 rust 编写的 rest api cache 中间件

    bloom-server 基于 rust 编写的 rest api cache 中间件,他位于lb 与api worker 之间,使用redis 作为缓存内容存储, 我们需要做的就是配置proxy,同 ...

  3. centos LNMP第一部分环境搭建 LAMP LNMP安装先后顺序 php安装 安装nginx 编写nginx启动脚本 懒汉模式 mv /usr/php/{p.conf.default,p.conf} php运行方式SAPI介绍 第二十三节课

    centos  LNMP第一部分环境搭建 LAMP安装先后顺序  LNMP安装先后顺序 php安装 安装nginx  编写nginx启动脚本   懒汉模式  mv   /usr/local/php/{ ...

  4. nginx配置多个TLS证书,以及TLS SNI简介

    背景 原来申请的正式域名备案通过,TLS证书也申请了.之前使用的临时域名和证书作为测试环境使用.于是要在单个ECS主机上配置nginx多个证书和多个域名. 实践 nginx部署多个TLS证书很简单,在 ...

  5. 从sslyze看TLS证书的点点滴滴

    纵观眼下,https已经深入大街小巷,成为网络生活中不可或缺的一部分了.提到了https,我们又不得不想到TLS(SSL),而提到了TLS,我们又不得不提到一个让人捉摸不透的东西:TLS证书. 关于证 ...

  6. 基于TLS证书手动部署kubernetes集群(下)

    一.master节点组件部署 承接上篇文章--基于TLS证书手动部署kubernetes集群(上),我们已经部署好了etcd集群.flannel网络以及每个节点的docker,接下来部署master节 ...

  7. 基于TLS证书手动部署kubernetes集群(上)

    一.简介 Kubernetes是Google在2014年6月开源的一个容器集群管理系统,使用Go语言开发,Kubernetes也叫K8S. K8S是Google内部一个叫Borg的容器集群管理系统衍生 ...

  8. (转)基于TLS证书手动部署kubernetes集群(下)

    转:https://www.cnblogs.com/wdliu/p/9152347.html 一.master节点组件部署 承接上篇文章--基于TLS证书手动部署kubernetes集群(上),我们已 ...

  9. (转)基于TLS证书手动部署kubernetes集群(上)

    转:https://www.cnblogs.com/wdliu/archive/2018/06/06/9147346.html 一.简介 Kubernetes是Google在2014年6月开源的一个容 ...

  10. 在阿里云托管kubernetes上利用 cert-manager 自动签发 TLS 证书[无坑版]

    前言 排错的过程是痛苦的也是有趣的. 运维乃至IT,排错能力是拉开人与人之间的重要差距. 本篇会记录我的排错之旅. 由来 现如今我司所有业务都运行在阿里云托管kubernetes环境上,因为前端需要对 ...

随机推荐

  1. [转帖]我们为什么放弃 MongoDB 和 MySQL,选择 TiDB

    https://zhuanlan.zhihu.com/p/164706527 写在前面的话 技术选型是由技术方向和业务场景 trade-off 决定的,脱离业务场景来说技术选型是没有任何意义的,所以本 ...

  2. [转帖]fio工具中的iodepth参数与numjobs参数-对测试结果的影响

    测试环境 3台服务器:ceph配置内外网分离,外网使用万兆线,内网使用千兆线,osd共21个. 1台客户端:安装fio工具.内核客户端,使用万兆线. 测试目的 针对fio工具中的iodepth(队列深 ...

  3. 【转帖】《MySQL高级篇》四、索引的存储结构

    1. 为什么使用索引 假如给数据使用 二叉树 这样的数据结构进行存储,如下图所示 2.索引及其优缺点 2.1 索引概述 2.2 优点 类似大学图书馆建书目索引,提高数据检索的效率,降低 数据库的 IO ...

  4. JVM启动速度大页内存验证

    大页内存设置 先查看 cat /proc/meminfo |grep -i huge 获取大页内存的大小信息. AnonHugePages: 42022912 kB HugePages_Total: ...

  5. 【贪心】AGC018C Coins

    Problem Link 现在有 \(X+Y+Z\) 个人,第 \(i\) 个人有三个权值 \(a_i,b_i,c_i\),现在要求依次选出 \(X\) 个人,\(Y\) 个人和 \(Z\) 个人(一 ...

  6. 【DS】P9062 [Ynoi2002] Adaptive Hsearch&Lsearch(区间最近点对)

    Problem Link 给定平面上 \(n\) 个点,\(Q\) 次询问编号在 \([l,r]\) 内的点的最近点对.\(n,Q\le 2.5\times 10^5\). 技巧:平面网格化 乱搞都是 ...

  7. 【代码分享】使用 avx512 + 查表法,优化凯撒加密

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 关于凯撒加密,具体请看:https://en.wikipe ...

  8. 强化学习从基础到进阶-案例与实践[4.2]:深度Q网络DQN-Cart pole游戏展示

    强化学习从基础到进阶-案例与实践[4.2]:深度Q网络DQN-Cart pole游戏展示 强化学习(Reinforcement learning,简称RL)是机器学习中的一个领域,区别与监督学习和无监 ...

  9. axios请求失败,获取接口返回错误信息

    一般vue项目都会对axios进行封装,后台统一规范默认让服务器对所有请求都返回成功,然后在成功的对象里面包装一层对象result,里面也包含code,msg,result信息,前端拿这个result ...

  10. 让 JuiceFS 帮你做好「异地备份」

    家住北京西二旗的小张是一家互联网金融公司的运维工程师,金融行业的数据可是很值钱的,任何的损坏和丢失都不能容忍. 为此,小张选了北京品质最高的机房,买了品质最好的硬件,做了全面的数据备份容灾策略: 每 ...