Skip to content

Commit 0b3d9d5

Browse files
committed
Eliminate dependency on GNU version of "date".
Handle parsing the OpenSSL expiry times ourselves, since the BusyBox and and BSD versions of the "date" command don't support parsing like the GNU version does. #195
1 parent 2a22999 commit 0b3d9d5

File tree

9 files changed

+304
-6
lines changed

9 files changed

+304
-6
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# lua-resty-auto-ssl Change Log
22

3+
## 0.13.1 - Unreleased
4+
5+
### Changed
6+
- Eliminate dependency on GNU version of the `date` command line utility to improve compatibility with Alpine Linux, BSDs, and others. Fixes warnings that may have started getting logged in v0.13.0. [#195](https://github.com/GUI/lua-resty-auto-ssl/issues/195)
7+
38
## 0.13.0 - 2019-09-30
49

510
### Upgrade Notes

Dockerfile-test-alpine

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ WORKDIR /app
66
# Runtime dependencies
77
RUN apk add --no-cache \
88
bash \
9-
coreutils \
109
curl \
1110
diffutils \
1211
grep \
@@ -27,6 +26,7 @@ RUN apk add --no-cache \
2726
procps \
2827
redis \
2928
sudo \
29+
tzdata \
3030
wget && \
3131
curl -fsSL -o /tmp/ngrok.tar.gz https://bin.equinox.io/a/naDTyS8Kyxv/ngrok-2.3.34-linux-386.tar.gz && \
3232
tar -xvf /tmp/ngrok.tar.gz -C /usr/local/bin/ && \

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ install: check-dependencies
4747
install -m 644 lib/resty/auto-ssl/storage_adapters/file.lua $(INST_LUADIR)/resty/auto-ssl/storage_adapters/file.lua
4848
install -m 644 lib/resty/auto-ssl/storage_adapters/redis.lua $(INST_LUADIR)/resty/auto-ssl/storage_adapters/redis.lua
4949
install -d $(INST_LUADIR)/resty/auto-ssl/utils
50+
install -m 644 lib/resty/auto-ssl/utils/parse_openssl_time.lua $(INST_LUADIR)/resty/auto-ssl/utils/parse_openssl_time.lua
5051
install -m 644 lib/resty/auto-ssl/utils/random_seed.lua $(INST_LUADIR)/resty/auto-ssl/utils/random_seed.lua
5152
install -m 644 lib/resty/auto-ssl/utils/shell_execute.lua $(INST_LUADIR)/resty/auto-ssl/utils/shell_execute.lua
5253
install -m 644 lib/resty/auto-ssl/utils/shuffle_table.lua $(INST_LUADIR)/resty/auto-ssl/utils/shuffle_table.lua
@@ -105,6 +106,7 @@ lint:
105106
luacheck lib spec
106107

107108
test:
109+
luarocks --tree=/tmp/resty-auto-ssl-test-luarocks make ./lua-resty-auto-ssl-git-1.rockspec
108110
rm -rf /tmp/resty-auto-ssl-server-luarocks
109111
luarocks --tree=/tmp/resty-auto-ssl-server-luarocks make ./lua-resty-auto-ssl-git-1.rockspec
110112
luarocks --tree=/tmp/resty-auto-ssl-server-luarocks install dkjson 2.5-2

bin/letsencrypt_hooks

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ clean_challenge() {
3434
deploy_cert() {
3535
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"
3636
local EXPIRY
37-
if ! EXPIRY=$(date --date="$(openssl x509 -enddate -noout -in "$CERTFILE"|cut -d= -f 2)" +%s); then
37+
if ! EXPIRY=$(openssl x509 -enddate -noout -in "$CERTFILE"); then
3838
echo "failed to get the expiry date"
3939
fi
4040

lib/resty/auto-ssl/jobs/renewal.lua

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
local lock = require "resty.lock"
2+
local parse_openssl_time = require "resty.auto-ssl.utils.parse_openssl_time"
23
local shell_blocking = require "shell-games"
34
local shuffle_table = require "resty.auto-ssl.utils.shuffle_table"
45
local ssl_provider = require "resty.auto-ssl.ssl_providers.lets_encrypt"
@@ -97,12 +98,16 @@ local function renew_check_cert(auto_ssl_instance, storage, domain)
9798
file:write(cert["fullchain_pem"])
9899
file:close()
99100

100-
local date_result, date_err = shell_blocking.run_raw('date --date="$(openssl x509 -enddate -noout -in "' .. shell_blocking.quote(cert_pem_path) .. '"|cut -d= -f 2)" +%s', { capture = true, stderr = "&1" })
101+
local date_result, date_err = shell_blocking.capture_combined({ "openssl", "x509", "-enddate", "-noout", "-in", cert_pem_path })
101102
if date_err then
102103
ngx.log(ngx.ERR, "auto-ssl: failed to extract expiry date from cert: ", date_err)
103104
else
104-
cert["expiry"] = tonumber(date_result["output"])
105-
if cert["expiry"] then
105+
local expiry, parse_err = parse_openssl_time(date_result["output"])
106+
if parse_err then
107+
ngx.log(ngx.ERR, "auto-ssl: failed to parse expiry date: ", parse_err)
108+
else
109+
cert["expiry"] = expiry
110+
106111
-- Update stored certificate to include expiry information
107112
ngx.log(ngx.NOTICE, "auto-ssl: setting expiration date of ", domain, " to ", cert["expiry"])
108113
local _, set_cert_err = storage:set_cert(domain, cert["fullchain_pem"], cert["privkey_pem"], cert["cert_pem"], cert["expiry"])

lib/resty/auto-ssl/servers/hook.lua

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
local parse_openssl_time = require "resty.auto-ssl.utils.parse_openssl_time"
12
local shell_blocking = require "shell-games"
23

34
-- This server provides an internal-only API for the dehydrated bash hook
@@ -42,7 +43,13 @@ return function(auto_ssl_instance)
4243
assert(params["fullchain"])
4344
assert(params["privkey"])
4445
assert(params["expiry"])
45-
local _, err = storage:set_cert(params["domain"], params["fullchain"], params["privkey"], params["cert"], tonumber(params["expiry"]))
46+
47+
local expiry, parse_err = parse_openssl_time(params["expiry"])
48+
if parse_err then
49+
ngx.log(ngx.ERR, "auto-ssl: failed to parse expiry date: ", parse_err)
50+
end
51+
52+
local _, err = storage:set_cert(params["domain"], params["fullchain"], params["privkey"], params["cert"], expiry)
4653
if err then
4754
ngx.log(ngx.ERR, "auto-ssl: failed to set cert: ", err)
4855
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
local floor = math.floor
2+
3+
local months = {
4+
Jan = 1,
5+
Feb = 2,
6+
Mar = 3,
7+
Apr = 4,
8+
May = 5,
9+
Jun = 6,
10+
Jul = 7,
11+
Aug = 8,
12+
Sep = 9,
13+
Oct = 10,
14+
Nov = 11,
15+
Dec = 12,
16+
}
17+
18+
return function(time_str)
19+
local matches, match_err = ngx.re.match(time_str, [[(?<month>[A-Za-z]{3}) +(?<day>\d{1,2}) +(?<hour>\d{2}):(?<minute>\d{2}):(?<second>\d{2})(?:\.\d+)? +(?<year>-?\d{4})]], "jo")
20+
if match_err then
21+
return nil, match_err
22+
elseif not matches then
23+
return nil, "could not parse openssl time string: " .. (tostring(time_str) or "")
24+
end
25+
26+
local month = months[matches["month"]]
27+
if not month then
28+
return nil, "could not parse month in openssl time string: " .. (tostring(time_str) or "")
29+
end
30+
31+
local year = tonumber(matches["year"])
32+
local day = tonumber(matches["day"])
33+
local hour = tonumber(matches["hour"])
34+
local minute = tonumber(matches["minute"])
35+
local second = tonumber(matches["second"])
36+
37+
-- Convert the parsed time into a unix epoch timestamp. Since the unix
38+
-- timestamp should always be returned according to UTC, we can't use Lua's
39+
-- "os.time", since it returns values based on local time
40+
-- (http://lua-users.org/lists/lua-l/2012-04/msg00557.html), and workarounds
41+
-- seem tricky (http://lua-users.org/lists/lua-l/2012-04/msg00588.html).
42+
--
43+
-- So instead, manually calculate the days since UTC epoch and output based
44+
-- on this math. The algorithm behind this is based on
45+
-- http://howardhinnant.github.io/date_algorithms.html#civil_from_days
46+
if month <= 2 then
47+
year = year - 1
48+
month = month + 9
49+
else
50+
month = month - 3
51+
end
52+
local era = floor(year / 400)
53+
local yoe = year - era * 400
54+
local doy = floor((153 * month + 2) / 5) + day - 1
55+
local doe = (yoe * 365) + floor(yoe / 4) - floor(yoe / 100) + doy
56+
local days = era * 146097 + doe - 719468
57+
58+
return (days * 86400) + (hour * 3600) + (minute * 60) + second
59+
end

spec/expiry_spec.lua

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,37 @@ describe("expiry", function()
88
before_each(server.stop)
99
after_each(server.stop)
1010

11+
it("stores the expiry date on issuance", function()
12+
server.start()
13+
14+
local httpc = http.new()
15+
local _, connect_err = httpc:connect("127.0.0.1", 9443)
16+
assert.equal(nil, connect_err)
17+
18+
local _, ssl_err = httpc:ssl_handshake(nil, server.ngrok_hostname, true)
19+
assert.equal(nil, ssl_err)
20+
21+
local res, request_err = httpc:request({ path = "/foo" })
22+
assert.equal(nil, request_err)
23+
assert.equal(200, res.status)
24+
25+
local body, body_err = res:read_body()
26+
assert.equal(nil, body_err)
27+
assert.equal("foo", body)
28+
29+
local error_log = server.nginx_error_log_tail:read()
30+
assert.matches("issuing new certificate for " .. server.ngrok_hostname, error_log, nil, true)
31+
assert.Not.matches("auto-ssl: checking certificate renewals for " .. server.ngrok_hostname, error_log, nil, true)
32+
assert.Not.matches("failed to get the expiry date", error_log, nil, true)
33+
34+
local cert_path = server.current_test_dir .. "/auto-ssl/storage/file/" .. ngx.escape_uri(server.ngrok_hostname .. ":latest")
35+
local content = assert(file.read(cert_path))
36+
assert.string(content)
37+
local data = assert(cjson.decode(content))
38+
assert.number(data["expiry"])
39+
assert(data["expiry"] > 0, data["expiry"] .. " is not greater than 0")
40+
end)
41+
1142
it("fills in missing expiry dates in storage from certificate expiration on renewal", function()
1243
server.start({
1344
auto_ssl_pre_new = [[

spec/parse_openssl_time_spec.lua

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
local parse_openssl_time = require "resty.auto-ssl.utils.parse_openssl_time"
2+
local stdlib = require "posix.stdlib"
3+
4+
describe("parse_openssl_time", function()
5+
local orig_tz
6+
before_each(function()
7+
orig_tz = os.getenv("TZ")
8+
end)
9+
after_each(function()
10+
stdlib.setenv("TZ", orig_tz)
11+
end)
12+
13+
it("parses basic openssl time", function()
14+
local timestamp, err = parse_openssl_time("Jun 22 00:00:00 2036 GMT")
15+
assert.Nil(err)
16+
assert.equal(2097705600, timestamp)
17+
end)
18+
19+
it("parses single digit days", function()
20+
local timestamp, err = parse_openssl_time("Mar 7 12:00:00 2020 GMT")
21+
assert.Nil(err)
22+
assert.equal(1583582400, timestamp)
23+
end)
24+
25+
it("parses prefixed output from openssl", function()
26+
local timestamp, err = parse_openssl_time("notAfter=Dec 17 17:55:50 2019 GMT")
27+
assert.Nil(err)
28+
assert.equal(1576605350, timestamp)
29+
end)
30+
31+
it("parses times with milliseconds", function()
32+
local timestamp, err = parse_openssl_time("Jul 31 22:20:50.123 2017 GMT")
33+
assert.Nil(err)
34+
assert.equal(1501539650, timestamp)
35+
end)
36+
37+
it("parses times with 1 fractional digit for seconds", function()
38+
local timestamp, err = parse_openssl_time("Jul 31 22:20:50.1 2017 GMT")
39+
assert.Nil(err)
40+
assert.equal(1501539650, timestamp)
41+
end)
42+
43+
it("parses times without GMT suffix", function()
44+
local timestamp, err = parse_openssl_time("Nov 28 20:21:47 2019")
45+
assert.Nil(err)
46+
assert.equal(1574972507, timestamp)
47+
end)
48+
49+
it("returns error for unknown data", function()
50+
local timestamp, err = parse_openssl_time("Bad time value")
51+
assert.Nil(timestamp)
52+
assert.equal("could not parse openssl time string: Bad time value", err)
53+
end)
54+
55+
it("returns error for unknown month", function()
56+
local timestamp, err = parse_openssl_time("Abc 22 00:00:00 2036 GMT")
57+
assert.Nil(timestamp)
58+
assert.equal("could not parse month in openssl time string: Abc 22 00:00:00 2036 GMT", err)
59+
end)
60+
61+
it("months are case sensitive", function()
62+
local timestamp, err = parse_openssl_time("jan 22 00:00:00 2036 GMT")
63+
assert.Nil(timestamp)
64+
assert.equal("could not parse month in openssl time string: jan 22 00:00:00 2036 GMT", err)
65+
end)
66+
67+
it("ignores the system time zone and always outputs in UTC unix timestamps", function()
68+
stdlib.setenv("TZ", "Pacific/Honolulu")
69+
-- Sanity check to ensure "os.time" behavior is picking up the TZ that is
70+
-- set (which is incorrect for our purposes), and that our values differ.
71+
local local_time = os.time({
72+
year = 2036,
73+
month = 6,
74+
day = 22,
75+
hour = 0,
76+
min = 0,
77+
sec = 0,
78+
})
79+
assert.equal(2097741600, local_time)
80+
local timestamp, err = parse_openssl_time("Jun 22 00:00:00 2036 GMT")
81+
assert.Nil(err)
82+
assert.equal(2097705600, timestamp)
83+
assert.Not.equal(timestamp, local_time)
84+
85+
stdlib.setenv("TZ", "Asia/Kolkata")
86+
local_time = os.time({
87+
year = 2036,
88+
month = 6,
89+
day = 22,
90+
hour = 0,
91+
min = 0,
92+
sec = 0,
93+
})
94+
assert.equal(2097685800, local_time)
95+
timestamp, err = parse_openssl_time("Jun 22 00:00:00 2036 GMT")
96+
assert.Nil(err)
97+
assert.equal(2097705600, timestamp)
98+
assert.Not.equal(timestamp, local_time)
99+
end)
100+
101+
-- Based on the eras from
102+
-- http://howardhinnant.github.io/date_algorithms.html#civil_from_days, along
103+
-- with other boundaries (epoch, Y2038, etc).
104+
it("parses various historical, future, and boundary times", function()
105+
local timestamp, err = parse_openssl_time("Mar 1 00:00:00 -0800 GMT")
106+
assert.Nil(err)
107+
assert.equal(-87407596800, timestamp)
108+
109+
timestamp, err = parse_openssl_time("Feb 29 00:00:00 -0400 GMT")
110+
assert.Nil(err)
111+
assert.equal(-74784902400, timestamp)
112+
113+
timestamp, err = parse_openssl_time("Mar 1 00:00:00 -0400 GMT")
114+
assert.Nil(err)
115+
assert.equal(-74784816000, timestamp)
116+
117+
timestamp, err = parse_openssl_time("Jan 1 00:00:00 0000 GMT")
118+
assert.Nil(err)
119+
assert.equal(-62167219200, timestamp)
120+
121+
timestamp, err = parse_openssl_time("Feb 29 00:00:00 0000 GMT")
122+
assert.Nil(err)
123+
assert.equal(-62162121600, timestamp)
124+
125+
timestamp, err = parse_openssl_time("Mar 1 00:00:00 0000 GMT")
126+
assert.Nil(err)
127+
assert.equal(-62162035200, timestamp)
128+
129+
timestamp, err = parse_openssl_time("Feb 29 00:00:00 0400 GMT")
130+
assert.Nil(err)
131+
assert.equal(-49539340800, timestamp)
132+
133+
timestamp, err = parse_openssl_time("Mar 1 00:00:00 0400 GMT")
134+
assert.Nil(err)
135+
assert.equal(-49539254400, timestamp)
136+
137+
timestamp, err = parse_openssl_time("Feb 29 00:00:00 0800 GMT")
138+
assert.Nil(err)
139+
assert.equal(-36916560000, timestamp)
140+
141+
timestamp, err = parse_openssl_time("Mar 1 00:00:00 0800 GMT")
142+
assert.Nil(err)
143+
assert.equal(-36916473600, timestamp)
144+
145+
timestamp, err = parse_openssl_time("Feb 29 00:00:00 1200 GMT")
146+
assert.Nil(err)
147+
assert.equal(-24293779200, timestamp)
148+
149+
timestamp, err = parse_openssl_time("Mar 1 00:00:00 1200 GMT")
150+
assert.Nil(err)
151+
assert.equal(-24293692800, timestamp)
152+
153+
timestamp, err = parse_openssl_time("Feb 29 00:00:00 1600 GMT")
154+
assert.Nil(err)
155+
assert.equal(-11670998400, timestamp)
156+
157+
timestamp, err = parse_openssl_time("Mar 1 00:00:00 1600 GMT")
158+
assert.Nil(err)
159+
assert.equal(-11670912000, timestamp)
160+
161+
timestamp, err = parse_openssl_time("Jan 1 00:00:00 1970 GMT")
162+
assert.Nil(err)
163+
assert.equal(0, timestamp)
164+
165+
timestamp, err = parse_openssl_time("Feb 29 00:00:00 2000 GMT")
166+
assert.Nil(err)
167+
assert.equal(951782400, timestamp)
168+
169+
timestamp, err = parse_openssl_time("Mar 1 00:00:00 2000 GMT")
170+
assert.Nil(err)
171+
assert.equal(951868800, timestamp)
172+
173+
timestamp, err = parse_openssl_time("Jan 18 00:00:00 2038 GMT")
174+
assert.Nil(err)
175+
assert.equal(2147385600, timestamp)
176+
177+
timestamp, err = parse_openssl_time("Jan 19 00:00:00 2038 GMT")
178+
assert.Nil(err)
179+
assert.equal(2147472000, timestamp)
180+
181+
timestamp, err = parse_openssl_time("Jan 20 00:00:00 2038 GMT")
182+
assert.Nil(err)
183+
assert.equal(2147558400, timestamp)
184+
185+
timestamp, err = parse_openssl_time("Feb 29 00:00:00 2400 GMT")
186+
assert.Nil(err)
187+
assert.equal(13574563200, timestamp)
188+
end)
189+
end)

0 commit comments

Comments
 (0)