Add support for sudo
This commit is contained in:
parent
3d2c4c0fec
commit
943c30fa96
117
app/ldap/controllers/Sudo.controller.js
Normal file
117
app/ldap/controllers/Sudo.controller.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
const LDAPController = require('./LDAPController')
|
||||||
|
const LDAP = require('ldapjs')
|
||||||
|
|
||||||
|
class SudoController extends LDAPController {
|
||||||
|
static get services() {
|
||||||
|
return [
|
||||||
|
...super.services,
|
||||||
|
'output',
|
||||||
|
'ldap_server',
|
||||||
|
'models',
|
||||||
|
'configs',
|
||||||
|
'auth'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.Group = this.models.get('auth:Group')
|
||||||
|
this.User = this.models.get('auth:User')
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO flitter-orm chunk query
|
||||||
|
// TODO generalize scoped search logic
|
||||||
|
async search_sudo(req, res, next) {
|
||||||
|
if ( !req.user.can('ldap:search:sudo') ) {
|
||||||
|
return next(new LDAP.InsufficientAccessRightsError())
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( req.scope === 'base' ) {
|
||||||
|
// If scope is base, check if the base DN matches the filter.
|
||||||
|
// If so, return it. Else, return empty.
|
||||||
|
this.output.debug(`Running base DN search for sudo with DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)
|
||||||
|
|
||||||
|
const user = await this.get_resource_from_dn(req.dn)
|
||||||
|
|
||||||
|
// Make sure the user is ldap visible && match the filter
|
||||||
|
if ( user && user.ldap_visible && req.filter.matches(await user.to_sudo()) ) {
|
||||||
|
|
||||||
|
// If so, send the object
|
||||||
|
res.send({
|
||||||
|
dn: user.sudo_dn.format(this.configs.get('ldap:server.format')),
|
||||||
|
attributes: await user.to_sudo(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if ( req.scope === 'one' ) {
|
||||||
|
// If scope is one, find all entries that are the immediate
|
||||||
|
// subordinates of the base DN that match the filter.
|
||||||
|
this.output.debug(`Running one DN search for sudo with DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)
|
||||||
|
|
||||||
|
// Fetch the LDAP-visible users
|
||||||
|
const users = await this.Group.sudo_directory()
|
||||||
|
for ( const user of users ) {
|
||||||
|
|
||||||
|
// Make sure the user os of the appropriate scope
|
||||||
|
if ( req.dn.equals(user.sudo_dn) || user.sudo_dn.parent().equals(req.dn) ) {
|
||||||
|
|
||||||
|
// Check if the filter matches
|
||||||
|
if ( req.filter.matches(await user.to_sudo()) ) {
|
||||||
|
|
||||||
|
// If so, send the object
|
||||||
|
res.send({
|
||||||
|
dn: user.sudo_dn.format(this.configs.get('ldap:server.format')),
|
||||||
|
attributes: await user.to_sudo(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if ( req.scope === 'sub' ) {
|
||||||
|
// If scope is sub, find all entries that are subordinates
|
||||||
|
// of the base DN at any level and match the filter.
|
||||||
|
this.output.debug(`Running sub DN search for sudo with DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)
|
||||||
|
|
||||||
|
// Fetch the users as LDAP objects
|
||||||
|
const users = await this.Group.sudo_directory()
|
||||||
|
for ( const user of users ) {
|
||||||
|
|
||||||
|
// Make sure the user is of appropriate scope
|
||||||
|
if ( req.dn.equals(user.sudo_dn) || req.dn.parentOf(user.sudo_dn) ) {
|
||||||
|
|
||||||
|
// Check if filter matches
|
||||||
|
if ( req.filter.matches(await user.to_sudo()) ) {
|
||||||
|
|
||||||
|
// If so, send the object
|
||||||
|
res.send({
|
||||||
|
dn: user.sudo_dn.format(this.configs.get('ldap:server.format')),
|
||||||
|
attributes: await user.to_sudo(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.output.error(`Attempted to perform LDAP search with invalid scope: ${req.scope}`)
|
||||||
|
return next(new LDAP.OtherError('Attempted to perform LDAP search with invalid scope.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end()
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
get_cn_from_dn(dn) {
|
||||||
|
try {
|
||||||
|
if ( typeof dn === 'string' ) dn = LDAP.parseDN(dn)
|
||||||
|
return dn.rdns[0].attrs.cn.value
|
||||||
|
} catch (e) { console.log('Error parsing CN from DN', e) }
|
||||||
|
}
|
||||||
|
|
||||||
|
async get_resource_from_dn(sudo_dn) {
|
||||||
|
const cn = this.get_cn_from_dn(sudo_dn)
|
||||||
|
if ( cn ) {
|
||||||
|
const user = this.User.findOne({uid: cn.substr(5), ldap_visible: true})
|
||||||
|
if ( user && (await user.has_sudo()) ) return user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exports = SudoController
|
28
app/ldap/routes/sudo.routes.js
Normal file
28
app/ldap/routes/sudo.routes.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const sudo_routes = {
|
||||||
|
|
||||||
|
prefix: false, // false | string
|
||||||
|
|
||||||
|
middleware: [
|
||||||
|
'Logger'
|
||||||
|
],
|
||||||
|
|
||||||
|
search: {
|
||||||
|
'ou=sudo': [
|
||||||
|
'ldap_middleware::BindUser',
|
||||||
|
'ldap_controller::Sudo.search_sudo',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
bind: {},
|
||||||
|
|
||||||
|
add: {},
|
||||||
|
|
||||||
|
del: {},
|
||||||
|
|
||||||
|
modify: {},
|
||||||
|
|
||||||
|
compare: {},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exports = sudo_routes
|
@ -59,6 +59,17 @@ class GroupModel extends Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async sudo_directory() {
|
||||||
|
const groups = await this.find({ ldap_visible: true, active: true, grants_sudo: true })
|
||||||
|
|
||||||
|
let users = []
|
||||||
|
for ( const group of groups ) {
|
||||||
|
users = [...users, ...(await group.users())]
|
||||||
|
}
|
||||||
|
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
|
||||||
static async ldap_directory() {
|
static async ldap_directory() {
|
||||||
const User = this.prototype.models.get('auth:User')
|
const User = this.prototype.models.get('auth:User')
|
||||||
const groups = await this.find({ ldap_visible: true, active: true })
|
const groups = await this.find({ ldap_visible: true, active: true })
|
||||||
|
@ -187,6 +187,23 @@ class User extends AuthUser {
|
|||||||
this.get_provider().logout(request)
|
this.get_provider().logout(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async has_sudo() {
|
||||||
|
const groups = await this.groups()
|
||||||
|
return groups.some(group => group.grants_sudo)
|
||||||
|
}
|
||||||
|
|
||||||
|
async to_sudo() {
|
||||||
|
return {
|
||||||
|
objectClass: ['sudoRole'],
|
||||||
|
objectclass: ['sudoRole'],
|
||||||
|
cn: `sudo_${this.uid.toLowerCase()}`,
|
||||||
|
sudoUser: this.uid.toLowerCase(),
|
||||||
|
sudoHost: 'ALL',
|
||||||
|
sudoRunAs: 'ALL',
|
||||||
|
sudoCommand: 'ALL',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async to_ldap(iam_targets = []) {
|
async to_ldap(iam_targets = []) {
|
||||||
const Policy = this.models.get('iam:Policy')
|
const Policy = this.models.get('iam:Policy')
|
||||||
|
|
||||||
@ -249,6 +266,10 @@ class User extends AuthUser {
|
|||||||
return LDAP.parseDN(`uid=${this.uid.toLowerCase()},${this.ldap_server.auth_dn().format(this.configs.get('ldap:server.format'))}`)
|
return LDAP.parseDN(`uid=${this.uid.toLowerCase()},${this.ldap_server.auth_dn().format(this.configs.get('ldap:server.format'))}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get sudo_dn() {
|
||||||
|
return LDAP.parseDN(`cn=sudo_${this.uid.toLowerCase()},${this.ldap_server.sudo_dn().format(this.configs.get('ldap:server.format'))}`)
|
||||||
|
}
|
||||||
|
|
||||||
// The following are used by OpenID connect
|
// The following are used by OpenID connect
|
||||||
|
|
||||||
async claims(use, scope) {
|
async claims(use, scope) {
|
||||||
|
@ -37,6 +37,10 @@ class LDAPServerUnit extends Unit {
|
|||||||
return this.build_dn(this.config.schema.group_base)
|
return this.build_dn(this.config.schema.group_base)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sudo_dn() {
|
||||||
|
return this.build_dn(this.config.schema.sudo_base)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the anonymous DN.
|
* Get the anonymous DN.
|
||||||
* @returns {ldap/DN}
|
* @returns {ldap/DN}
|
||||||
|
@ -15,6 +15,7 @@ const ldap_server = {
|
|||||||
base_dc: env('LDAP_BASE_DC', 'dc=example,dc=com'),
|
base_dc: env('LDAP_BASE_DC', 'dc=example,dc=com'),
|
||||||
authentication_base: env('LDAP_AUTH_BASE', 'ou=people'),
|
authentication_base: env('LDAP_AUTH_BASE', 'ou=people'),
|
||||||
group_base: env('LDAP_GROUP_BASE', 'ou=groups'),
|
group_base: env('LDAP_GROUP_BASE', 'ou=groups'),
|
||||||
|
sudo_base: env('LDAP_SUDO_BASE', 'ou=sudo'),
|
||||||
auth: {
|
auth: {
|
||||||
user_id: 'uid',
|
user_id: 'uid',
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user