summaryrefslogtreecommitdiffstats
path: root/daemons/dnssec/ipa-dnskeysync-replica
blob: b80b38962957f922cc871ead471f8da0831bec4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#!/usr/bin/python2
#
# Copyright (C) 2014  FreeIPA Contributors see COPYING for license
#
"""
Download keys from LDAP to local HSM.

This program should be run only on replicas, not on DNSSEC masters.
"""

from binascii import hexlify
from datetime import datetime
import dns.dnssec
import fcntl
from krbV import Krb5Error
import logging
import os
from pprint import pprint
import subprocess
import socket
import sys
import systemd.daemon
import systemd.journal
import time

import ipalib
from ipapython.dn import DN
from ipapython.ipa_log_manager import root_logger, standard_logging_setup
from ipapython import ipaldap
from ipapython import ipautil
from ipaserver.plugins.ldap2 import ldap2
from ipaplatform.paths import paths

from ipapython.dnssec.abshsm import sync_pkcs11_metadata, ldap2p11helper_api_params, wrappingmech_name2id
from ipapython.dnssec.ldapkeydb import LdapKeyDB
from ipapython.dnssec.localhsm import LocalHSM
import _ipap11helper

DAEMONNAME = 'ipa-dnskeysyncd'
PRINCIPAL = None  # not initialized yet
WORKDIR = '/tmp'

def hex_set(s):
    out = set()
    for i in s:
        out.add("0x%s" % hexlify(i))
    return out

def update_metadata_set(log, source_set, target_set):
    """sync metadata from source key set to target key set

    Keys not present in both sets are left intact."""
    log = log.getChild('sync_metadata')
    matching_keys = set(source_set.keys()).intersection(set(target_set.keys()))
    log.info("keys in local HSM & LDAP: %s", hex_set(matching_keys))
    for key_id in matching_keys:
        sync_pkcs11_metadata(log, source_set[key_id], target_set[key_id])


def find_unwrapping_key(log, localhsm, wrapping_key_uri):
    wrap_keys = localhsm.find_keys(uri=wrapping_key_uri)
    # find usable unwrapping key with matching ID
    for key_id, key in wrap_keys.iteritems():
        unwrap_keys = localhsm.find_keys(id=key_id, cka_unwrap=True)
        if len(unwrap_keys) > 0:
            return unwrap_keys.popitem()[1]

def ldap2replica_master_keys_sync(log, ldapkeydb, localhsm):
    ## LDAP -> replica master key synchronization
    # import new master keys from LDAP
    new_keys = set(ldapkeydb.master_keys.keys()) \
            - set(localhsm.master_keys.keys())
    log.debug("master keys in local HSM: %s", hex_set(localhsm.master_keys.keys()))
    log.debug("master keys in LDAP HSM: %s", hex_set(ldapkeydb.master_keys.keys()))
    log.debug("new master keys in LDAP HSM: %s", hex_set(new_keys))
    for mkey_id in new_keys:
        mkey_ldap = ldapkeydb.master_keys[mkey_id]
        assert mkey_ldap.wrapped_entries, "Master key 0x%s in LDAP is missing key material referenced by ipaSecretKeyRefObject attribute" % hexlify(mkey_id)
        for wrapped_ldap in mkey_ldap.wrapped_entries:
            unwrapping_key = find_unwrapping_key(log, localhsm,
                    wrapped_ldap.single_value['ipaWrappingKey'])
            if unwrapping_key:
                break

        # TODO: Could it happen in normal cases?
        assert unwrapping_key is not None, "Local HSM does not contain suitable unwrapping key for master key 0x%s" % hexlify(mkey_id)

        params = ldap2p11helper_api_params(mkey_ldap)
        params['data'] = wrapped_ldap.single_value['ipaSecretKey']
        params['unwrapping_key'] = unwrapping_key.handle
        params['wrapping_mech'] = wrappingmech_name2id[wrapped_ldap.single_value['ipaWrappingMech']]
        log.debug('Importing new master key: 0x%s %s', hexlify(mkey_id), params)
        localhsm.p11.import_wrapped_secret_key(**params)

    # synchronize metadata about master keys in LDAP
    update_metadata_set(log, ldapkeydb.master_keys, localhsm.master_keys)

def ldap2replica_zone_keys_sync(log, ldapkeydb, localhsm):
    ## LDAP -> replica zone key synchronization
    # import new zone keys from LDAP
    new_keys = set(ldapkeydb.zone_keypairs.keys()) \
            - set(localhsm.zone_privkeys.keys())

    log.debug("zone keys in local HSM: %s", hex_set(localhsm.master_keys.keys()))
    log.debug("zone keys in LDAP HSM: %s", hex_set(ldapkeydb.master_keys.keys()))
    log.debug("new zone keys in LDAP HSM: %s", hex_set(new_keys))
    for zkey_id in new_keys:
        zkey_ldap = ldapkeydb.zone_keypairs[zkey_id]
        log.debug('Looking for unwrapping key "%s" for zone key 0x%s',
                zkey_ldap['ipaWrappingKey'], hexlify(zkey_id))
        unwrapping_key = find_unwrapping_key(log, localhsm,
                zkey_ldap['ipaWrappingKey'])
        assert unwrapping_key is not None, \
                "Local HSM does not contain suitable unwrapping key for ' \
                'zone key 0x%s" % hexlify(zkey_id)

        log.debug('Importing zone key pair 0x%s', hexlify(zkey_id))
        localhsm.import_private_key(zkey_ldap, zkey_ldap['ipaPrivateKey'],
                unwrapping_key)
        localhsm.import_public_key(zkey_ldap, zkey_ldap['ipaPublicKey'])

    # synchronize metadata about zone keys in LDAP & local HSM
    update_metadata_set(log, ldapkeydb.master_keys, localhsm.master_keys)

    # delete keys removed from LDAP
    deleted_keys = set(localhsm.zone_privkeys.keys()) \
                - set(ldapkeydb.zone_keypairs.keys())

    for zkey_id in deleted_keys:
        localhsm.p11.delete_key(localhsm.zone_pubkeys[zkey_id].handle)
        localhsm.p11.delete_key(localhsm.zone_privkeys[zkey_id].handle)


# IPA framework initialization
ipalib.api.bootstrap(in_server=True, log=None)  # no logging to file
ipalib.api.finalize()
standard_logging_setup(verbose=True, debug = True)  # debug=ipalib.api.env.debug)
log = root_logger
log.setLevel(level=logging.DEBUG)

# Kerberos initialization
PRINCIPAL = str('%s/%s' % (DAEMONNAME, ipalib.api.env.host))
log.debug('Kerberos principal: %s', PRINCIPAL)
ccache_filename = os.path.join(WORKDIR, 'ipa-dnskeysync-replica.ccache')

try:
    ipautil.kinit_keytab(PRINCIPAL, paths.IPA_DNSKEYSYNCD_KEYTAB,
                         ccache_filename, attempts=5)
except Krb5Error as e:
    log.critical('Kerberos authentication failed: %s', e)
    sys.exit(1)

os.environ['KRB5CCNAME'] = ccache_filename
log.debug('Got TGT')

# LDAP initialization
ldap = ipalib.api.Backend[ldap2]
# fixme
log.debug('Connecting to LDAP')
ldap.connect(ccache=ccache_filename)
log.debug('Connected')


### DNSSEC master: key synchronization
ldapkeydb = LdapKeyDB(log, ldap,
        DN(('cn', 'keys'), ('cn', 'sec'), ipalib.api.env.container_dns,
           ipalib.api.env.basedn))

# TODO: slot number could be configurable
localhsm = LocalHSM(paths.LIBSOFTHSM2_SO, 0,
        open(paths.DNSSEC_SOFTHSM_PIN).read())

ldap2replica_master_keys_sync(log, ldapkeydb, localhsm)
ldap2replica_zone_keys_sync(log, ldapkeydb, localhsm)

sys.exit(0)