-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgithub_access.lua
More file actions
278 lines (232 loc) · 8.67 KB
/
github_access.lua
File metadata and controls
278 lines (232 loc) · 8.67 KB
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
--
--
-- Verify throw basic auth, the user is in the github company
--
--
local github_api_enabled = os.getenv('GITHUB_API_ENABLED')
local github_api_company = os.getenv('GITHUB_API_COMPANY')
local env_github_restriction_upload = os.getenv('GITHUB_USER_ALLOWED_UPLOAD')
local github_auth_cache_dir = "/data/nginx/artifacts_github_auth_cache"
local github_restriction_users = {}
local github_restriction_paths = { "/upload/", "/copy/", "/version/", "/add_metadata/" }
local bot_username = os.getenv('BOT_USERNAME')
local bot_token = os.getenv('BOT_TOKEN')
local local_bot_creds_enabled = false
local error_message = '<br/><h2>You are not allowed to connect to artifacts, for more information on how to connect, check the <a href=https://devdocs.scality.net/support/faq/#artifacts>documentation</a><h2>'
-- Set default values if needed
--
if github_api_enabled == nil then
github_api_enabled = "true"
end
if github_api_company == nil then
github_api_company = "scality"
end
-- Feed github_restriction_users if neeeded
--
if env_github_restriction_upload ~= nil then
for allowed_user in env_github_restriction_upload:gmatch("([^,]+)") do
table.insert(github_restriction_users, allowed_user)
end
end
-- Detect if we have to use local bot creds
--
if bot_username ~= nil and bot_username ~= "" and bot_token ~= nil and bot_token ~= "" then
local_bot_creds_enabled = true
end
function wrong_credentials()
ngx.header.content_type = 'text/plain'
ngx.header['WWW-Authenticate'] = 'Basic realm="Access to the Scality Artifacts", charset="UTF-8"'
ngx.status = ngx.HTTP_UNAUTHORIZED
ngx.header["Content-Type"] = "text/html"
ngx.say('<html><head><title>401 Not Authorized</title></head><body><center><h1>401 Not Authorized</h1></center><hr><center>nginx'.. error_message .. '</center></body><html>')
return ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
function not_allowed()
ngx.header.content_type = 'text/plain'
ngx.status = ngx.HTTP_FORBIDDEN
ngx.header["Content-Type"] = "text/html"
ngx.say('<html><head><title>403 Forbidden</title></head><body><center><h1>403 Forbidden</h1></center><hr><center>nginx'.. error_message .. '</center></body><html>')
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
function read_cache(auth_md5)
local path = github_auth_cache_dir .. "/" .. auth_md5
local file = io.open(path, "rb")
if not file then
return nil
end
local content = file:read("*a")
file:close()
return content
end
function update_cache(auth_md5, status)
local path = github_auth_cache_dir .. "/" .. auth_md5
local cmd = io.popen("mktemp -p " .. github_auth_cache_dir .. " tmp_XXXXXXXXX")
local path_orig = cmd:read()
cmd:close()
if path_orig == nil then
return nil
end
local file = io.open(path_orig, "wb")
if not file then
return nil
end
file:write(status)
file:close()
local res = os.rename(path_orig, path)
if res == nil then
os.remove(path_orig)
return nil
end
end
function lock_cache(auth_md5, username)
local path = github_auth_cache_dir .. "/" .. auth_md5 .. ".lock"
local status = os.execute("mkdir " .. path .. " 2> /dev/null")
-- In OpenResty 1.29.x / LuaJIT with Lua 5.2 semantics, os.execute returns
-- true on success and nil on failure (not integer exit codes as in Lua 5.1).
-- The original check `status ~= 0` is always true in both cases under Lua 5.2,
-- so we need to check for the boolean true to detect a successful mkdir.
local lock_created = (status == true) or (status == 0)
if not lock_created then
-- Someone else is already in the process of updating this cache entry.
-- Wait for the entry to be there.
ngx.log(ngx.DEBUG, "cache write already locked for " .. username .. " (" .. auth_md5 .. "), waiting")
while read_cache(auth_md5) == nil do
ngx.sleep(0.1)
end
ngx.log(ngx.DEBUG, "cache for " .. username .. " (" .. auth_md5 .. ") ready for read, wait is over")
else
ngx.log(ngx.DEBUG, "cache write locked for " .. username .. " (" .. auth_md5 .. ")")
end
end
function unlock_cache(auth_md5, username)
local path = github_auth_cache_dir .. "/" .. auth_md5 .. ".lock"
os.execute("rmdir " .. path .. " 2> /dev/null")
ngx.log(ngx.DEBUG, "cache write unlocked for " .. username .. " (" .. auth_md5 .. ")")
end
function check_github(auth_md5, username)
ngx.log(ngx.STDERR, "checking github for " .. username .. " (" .. auth_md5 .. ")")
-- authenticate
--
local res = ngx.location.capture("/force_github_request/user")
local username_from_token = nil
if res.status == 200 then
username_from_token = string.match(res.body, '"login": "([^"]+)",')
end
if res.status ~= 200 or username ~= username_from_token then
ngx.log(ngx.STDERR, "authentication failed for " .. username .. " (" .. res.status .. ")")
return "FORBIDDEN"
end
-- authorize
--
local res = ngx.location.capture("/force_github_request/orgs/" .. github_api_company .. "/members/" .. username)
if res.status ~= 204 then
ngx.log(ngx.STDERR, "authorization failed for " .. username .. " (" .. res.status .. ")")
return "FORBIDDEN"
end
return "GRANTED"
end
function authenticate_and_authorize(auth)
local divider = auth:find(':')
local username = auth:sub(0, divider-1)
local token = auth:sub(divider+1)
local auth_md5 = ngx.md5(auth)
if local_bot_creds_enabled == true and username == bot_username then
if token == bot_token then
return true
end
ngx.log(ngx.STDERR, '\nUser ' .. username .. ' not allowed (forbidden by local bot creds)\n')
return false
end
local cache_hit = false
local cached_status = read_cache(auth_md5)
if cached_status == nil then
-- No entry found in cache, do a cache write lock for this entry
--
lock_cache(auth_md5, username)
-- Check if the cache has been populated in the meantime
--
cached_status = read_cache(auth_md5)
if cached_status == nil then
-- Still no cache entry, make a github request
--
local status = check_github(auth_md5, username)
-- Update cache
--
update_cache(auth_md5, status)
cached_status = read_cache(auth_md5)
else
cache_hit = true
end
-- Unlock the cache entry
--
unlock_cache(auth_md5, username)
else
cache_hit = true
end
if cached_status ~= "GRANTED" then
if cache_hit == true then
ngx.log(ngx.STDERR, '\nUser ' .. username .. ' not allowed (github auth cache HIT)\n')
else
ngx.log(ngx.STDERR, '\nUser ' .. username .. ' not allowed (github auth cache MISS)\n')
end
return false
end
return true
end
function verify_header()
-- Test Authentication header is set and with a value
local header = ngx.req.get_headers()['Authorization']
if header == nil or header:find(" ") == nil then
return false
end
local divider = header:find(' ')
if header:sub(0, divider-1) ~= 'Basic' then
return false
end
local auth = ngx.decode_base64(header:sub(divider+1))
if auth == nil or auth:find(':') == nil then
return false
end
return auth
end
function restriction_check(auth)
local divider = auth:find(':')
local username = auth:sub(0, divider-1)
local location = nil
for _,v in pairs(github_restriction_paths) do
if string.sub(ngx.var.request_uri, 1,string.len(v)) == v then
location = v
break
end
end
if location == nil then
return true
end
for _,v in pairs(github_restriction_users) do
if v == username then
return true
end
end
ngx.log(ngx.STDERR, 'User ' .. username .. ' not allowed for restricted access to ' .. location)
return false
end
-- Skip auth for the internal github API proxy location to prevent deadlock:
-- ngx.location.capture("/force_github_request/...") triggers this access_by_lua_file
-- again, and the cache lock mechanism would deadlock since the parent request holds
-- the lock while waiting for the subrequest to complete.
if string.sub(ngx.var.uri, 1, 21) == "/force_github_request" then
return
end
if github_api_enabled == 'true' and ngx.var.remote_addr ~= '127.0.0.1' then
local auth = verify_header()
if not auth then
return wrong_credentials()
end
local user = authenticate_and_authorize(auth)
if not user then
return not_allowed()
end
if not restriction_check(auth) then
return not_allowed()
end
end