mecano
Version:
Common functions for system deployment.
207 lines (193 loc) • 7.1 kB
Markdown
# `ldap_acl(options, [goptions], callback)`
Create new [ACLs](acls) for the OpenLDAP server.
This implementation currently doesn't execute remote SSH commands. Instead, it
connects directly to the LDAP database and thus requires a specific port to be
accessible.
## Options
* `to`
What to control access to as a string.
* `by`
Who to grant access to and the access to grant as an array
(eg: `{..., by:["ssf=64 anonymous auth"]}`).
* `url`
Specify URI referring to the ldap server, alternative to providing an
[ldapjs client] instance.
* `binddn`
Distinguished Name to bind to the LDAP directory, alternative to providing
an [ldapjs client] instance.
* `passwd`
Password for simple authentication, alternative to providing an
[ldapjs client] instance.
* `ldap`
Instance of an [ldapjs client][ldapclt], alternative to providing the `url`,
`binddn` and `passwd` connection properties.
* `unbind`
Close the ldap connection, default to false if connection is an
[ldapjs client][ldapclt] instance.
* `name`
Distinguish name storing the "olcAccess" property, using the database adress
(eg: "olcDatabase={2}bdb,cn=config").
* `overwrite`
Overwrite existing "olcAccess", default is to merge.
* `log`
Function called with a log related messages.
* `acl`
In case of multiple acls, regroup "before", "to" and "by" as an array.
## Example
```js
require('mecano/alt/ldap_acl')({
url: 'ldap://openldap.server/',
binddn: 'cn=admin,cn=config',
passwd: 'password',
name: 'olcDatabase={2}bdb,cn=config',
acls: [{
before: 'dn.subtree="dc=domain,dc=com"',
to: 'dn.subtree="ou=users,dc=domain,dc=com"',
by: [
'dn.exact="ou=users,dc=domain,dc=com" write',
"dn.base='gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth' read",
"* none"
]
},{
to: 'dn.subtree="dc=domain,dc=com"',
by: [
'dn.exact="ou=kerberos,dc=domain,dc=com" write'
]
}]
}, function(err, modified){
console.log(err ? err.message : "ACL modified: " + !!modified);
});
```
module.exports = (goptions, options, callback) ->
options.acls ?= [{}]
updated = false
each(options.acls)
.run (acl, next) ->
acl.before ?= options.before
acl.to ?= options.to
acl.by ?= options.by
client = null
acl.to = acl.to.trim()
for b, i in acl.by
acl.by[i] = b.trim()
connect = ->
# if options.ldap instanceof ldap_client
if options.ldap?.url?.protocol?.indexOf('ldap') is 0
client = options.ldap
return search()
options.log? 'Open and bind connection'
client = ldap.createClient url: options.url
client.bind options.binddn, options.passwd, (err) ->
return end err if err
search()
search = ->
options.log? 'Search attribute olcAccess'
client.search options.name,
scope: 'base'
attributes: ['olcAccess']
, (err, search) ->
return unbind err if err
olcAccess = null
search.on 'searchEntry', (entry) ->
options.log? "Found #{JSON.stringify entry.object}"
# typeof olcAccess may be undefined, array or string
olcAccess = entry.object.olcAccess or []
olcAccess = [olcAccess] unless Array.isArray olcAccess
search.on 'end', ->
options.log? "Attribute olcAccess was #{JSON.stringify olcAccess}"
parse olcAccess
parse = (_olcAccess) ->
olcAccess = []
for access, i in _olcAccess
to = ''
bys = []
buftype = 0 # 0: start, 1: to, 2:by
buf = ''
for c, i in access
buf += c
if buftype is 0
if /to$/.test buf
buf = ''
buftype = 1
if buftype is 1
if matches = /^(.*)by$/.exec buf
to = matches[1].trim()
buf = ''
buftype = 2
if buftype is 2
if matches = /^(.*)by$/.exec buf
bys.push matches[1].trim()
buf = ''
else if i+1 is access.length
bys.push buf.trim()
olcAccess.push
to: to
by: bys
do_diff olcAccess
do_diff = (olcAccess) ->
toAlreadyExist = false
for access, i in olcAccess
continue unless acl.to is access.to
toAlreadyExist = true
fby = unless options.overwrite then access.by else []
for oby in acl.by
found = false
for aby in access.by
if oby is aby
found = true
break
unless found
updated = true
fby.push oby
olcAccess[i].by = fby
unless toAlreadyExist
updated = true
# place before
if acl.before
found = null
for access, i in olcAccess
found = i if access.to is acl.before
# throw new Error 'Before does not match any "to" rule' unless found?
olcAccess.splice found-1, 0, to: acl.to, by: acl.by
# place after
else if acl.after
found = false
for access, i in olcAccess
found = i if access.to is options.after
# throw new Error 'After does not match any "to" rule'
olcAccess.splice found, 0, to: acl.to, by: acl.by
# append
else
olcAccess.push to: acl.to, by: acl.by
if updated then stringify(olcAccess) else unbind()
stringify = (olcAccess) ->
for access, i in olcAccess
value = "{#{i}}to #{access.to}"
for bie in access.by
value += " by #{bie}"
olcAccess[i] = value
save olcAccess
save = (olcAccess) ->
change = new ldap.Change
operation: 'replace'
modification: olcAccess: olcAccess
client.modify options.name, change, (err) ->
unbind err
unbind = (err) ->
options.log? 'Unbind connection'
# return end err if options.ldap instanceof ldap_client and not options.unbind
return end err if options.ldap?.url?.protocol?.indexOf('ldap') is 0 and not options.unbind
client.unbind (e) ->
return next e if e
end err
end = (err) ->
next err
connect()
.then (err) ->
next err, updated
## Dependencies
each = require 'each'
ldap = require 'ldapjs'
wrap = require '../misc/wrap'
[acls]: http://www.openldap.org/doc/admin24/access-control.html
[ldapclt]: http://ldapjs.org/client.html