masson
Version:
Module execution engine for cluster deployments.
311 lines (277 loc) • 12.9 kB
Markdown
---
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.