UNPKG

masson

Version:

Module execution engine for cluster deployments.

311 lines (277 loc) 12.9 kB
--- title: Kerberos Client module: masson/core/krb5_client layout: module --- # Kerberos Client Kerberos is a network authentication protocol. It is designed to provide strong authentication for client/server applications by using secret-key cryptography. This module install the client tools written by the [Massachusetts Institute of Technology](http://web.mit.edu). each = require 'each' misc = require 'mecano/lib/misc' krb5_server = require './krb5_server' module.exports = [] module.exports.push 'masson/bootstrap/' module.exports.push 'masson/bootstrap/utils' module.exports.push 'masson/core/yum' module.exports.push 'masson/core/ssh' module.exports.push 'masson/core/ntp' module.exports.push 'masson/core/openldap_client' ## Configuration * `krb5.kadmin_principal` (string, required) * `krb5.kadmin_password` (string, required) * `krb5.kadmin_server` (string, required) * `krb5.realm` (string, required) * `krb5.etc_krb5_conf` (object) Object representing the full ini file in "/etc/krb5.conf". It is generated by default. * `krb5.sshd` (object) Properties inserted in the "/etc/ssh/sshd_config" file. Example: ```json { "krb5": { "realm": "ADALTAS.COM", "kdc": "krb5.hadoop", "kadmin_server": "krb5.hadoop", "kadmin_principal": "wdavidw/admin@ADALTAS.COM", "kadmin_password": "test", "sshd": { "ChallengeResponseAuthentication: "yes", "KerberosAuthentication: "yes", "KerberosOrLocalPasswd: "yes", "KerberosTicketCleanup: "yes", "GSSAPIAuthentication: "yes", "GSSAPICleanupCredentials: "yes" } } } ``` safe_etc_krb5_conf = module.exports.safe_etc_krb5_conf = (etc_krb5_conf) -> etc_krb5_conf = krb5_server.safe_etc_krb5_conf etc_krb5_conf for realm, config of etc_krb5_conf.realms delete config.database_module delete etc_krb5_conf.dbmodules etc_krb5_conf module.exports.etc_krb5_conf = 'logging': 'default': 'SYSLOG:INFO:LOCAL1' 'kdc': 'SYSLOG:NOTICE:LOCAL1' 'admin_server': 'SYSLOG:WARNING:LOCAL1' 'libdefaults': # 'default_realm': "#{REALM}" 'dns_lookup_realm': false 'dns_lookup_kdc': false 'ticket_lifetime': '24h' 'renew_lifetime': '7d' 'forwardable': true 'realms': {} 'domain_realm': {} 'appdefaults': 'pam': 'debug': false 'ticket_lifetime': 36000 'renew_lifetime': 36000 'forwardable': true 'krb4_convert': false 'dbmodules': {} module.exports.push module.exports.configure = (ctx) -> ctx.config.krb5.sshd ?= {} ctx.config.krb5 ?= {} etc_krb5_conf = misc.merge {}, module.exports.etc_krb5_conf, ctx.config.krb5.etc_krb5_conf ctx.config.krb5.etc_krb5_conf = etc_krb5_conf openldap_hosts = ctx.hosts_with_module 'masson/core/openldap_server_krb5' # Generate dynamic "krb5.dbmodules" object for host in openldap_hosts {kerberos_container_dn, users_container_dn, manager_dn, manager_password} = ctx.hosts[host].config.openldap_krb5 name = "openldap_#{host.split('.')[0]}" scheme = if ctx.hosts[host].has_module 'masson/core/openldap_server_tls' then "ldap://" else "ldaps://" ldap_server = "#{scheme}#{host}" etc_krb5_conf.dbmodules[name] = misc.merge 'db_library': 'kldap' 'ldap_kerberos_container_dn': kerberos_container_dn 'ldap_kdc_dn': users_container_dn # this object needs to have read rights on # the realm container, principal container and realm sub-trees 'ldap_kadmind_dn': users_container_dn # this object needs to have read and write rights on # the realm container, principal container and realm sub-trees 'ldap_service_password_file': "/etc/krb5.d/#{name}.stash.keyfile" # 'ldap_servers': 'ldapi:///' 'ldap_servers': ldap_server 'ldap_conns_per_server': 5 'manager_dn': manager_dn 'manager_password': manager_password , etc_krb5_conf.dbmodules[name] ldapservers = etc_krb5_conf.dbmodules[name].ldap_servers etc_krb5_conf.dbmodules[name].ldap_servers = ldapservers.join ' ' if Array.isArray ldapservers # Merge global with server-based configuration krb5_server_hosts = ctx.hosts_with_module "masson/core/krb5_server" for krb5_server_host in krb5_server_hosts {realms} = misc.merge {}, ctx.hosts[krb5_server_host].config.krb5.etc_krb5_conf for realm, config of realms delete config.database_module realms[realm].kdc ?= krb5_server_host realms[realm].kdc = [realms[realm].kdc] unless Array.isArray realms[realm].kdc realms[realm].admin_server ?= krb5_server_host realms[realm].default_domain ?= realm.toLowerCase() misc.merge etc_krb5_conf.realms, realms for realm, config of etc_krb5_conf.realms # Check if realm point to a database_module if config.database_module # Make sure this db module is registered dbmodules = Object.keys(etc_krb5_conf.dbmodules).join ',' valid = etc_krb5_conf.dbmodules[config.database_module]? throw new Error "Property database_module \"#{config.database_module}\" not in list: \"#{dbmodules}\"" unless valid # Set a database module if we manage the realm locally if config.admin_server is ctx.config.host # Valid if # * only one OpenLDAP server accross the cluster or # * an OpenLDAP server in this host openldap_index = openldap_hosts.indexOf ctx.config.host openldap_host = if openldap_hosts.length is 1 then openldap_hosts[0] else if openldap_index isnt -1 then openldap_hosts[openldap_index] throw new Error "Could not find a suitable OpenLDAP server" unless openldap_host config.database_module = "openldap_#{openldap_host.split('.')[0]}" config.principals ?= [] # Now that we have db_modules and realms, filter and validate the used db_modules database_modules = for realm, config of etc_krb5_conf.realms config.database_module for name, config of etc_krb5_conf.dbmodules # Filter if database_modules.indexOf(name) is -1 delete etc_krb5_conf.dbmodules[name] continue # Validate throw new Error "Kerberos property `krb5.dbmodules.#{name}.kdc_master_key` is required" unless config.kdc_master_key throw new Error "Kerberos property `krb5.dbmodules.#{name}.ldap_kerberos_container_dn` is required" unless config.ldap_kerberos_container_dn throw new Error "Kerberos property `krb5.dbmodules.#{name}.ldap_kdc_dn` is required" unless config.ldap_kdc_dn throw new Error "Kerberos property `krb5.dbmodules.#{name}.ldap_kadmind_dn` is required" unless config.ldap_kadmind_dn # Generate the "domain_realm" property for realm of etc_krb5_conf.realms etc_krb5_conf.domain_realm[".#{realm.toLowerCase()}"] = realm etc_krb5_conf.domain_realm["#{realm.toLowerCase()}"] = realm ## Install The package "krb5-workstation" is installed. module.exports.push name: 'Krb5 Client # Install', timeout: -1, callback: (ctx, next) -> ctx.service name: 'krb5-workstation' , (err, serviced) -> next err, if serviced then ctx.OK else ctx.PASS ## Configure Modify the Kerberos configuration file in "/etc/krb5.conf". Note, this action wont be run if the server host a Kerberos server. This is to avoid any conflict where both modules would try to write their own configuration one. We give the priority to the server module which create a Kerberos file with complementary information. module.exports.push name: 'Krb5 Client # Configure', timeout: -1, callback: (ctx, next) -> # Kerberos config is also managed by the kerberos server action. ctx.log 'Check who manage /etc/krb5.conf' return next null, ctx.INAPPLICABLE if ctx.has_module 'masson/core/krb5_server' {etc_krb5_conf} = ctx.config.krb5 ctx.log 'Update /etc/krb5.conf' ctx.ini content: safe_etc_krb5_conf etc_krb5_conf destination: '/etc/krb5.conf' stringify: misc.ini.stringify_square_then_curly , (err, written) -> return next err, if written then ctx.OK else ctx.PASS ## Host Principal Create a user principal for this host. The principal is named like "host/{hostname}@{realm}". module.exports.push name: 'Krb5 Client # Host Principal', timeout: -1, callback: (ctx, next) -> {etc_krb5_conf} = ctx.config.krb5 modified = false each(etc_krb5_conf.realms) .on 'item', (realm, config, next) -> {kadmin_principal, kadmin_password, admin_server} = config cmd = misc.kadmin realm: realm kadmin_principal: kadmin_principal kadmin_password: kadmin_password kadmin_server: admin_server , 'listprincs' ctx.waitForExecution cmd, (err) -> return next err if err ctx.krb5_addprinc principal: "host/#{ctx.config.host}@#{realm}" randkey: true # kadmin_principal: kadmin_principal if admin_server isnt ctx.config.host # kadmin_password: kadmin_password if admin_server isnt ctx.config.host # kadmin_server: admin_server if admin_server isnt ctx.config.host kadmin_principal: kadmin_principal kadmin_password: kadmin_password kadmin_server: admin_server , (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 ## principals Populate the Kerberos database with new principals. module.exports.push name: 'Krb5 Client # Principals', callback: (ctx, next) -> {etc_krb5_conf} = ctx.config.krb5 modified = false utils = require 'util' each(etc_krb5_conf.realms) .on 'item', (realm, config, next) -> {kadmin_principal, kadmin_password, admin_server, principals} = config return next() if principals.length is 0 principals = for principal in principals misc.merge kadmin_principal: kadmin_principal kadmin_password: kadmin_password kadmin_server: admin_server , principal ctx.log "Create principal #{principal.principal}" ctx.krb5_addprinc principals, (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 ## Configure SSHD Updated the "/etc/ssh/sshd\_config" file with properties provided by the "krb5.sshd" configuration object. By default, we set the following properties to "yes": "ChallengeResponseAuthentication", "KerberosAuthentication", "KerberosOrLocalPasswd", "KerberosTicketCleanup", "GSSAPIAuthentication", "GSSAPICleanupCredentials". The "sshd" service will be restarted if a change to the configuration is detected. module.exports.push name: 'Krb5 Client # Configure SSHD', timeout: -1, callback: (ctx, next) -> {sshd} = ctx.config.krb5 return next null, ctx.DISABLED unless sshd write = for k, v of sshd match: new RegExp "^#{k}.*$", 'mg' replace: "#{k} #{v}" append: true return next null, ctx.DISABLED if write.length is 0 ctx.log 'Write /etc/ssh/sshd_config' ctx.write write: write destination: '/etc/ssh/sshd_config' , (err, written) -> return next err if err return next null, ctx.PASS unless written ctx.log 'Restart openssh' ctx.service name: 'openssh' srv_name: 'sshd' action: 'restart' , (err, restarted) -> next err, ctx.OK ## Usefull client commands * List all the current principals in the realm: `getprincs` * Login to a local kadmin: `kadmin.local` * Login to a remote kadmin: `kadmin -p wdavidw/admin@ADALTAS.COM -s krb5.hadoop` * Print details on a principal: `getprinc host/hadoop1.hadoop@ADALTAS.COM` * Examine the content of the /etc/krb5.keytab: `klist -etk /etc/krb5.keytab` * Destroy our own tickets: `kdestroy` * Get a user ticket: `kinit -p wdavidw@ADALTAS.COM` * Confirm that we do indeed have the new ticket: `klist` * Check krb5kdc is listening: `netstat -nap | grep :750` and `netstat -nap | grep :88` ## Todo * Enable sshd(8) Kerberos authentication. * Enable PAM Kerberos authentication. * SASL GSSAPI OpenLDAP authentication. * Use SASL GSSAPI Authentication with AutoFS. ## Notes Kerberos clients require connectivity to the KDC's TCP ports 88 and 749.