UNPKG

masson

Version:

Module execution engine for cluster deployments.

469 lines (432 loc) 19.1 kB
--- title: Kerberos with OpenLDAP Back-End module: masson/core/krb5_server layout: module --- ## Kerberos with OpenLDAP Back-End robert.mroczkowski@allegrogroup.com Usefull server commands: * Backup the db: `kdb5_util dump /path/to/dumpfile` * Initialize realm: `kdb5_ldap_util -D "cn=Manager,dc=adaltas,dc=com" -w test create -subtrees "ou=kerberos,ou=services,dc=adaltas,dc=com" -r ADALTAS.COM -s -P test` * Load the db: `kdb5_util load -update /path/to/dumpfile` * Stash password: `kdb5_ldap_util -D "cn=Manager,dc=adaltas,dc=com" -w test stashsrvpw -f /etc/krb5.d/stash.keyfile cn=krbadmin,ou=users,dc=adaltas,dc=com` Resources: * [Kerberos with LDAP backend on centos](http://itdavid.blogspot.fr/2012/05/howto-centos-62-kerberos-kdc-with.html) * [Propagation](http://www-old.grantcohoe.com/guides/services/krb5-kdc) * [Replication](http://tldp.org/HOWTO/Kerberos-Infrastructure-HOWTO/server-replication.html) * [Kerberos with LDAP backend on ubuntu](http://labs.opinsys.com/blog/2010/02/05/setting-up-openldap-kerberos-on-ubuntu-10-04-lucid/) each = require 'each' misc = require 'mecano/lib/misc' module.exports = [] module.exports.push 'masson/bootstrap/' module.exports.push 'masson/core/openldap_client' module.exports.push 'masson/core/iptables' module.exports.push 'masson/core/yum' ## Configuration * `krb5_server.{realm}.ldap_manager_dn` (string) The LDAP user with read and write access to the realm dn defined by the `ldap_realms_dn` property. Default to the `openldap_krb5.manager_dn` property if you have one OpenLDAP server with kerberos support declared inside the cluster by the "masson/core/openldap\_server\_krb5" module, otherwise required. * `krb5_server.{realm}.ldap_manager_password` (string) The password of the LDAP user with read and write access to the realm dn defined by the `ldap_realms_dn` property. Default to the `openldap_krb5.manager_password` property if you have one OpenLDAP server with kerberos support declared inside the cluster by the "masson/core/openldap\_server\_krb5" module, otherwise required. * `krb5_server.{realm}.ldap_realms_dn` (string) The location where to store the realms inside the LDAP tree. Default to the `openldap_krb5.realms_dn` property if you have one OpenLDAP server with kerberos support declared inside the cluster by the "masson/core/openldap\_server\_krb5" module, otherwise required. Example: ```json { "krb5": { "ADALTAS.COM": { "kdc": "master3.hadoop", "kadmin_server": "master3.hadoop", "kadmin_principal": "wdavidw/admin@ADALTAS.COM", "kadmin_password": "test", "ldap\_kerberos\_container_dn": "ou=kerberos,dc=adaltas,dc=com", "ldap\_kdc\_dn": "cn=krbadmin,ou=users,dc=adaltas,dc=com", "ldap\_kadmind\_dn": "cn=krbadmin,ou=users,dc=adaltas,dc=com", "ldap_servers": [ "ldaps://master3.hadoop", ], "principals": [ "principal": "wdavidw@ADALTAS.COM", "password": "test" ] } } } ``` safe_etc_krb5_conf = module.exports.safe_etc_krb5_conf = (etc_krb5_conf) -> etc_krb5_conf = misc.merge {}, etc_krb5_conf for realm, config of etc_krb5_conf.realms delete config.kadmin_principal delete config.kadmin_password delete config.principals for name, config of etc_krb5_conf.dbmodules delete config.kdc_master_key delete config.manager_dn delete config.manager_password etc_krb5_conf module.exports.push module.exports.configure = (ctx) -> require('./krb5_client').configure ctx require('./iptables').configure ctx {etc_krb5_conf} = ctx.config.krb5 openldap_hosts = ctx.hosts_with_module 'masson/core/openldap_server_krb5' throw new Error "Expect at least one server with action \"masson/core/openldap_server_krb5\"" if openldap_hosts.length is 0 # Prepare configuration for "kdc.conf" kdc_conf = ctx.config.krb5.kdc_conf ?= {} misc.merge kdc_conf, 'kdcdefaults': 'kdc_ports': '88' 'kdc_tcp_ports': '88' 'realms': {} 'logging': 'kdc': 'FILE:/var/log/kdc.log' , kdc_conf # Multiple kerberos servers accross the cluster are defined in server # specific configuration realms = ctx.config.servers[ctx.config.host].krb5?.etc_krb5_conf?.realms realms = etc_krb5_conf.realms if not realms or realms.length is 0 for realm, i of realms kdc_conf.realms[realm] = {} # Set default values each realm for realm, config of kdc_conf.realms kdc_conf.realms[realm] = misc.merge # 'kadmind_port': 749 # 'kpasswd_port': 464 # http://www.opensource.apple.com/source/Kerberos/Kerberos-47/KerberosFramework/Kerberos5/Documentation/kadmin/kpasswd.protocol 'max_life': '10h 0m 0s' 'max_renewable_life': '7d 0h 0m 0s' 'master_key_type': 'aes256-cts' 'default_principal_flags': '+preauth' 'acl_file': '/var/kerberos/krb5kdc/kadm5.acl' 'dict_file': '/usr/share/dict/words' 'admin_keytab': '/var/kerberos/krb5kdc/kadm5.keytab' 'supported_enctypes': 'aes256-cts:normal aes128-cts:normal des3-hmac-sha1:normal arcfour-hmac:normal des-hmac-sha1:normal des-cbc-md5:normal des-cbc-crc:normal' , config ## IPTables | Service | Port | Protocol | Parameter | |----------------------------|--------|--------|-------------------- | | NameNode WebUI | 50070 | http | `dfs.http.address` | | | 50470 | https | `dfs.https.address` | | NameNode metadata service | 8020 | ipc | `fs.default.name` | IPTables rules are only inserted if the parameter "iptables.action" is set to "start" (default value). module.exports.push name: 'Krb5 Server # IPTables', callback: (ctx, next) -> {etc_krb5_conf, kdc_conf} = ctx.config.krb5 rules = [] add_default_kadmind_port = false add_default_kdc_ports = false add_default_kdc_tcp_ports = false for realm, config of kdc_conf.realms if config.kadmind_port rules.push chain: 'INPUT', target: 'ACCEPT', dport: kadmind_port, protocol: 'tcp', state: 'NEW', comment: "Kerberos administration server (kadmind daemon)" else add_default_kadmind_port = true if config.kdc_ports for port in config.kdc_ports.split /\s,/ rules.push chain: 'INPUT', target: 'ACCEPT', dport: port, protocol: 'udp', state: 'NEW', comment: "Kerberos Authentication Service and Key Distribution Center (krb5kdc daemon)" else add_default_kdc_ports = true if config.kdc_tcp_ports kdc_tcp_ports = true for port in config.kdc_ports.split /\s,/ rules.push chain: 'INPUT', target: 'ACCEPT', dport: port, protocol: 'tcp', state: 'NEW', comment: "Kerberos Authentication Service and Key Distribution Center (krb5kdc daemon)" else add_default_kdc_tcp_ports = true if add_default_kadmind_port port = kdc_conf.kdcdefaults.kadmind_port or '749' rules.push chain: 'INPUT', target: 'ACCEPT', dport: port, protocol: 'tcp', state: 'NEW', comment: "Kerberos administration server (kadmind daemon)" if add_default_kdc_ports for port in (kdc_conf.kdcdefaults.kdc_ports or '88').split /\s,/ rules.push chain: 'INPUT', target: 'ACCEPT', dport: port, protocol: 'udp', state: 'NEW', comment: "Kerberos Authentication Service and Key Distribution Center (krb5kdc daemon)" if add_default_kdc_tcp_ports for port in (kdc_conf.kdcdefaults.kdc_tcp_ports or '88').split /\s,/ rules.push chain: 'INPUT', target: 'ACCEPT', dport: port, protocol: 'tcp', state: 'NEW', comment: "Kerberos Authentication Service and Key Distribution Center (krb5kdc daemon)" ctx.iptables rules: rules if: ctx.config.iptables.action is 'start' , (err, configured) -> next err, if configured then ctx.OK else ctx.PASS module.exports.push name: 'Krb5 Server # LDAP Install', timeout: -1, callback: (ctx, next) -> ctx.service name: 'krb5-server-ldap' , (err, installed) -> next err, if installed then ctx.OK else ctx.PASS module.exports.push name: 'Krb5 Server # LDAP Configuration', timeout: 100000, callback: (ctx, next) -> {etc_krb5_conf} = ctx.config.krb5 ctx.ini content: safe_etc_krb5_conf etc_krb5_conf destination: '/etc/krb5.conf' stringify: misc.ini.stringify_square_then_curly backup: true , (err, written) -> return next err if err next err, if written then ctx.OK else ctx.PASS module.exports.push name: 'Krb5 Server # LDAP Insert Entries', timeout: 100000, callback: (ctx, next) -> {etc_krb5_conf, kdc_conf} = ctx.config.krb5 modified = false each(etc_krb5_conf.realms) .on 'item', (realm, config, next) -> return next() unless config.database_module {kdc_master_key, ldap_kerberos_container_dn, manager_dn, manager_password, ldap_servers} = etc_krb5_conf.dbmodules[config.database_module] ldap_server = ldap_servers.split(' ')[0] do_wait = -> ctx.waitForExecution cmd: "ldapsearch -x -LLL -H #{ldap_server} -D \"#{manager_dn}\" -w #{manager_password} -b \"#{ldap_kerberos_container_dn}\"" code_skipped: 32 , (err) -> return next err if err do_exists() do_exists = -> searchbase = "cn=#{realm},#{ldap_kerberos_container_dn}" ctx.execute cmd: "ldapsearch -x -H #{ldap_server} -D \"#{manager_dn}\" -w #{manager_password} -b \"#{searchbase}\"" code_skipped: 32 , (err, exists) -> return next err if err if exists then next() else do_subtrees() do_subtrees = -> # Note, kdb5_ldap_util is using /etc/krb5.conf (server version) ctx.execute cmd: "kdb5_ldap_util -D \"#{manager_dn}\" -w #{manager_password} create -subtrees \"#{ldap_kerberos_container_dn}\" -r #{realm} -s -P #{kdc_master_key}" , (err, executed, stdout, stderr) -> return next err if err modified = true if executed next() do_wait() .on 'both', (err) -> next err, if modified then ctx.OK else ctx.PASS module.exports.push name: 'Krb5 Server # LDAP Stash password', callback: (ctx, next) -> {etc_krb5_conf} = ctx.config.krb5 modified = false each(etc_krb5_conf.dbmodules) .on 'item', (name, dbmodule, next) -> {kdc_master_key, manager_dn, manager_password, ldap_service_password_file, ldap_kadmind_dn} = dbmodule ctx.log "Stash key file is: #{dbmodule.ldap_service_password_file}" keyfileContent = null do_read = -> ctx.log 'Read current keyfile if it exists' ctx.fs.readFile "#{ldap_service_password_file}", 'utf8', (err, content) -> return do_mkdir() if err and err.code is 'ENOENT' return next err if err keyfileContent = content do_stash() do_mkdir = -> ctx.log 'Create directory "/etc/krb5.d"' ctx.mkdir '/etc/krb5.d', (err, created) -> return next err if err do_stash() do_stash = -> ctx.log 'Stash password into local file' ctx.ssh.shell (err, stream) -> return next err if err cmd = "kdb5_ldap_util -D \"#{manager_dn}\" -w #{manager_password} stashsrvpw -f #{ldap_service_password_file} #{ldap_kadmind_dn}" ctx.log "Run #{cmd}" reentered = false stream.write "#{cmd}\n" stream.on 'data', (data, stderr) -> ctx.log[if stderr then 'err' else 'out'].write data data = data.toString() if /Password for/.test data stream.write "#{kdc_master_key}\n" else if /Re-enter password for/.test data stream.write "#{kdc_master_key}\n\n" reentered = true else if reentered stream.end() stream.on 'close', -> do_compare() do_compare = -> unless keyfileContent modified = true return next() ctx.fs.readFile "#{ldap_service_password_file}", 'utf8', (err, content) -> return next err if err modified = if keyfileContent is content then false else true next() do_read() .on 'both', (err) -> next err, if modified then ctx.OK else ctx.PASS module.exports.push name: 'Krb5 Server # Install', timeout: -1, callback: (ctx, next) -> ctx.log 'Install krb5kdc and kadmin services' ctx.service [ name: 'krb5-pkinit-openssl' , name: 'krb5-server-ldap' startup: true chk_name: 'krb5kdc' srv_name: 'krb5kdc' , name: 'krb5-server-ldap' startup: true chk_name: 'kadmin' srv_name: 'kadmin' , name: 'words' , name: 'krb5-workstation' ], (err, serviced) -> next err, if serviced then ctx.OK else ctx.PASS module.exports.push name: 'Krb5 Server # Configure', timeout: 100000, callback: (ctx, next) -> {realm, etc_krb5_conf, kdc_conf} = ctx.config.krb5 modified = false exists = false do_exists = -> ctx.fs.exists '/etc/krb5.conf', (err, e) -> exists = e do_krb5() do_krb5 = -> ctx.log 'Update /etc/krb5.conf' # Clone etc_krb5_conf etc_krb5_conf = misc.merge {}, etc_krb5_conf ctx.ini content: safe_etc_krb5_conf etc_krb5_conf destination: '/etc/krb5.conf' stringify: misc.ini.stringify_square_then_curly backup: true , (err, written) -> return next err if err modified = true if written do_kadm5() do_kadm5 = -> ctx.log 'Update /var/kerberos/krb5kdc/kadm5.acl' writes = for realm of etc_krb5_conf.realms match: ///^\*/\w+@#{misc.regexp.escape realm}\s+\*///mg replace: "*/admin@#{realm} *" append: true ctx.write write: writes destination: '/var/kerberos/krb5kdc/kadm5.acl' backup: true , (err, written) -> return next err if err modified = true if written do_kdc() do_kdc = -> ctx.log 'Update /var/kerberos/krb5kdc/kdc.conf' ctx.ini content: kdc_conf destination: '/var/kerberos/krb5kdc/kdc.conf' stringify: misc.ini.stringify_square_then_curly backup: true , (err, written) -> return next err if err modified = true if written do_end() do_end = (err) -> return next err if err return next null, ctx.PASS unless modified # The first time, we dont restart because ldap conf is # not there yet return next null, ctx.OK unless exists ctx.log '(Re)start krb5kdc and kadmin services' ctx.service [ name: 'krb5-server' action: 'restart' srv_name: 'krb5kdc' , name: 'krb5-server' action: 'restart' srv_name: 'kadmin' ], (err, serviced) -> next err, ctx.OK do_exists() module.exports.push name: 'Krb5 Server # Log', timeout: 100000, callback: (ctx, next) -> modified = false touch = -> ctx.log 'Touch "/etc/logrotate.d/krb5kdc" and "/etc/logrotate.d/kadmind"' ctx.write [ content: '' destination: '/var/log/krb5kdc.log' not_if_exists: true , content: '' destination: '/var/log/kadmind.log' not_if_exists: true ], (err, written) -> return done err if err modified = true if written rsyslog() rsyslog = -> ctx.log 'Update /etc/rsyslog.conf' ctx.write destination: '/etc/rsyslog.conf' write: [ match: /.*krb5kdc.*/mg replace: 'if $programname == \'krb5kdc\' then /var/log/krb5kdc.log' append: '### RULES ###' , match: /.*kadmind.*/mg replace: 'if $programname == \'kadmind\' then /var/log/kadmind.log' append: '### RULES ###' ] , (err, written) -> return done err if err modified = true if written if written then restart() else done() restart = -> ctx.log 'Restart krb5kdc and kadmin' ctx.service [ name: 'krb5-server' action: 'start' srv_name: 'krb5kdc' , name: 'krb5-server' action: 'start' srv_name: 'kadmin' ], (err, restarted) -> return done err if err ctx.log 'Restart rsyslog' ctx.service name: 'rsyslog' action: 'restart' , (err, restarted) -> done err done = (err) -> next err, if modified then ctx.OK else ctx.PASS touch() module.exports.push name: 'Krb5 Server # Admin principal', timeout: -1, callback: (ctx, next) -> {etc_krb5_conf} = ctx.config.krb5 modified = false each(etc_krb5_conf.realms) .on 'item', (realm, config, next) -> {database_module, kadmin_principal, kadmin_password} = config return next() unless database_module ctx.log "Create principal #{kadmin_principal}" ctx.krb5_addprinc # We dont provide an "kadmin_server". Instead, we need # to use "kadmin.local" because the principal used # to login with "kadmin" isnt created yet principal: kadmin_principal password: kadmin_password , (err, created) -> return next err if err modified = true if created next() .on 'both', (err) -> next err, if modified then ctx.OK else ctx.PASS module.exports.push name: 'Krb5 Server # Start', timeout: 100000, callback: (ctx, next) -> ctx.service [ name: 'krb5-server-ldap' action: 'start' srv_name: 'krb5kdc' , name: 'krb5-server-ldap' action: 'start' srv_name: 'kadmin' ], (err, serviced) -> next err, if serviced then ctx.OK else ctx.PASS ## Krb5 Client Call the "masson/core/krb5_client" dependency which will register this host to each Kerberos servers. module.exports.push '!masson/core/krb5_client' ## Notes Renewable tickets is per default disallowed in the most linux distributions. This can be done per: ```bash kadmin.local: modprinc -maxrenewlife 7day krbtgt/YOUR_REALM kadmin.local: modprinc -maxrenewlife 7day +allow_renewable hue/FQRN ```