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. [转帖]使用 TiDB 读取 TiFlash

    https://docs.pingcap.com/zh/tidb/stable/use-tidb-to-read-tiflash 本文档介绍如何使用 TiDB 读取 TiFlash 副本. TiDB ...

  2. [转帖]tidb4.0.4使用tiup扩容TiKV 节点

    https://blog.csdn.net/mchdba/article/details/108896766 环境:centos7.tidb4.0.4.tiup-v1.0.8 添加两个tikv节点   ...

  3. [转帖]decimal,float和double的区别是什么?

    https://zhuanlan.zhihu.com/p/352503879 今天复习mysql理论知识,在看常用数据类型的时候发现float和decimal类型都是表示小数,就展开搜索学习了一下区别 ...

  4. [转帖]ext4的fsync性能和nodelalloc参数的分析

    原文:http://blog.thinksrc.com/?p=189001 感叹归感叹,发泄完了还得继续过. 前几天忙的不可开交,周报上面竟然能列出11项,想想以前在T公司时候的清闲,现在的老板的真幸 ...

  5. [转帖]LTP使用和分析

    一.安装及编译流程 1.下载LTP LTP 项目目前位于 GitHub,项目地址:https://github.com/linux-test-project/ltp . 获取最新版可以执行以下命令: ...

  6. jconsole的简单学习

    摘要 jconsole 是JDK自带的一款图形化监测工具 他可以监测本地程序,也可以检测远程的机器 在没有其他监控手段可以使用的情况下可以快速进行必要的监测 使用方法也比较简单. 本地监控 jcons ...

  7. ELK运维文档

    Logstash 目录 Logstash Monitoring API Node Info API Plugins Info API Node Stats API Hot Threads API lo ...

  8. postman中monitor的使用

    monitor就是一个摸鱼的功能,我们把写好的接口部署到postman的web服务器中, 绑定自己的邮箱,运行结果会发送到自己的邮箱中,不用实时监控,是个非常方便 的功能(不安全) 1.crete a ...

  9. 一种轻量分表方案-MyBatis拦截器分表实践

    背景 部门内有一些亿级别核心业务表增速非常快,增量日均100W,但线上业务只依赖近一周的数据.随着数据量的迅速增长,慢SQL频发,数据库性能下降,系统稳定性受到严重影响.本篇文章,将分享如何使用MyB ...

  10. Hutool中那些常用的工具类和实用方法

    背景 灵魂拷问1:还在为新项目工具类搬迁而烦恼? 灵魂拷问2:还在为项目中工具类维护而烦恼? 简述 **Hutool**它是一个Java工具集类库,包含了很多静态方法的封装:流处理.时间日期处理.正则 ...