Skip to content

Commit afce218

Browse files
authored
Merge pull request #433 from Subv/oidc
Add the ability to secure proxy hosts with OpenID Connect
2 parents 7599617 + 9ee912b commit afce218

File tree

15 files changed

+495
-5
lines changed

15 files changed

+495
-5
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const migrate_name = 'openid_connect';
2+
const logger = require('../logger').migrate;
3+
4+
/**
5+
* Migrate
6+
*
7+
* @see http://knexjs.org/#Schema
8+
*
9+
* @param {Object} knex
10+
* @param {Promise} Promise
11+
* @returns {Promise}
12+
*/
13+
exports.up = function (knex/*, Promise*/) {
14+
logger.info('[' + migrate_name + '] Migrating Up...');
15+
16+
return knex.schema.table('proxy_host', function (proxy_host) {
17+
proxy_host.integer('openidc_enabled').notNull().unsigned().defaultTo(0);
18+
proxy_host.text('openidc_redirect_uri').notNull().defaultTo('');
19+
proxy_host.text('openidc_discovery').notNull().defaultTo('');
20+
proxy_host.text('openidc_auth_method').notNull().defaultTo('client_secret_post');
21+
proxy_host.text('openidc_client_id').notNull().defaultTo('');
22+
proxy_host.text('openidc_client_secret').notNull().defaultTo('');
23+
})
24+
.then(() => {
25+
logger.info('[' + migrate_name + '] proxy_host Table altered');
26+
});
27+
};
28+
29+
/**
30+
* Undo Migrate
31+
*
32+
* @param {Object} knex
33+
* @param {Promise} Promise
34+
* @returns {Promise}
35+
*/
36+
exports.down = function (knex/*, Promise*/) {
37+
return knex.schema.table('proxy_host', function (proxy_host) {
38+
proxy_host.dropColumn('openidc_enabled');
39+
proxy_host.dropColumn('openidc_redirect_uri');
40+
proxy_host.dropColumn('openidc_discovery');
41+
proxy_host.dropColumn('openidc_auth_method');
42+
proxy_host.dropColumn('openidc_client_id');
43+
proxy_host.dropColumn('openidc_client_secret');
44+
})
45+
.then(() => {
46+
logger.info('[' + migrate_name + '] proxy_host Table altered');
47+
});
48+
};
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const migrate_name = 'openid_allowed_users';
2+
const logger = require('../logger').migrate;
3+
4+
/**
5+
* Migrate
6+
*
7+
* @see http://knexjs.org/#Schema
8+
*
9+
* @param {Object} knex
10+
* @param {Promise} Promise
11+
* @returns {Promise}
12+
*/
13+
exports.up = function (knex/*, Promise*/) {
14+
logger.info('[' + migrate_name + '] Migrating Up...');
15+
16+
return knex.schema.table('proxy_host', function (proxy_host) {
17+
proxy_host.integer('openidc_restrict_users_enabled').notNull().unsigned().defaultTo(0);
18+
proxy_host.json('openidc_allowed_users').notNull().defaultTo([]);
19+
})
20+
.then(() => {
21+
logger.info('[' + migrate_name + '] proxy_host Table altered');
22+
});
23+
};
24+
25+
/**
26+
* Undo Migrate
27+
*
28+
* @param {Object} knex
29+
* @param {Promise} Promise
30+
* @returns {Promise}
31+
*/
32+
exports.down = function (knex/*, Promise*/) {
33+
return knex.schema.table('proxy_host', function (proxy_host) {
34+
proxy_host.dropColumn('openidc_restrict_users_enabled');
35+
proxy_host.dropColumn('openidc_allowed_users');
36+
})
37+
.then(() => {
38+
logger.info('[' + migrate_name + '] proxy_host Table altered');
39+
});
40+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const migrate_name = 'openid_default_values';
2+
const logger = require('../logger').migrate;
3+
4+
/**
5+
* Migrate
6+
*
7+
* @see http://knexjs.org/#Schema
8+
*
9+
* @param {Object} knex
10+
* @param {Promise} Promise
11+
* @returns {Promise}
12+
*/
13+
exports.up = function (knex/*, Promise*/) {
14+
logger.info('[' + migrate_name + '] Migrating Up...');
15+
16+
return knex.schema.raw('ALTER TABLE proxy_host ALTER openidc_redirect_uri SET DEFAULT \'\'')
17+
.then(() => knex.schema.raw('ALTER TABLE proxy_host ALTER openidc_discovery SET DEFAULT \'\''))
18+
.then(() => knex.schema.raw('ALTER TABLE proxy_host ALTER openidc_auth_method SET DEFAULT \'client_secret_post\''))
19+
.then(() => knex.schema.raw('ALTER TABLE proxy_host ALTER openidc_client_id SET DEFAULT \'\''))
20+
.then(() => knex.schema.raw('ALTER TABLE proxy_host ALTER openidc_client_secret SET DEFAULT \'\''))
21+
.then(() => {
22+
logger.info('[' + migrate_name + '] proxy_host Table altered');
23+
});
24+
};
25+
26+
/**
27+
* Undo Migrate
28+
*
29+
* @param {Object} knex
30+
* @param {Promise} Promise
31+
* @returns {Promise}
32+
*/
33+
exports.down = function (knex, Promise) {
34+
logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
35+
return Promise.resolve(true);
36+
};

backend/models/proxy_host.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,18 @@ class ProxyHost extends Model {
1919
this.domain_names = [];
2020
}
2121

22+
// Default for openidc_allowed_users
23+
if (typeof this.openidc_allowed_users === 'undefined') {
24+
this.openidc_allowed_users = [];
25+
}
26+
2227
// Default for meta
2328
if (typeof this.meta === 'undefined') {
2429
this.meta = {};
2530
}
2631

2732
this.domain_names.sort();
33+
this.openidc_allowed_users.sort();
2834
}
2935

3036
$beforeUpdate () {
@@ -34,6 +40,11 @@ class ProxyHost extends Model {
3440
if (typeof this.domain_names !== 'undefined') {
3541
this.domain_names.sort();
3642
}
43+
44+
// Sort openidc_allowed_users
45+
if (typeof this.openidc_allowed_users !== 'undefined') {
46+
this.openidc_allowed_users.sort();
47+
}
3748
}
3849

3950
static get name () {
@@ -45,7 +56,7 @@ class ProxyHost extends Model {
4556
}
4657

4758
static get jsonAttributes () {
48-
return ['domain_names', 'meta', 'locations'];
59+
return ['domain_names', 'meta', 'locations', 'openidc_allowed_users'];
4960
}
5061

5162
static get relationMappings () {

backend/schema/definitions.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,43 @@
222222
"description": "Should we cache assets",
223223
"example": true,
224224
"type": "boolean"
225+
},
226+
"openidc_enabled": {
227+
"description": "Is OpenID Connect authentication enabled",
228+
"example": true,
229+
"type": "boolean"
230+
},
231+
"openidc_redirect_uri": {
232+
"type": "string"
233+
},
234+
"openidc_discovery": {
235+
"type": "string"
236+
},
237+
"openidc_auth_method": {
238+
"type": "string",
239+
"pattern": "^(client_secret_basic|client_secret_post)$"
240+
},
241+
"openidc_client_id": {
242+
"type": "string"
243+
},
244+
"openidc_client_secret": {
245+
"type": "string"
246+
},
247+
"openidc_restrict_users_enabled": {
248+
"description": "Only allow a specific set of OpenID Connect emails to access the resource",
249+
"example": true,
250+
"type": "boolean"
251+
},
252+
"openidc_allowed_users": {
253+
"type": "array",
254+
"minItems": 0,
255+
"items": {
256+
"type": "string",
257+
"description": "Email Address",
258+
"example": "john@example.com",
259+
"format": "email",
260+
"minLength": 1
261+
}
225262
}
226263
}
227264
}

backend/schema/endpoints/proxy-hosts.json

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,30 @@
6464
"advanced_config": {
6565
"type": "string"
6666
},
67+
"openidc_enabled": {
68+
"$ref": "../definitions.json#/definitions/openidc_enabled"
69+
},
70+
"openidc_redirect_uri": {
71+
"$ref": "../definitions.json#/definitions/openidc_redirect_uri"
72+
},
73+
"openidc_discovery": {
74+
"$ref": "../definitions.json#/definitions/openidc_discovery"
75+
},
76+
"openidc_auth_method": {
77+
"$ref": "../definitions.json#/definitions/openidc_auth_method"
78+
},
79+
"openidc_client_id": {
80+
"$ref": "../definitions.json#/definitions/openidc_client_id"
81+
},
82+
"openidc_client_secret": {
83+
"$ref": "../definitions.json#/definitions/openidc_client_secret"
84+
},
85+
"openidc_restrict_users_enabled": {
86+
"$ref": "../definitions.json#/definitions/openidc_restrict_users_enabled"
87+
},
88+
"openidc_allowed_users": {
89+
"$ref": "../definitions.json#/definitions/openidc_allowed_users"
90+
},
6791
"enabled": {
6892
"$ref": "../definitions.json#/definitions/enabled"
6993
},
@@ -161,6 +185,30 @@
161185
"advanced_config": {
162186
"$ref": "#/definitions/advanced_config"
163187
},
188+
"openidc_enabled": {
189+
"$ref": "#/definitions/openidc_enabled"
190+
},
191+
"openidc_redirect_uri": {
192+
"$ref": "#/definitions/openidc_redirect_uri"
193+
},
194+
"openidc_discovery": {
195+
"$ref": "#/definitions/openidc_discovery"
196+
},
197+
"openidc_auth_method": {
198+
"$ref": "#/definitions/openidc_auth_method"
199+
},
200+
"openidc_client_id": {
201+
"$ref": "#/definitions/openidc_client_id"
202+
},
203+
"openidc_client_secret": {
204+
"$ref": "#/definitions/openidc_client_secret"
205+
},
206+
"openidc_restrict_users_enabled": {
207+
"$ref": "#/definitions/openidc_restrict_users_enabled"
208+
},
209+
"openidc_allowed_users": {
210+
"$ref": "#/definitions/openidc_allowed_users"
211+
},
164212
"enabled": {
165213
"$ref": "#/definitions/enabled"
166214
},
@@ -251,6 +299,30 @@
251299
"advanced_config": {
252300
"$ref": "#/definitions/advanced_config"
253301
},
302+
"openidc_enabled": {
303+
"$ref": "#/definitions/openidc_enabled"
304+
},
305+
"openidc_redirect_uri": {
306+
"$ref": "#/definitions/openidc_redirect_uri"
307+
},
308+
"openidc_discovery": {
309+
"$ref": "#/definitions/openidc_discovery"
310+
},
311+
"openidc_auth_method": {
312+
"$ref": "#/definitions/openidc_auth_method"
313+
},
314+
"openidc_client_id": {
315+
"$ref": "#/definitions/openidc_client_id"
316+
},
317+
"openidc_client_secret": {
318+
"$ref": "#/definitions/openidc_client_secret"
319+
},
320+
"openidc_restrict_users_enabled": {
321+
"$ref": "#/definitions/openidc_restrict_users_enabled"
322+
},
323+
"openidc_allowed_users": {
324+
"$ref": "#/definitions/openidc_allowed_users"
325+
},
254326
"enabled": {
255327
"$ref": "#/definitions/enabled"
256328
},
@@ -324,6 +396,30 @@
324396
"advanced_config": {
325397
"$ref": "#/definitions/advanced_config"
326398
},
399+
"openidc_enabled": {
400+
"$ref": "#/definitions/openidc_enabled"
401+
},
402+
"openidc_redirect_uri": {
403+
"$ref": "#/definitions/openidc_redirect_uri"
404+
},
405+
"openidc_discovery": {
406+
"$ref": "#/definitions/openidc_discovery"
407+
},
408+
"openidc_auth_method": {
409+
"$ref": "#/definitions/openidc_auth_method"
410+
},
411+
"openidc_client_id": {
412+
"$ref": "#/definitions/openidc_client_id"
413+
},
414+
"openidc_client_secret": {
415+
"$ref": "#/definitions/openidc_client_secret"
416+
},
417+
"openidc_restrict_users_enabled": {
418+
"$ref": "#/definitions/openidc_restrict_users_enabled"
419+
},
420+
"openidc_allowed_users": {
421+
"$ref": "#/definitions/openidc_allowed_users"
422+
},
327423
"enabled": {
328424
"$ref": "#/definitions/enabled"
329425
},
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{% if openidc_enabled == 1 or openidc_enabled == true -%}
2+
access_by_lua_block {
3+
local openidc = require("resty.openidc")
4+
local opts = {
5+
redirect_uri = "{{- openidc_redirect_uri -}}",
6+
discovery = "{{- openidc_discovery -}}",
7+
token_endpoint_auth_method = "{{- openidc_auth_method -}}",
8+
client_id = "{{- openidc_client_id -}}",
9+
client_secret = "{{- openidc_client_secret -}}",
10+
scope = "openid email profile"
11+
}
12+
13+
local res, err = openidc.authenticate(opts)
14+
15+
if err then
16+
ngx.status = 500
17+
ngx.say(err)
18+
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
19+
end
20+
21+
{% if openidc_restrict_users_enabled == 1 or openidc_restrict_users_enabled == true -%}
22+
local function contains(table, val)
23+
for i=1,#table do
24+
if table[i] == val then
25+
return true
26+
end
27+
end
28+
return false
29+
end
30+
31+
local allowed_users = {
32+
{% for user in openidc_allowed_users %}
33+
"{{ user }}",
34+
{% endfor %}
35+
}
36+
37+
if not contains(allowed_users, res.id_token.email) then
38+
ngx.exit(ngx.HTTP_FORBIDDEN)
39+
end
40+
{% endif -%}
41+
42+
43+
ngx.req.set_header("X-OIDC-SUB", res.id_token.sub)
44+
ngx.req.set_header("X-OIDC-EMAIL", res.id_token.email)
45+
ngx.req.set_header("X-OIDC-NAME", res.id_token.name)
46+
}
47+
{% endif %}

backend/templates/proxy_host.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ server {
3939

4040
{% endif %}
4141

42+
{% include "_openid_connect.conf" %}
43+
4244
{% include "_forced_ssl.conf" %}
4345
{% include "_hsts.conf" %}
4446

0 commit comments

Comments
 (0)