nginx ocsp cache 分析

nginx ocsp cache 分析

本文分析 nginx ocsp cache 的原理,用于回答相关业务的解惑

缓存时间

OCSP 的缓存时间和证书 OCSP Response 的 Next Update 时间相关.如果证书未提供 Next Update 时间,过期时间=当前时间+3600s.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// nextupdate 存在时设置为 nextupdate ,不存在时设置为 NGX_MAX_TIME_T_VALUE
//ngx_ssl_ocsp_verify
if (nextupdate) {
ctx->valid = ngx_ssl_stapling_time(nextupdate);
if (ctx->valid == (time_t) NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
"invalid nextUpdate time in certificate status");
goto error;
}

} else {
ctx->valid = NGX_MAX_TIME_T_VALUE;
}
// 在存储到缓存中时,如果时间为 NGX_MAX_TIME_T_VALUE ,则是当前时间 +3600s

// ngx_ssl_ocsp_cache_store ocsp cache 存储
valid = ctx->valid;
now = ngx_time();
// 过期时间小于当前,不缓存,直接返回
if (valid < now) {
return NGX_OK;
}
// 过期时间未默认值是设置为 3600s
if (valid == NGX_MAX_TIME_T_VALUE) {
valid = now + 3600;
}


Thisupdate 和 nextupdate 的时间由 OCSP_resp_find_status 从证书中获取, OCSP_resp_find_status 是 openssl 提供的函数.

https://github.com/openssl/openssl/blob/e3d897d3fa3b48bb835fab0665a435469beea7ae/crypto/ocsp/ocsp_cl.c#L248

single = OCSP_resp_get0(bs, i); // 负责获取证书信息 .

调用

1
2
3
4
5
6
7
8
#define sk_OCSP_SINGLERESP_value( st,
i
)SKM_sk_value(OCSP_SINGLERESP, (st), (i))

#defineSKM_sk_value(type, st, i) ((type *)sk_value(CHECKED_STACK_OF(type, st), i))



OCSP_single_get0_status 获取状态后直接返回值,并没有做对应的值转换或者判断.

存储的 key 和谁有关

Ocsp 缓存使用使用证书的 Serial Number 作为key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ngx_ssl_ocsp_create_key
static ngx_int_t
ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx_t *ctx)
{
u_char *p;
......

ctx->key.data = p; // 设置p 为key 默认设置长度 60
ctx->key.len = 60;

......
// 获取 serial number
serial = X509_get_serialNumber(ctx->cert);
if (serial->length > 20) {
return NGX_ERROR;
}
// 将证书得 serial 复制到p中
p = ngx_cpymem(p, serial->data, serial->length);
ngx_memzero(p, 20 - serial->length);

......

return NGX_OK;
}

Ocsp 存储的大小

Ocsp 的存储使用 nginx 的共享内存来存储,关于 nginx 共享内存可以看 OpenResty 和 Nginx 如何分配和管理内存

在OCSP 的数据在存储到红黑树中只存储了数据的状态和过期时间,并不存储整张证书.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ngx_ssl_ocsp_cache_store  ocsp cache 存储


// node 分配的内存块
node->node.str.len = ctx->key.len;
node->node.str.data = (u_char *) node + sizeof(ngx_ssl_ocsp_cache_node_t);
// 将OCSP ctx->key 存储到 node.str
ngx_memcpy(node->node.str.data, ctx->key.data, ctx->key.len);
// node.key 为 ctx->key 的 hash 值
node->node.node.key = hash;
// 证书的状态
node->status = ctx->status;
// 证书的过期时间
node->valid = valid;
// 将node插入到树中
ngx_rbtree_insert(&cache->rbtree, &node->node.node);
// 将数据加入到 LRU 队列
ngx_queue_insert_head(&cache->expire_queue, &node->queue);

由于无法直接读取到 nginx 创建的共享内存空间,我们在 lua 里面创建一个共享对象来验证占用.

1
2
3
4
5
6
7
8
typedef struct {
ngx_str_node_t node; // 16 bytes
ngx_queue_t queue; // 32 bytes
int status; // 4 bytes
time_t valid; // 8 bytes
} ngx_ssl_ocsp_cache_node_t;
size_t size = sizeof(ngx_ssl_ocsp_cache_node_t); // 60 bytes

写入30w 个key value 长度为 60 的字符串,内存占用 79.078125 M , 当把字符串长度扩展到80时,内存占用为 79.078125 M, 和 60 是一致的. (实际分配了 128 bytes 的内存)

以上的验证可能并不精确,但是可以大概描述 ocsp cache 在内存中的存储的大致量. 当 cache 数为 500M 时 ,大致可以缓存 180 w 个.

缓存满了的淘汰策略

淘汰策略使用 LRU (最近最少) .

当 cache 缓存满了后, 会自动将新的添加到队列的头部,删除尾部的数据.

Ocsp stapling 只会下发 good 的状态.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static void
ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx)
{
time_t now;
ngx_str_t response;
ngx_ssl_stapling_t *staple;

staple = ctx->data;
now = ngx_time();

if (ngx_ssl_ocsp_verify(ctx) != NGX_OK) {
goto error;
}
-- 如果校验的状不是 GOOD 报错退出
**if (ctx->status != V_OCSP_CERTSTATUS_GOOD) {
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
"certificate status \"%s\" in the OCSP response",
OCSP_cert_status_str(ctx->status));
goto error;
}**

/* copy the response to memory not in ctx->pool */

response.len = ctx->response->last - ctx->response->pos;
response.data = ngx_alloc(response.len, ctx->log);
...

nginx在proxy_pass里使用变量

nginx server_name 配置文档: http://nginx.org/en/docs/http/server_names.html

在做 nginx 正则表达式 proxy_pass,nginx 反向代理不过去。 比如

1
2
3
4
5
6
7
8
9
server {
listen 80;
server_name ~^(?<user>.+)\.domain\.com$;
location / {
proxy_pass <http://$user.domain1.com>;
}

}

会报出如下错误

no resolver defined to resolve xxx.xxx

web端返回http 502 错误。

在Ngnix中如果用变量作为反向代理的地址时,容易出现“no resolver defined to resolve xxx.xxx”的问题

在 Nginx 0.6.18 后启用了 resolver 指令,在使用变量来构造某个server地址的时候一定要用resolver指令来指定DNS服务器的地址

所以在nginx的配置文件中的http{}部分添加一行resolver 8.8.8.8;

resolver 8.8.8.8;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 80;
server_name ~^(?<user>.+)\.pay\.iov-smart\.net$;

location / {
# set $subdomain "";
# if ($host ~* "^(.+)\.pay\.iov-smart\.net$"){
# set $subdomain $1;
# }

resolver 8.8.8.8;
proxy_pass http://$user.pay.iov-smart.net:6001;
}
}

nginx中gzip和cache配置

gzip

1
2
3
4
5
6
7
8
9
10
11
gzip on;         #开启 gzip      
gzip_min_length 1k;    # 最小压缩字节
gzip_buffers 16 64k;   # 16 *64k 内存
gzip_http_version 1.1;  #识别协议版本
gzip_comp_level 6;      # 压缩比率
gzip_types text/plain application/javascript text/css application/xml; # 指定压缩的头
gzip_vary on;    # 给代理服务器使用  

gzip_proxied any; #代理结果压缩


阅读更多

php和nginx使用x-accel-redirect做文件下载

1
2
3
4
5
6
7
8
9
10
11
12

$file_url= "g1/".$file_path;
$file_name=$title.'.'.$file_ext;

header("Content-Type:application/octet-stream;charset=utf-8");
header('Content-Disposition: attachment; filename='.$file_name);
header('X-Accel-Redirect: /filedfs/'.$file_url);
header("X-Accel-Buffering: yes");
header("X-Accel-Limit-Rate :102400"); //速度限制 Byte/s
header("Accept-Ranges: none");//单线程 限制多线程
header("X-Accel-Charset: utf-8");

阅读更多