在 NGINX 开源版中无需重启即可轮换 SSL/TLS 证书

在高性能 Web 服务器领域,NGINX 凭借轻量、高效的架构,成为承载大量流量的常见选择。NGINX JavaScript 模块(njs)引入共享字典功能后,又进一步扩展了其能力。

本文介绍 njs 共享字典的功能和优势,以及如何配置 NGINX 开源版,在轮换 SSL/TLS 证书时无需重启服务。

共享字典的基础与优势

新的 js_shared_dict_zone 指令允许 NGINX 开源版用户启用共享内存区域,在工作进程之间高效交换数据。这些区域充当键值字典,存储可实时读取和修改的动态配置。

共享字典主要有以下优势:

  • 开销小,容易使用:直接内置于 njs,API 直观,实现简单,配置和使用都很方便,也简化了工作进程之间的数据管理和共享。
  • 轻量高效:与 NGINX 无缝集成,利用事件驱动、非阻塞 I/O 模型降低内存占用、提高并发能力,让 NGINX 高效处理大量并发连接。
  • 可扩展:利用 NGINX 多工作进程的横向扩展能力,在进程之间共享、同步数据,无需复杂的进程间通信。生存时间(TTL)设置可让共享字典条目因不活动而被移除;evict 参数会移除最旧的键值对,为新条目腾出空间。

使用共享字典轮换 SSL 证书

SSL/TLS 轮换是共享字典最有价值的用途之一。使用 js_shared_dict_zone 后,更新 SSL/TLS 证书或密钥时无需重启 NGINX,同时还能为证书管理提供类似 REST 的 API。

下面的 NGINX 配置使用 js_set 和 ssl_certificate 设置 HTTPS 服务器。JavaScript 处理函数通过 js_set 从文件读取证书或密钥。

配置片段把证书和密钥存入共享字典,作为共享内存缓存。如果缓存里没有对应键,就从磁盘读取并写入缓存。

还可以暴露一个清空缓存的 location。磁盘文件更新后,例如完成证书与密钥续期,清空共享字典会使下一次读取重新从磁盘加载。这样便能在不重启 NGINX 进程的情况下轮换证书和密钥。

http {
     ...
    js_shared_dict_zone zone=kv:1m;
   

    server {
    …
     # Sets an njs function for the variable. Returns a value of cert/key
      js_set $dynamic_ssl_cert main.js_cert;
      js_set $dynamic_ssl_key main.js_key;
   

      # use variable's data
      ssl_certificate data:$dynamic_ssl_cert;
      ssl_certificate_key data:$dynamic_ssl_key;
     

     # a location to clear cache 
   location = /clear {
      js_content main.clear_cache;
      # allow 127.0.0.1;
      # deny all;
    }

  ...
  }

下面是使用 js_shared_dict_zone 轮换 SSL/TLS 证书和密钥的 JavaScript 实现:

function js_cert(r) {
  if (r.variables['ssl_server_name']) {
    return read_cert_or_key(r, '.cert.pem');
  } else {
    return '';
  }
}

function js_key(r) {
  if (r.variables['ssl_server_name']) {
    return read_cert_or_key(r, '.key.pem');
  } else {
    return '';
  }
}
/** 
   * Retrieves the key/cert value from Shared memory or fallback to disk
   */
  function read_cert_or_key(r, fileExtension) {
    let data = '';
    let path = '';
    const zone = 'kv';
    let certName = r.variables.ssl_server_name;
    let prefix =  '/etc/nginx/certs/';
    path = prefix + certName + fileExtension;
    r.log('Resolving ${path}');
    const key = ['certs', path].join(':');
    const cache = zone && ngx.shared && ngx.shared[zone];
   

    if (cache) {
    data = cache.get(key) || '';
    if (data) {
      r.log(`Read ${key} from cache`);
      return data;
    }
  }
  try {
    data = fs.readFileSync(path, 'utf8');
    r.log('Read from cache');
  } catch (e) {
    data = '';
    r.log(`Error reading from file:${path}. Error=${e}`);
  }
  if (cache && data) {
    try {
      cache.set(key, data);
      r.log('Persisted in cache');
    } catch (e) {
      const errMsg = `Error writing to shared dict zone: ${zone}. Error=${e}`;
      r.log(errMsg);
    }
  }
  return data
}

发送 /clear 请求会使缓存失效,NGINX 在下一次 SSL/TLS 握手中从磁盘读取证书或密钥。还可以实现一个 js_content 处理函数,从请求中接受证书或密钥,在持久化的同时更新缓存。

本例完整代码见 njs 官方 GitHub 示例仓库。

开始使用

共享字典是增强应用可编程能力的工具,在简化实现和扩展方面有明显优势。利用 js_shared_dict_zone,可以拓展新的使用场景,更高效地应对不断增长的流量需求。

可以升级 NGINX 部署以使用 js_shared_dict_zone,并通过官方示例文档了解这项功能。原文还介绍了当时新发布的 njs-acme 项目,它提供共享字典的完整示例,让 njs 模块运行时能够与 ACME 服务提供方交互。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容