{"id":13776526,"url":"https://github.com/fffonion/lua-resty-acme","last_synced_at":"2025-05-12T17:24:42.501Z","repository":{"id":37963708,"uuid":"209203135","full_name":"fffonion/lua-resty-acme","owner":"fffonion","description":"Automatic Let's Encrypt certificate serving and Lua implementation of ACMEv2 procotol ","archived":false,"fork":false,"pushed_at":"2024-05-22T08:27:03.000Z","size":483,"stargazers_count":151,"open_issues_count":3,"forks_count":37,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-05-23T05:22:15.226Z","etag":null,"topics":["acme","acme-lua","acme-v2","letsencrypt","openresty"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fffonion.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-09-18T02:50:04.000Z","updated_at":"2024-05-28T17:45:05.253Z","dependencies_parsed_at":"2023-01-23T07:30:36.061Z","dependency_job_id":"9b68a6c9-a9d6-4d87-97c8-08247a9b1124","html_url":"https://github.com/fffonion/lua-resty-acme","commit_stats":null,"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fffonion%2Flua-resty-acme","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fffonion%2Flua-resty-acme/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fffonion%2Flua-resty-acme/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fffonion%2Flua-resty-acme/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fffonion","download_url":"https://codeload.github.com/fffonion/lua-resty-acme/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253785558,"owners_count":21963985,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["acme","acme-lua","acme-v2","letsencrypt","openresty"],"created_at":"2024-08-03T18:00:28.228Z","updated_at":"2025-05-12T17:24:42.468Z","avatar_url":"https://github.com/fffonion.png","language":"Lua","funding_links":[],"categories":["Libraries","Lua"],"sub_categories":[],"readme":"# lua-resty-acme\n\nAutomatic Let's Encrypt certificate serving (RSA + ECC) and pure Lua implementation of the ACMEv2 protocol.\n\n`http-01` and `tls-alpn-01` challenges are supported.\n\n![Build Status](https://github.com/fffonion/lua-resty-acme/workflows/Tests/badge.svg) ![luarocks](https://img.shields.io/luarocks/v/fffonion/lua-resty-acme?color=%232c3e67) ![opm](https://img.shields.io/opm/v/fffonion/lua-resty-acme?color=%23599059)\n\n[简体中文](https://yooooo.us/2019/lua-resty-acme)\n\nTable of Contents\n=================\n\n- [Description](#description)\n- [Status](#status)\n- [Synopsis](#synopsis)\n- [TODO](#todo)\n- [Testing](#testing)\n- [Copyright and License](#copyright-and-license)\n- [See Also](#see-also)\n\n\nDescription\n===========\n\nThis library consists of two parts:\n\n- `resty.acme.autossl`: automatic lifecycle management of Let's Encrypt certificates\n- `resty.acme.client`: Lua implementation of ACME v2 protocol\n\nInstall using opm:\n\n```shell\nopm install fffonion/lua-resty-acme\n```\n\nAlternatively, to install using luarocks:\n\n```shell\nluarocks install lua-resty-acme\n# manually install a luafilesystem\nluarocks install luafilesystem\n```\n\nNote you will need to manually install `luafilesystem` when using LuaRocks. This is made to maintain\nbackward compatibility.\n\nThis library uses [an FFI-based openssl backend](https://github.com/fffonion/lua-resty-openssl),\nwhich currently supports OpenSSL `1.1.1`, `1.1.0` and `1.0.2` series.\n\n\n[Back to TOC](#table-of-contents)\n\nStatus\n========\n\nProduction.\n\nSynopsis\n========\n\nCreate account private key and fallback certs:\n\n```shell\n# create account key\nopenssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out /etc/openresty/account.key\n# create fallback cert and key\nopenssl req -newkey rsa:2048 -nodes -keyout /etc/openresty/default.key -x509 -days 365 -out /etc/openresty/default.pem\n```\n\nUse the following example config:\n\n```lua\nevents {}\n\nhttp {\n    resolver 8.8.8.8 ipv6=off;\n\n    lua_shared_dict acme 16m;\n\n    # required to verify Let's Encrypt API\n    lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;\n    lua_ssl_verify_depth 2;\n\n    init_by_lua_block {\n        require(\"resty.acme.autossl\").init({\n            -- setting the following to true\n            -- implies that you read and accepted https://letsencrypt.org/repository/\n            tos_accepted = true,\n            -- uncomment following for first time setup\n            -- staging = true,\n            -- uncomment following to enable RSA + ECC double cert\n            -- domain_key_types = { 'rsa', 'ecc' },\n            -- uncomment following to enable tls-alpn-01 challenge\n            -- enabled_challenge_handlers = { 'http-01', 'tls-alpn-01' },\n            account_key_path = \"/etc/openresty/account.key\",\n            account_email = \"youemail@youdomain.com\",\n            domain_whitelist = { \"example.com\" },\n        })\n    }\n\n    init_worker_by_lua_block {\n        require(\"resty.acme.autossl\").init_worker()\n    }\n\n    server {\n        listen 80;\n        listen 443 ssl;\n        server_name example.com;\n\n        # fallback certs, make sure to create them before hand\n        ssl_certificate /etc/openresty/default.pem;\n        ssl_certificate_key /etc/openresty/default.key;\n\n        ssl_certificate_by_lua_block {\n            require(\"resty.acme.autossl\").ssl_certificate()\n        }\n\n        location /.well-known {\n            content_by_lua_block {\n                require(\"resty.acme.autossl\").serve_http_challenge()\n            }\n        }\n    }\n}\n```\n\nWhen testing deployment, it's recommanded to uncomment the `staging = true` to allow an\nend-to-end test of your environment. This can avoid configuration failure result into too\nmany requests that hits [rate limiting](https://letsencrypt.org/docs/rate-limits/) on Let's Encrypt API.\n\nBy default `autossl` only creates RSA certificates. To use ECC certificates or both, uncomment\n`domain_key_types = { 'rsa', 'ecc' }`. Note that multiple certificate\nchain is only supported by NGINX 1.11.0 or later.\n\nA certificate will be *queued* to create after Nginx seen request with such SNI, which might\ntake tens of seconds to finish. During the meantime, requests with such SNI are responsed\nwith the fallback certificate.\n\nNote that `domain_whitelist` or `domain_whitelist_callback` must be set to include your domain\nthat you wish to server autossl, to prevent potential abuse using fake SNI in SSL handshake.\n\n`domain_whitelist` defines a table that includes all domains should be included and the CN to be\nused to create cert for. Only a single `*` is allowed as a wildcard.\n\n```lua\ndomain_whitelist = { \"domain1.com\", \"domain2.com\", \"domain3.com\", \"*.domain4.com\" },\n```\n\n## Wildcard certificates\n\nTo enable this library to create wildcard certificate, the following requirements must be met:\n\n- The wildcard domain appear exactly as `*.somedomain.com` in `domain_whitelist`.\n- `dns-01` challenge is enabled and a dns provider that has `domains` matching the domain is configured.\n\nOtherwise a non-wildcard certificate will be created as fallback.\n\nBy default, the wildcard domain `*.example.com` will appear in Common Name. When `wildcard_domain_in_san` is set to `true` however, a cert with Common Name `example.com` and Subject Alternate Name `*.example.com` will be created. Note both `*.example.com` and `example.com` should appear in `dns_provider_accounts`.\n\n## Advanced Usage\n\n### Use a function to include domains\n\n`domain_whitelist_callback` defines a function that accepts domain as parameter and return\nboolean to indicate if it should be included.\n\nTo match a pattern in your domain name, for example **all** subdomains under `example.com`, use:\n\n```lua\ndomain_whitelist_callback = function(domain, is_new_cert_needed)\n    return ngx.re.match(domain, [[\\.example\\.com$]], \"jo\")\nend\n```\n\nFurthermore, since checking domain whitelist is running in certificate phase.\nIt's possible to use cosocket API here. Do note that this will increase the SSL handshake\nlatency.\n\n```lua\ndomain_whitelist_callback = function(domain, is_new_cert_needed)\n    -- send HTTP request\n    local http = require(\"resty.http\")\n    local res, err = httpc:request_uri(\"http://example.com\")\n    -- access the storage\n    local acme = require(\"resty.acme.autossl\")\n    local value, err = acme.storage:get(\"key\")\n    -- get cert from resty LRU cache\n    -- cached = { pkey, cert } or nil if cert is not in cache\n    local cached, staled, flags = acme.get_cert_from_cache(domain, \"rsa\")\n    -- do something to check the domain\n    -- return is_domain_included\nend}),\n```\n\n`domain_whitelist_callback` function is provided with a second argument,\nwhich indicates whether the certificate is about to be served on incoming HTTP request (false) or new certificate is about to be requested (true). This allows to use cached values on hot path (serving requests) while fetching fresh data from storage for new certificates. One may also implement different logic, e.g. do extra checks before requesting new cert.\n\n### Define failure cooloff period\n\nIn case of certificate request failure one may want to prevent ACME client to request another certificate immediatelly. By default, the cooloff period it is set to 300 seconds (5 minutes). It may be customized with `failure_cooloff` or with `failure_cooloff_callback` function, e.g. to implement exponential backoff.\n\n```lua\n    failure_cooloff_callback = function(domain, count)\n      if count == 1 then\n        return 600 -- 10 minutes\n      elseif count == 2 then\n        return 1800 -- 30 minutes\n      elseif count == 3 then\n        return 3600 -- 1 hour\n      elseif count == 4 then\n        return 43200 -- 12 hours\n      elseif count == 5 then\n        return 43200 -- 12 hours\n      else\n        return 86400 -- 24 hours\n      end\n    end\n```\n\n## tls-alpn-01 challenge\n\ntls-alpn-01 challenge is currently supported on Openresty `1.15.8.x`, `1.17.8.x` and `1.19.3.x`.\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand sample config\u003c/summary\u003e\n\n```lua\nevents {}\n\nhttp {\n    resolver 8.8.8.8 ipv6=off;\n\n    lua_shared_dict acme 16m;\n\n    # required to verify Let's Encrypt API\n    lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;\n    lua_ssl_verify_depth 2;\n\n    init_by_lua_block {\n        require(\"resty.acme.autossl\").init({\n            -- setting the following to true\n            -- implies that you read and accepted https://letsencrypt.org/repository/\n            tos_accepted = true,\n            -- uncomment following for first time setup\n            -- staging = true,\n            -- uncomment folloing to enable RSA + ECC double cert\n            -- domain_key_types = { 'rsa', 'ecc' },\n            -- uncomment following to enable tls-alpn-01 challenge\n            enabled_challenge_handlers = { 'http-01', 'tls-alpn-01' },\n            account_key_path = \"/etc/openresty/account.key\",\n            account_email = \"youemail@youdomain.com\",\n            domain_whitelist = { \"example.com\" },\n            storage_adapter = \"file\",\n        })\n    }\n    init_worker_by_lua_block {\n        require(\"resty.acme.autossl\").init_worker()\n    }\n\n    server {\n        listen 80;\n        listen unix:/tmp/nginx-default.sock ssl;\n        # listen unix:/tmp/nginx-default.sock ssl proxy_protocol;\n        server_name example.com;\n\n        # set_real_ip_from unix:;\n        # real_ip_header proxy_protocol;\n\n        # fallback certs, make sure to create them before hand\n        ssl_certificate /etc/openresty/default.pem;\n        ssl_certificate_key /etc/openresty/default.key;\n\n        ssl_certificate_by_lua_block {\n            require(\"resty.acme.autossl\").ssl_certificate()\n        }\n\n        location /.well-known {\n            content_by_lua_block {\n                require(\"resty.acme.autossl\").serve_http_challenge()\n            }\n        }\n    }\n}\n\nstream {\n    init_worker_by_lua_block {\n        require(\"resty.acme.autossl\").init({\n            -- setting the following to true\n            -- implies that you read and accepted https://letsencrypt.org/repository/\n            tos_accepted = true,\n            -- uncomment following for first time setup\n            -- staging = true,\n            -- uncomment folloing to enable RSA + ECC double cert\n            -- domain_key_types = { 'rsa', 'ecc' },\n            -- uncomment following to enable tls-alpn-01 challenge\n            enabled_challenge_handlers = { 'http-01', 'tls-alpn-01' },\n            account_key_path = \"/etc/openresty/account.key\",\n            account_email = \"youemail@youdomain.com\",\n            domain_whitelist = { \"example.com\" },\n            storage_adapter = \"file\"\n        })\n        require(\"resty.acme.autossl\").init_worker()\n    }\n\n    map $ssl_preread_alpn_protocols $backend {\n        ~\\bacme-tls/1\\b unix:/tmp/nginx-tls-alpn.sock;\n        default unix:/tmp/nginx-default.sock;\n    }\n\n    server {\n            listen 443;\n            listen [::]:443;\n\n            ssl_preread on;\n            proxy_pass $backend;\n\n            # proxy_protocol on;\n    }\n\n    server {\n            listen unix:/tmp/nginx-tls-alpn.sock ssl;\n            # listen nix:/tmp/nginx-tls-alpn.sock ssl proxy_protocol;\n            ssl_certificate certs/default.pem;\n            ssl_certificate_key certs/default.key;\n\n            # requires --with-stream_realip_module\n            # set_real_ip_from unix:;\n\n            ssl_certificate_by_lua_block {\n                    require(\"resty.acme.autossl\").serve_tls_alpn_challenge()\n            }\n\n            content_by_lua_block {\n                    ngx.exit(0)\n            }\n    }\n}\n```\n\n\u003c/details\u003e\n\nIn the above sample config, we set a http server and two stream server.\n\nThe very front stream server listens for 443 port and route to different upstream\nbased on client ALPN. The tls-alpn-01 responder listens on `unix:/tmp/nginx-tls-alpn.sock`.\nAll normal https traffic listens on `unix:/tmp/nginx-default.sock`.\n\n```\n                                                [stream server unix:/tmp/nginx-tls-alpn.sock ssl]\n                                            Y /\n[stream server 443] --- ALPN is acme-tls ?\n                                            N \\\n                                                [http server unix:/tmp/nginx-default.sock ssl]\n```\n\n- The config passed to `require(\"resty.acme.autossl\").init` in both subsystem should be\nkept same as possible.\n- `tls-alpn-01` challenge handler doesn't need any third party dependency.\n- You can enable `http-01` and `tls-alpn-01` challenge handlers at the same time.\n- `http` and `stream` subsystem doesn't share shm, thus considering use a storage other\nthan `shm`. If you must use `shm`, you will need to apply\n[this patch](https://github.com/fffonion/lua-resty-shdict-server/tree/master/patches).\n\n## dns-01 challenge\n\nDNS-01 challenge is supported on lua-resty-acme \u003e 0.13.0. Currently, following DNS providers are supported:\n\n- `cloudflare`: Cloudflare\n- `dynv6`: Dynv6\n- `dnspod-intl`: Dnspod International (only Dnspod token is supported and use `id,token` in secret field)\n\nTo read to how to extend a new DNS provider to work with `dns-01` challenge, see [DNS provider](#dns-providers).\n\nAn example config to use `dns-01` challenge would be:\n\n```lua\nrequire(\"resty.acme.autossl\").init({\n  -- setting the following to true\n  -- implies that you read and accepted https://letsencrypt.org/repository/\n  tos_accepted = true,\n  -- uncomment following for first time setup\n  -- staging = true,\n  -- uncomment following to enable RSA + ECC double cert\n  -- domain_key_types = { 'rsa', 'ecc' },\n  -- do not set `http-01` or `tls-alpn-01` if you only plan to use dns-01.\n  enabled_challenge_handlers = { 'dns-01' },\n  account_key_path = \"/etc/openresty/account.key\",\n  account_email = \"youemail@youdomain.com\",\n  domain_whitelist = { \"example.com\", \"subdomain.anotherdomain.com\" },\n\n  dns_provider_accounts = {\n    {\n      name = \"cloudflare_prod\",\n      provider = \"cloudflare\",\n      secret = \"apikey of cloudflare\",\n      domains = { \"example.com\" },\n    },\n    {\n      name = \"dynv6_staging\",\n      provider = \"dynv6\",\n      secret = \"apikey of dynv6\",\n      domains = { \"*.anotherdomain.com\" },\n    },\n  },\n  -- uncomment following to create anotherdomain.com in CN and *.anotherdomain.com in SAN\n  -- wildcard_domain_in_san = true,\n})\n```\n\nBy default, this library tries up to 5 minutes for DNS propagation. If the default TTL for dns provider is longer than that, user may want to tune up `challenge_start_delay` manually to wait longer.\n\n## resty.acme.autossl\n\nA config table can be passed to `resty.acme.autossl.init()`, the default values are:\n\n```lua\ndefault_config = {\n  -- accept term of service https://letsencrypt.org/repository/\n  tos_accepted = false,\n  -- if using the let's encrypt staging API\n  staging = false,\n  -- the path to account private key in PEM format\n  account_key_path = nil,\n  -- the account email to register\n  account_email = nil,\n  -- number of certificate cache, per type\n  cache_size = 100,\n  domain_key_paths = {\n    -- the global domain RSA private key\n    rsa = nil,\n    -- the global domain ECC private key\n    ecc = nil,\n  },\n  -- the private key algorithm to use, can be one or both of\n  -- 'rsa' and 'ecc'\n  domain_key_types = { 'rsa' },\n  -- restrict registering new cert only with domain defined in this table\n  domain_whitelist = nil,\n  -- restrict registering new cert only with domain checked by this function\n  domain_whitelist_callback = nil,\n  -- interval to wait before retrying after failed certificate request\n  failure_cooloff = 300,\n  -- function that returns interval to wait before retrying after failed certificate request\n  failure_cooloff_callback = nil,\n  -- the threshold to renew a cert before it expires, in seconds\n  renew_threshold = 7 * 86400,\n  -- interval to check cert renewal, in seconds\n  renew_check_interval = 6 * 3600,\n  -- the store certificates\n  storage_adapter = \"shm\",\n  -- the storage config passed to storage adapter\n  storage_config = {\n    shm_name = 'acme',\n  },\n  -- the challenge types enabled\n  enabled_challenge_handlers = { 'http-01' },\n  -- time to wait before signaling ACME server to validate in seconds\n  challenge_start_delay = 0,\n  -- if true, the request to nginx waits until the cert has been generated and it is used right away\n  blocking = false,\n  -- if true, the certificate for domain not in whitelist will be deleted from storage\n  enabled_delete_not_whitelisted_domain = false,\n  -- the dict of dns providers, each provider should have following struct:\n  -- {\n  --   name = \"prod_account\",\n  --   provider = \"provider_name\", -- \"cloudflare\" or \"dynv6\"\n  --   secret  = \"the api key or token\",\n  --   domains = { \"example.com\", \"*.example.com\" }, -- the list of domains that can be used with this provider\n  -- }\n  dns_provider_accounts = {},\n  -- if enabled, wildcard domains like *.example.com will be created as SAN and CN will be example.com\n  wildcard_domain_in_san = false,\n}\n```\n\nIf `account_key_path` is not specified, a new account key will be created\n**everytime** Nginx reloads configuration. Note this may trigger **New Account**\n[rate limiting](https://letsencrypt.org/docs/rate-limits/) on Let's Encrypt API.\n\nIf `domain_key_paths` is not specified, a new private key will be generated\nfor each certificate (4096-bits RSA and 256-bits prime256v1 ECC). Note that\ngenerating such key will block worker and will be especially noticable on VMs\nwhere entropy is low.\n\nPass config table directly to ACME client as second parameter. The following example\ndemonstrates how to use a CA provider other than Let's Encrypt and also set\nthe preferred chain.\n\n```lua\nresty.acme.autossl.init({\n    tos_accepted = true,\n    account_email = \"example@example.com\",\n  }, {\n    api_uri = \"https://acme.otherca.com/directory\",\n    preferred_chain = \"OtherCA PKI Root CA\",\n  }\n)\n```\n\nSee also [Storage Adapters](#storage-adapters) below.\n\nWhen using distributed storage types, it's useful to bump up `challenge_start_delay` to allow\nchanges in storage to propogate around. When `challenge_start_delay` is set to 0, no wait\nwill be performed before start validating challenges.\n\n### autossl.get_certkey\n\n**syntax**: *certkey, err = autossl.get_certkey(domain, type?)*\n\nReturn the PEM-encoded certificate and private key for `domain` from storage. Optionally\naccepts a `type` parameter which can be `\"rsa\"` or `\"ecc\"`; if omitted, `type` will default\nto `\"rsa\"`.\n\n[Back to TOC](#table-of-contents)\n\n## resty.acme.client\n\n### client.new\n\n**syntax**: *c, err = client.new(config)*\n\nCreate a ACMEv2 client.\n\nDefault values for `config` are:\n\n```lua\ndefault_config = {\n  -- the ACME v2 API endpoint to use\n  api_uri = \"https://acme-v02.api.letsencrypt.org/directory\",\n  -- the account email to register\n  account_email = nil,\n  -- the account key in PEM format text\n  account_key = nil,\n  -- the account kid (as an URL)\n  account_kid = nil,\n  -- external account binding key id\n  eab_kid = nil,\n  -- external account binding hmac key, base64url encoded\n  eab_hmac_key = nil,\n  -- external account registering handler\n  eab_handler = nil,\n  -- storage for challenge\n  storage_adapter = \"shm\",\n  -- the storage config passed to storage adapter\n  storage_config = {\n    shm_name = \"acme\"\n  },\n  -- the challenge types enabled, selection of `http-01` and `tls-alpn-01`\n  enabled_challenge_handlers = {\"http-01\"},\n  -- select preferred root CA issuer's Common Name if appliable\n  preferred_chain = nil,\n  -- callback function that allows to wait before signaling ACME server to validate\n  challenge_start_callback = nil,\n  -- the dict of dns providers, each provider should have following struct:\n  dns_provider_accounts = {},\n}\n```\n\nIf `account_kid` is omitted, user must call `client:new_account()` to register a\nnew account. Note that when using the same `account_key`, `client:new_account()`\nwill return the same `kid` that is previosuly registered.\n\nIf CA requires [External Account Binding](#external-account-binding), user can set\n`eab_kid` and `eab_hmac_key` to load an existing account, or set `account_email` and\n`eab_handler` to register a new account. `eab_hmac_key` must be base64 url encoded.\nIn later case, user must call `client:new_account()` to register a new account.\n`eab_handler` must be an function that accepts account_email as parameter and\nreturns `eab_kid`, `eab_hmac_key` and error if any.\n\n```lua\neab_handler = function(account_email)\n  -- do something to register an account with account_email\n  -- if err then\n  --  return nil, nil, err\n  -- end\n  return eab_kid, eab_hmac_key\nend\n```\n\nThe following CA provider's EAB handler is supported by lua-resty-acme and user doesn't\nneed to implement their own `eab_handler`:\n\n- [ZeroSSL](https://zerossl.com/)\n\n`preferred_chain` is used to select a chain with matching Common Name in its root CA. For example,\nuser can use use `\"ISRG Root X1\"` to force use the new default chain in Let's Encrypt. When no\nvalue is configured or the configured name is not found in any chain, the default chain will be\nused.\n\n`challenge_start_callback` is a callback function to allow the client to wait before signalling\nACME server to start validate challenge. It's useful in a distributed setup where challenges take\ntime to propogate. `challenge_start_callback` accepts `challenge_type` and `challenge_token`.\nThe client calls this function every second until it returns `true` indicating challenge should start;\nif this `challenge_start_callback` is not set, no wait will be performed.\n\n```lua\nchallenge_start_callback = function(challenge_type, challenge_token)\n  -- do something here\n  -- if we are good\n  return true\nend\n```\n\nSee also [Storage Adapters](#storage-adapters) below.\n\n[Back to TOC](#table-of-contents)\n\n### client:init\n\n**syntax**: *err = client:init()*\n\nInitialize the client, requires availability of cosocket API. This function will\nlogin or register an account.\n\n[Back to TOC](#table-of-contents)\n\n### client:order_certificate\n\n**syntax**: *err = client:order_certificate(domain,...)*\n\nCreate a certificate with one or more domains. Note that wildcard domains are not\nsupported as it can only be verified by [dns-01](https://letsencrypt.org/docs/challenge-types/) challenge.\n\n[Back to TOC](#table-of-contents)\n\n### client:serve_http_challenge\n\n**syntax**: *client:serve_http_challenge()*\n\nServe [http-01](https://letsencrypt.org/docs/challenge-types/) challenge. A common use case will be to\nput this as a content_by_* block for `/.well-known` path.\n\n[Back to TOC](#table-of-contents)\n\n### client:serve_tls_alpn_challenge\n\n**syntax**: *client:serve_tls_alpn_challenge()*\n\nServe [tls-alpn-01](https://letsencrypt.org/docs/challenge-types/) challenge. See\n[this section](https://github.com/fffonion/lua-resty-acme#tls-alpn-01-challenge) on how to use this handler.\n\n[Back to TOC](#table-of-contents)\n\n\n## Storage Adapters\n\nStorage adapters are used in `autossl` or acme `client` to storage temporary or\npersistent data. Depending on the deployment environment, there're currently\nfive storage adapters available to select from. To implement a custom storage\nadapter, please refer to\n[this doc](https://github.com/fffonion/lua-resty-acme/blob/master/lib/resty/acme/storage/README.md).\n\n### file\n\nFilesystem based storage. Sample configuration:\n\n```lua\nstorage_config = {\n    dir = '/etc/openresty/storage',\n}\n```\nIf `dir` is omitted, the OS temporary directory will be used.\n\n`luafilesystem` or `luafilesystem-ffi` is needed when using the `file` storage for renewal.\n\n### shm\n\nLua shared dict based storage. Note this storage is volatile between Nginx restarts\n(not reloads). Sample configuration:\n\n```lua\nstorage_config = {\n    shm_name = 'dict_name',\n}\n```\n\n### redis\n\nRedis based storage. The default config is:\n\n```lua\nstorage_config = {\n    host = '127.0.0.1',\n    port = 6379,\n    database = 0,\n    -- Redis authentication key\n    auth = nil,\n    ssl = false,\n    ssl_verify = false,\n    ssl_server_name = nil,\n    -- namespace as a prefix of key\n    namespace = \"\",\n}\n```\n\nRedis \u003e= 2.6.12 is required as this storage requires [SET EX](https://redis.io/commands/set).\n\n### vault\n\nHashicorp [Vault](https://www.vaultproject.io/) based storage.\nOnly [KV V2](https://www.vaultproject.io/api/secret/kv/kv-v2.html) backend is supported.\nThe default config is:\n\n\n```lua\nstorage_config = {\n    host = '127.0.0.1',\n    port = 8200,\n    -- secrets kv prefix path\n    kv_path = \"acme\",\n    -- timeout in ms\n    timeout = 2000,\n    -- use HTTPS\n    https = false,\n    -- turn on tls verification\n    tls_verify = true\n    -- SNI used in request, default to host if omitted\n    tls_server_name = nil,\n    -- Auth Method, default to token, can be \"token\" or \"kubernetes\"\n    auth_method = \"token\"\n    -- Vault token\n    token = nil,\n    -- Vault's authentication path to use\n    auth_path =  \"kubernetes\",\n    -- The role to try and assign\n    auth_role = nil,\n    -- The path to the JWT\n    jwt_path = \"/var/run/secrets/kubernetes.io/serviceaccount/token\",\n    -- Vault namespace\n    namespace = nil,\n}\n```\n\n#### Support for different auth method\n\n- Token: This is the default and allows to pass a literal \"token\" in the configuration\n- Kubernetes: Via this method, one can utilize vault's built-in auth method for kubernetes\n  What this basically this is take the service account token and validates it has been signed by Kubernetes CA.\n  The major benefit here, is that config files don't expose your token anymore.\n\n  The following configurations apply here:\n  ```lua\n    -- Vault's authentication path to use\n    auth_path =  \"kubernetes\",\n    -- The role to try and assign\n    auth_role = nil,\n    -- The path to the JWT\n    jwt_path = \"/var/run/secrets/kubernetes.io/serviceaccount/token\",\n   ```\n\n### consul\n\nHashicorp [Consul](https://www.consul.io/) based storage. The default config is:\n\n\n```lua\nstorage_config = {\n    host = '127.0.0.1',\n    port = 8500,\n    -- kv prefix path\n    kv_path = \"acme\",\n    -- Consul ACL token\n    token = nil,\n    -- timeout in ms\n    timeout = 2000,\n}\n```\n\n### etcd\n\n[etcd](https://etcd.io) based storage. Right now only `v3` protocol is supported, and etcd server\nversion should be \u003e= v3.4.0.\nThe default config is:\n\n```lua\nstorage_config = {\n    http_host = 'http://127.0.0.1:4001',\n    key_prefix = '',\n    timeout = 60,\n    ssl_verify = false,\n}\n```\n\nEtcd storage requires [lua-resty-etcd](https://github.com/api7/lua-resty-etcd) library to installed.\nIt can be manually installed with `opm install api7/lua-resty-etcd` or `luarocks install lua-resty-etcd`.\n\n\n## DNS providers\n\nTO create a custom DNS provider, follow these steps:\n\n- Create a file like `route53.lua` under `lib/resty/acme/dns_provider`\n- Implement following function signature\n\n```lua\nfunction _M.new(token)\n  -- ... \n  return self\nend\n\nfunction _M:post_txt_record(fqdn, content)\n  return ok, err\nend\n\nfunction _M:delete_txt_record(fqdn)\n  return ok, err\nend\n```\n\nWhere `token` is the apikey, `fqdn` is the DNS record name to set record, and `content` is the value of the record.\n\n\nTODO\n====\n- autossl: ocsp staping\n\n[Back to TOC](#table-of-contents)\n\n\nTesting\n====\nSetup e2e test environment by running `bash t/fixtures/prepare_env.sh`.\n\nThen run `cpanm install Test::Nginx::Socket` and then `prove -r t`.\n\n[Back to TOC](#table-of-contents)\n\n\nCredits\n=======\n\n- Improvements of `file` storage by [@dbalagansky](https://github.com/dbalagansky)\n- Addition of kubernetes auth in 'vault' storage by [@UXabre](https://github.com/UXabre/)\n- Initial support of `dns-01` challenge by [@yuweizzz](https://github.com/yuweizzz)\n\n\nCopyright and License\n=====================\n\nThis module is licensed under the BSD license.\n\nCopyright (C) 2019, by fffonion \u003cfffonion@gmail.com\u003e.\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n[Back to TOC](#table-of-contents)\n\nSee Also\n========\n* [Automatic Certificate Management Environment (ACME)](https://tools.ietf.org/html/rfc8555)\n* [haproxytech/haproxy-lua-acme](https://github.com/haproxytech/haproxy-lua-acme) The ACME Lua implementation used in HAProxy.\n* [GUI/lua-resty-auto-ssl](https://github.com/GUI/lua-resty-auto-ssl)\n* [lua-resty-openssl](https://github.com/fffonion/lua-resty-openssl)\n* [Let's Encrypt API rate limits](https://letsencrypt.org/docs/rate-limits/)\n\n[Back to TOC](#table-of-contents)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffffonion%2Flua-resty-acme","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffffonion%2Flua-resty-acme","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffffonion%2Flua-resty-acme/lists"}