2021-03-11 05:43:16 +00:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
|
2021-04-15 17:51:23 +00:00
|
|
|
console.log('[SUDO SEARCH]', req.filter.toString())
|
2021-04-15 18:00:13 +00:00
|
|
|
const sudo_hosts = this.parse_sudo_hosts(req.filter)
|
|
|
|
console.log('[SUDO HOSTS]', sudo_hosts)
|
|
|
|
const iam_targets = await this.get_targets_from_hosts(sudo_hosts)
|
|
|
|
console.log('[SUDO IAM]', iam_targets)
|
2021-04-15 17:51:23 +00:00
|
|
|
|
2021-03-11 05:43:16 +00:00
|
|
|
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
|
2021-04-15 15:56:11 +00:00
|
|
|
if ( user && user.ldap_visible && req.filter.matches(await user.to_sudo(iam_targets)) ) {
|
2021-03-11 05:43:16 +00:00
|
|
|
|
|
|
|
// If so, send the object
|
|
|
|
res.send({
|
|
|
|
dn: user.sudo_dn.format(this.configs.get('ldap:server.format')),
|
2021-04-15 15:56:11 +00:00
|
|
|
attributes: await user.to_sudo(iam_targets),
|
2021-03-11 05:43:16 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
} 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
|
2021-04-15 15:56:11 +00:00
|
|
|
if ( req.filter.matches(await user.to_sudo(iam_targets)) ) {
|
2021-03-11 05:43:16 +00:00
|
|
|
|
|
|
|
// If so, send the object
|
|
|
|
res.send({
|
|
|
|
dn: user.sudo_dn.format(this.configs.get('ldap:server.format')),
|
2021-04-15 15:56:11 +00:00
|
|
|
attributes: await user.to_sudo(iam_targets),
|
2021-03-11 05:43:16 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} 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
|
2021-04-15 15:56:11 +00:00
|
|
|
if ( req.filter.matches(await user.to_sudo(iam_targets)) ) {
|
2021-03-11 05:43:16 +00:00
|
|
|
|
|
|
|
// If so, send the object
|
|
|
|
res.send({
|
|
|
|
dn: user.sudo_dn.format(this.configs.get('ldap:server.format')),
|
2021-04-15 15:56:11 +00:00
|
|
|
attributes: await user.to_sudo(iam_targets),
|
2021-03-11 05:43:16 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} 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()
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:00:13 +00:00
|
|
|
parse_sudo_hosts(filter, target_hosts = []) {
|
2021-04-15 15:56:11 +00:00
|
|
|
if ( Array.isArray(filter?.filters) ) {
|
|
|
|
for ( const sub_filter of filter.filters ) {
|
2021-04-15 18:02:18 +00:00
|
|
|
target_hosts = [...target_hosts, ...this.parse_sudo_hosts(sub_filter)]
|
2021-04-15 15:56:11 +00:00
|
|
|
}
|
|
|
|
} else if ( filter?.attribute ) {
|
2021-04-15 18:00:13 +00:00
|
|
|
if ( filter.attribute === 'sudohost' ) {
|
|
|
|
target_hosts.push(filter.value)
|
2021-04-15 15:56:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:00:13 +00:00
|
|
|
return target_hosts
|
|
|
|
}
|
|
|
|
|
|
|
|
async get_targets_from_hosts(sudo_hosts) {
|
|
|
|
const Machine = this.models.get('ldap:Machine')
|
|
|
|
const machines = await Machine.find({
|
|
|
|
active: true,
|
|
|
|
ldap_visible: true,
|
|
|
|
host_name: {
|
|
|
|
$in: sudo_hosts.filter(x => x.toLowerCase() !== 'all' && x.indexOf('*') < 0),
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return machines.map(x => x.id)
|
2021-04-15 15:56:11 +00:00
|
|
|
}
|
|
|
|
|
2021-03-11 05:43:16 +00:00
|
|
|
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 ) {
|
2021-04-15 15:56:11 +00:00
|
|
|
return this.User.findOne({uid: cn.substr(5), ldap_visible: true})
|
2021-03-11 05:43:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = exports = SudoController
|