<commit_before>---
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": "<PASSWORD>",
    "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
      # 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.config.servers[krb5_server_host].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()
        etc_krb5_conf.realms = misc.merge {}, realms, etc_krb5_conf.realms
      # 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: <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: <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: <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<commit_msg>dont create hosts unless default_realm<commit_after>---
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": "<PASSWORD>",
    "sshd": {
      "ChallengeResponseAuthentication: "yes",
      "KerberosAuthentication: "yes",
      "KerberosOrLocalPasswd: "<PASSWORD>",
      "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
      # 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.config.servers[krb5_server_host].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()
        etc_krb5_conf.realms = misc.merge {}, realms, etc_krb5_conf.realms
      # 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}". Only apply to the default realm 
("krb5.etc\_krb5\_conf.libdefaults.default_realm") unless the property
"etc_krb5_conf[realm].create\_hosts" is set.

    module.exports.push name: 'Krb5 Client # Host Principal', timeout: -1, callback: (ctx, next) ->
      {etc_krb5_conf} = ctx.config.krb5
      default_realm = etc_krb5_conf.libdefaults.default_realm
      modified = false
      each(etc_krb5_conf.realms)
      .on 'item', (realm, config, next) ->
        return next() unless default_realm is realm or not config.create_hosts
        {kadmin_principal, kadmin_password, admin_server} = config
        cmd = misc.kadmin
          realm: realm
          kadmin_principal: kadmin_principal
          kadmin_password: <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: k<PASSWORD>_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: <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() unless principals?.length > 0
        principals = for principal in principals
{'fraction_non_alphanumeric': 0.10606380731037397, 'fraction_numerical': 0.013099514050285232, 'mean_word_length': 2.9043167445697002, 'pattern_counts': {'":': 14, '<': 12, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1750170', 'n_tokens_mistral': 4784, 'n_tokens_neox': 4713, 'n_words': 1094}