Skip to content

Commit 93c2121

Browse files
authored
feat(storage/vault) add support for kubernetes auth (#37)
1 parent c0fac32 commit 93c2121

File tree

5 files changed

+171
-3
lines changed

5 files changed

+171
-3
lines changed

.github/workflows/tests.yml

+7
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ jobs:
7474
docker logs vault
7575
docker run -d -v /usr/share/ca-certificates/:/etc/ssl/certs -p 4001:4001 -p 2380:2380 -p 2379:2379 --name etcd quay.io/coreos/etcd:v2.3.8 -name etcd0 -advertise-client-urls http://${HostIP}:2379,http://${HostIP}:4001 -listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 -initial-advertise-peer-urls http://${HostIP}:2380 -listen-peer-urls http://0.0.0.0:2380 -initial-cluster-token etcd-cluster-1 -initial-cluster etcd0=http://${HostIP}:2380 -initial-cluster-state new
7676
docker logs etcd
77+
78+
- name: Prepare vault for JWT auth
79+
run: |
80+
curl 'https://localhost:8210/v1/sys/auth/kubernetes.test' -k -X POST -H 'X-Vault-Token: root' -H 'Content-Type: application/json; charset=utf-8' --data-raw '{"path":"kubernetes.test","type":"jwt","config":{}}'
81+
curl 'https://localhost:8210/v1/auth/kubernetes.test/config' -k -X PUT -H 'X-Vault-Token: root' -H 'content-type: application/json; charset=utf-8' --data-raw '{"jwt_validation_pubkeys":["-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtMCbmrsltFKqStOoxl8V\nK5ZlrIMb8d+W62yoXW1DKdg+cPNq0vGD94cxl9NjjRzlSR/NVZq6Q34c1lkbenPw\nf3CYfmbQupOKTJKhBdn9sFCCbW0gi6gQv0BaU3Pa8iGfVcZPctAtdbwmNKVd26hW\nmvnoJYhyewhY+j3ooLdnmh55cZU9w1VO0PaSf2zGSmCUeIao77jWcnkEauK2RrYv\nq5yB6w54Q71+lp2jZil9e4IJP/WqcS1CtmKgiWLoZuWNJXDWaa8LbcgQfsxudn3X\nsgHaYnAdZJOaCsDS/ablKmUOLIiI3TBM6dkUlBUMK9OgAsu+wBdX521rK3u+NNVX\n3wIDAQAB\n-----END PUBLIC KEY-----"],"default_role":"root","namespace_in_state":false,"provider_config":{}}'
82+
curl 'https://localhost:8210/v1/auth/kubernetes.test/role/root' -k -X POST -H 'X-Vault-Token: root' -H 'content-type: application/json; charset=utf-8' --data-raw '{"token_policies":["acme"],"role_type":"jwt","user_claim":"kubernetes.io/serviceaccount/service-account.uid","bound_subject":"system:serviceaccount:kong:gateway-kong"}'
83+
curl 'https://localhost:8210/v1/sys/policies/acl/acme' -k -X PUT -H 'X-Vault-Token: root' -H 'Content-Type: application/json; charset=utf-8' --data-raw '{"name":"acme","policy":"path \"secret/*\" {\n capabilities = [\"create\", \"read\", \"update\", \"delete\"]\n}"}'
7784
7885
- name: Setup tools
7986
run: |

README.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -553,8 +553,6 @@ storage_config = {
553553
port = 8200,
554554
-- secrets kv prefix path
555555
kv_path = "acme",
556-
-- Vault token
557-
token = nil,
558556
-- timeout in ms
559557
timeout = 2000,
560558
-- use HTTPS
@@ -563,6 +561,16 @@ storage_config = {
563561
tls_verify = true
564562
-- SNI used in request, default to host if omitted
565563
tls_server_name = nil,
564+
-- Auth Method, default to token, can be "token" or "kubernetes"
565+
auth_method = "token"
566+
-- Vault token
567+
token = nil,
568+
-- Vault's authentication path to use
569+
auth_path = "kubernetes",
570+
-- The role to try and assign
571+
auth_role = nil,
572+
-- The path to the JWT
573+
jwt_path = "/var/run/secrets/kubernetes.io/serviceaccount/token",
566574
}
567575
```
568576

lib/resty/acme/storage/vault.lua

+45-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ local cjson = require "cjson.safe"
33

44
local _M = {}
55
local mt = {__index = _M}
6+
local auth
67

78
local function valid_vault_key(key)
89
local newstr, _ = ngx.re.gsub(key, [=[[/]]=], "-")
@@ -37,6 +38,10 @@ function _M.new(conf)
3738
{
3839
host = conf.host or "127.0.0.1",
3940
port = conf.port or 8200,
41+
auth_method = string.lower(conf.auth_method or "token"),
42+
auth_path = conf.auth_path or "kubernetes",
43+
auth_role = conf.auth_role,
44+
jwt_path = conf.jwt_path or "/var/run/secrets/kubernetes.io/serviceaccount/token",
4045
https = conf.https,
4146
tls_verify = tls_verify,
4247
tls_server_name = conf.tls_server_name,
@@ -47,9 +52,16 @@ function _M.new(conf)
4752
mt
4853
)
4954

55+
local token, err = auth(self, conf)
56+
57+
if err then
58+
return nil, err
59+
end
60+
5061
self.headers = {
51-
["X-Vault-Token"] = conf.token,
62+
["X-Vault-Token"] = token
5263
}
64+
5365
if self.https then
5466
if not self.tls_server_name then
5567
self.tls_server_name = self.host
@@ -114,6 +126,38 @@ local function api(self, method, uri, payload)
114126
return decoded, err
115127
end
116128

129+
function auth(self, conf)
130+
if self.auth_method == "token" then
131+
return conf.token, nil
132+
elseif self.auth_method ~= "kubernetes" then
133+
return nil, "Unknown authentication method"
134+
end
135+
136+
local file, err = io.open(self.jwt_path, "r")
137+
138+
if err then
139+
return nil, err
140+
end
141+
142+
local token = file:read("*all")
143+
file:close()
144+
145+
local response, err = api(self, "POST", "/v1/auth/" .. self.auth_path .. "/login", {
146+
role = self.auth_role,
147+
jwt = token
148+
})
149+
150+
if err then
151+
return nil, err
152+
end
153+
154+
if not response["auth"] or not response["auth"]["client_token"] then
155+
return nil, "Could not authenticate"
156+
end
157+
158+
return response["auth"]["client_token"]
159+
end
160+
117161
local function set_cas(self, k, v, cas, ttl)
118162
if ttl then
119163
if ttl > 0 and ttl < 1 then

t/storage/serviceaccount.jwt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1MTYyMzkwMjIsImV4cCI6OTk5OTk5OTk5OSwiaXNzIjoia3ViZXJuZXRlcy9zZXJ2aWNlYWNjb3VudCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvbmFtZXNwYWNlIjoia29uZyIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJnYXRld2F5LWtvbmctdG9rZW4tNWw5YmIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZ2F0ZXdheS1rb25nIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiZWJkOTlkYmUtMWJlZC00NDlmLWIzNzktMjBmNWY1ZDY5MjJhIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omtvbmc6Z2F0ZXdheS1rb25nIn0.qitzPG8qtC-tTj4z6kdUo6g8sWO_94KIhFrafeuaziM3hdi_5bzKiCBKJkxiimN05VQ_4IoSgn0pZrwU8nwPu9Vzv9lRmI1guXiPifJeJzKu1vyZR_9OqnKb-RqRGlVmjibosuTfXe5de6MtCM5cm6NLfLUtVTeLRdFHkT4ZLvU1IlR0CnsD0szgjh8AjvJvXfWddvJ8EFShvlrsvCS_1SolTgF5Fkl4b7iQA5ToaOetC9Rq6XQNp2Qp2-Vw5pCIJ6kpAtU7FNc9Ufq6tuVIqvBxDDpanKIQJ5j_90Ytlh-xOlug8ObaRN2UqRZdogMPHsrb36U3iTnr7gaNuraIzw

t/storage/vault_kubernetes.t

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# vim:set ft= ts=4 sw=4 et fdm=marker:
2+
3+
use Test::Nginx::Socket::Lua 'no_plan';
4+
use Cwd qw(cwd);
5+
6+
7+
my $pwd = cwd();
8+
9+
our $HttpConfig = qq{
10+
lua_package_path "$pwd/lib/?.lua;$pwd/lib/?/init.lua;$pwd/../lib/?.lua;$pwd/../lib/?/init.lua;;";
11+
init_by_lua_block {
12+
_G.test_lib = require("resty.acme.storage.vault")
13+
}
14+
};
15+
16+
17+
run_tests();
18+
19+
__DATA__
20+
=== TEST 1: Vault authentication failed if no token or jwt provided
21+
--- http_config eval: $::HttpConfig
22+
--- config
23+
location =/t {
24+
content_by_lua_block {
25+
local st = test_lib.new({
26+
https = true,
27+
tls_verify = false,
28+
port = 8210,
29+
kv_path = "secret/acme",
30+
})
31+
local err = st:set("keyssl1", "1")
32+
ngx.say(err)
33+
}
34+
}
35+
--- request
36+
GET /t
37+
--- response_body_like eval
38+
"errors from vault: \\[\"missing client token\"\\]
39+
"
40+
--- no_error_log
41+
[error]
42+
43+
44+
=== TEST 2: Vault authenticate using kubernetes
45+
--- http_config eval: $::HttpConfig
46+
--- config
47+
location =/t {
48+
content_by_lua_block {
49+
local st = test_lib.new({
50+
https = true,
51+
tls_verify = false,
52+
auth_method = "kubernetes",
53+
auth_path = "kubernetes.test",
54+
jwt_path = "t/storage/serviceaccount.jwt",
55+
auth_role = "root",
56+
port = 8210,
57+
kv_path = "secret/acme",
58+
})
59+
local err = st:set("keyssl2", "2")
60+
ngx.say(err)
61+
local v, err = st:get("keyssl2")
62+
ngx.say(err)
63+
ngx.say(v)
64+
}
65+
}
66+
--- request
67+
GET /t
68+
--- response_body_like eval
69+
"nil
70+
nil
71+
2
72+
"
73+
--- no_error_log
74+
[error]
75+
76+
77+
=== TEST 3: Vault authenticate using kubernetes (case-insensitivity test)
78+
--- http_config eval: $::HttpConfig
79+
--- config
80+
location =/t {
81+
content_by_lua_block {
82+
local st = test_lib.new({
83+
https = true,
84+
tls_verify = false,
85+
auth_method = "KuBeRnEtEs",
86+
auth_path = "kubernetes.test",
87+
jwt_path = "t/storage/serviceaccount.jwt",
88+
auth_role = "root",
89+
port = 8210,
90+
kv_path = "secret/acme",
91+
})
92+
local err = st:set("keyssl3", "3")
93+
ngx.say(err)
94+
local v, err = st:get("keyssl3")
95+
ngx.say(err)
96+
ngx.say(v)
97+
}
98+
}
99+
--- request
100+
GET /t
101+
--- response_body_like eval
102+
"nil
103+
nil
104+
3
105+
"
106+
--- no_error_log
107+
[error]
108+

0 commit comments

Comments
 (0)