routine.py 19.8 KB
Newer Older
Valentin Samir's avatar
Valentin Samir committed
1
#!/usr/bin/env python
2
# -*- coding: utf8 -*-
Valentin Samir's avatar
Valentin Samir committed
3
4
5
6

import os
import sys
import datetime
Valentin Samir's avatar
style    
Valentin Samir committed
7
import subprocess
Valentin Samir's avatar
Valentin Samir committed
8
import argparse
9
10
import pwd

Valentin Samir's avatar
Valentin Samir committed
11
12
from functools import total_ordering

13

Valentin Samir's avatar
Valentin Samir committed
14
BASE = "/etc/bind/keys"
15
16
17
# Interval entre 2 opérations sur les clefs dns.
# Par exemple si vous avec la clef1 d'utilisé,
# clef2 est publié INTERVAL avant la désactivation de clef1.
Valentin Samir's avatar
Valentin Samir committed
18
# clef1 est désactivé quand clef2 est activé,
19
20
# clef2 est supprimé INTRERVAL après sa désactivation.
# INTERVAL DOIT être supérieur aux plus long TTL que les enregistrement DS peuvent avoir.
Valentin Samir's avatar
Valentin Samir committed
21
# INTERVAL DOIT egalement être supérieur a l'intervale de signature de bind (défaut de 22.5 jours)
22
23
# Cela dépent essentiellement de la configuration de la zone parente et vous n'avez pas forcement
# de controle dessus.
Valentin Samir's avatar
Valentin Samir committed
24
INTERVAL = datetime.timedelta(days=23)
25
26
27
28
29
30
31
32
33
34
35
36
# Durée au bout de laquelle une ZSK est remplacé par une nouvelle ZSK.
# La génération des ZSK et leur activation/désactivation/suppression est géré
# automatiquement tant que routine.py -c est appelé au moins une fois par
# jour.
ZSK_VALIDITY = datetime.timedelta(days=30)  # ~1 mois
# Temps au bout duquelle une nouvelle KSK est généré et publié pour la zone
# (et activé après INTERVAL). L'ancienne clef n'est retiré que INTERVAL après que la nouvelle
# clef a été routine.py --ds-seen. Cela demande en général une opération
# manuelle avec le registrar (publier le DS de la nouvelle clef dans la zone parente)
# et routine.py -c affiche un message tant que cela n'a pas été fait.
KSK_VALIDITY = datetime.timedelta(days=366)  # ~1 an

Valentin Samir's avatar
Valentin Samir committed
37
38
39
40
41

def get_zones(zone_names=None):
    l = []
    if zone_names is None:
        for f in os.listdir(BASE):
Valentin Samir's avatar
Valentin Samir committed
42
            if os.path.isdir(os.path.join(BASE, f)) and not f.startswith('.'):
Valentin Samir's avatar
Valentin Samir committed
43
44
45
46
47
48
                l.append(Zone(f))
    else:
        for name in zone_names:
            l.append(Zone(name))
    return l

Valentin Samir's avatar
style    
Valentin Samir committed
49

Valentin Samir's avatar
Valentin Samir committed
50
def settime(path, flag, date):
Valentin Samir's avatar
style    
Valentin Samir committed
51
52
53
54
55
    cmd = [
        "/usr/sbin/dnssec-settime",
        "-i", str(int(INTERVAL.total_seconds())),
        "-%s" % flag, date, path
    ]
56
    p = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
Valentin Samir's avatar
Valentin Samir committed
57
58
59
60
    err = p.communicate()[1]
    if p.returncode != 0:
        raise ValueError("err %s: %s" % (p.returncode, err))
    if err:
61
        sys.stderr.write("%s\n" % err)
Valentin Samir's avatar
Valentin Samir committed
62

Valentin Samir's avatar
style    
Valentin Samir committed
63

Valentin Samir's avatar
Valentin Samir committed
64
def bind_chown(path):
65
66
67
68
69
70
71
72
73
74
75
76
77
    """
        Gives the files to the bind user and sets the modes in a relevant way.
    """
    try:
        bind_uid = pwd.getpwnam('bind').pw_uid
        os.chown(path, bind_uid, -1)
        for root, dirs, files in os.walk(path):
            for momo in dirs:
                os.chown(os.path.join(root, momo), bind_uid, -1)
            for momo in files:
                os.chown(os.path.join(root, momo), bind_uid, -1)
    except KeyError:
        sys.stderr.write("User bind not found, failing to give keys ownership to bind\n")
Valentin Samir's avatar
style    
Valentin Samir committed
78

Valentin Samir's avatar
Valentin Samir committed
79
80
81
82
83
def bind_reload():
    cmd = ["/usr/sbin/rndc", "reload"]
    p = subprocess.Popen(cmd)
    p.wait()

Valentin Samir's avatar
style    
Valentin Samir committed
84

Valentin Samir's avatar
Valentin Samir committed
85
86
87
88
89
90
91
92
93
def nsec3(zone, salt="-"):
    cmd = ["rndc", "signing", "-nsec3param", "1", "0", "10", salt, zone]
    sys.stdout.write("Enabling nsec3 for zone %s: " % zone)
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    out = p.communicate()[0]
    sys.stdout.write(out)
    p.wait()


Valentin Samir's avatar
Valentin Samir committed
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
class Zone(object):
    ZSK = None
    KSK = None
    _path = None
    name = None

    def __str__(self):
        return self.name

    def __unicode__(self):
        return self.name.decode("utf-8")

    def __repr__(self):
        return "Zone %s" % self.name

    @classmethod
    def create(cls, name):
        path = os.path.join(BASE, name)
        if os.path.isdir(path):
            raise ValueError("%s existe" % path)
        os.mkdir(path)
        bind_chown(path)
        return cls(name)

    def do_zsk(self):
        for zsk in self.ZSK:
            if zsk.is_activate:
121
122
                zsk.inactive = zsk.activate + ZSK_VALIDITY
                zsk.delete = zsk.inactive + INTERVAL
123
                last_activate_zsk = zsk
124
        now = datetime.datetime.utcnow()
Valentin Samir's avatar
Valentin Samir committed
125
        if zsk.is_activate:
126
127
            zsk.inactive = max(zsk.inactive, now + INTERVAL)
            zsk.delete = zsk.inactive + INTERVAL
Valentin Samir's avatar
Valentin Samir committed
128
129
            zsk.gen_successor()
            bind_reload()
130
131
        else:
            zsk.activate = last_activate_zsk.inactive
Valentin Samir's avatar
Valentin Samir committed
132

133
134
135
136
137
138
    def do_ksk(self):
        ksk = self.KSK[-1]
        if ksk.need_renew:
            now = datetime.datetime.utcnow()
            new_ksk = Key.create("KSK", self.name)
            new_ksk.publish = now
139
140
            # do not activate the new key until ds-seen
            new_ksk.activate = None
141
            bind_reload()
Valentin Samir's avatar
style    
Valentin Samir committed
142
143
144
145
146
147
148
149
        active_ksk = [key for key in self.KSK if key.is_publish and key.delete is None]
        if len(active_ksk) >= 2:
            sys.stderr.write(
                (
                    "New KSK needs DS seen and/or old KSK needs "
                    "inactivate/remove for zone %s\n"
                ) % self.name
            )
150
151
152
153
154
155
156
157
158
159
160
161
162

    def ds_seen(self, keyid):
        old_ksks = []
        for ksk in self.KSK:
            if ksk.keyid == keyid:
                seen_ksk = ksk
                break
            old_ksks.append(ksk)
        else:
            sys.stderr.write("Key not found\n")
            return
        print "Key %s found" % keyid
        now = datetime.datetime.utcnow()
163
164
        if seen_ksk.activate is None:
            seen_ksk.activate = (now + INTERVAL)
165
166
167
        for ksk in old_ksks:
            print " * program key %s removal" % ksk.keyid
            # set inactive in at least INTERVAL
168
169
170
            ksk.inactive = seen_ksk.activate
            # delete INTERVAL after being inactive
            ksk.delete = ksk.inactive + INTERVAL
171
172
173
174
175
176
177
178
179
        bind_reload()

    def remove_deleted(self):
        deleted_path = os.path.join(self._path, "deleted")
        try:
            os.mkdir(deleted_path)
        except OSError as error:
            if error.errno != 17:  # File exists
                raise
180
        now = datetime.datetime.utcnow()
181
        for key in self.ZSK + self.KSK:
182
            if key.delete and (key.delete + INTERVAL) <= now:
183
184
185
186
187
                for path in [key._path, key._path_private]:
                    basename = os.path.basename(path)
                    new_path = os.path.join(deleted_path, basename)
                    os.rename(path, new_path)

Valentin Samir's avatar
Valentin Samir committed
188
189
190
191
192
    def ds(self):
        for ksk in self.KSK:
            cmd = ["/usr/sbin/dnssec-dsfromkey", ksk._path]
            p = subprocess.Popen(cmd)
            p.wait()
Valentin Samir's avatar
style    
Valentin Samir committed
193

194
195
196
197
198
199
200
    def key(self, show_ksk=False, show_zsk=False):
        if show_ksk:
            for ksk in self.KSK:
                print ksk
        if show_zsk:
            for zsk in self.ZSK:
                print zsk
Valentin Samir's avatar
Valentin Samir committed
201
202
203
204
205
206
207
208
209
210
211

    def __init__(self, name):
        path = os.path.join(BASE, name)
        if not os.path.isdir(path):
            raise ValueError("%s n'est pas un dossier" % path)
        self.name = name
        self._path = path
        self.ZSK = []
        self.KSK = []
        for file in os.listdir(path):
            file_path = os.path.join(path, file)
212
            if os.path.isfile(file_path) and file_path.endswith(".private"):
Valentin Samir's avatar
style    
Valentin Samir committed
213
214
215
216
217
218
219
220
221
222
                try:
                    key = Key(file_path)
                    if key.type == "ZSK":
                        self.ZSK.append(key)
                    elif key.type == "KSK":
                        self.KSK.append(key)
                    else:
                        raise RuntimeError("impossible")
                except ValueError as error:
                    sys.stderr.write("%s\n" % error)
Valentin Samir's avatar
Valentin Samir committed
223
224
225
226
227
228
229
        self.ZSK.sort()
        self.KSK.sort()
        if not self.ZSK:
            self.ZSK.append(Key.create("ZSK", name))
        if not self.KSK:
            self.KSK.append(Key.create("KSK", name))

Valentin Samir's avatar
style    
Valentin Samir committed
230

Valentin Samir's avatar
Valentin Samir committed
231
232
233
234
235
236
237
238
239
240
241
@total_ordering
class Key(object):
    _created = None
    _publish = None
    _activate = None
    _inactive = None
    _delete = None
    _data = None
    _path = None
    type = None
    keyid = None
242
243
    flag = None
    zone_name = None
Valentin Samir's avatar
Valentin Samir committed
244
245
246
247
248
249
250

    def __str__(self):
        return self._data

    def __repr__(self):
        r = os.path.basename(self._path)
        return r
Valentin Samir's avatar
style    
Valentin Samir committed
251

Valentin Samir's avatar
Valentin Samir committed
252
253
254
    def _date_from_key(self, date):
        if date is not None:
            return datetime.datetime.strptime(date, "%Y%m%d%H%M%S")
Valentin Samir's avatar
style    
Valentin Samir committed
255

Valentin Samir's avatar
Valentin Samir committed
256
    def _date_to_key(self, date):
257
258
259
260
        if date is None:
            return 'none'
        else:
            return datetime.datetime.strftime(date, "%Y%m%d%H%M%S")
Valentin Samir's avatar
Valentin Samir committed
261

262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
    def _date_check(self, value, needed_date, value_name, needed_date_name):
        if value is not None:
            if needed_date is None or value < needed_date:
                raise RuntimeError(
                    "Cannot set %s date before %s date on key %s on zone %s" % (
                        value_name,
                        needed_date_name,
                        self.keyid,
                        self.zone_name
                     )
                )

    def _date_check2(self, value, needed_date, value_name, needed_date_name):
        msg = "Cannot set %s date after %s date on key %s on zone %s" % (
                  value_name,
                  needed_date_name,
                  self.keyid,
                  self.zone_name
              )
        if value is None and needed_date is not None:
            raise RuntimeError(msg)
        elif value is not None and needed_date is not None:
            if value > needed_date:
                raise RuntimeError(msg)

Valentin Samir's avatar
Valentin Samir committed
287
    @classmethod
288
289
290
    def create(cls, typ, name, options=None):
        if options is None:
            options = []
Valentin Samir's avatar
Valentin Samir committed
291
        path = os.path.join(BASE, name)
292
        cmd = ["/usr/sbin/dnssec-keygen", "-a", "RSASHA256"]
Valentin Samir's avatar
Valentin Samir committed
293
        if typ == "KSK":
294
            cmd.extend(["-b", "2048", "-f", "KSK"])
Valentin Samir's avatar
Valentin Samir committed
295
        elif typ == "ZSK":
296
            cmd.extend(["-b", "1024"])
297
298
        else:
            raise ValueError("typ must be KSK or ZSK")
299
300
        cmd.extend(options)
        cmd.extend(["-K", path,  name])
Valentin Samir's avatar
Valentin Samir committed
301
302
303
304
305
306
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
        p.wait()
        if p.returncode != 0:
            raise ValueError("La creation de la clef a echoue")
        keyname = p.communicate()[0].strip()
        bind_chown(path)
307
        return cls(os.path.join(path, "%s.private" % keyname))
Valentin Samir's avatar
style    
Valentin Samir committed
308

Valentin Samir's avatar
Valentin Samir committed
309
    def gen_successor(self):
Valentin Samir's avatar
style    
Valentin Samir committed
310
311
312
313
        cmd = [
            "/usr/sbin/dnssec-keygen", "-i", str(int(INTERVAL.total_seconds())),
            "-S", self._path, "-K", os.path.dirname(self._path)
        ]
Valentin Samir's avatar
Valentin Samir committed
314
315
316
317
318
319
320
321
322
323
        p = subprocess.Popen(cmd, stderr=subprocess.PIPE)
        err = p.communicate()[1]
        if p.returncode != 0:
            raise ValueError("err %s: %s" % (p.returncode, err))
        if err:
            print err
        bind_chown(os.path.dirname(self._path))

    @property
    def created(self):
324
325
        if self._created is not None:
            return self._date_from_key(self._created)
Valentin Samir's avatar
Valentin Samir committed
326
327
328

    @property
    def publish(self):
329
330
        if self._publish is not None:
            return self._date_from_key(self._publish)
Valentin Samir's avatar
Valentin Samir committed
331
332
    @publish.setter
    def publish(self, value):
333
334
        self._date_check(value, self.created, "publish", "created")
        self._date_check2(value, self.activate, "publish", "activate")
Valentin Samir's avatar
Valentin Samir committed
335
336
337
338
        date = self._date_to_key(value)
        if date != self._publish:
            settime(self._path, 'P', date)
            self._publish = date
Valentin Samir's avatar
style    
Valentin Samir committed
339
            with open(self._path, 'r') as f:
Valentin Samir's avatar
Valentin Samir committed
340
341
342
343
                self._data = f.read()

    @property
    def activate(self):
344
345
        if self._activate is not None:
            return self._date_from_key(self._activate)
Valentin Samir's avatar
Valentin Samir committed
346
347
    @activate.setter
    def activate(self, value):
348
349
        self._date_check(value, self.publish, "active", "publish")
        self._date_check2(value, self.inactive, "activate", "inactive")
Valentin Samir's avatar
Valentin Samir committed
350
351
352
353
        date = self._date_to_key(value)
        if date != self._activate:
            settime(self._path, 'A', date)
            self._activate = date
Valentin Samir's avatar
style    
Valentin Samir committed
354
            with open(self._path, 'r') as f:
Valentin Samir's avatar
Valentin Samir committed
355
356
357
358
                self._data = f.read()

    @property
    def inactive(self):
359
360
        if self._inactive is not None:
            return self._date_from_key(self._inactive)
Valentin Samir's avatar
Valentin Samir committed
361
362
    @inactive.setter
    def inactive(self, value):
363
364
        self._date_check(value, self.activate, "inactive", "activate")
        self._date_check2(value, self.delete, "inactive", "delete")
Valentin Samir's avatar
Valentin Samir committed
365
366
367
368
        date = self._date_to_key(value)
        if date != self._inactive:
            settime(self._path, 'I', date)
            self._inactive = date
Valentin Samir's avatar
style    
Valentin Samir committed
369
            with open(self._path, 'r') as f:
Valentin Samir's avatar
Valentin Samir committed
370
371
372
373
                self._data = f.read()

    @property
    def delete(self):
374
375
        if self._delete:
            return self._date_from_key(self._delete)
Valentin Samir's avatar
Valentin Samir committed
376
377
    @delete.setter
    def delete(self, value):
378
        self._date_check(value, self.inactive, "delete", "inactive")
Valentin Samir's avatar
Valentin Samir committed
379
380
381
382
        date = self._date_to_key(value)
        if date != self._delete:
            settime(self._path, 'D', date)
            self._delete = date
Valentin Samir's avatar
style    
Valentin Samir committed
383
            with open(self._path, 'r') as f:
Valentin Samir's avatar
Valentin Samir committed
384
385
                self._data = f.read()

386
387
388
    @property
    def is_publish(self):
        return self.publish is not None and self.publish <= datetime.datetime.utcnow()
Valentin Samir's avatar
style    
Valentin Samir committed
389

390
391
392
    @property
    def is_activate(self):
        return self.activate is not None and self.activate <= datetime.datetime.utcnow()
Valentin Samir's avatar
style    
Valentin Samir committed
393

394
395
396
    @property
    def is_inactive(self):
        return self.inactive is not None and self.inactive <= datetime.datetime.utcnow()
Valentin Samir's avatar
style    
Valentin Samir committed
397

398
399
400
401
402
403
404
405
406
407
408
409
410
    @property
    def is_delete(self):
        return self.delete is not None and self.delete <= datetime.datetime.utcnow()

    @property
    def need_renew(self):
        if self.type == "KSK":
            return (self.activate + KSK_VALIDITY) <= (datetime.datetime.utcnow() + INTERVAL)
        elif self.type == "ZSK":
            return (self.activate + ZSK_VALIDITY) <= (datetime.datetime.utcnow() + INTERVAL)
        else:
            raise RuntimeError("impossible")

Valentin Samir's avatar
Valentin Samir committed
411
    def __init__(self, path):
412
413
414
415
416
417
418
419
        if not path.endswith(".private"):
            raise ValueError("%s n'est pas une clef valide" % path)
        if not os.path.isfile(path):
            raise ValueError("%s n'existe pas" % path)
        ppath = "%s.key" % path[:-8]
        if not os.path.isfile(ppath):
            raise ValueError("la clef publique (%s) de %s n'existe pas" % (ppath, path))
        with open(ppath, 'r') as f:
Valentin Samir's avatar
Valentin Samir committed
420
            self._data = f.read()
421
422
423
424
425
426
427
428
        with open(path, 'r') as f:
            private_data = f.read()
        for line in self._data.split("\n"):
            if line.startswith(";") or not line:
                continue
            line = line.split(";", 1)[0].strip()
            line = line.split()
            if len(line) < 7:
Valentin Samir's avatar
style    
Valentin Samir committed
429
430
431
                raise ValueError(
                    "La clef publique %s devrait avoir au moins 7 champs: %r" % (ppath, line)
                )
432
            if not line[0].endswith('.'):
Valentin Samir's avatar
style    
Valentin Samir committed
433
434
435
436
437
438
                raise ValueError(
                    (
                        "La clef publique %s devrait commencer par le fqdn "
                        "(finissant par un .) de la zone"
                    ) % ppath
                )
439
440
441
442
            self.zone_name = line[0][:-1]
            try:
                self.flag = int(line[3])
            except ValueError:
Valentin Samir's avatar
style    
Valentin Samir committed
443
444
445
                raise ValueError(
                    "Le flag %s de la clef publique %s devrait être un entier" % (line[3], ppath)
                )
446
        if self.flag == 256:
Valentin Samir's avatar
Valentin Samir committed
447
            self.type = "ZSK"
448
        elif self.flag == 257:
Valentin Samir's avatar
Valentin Samir committed
449
450
            self.type = "KSK"
        else:
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
            raise ValueError("%s n'est pas une clef valide: flag %s inconnu" % (ppath, self.flag))
        self._path = ppath
        self._path_private = path
        keyid = path.split('.')[-2].split('+')[-1]
        try:
            self.keyid = int(keyid)
        except ValueError:
            raise ValueError("Le keyid %s de la clef %s devrait être un entier" % (keyid, path))
        for line in private_data.split("\n"):
            if line.startswith("Created:"):
                self._created = line[8:].strip()
                self._date_from_key(self._created)
            elif line.startswith("Publish:"):
                self._publish = line[8:].strip()
                self._date_from_key(self._publish)
            elif line.startswith("Activate:"):
                self._activate = line[9:].strip()
                self._date_from_key(self._activate)
            elif line.startswith("Inactive:"):
                self._inactive = line[9:].strip()
                self._date_from_key(self._inactive)
            elif line.startswith("Delete:"):
                self._delete = line[7:].strip()
                self._date_from_key(self._delete)
        if self.created is None:
            raise ValueError("La clef %s doit au moins avoir le champs Created de définit" % path)
Valentin Samir's avatar
Valentin Samir committed
477
478

    def __lt__(self, y):
Valentin Samir's avatar
style    
Valentin Samir committed
479
480
481
482
483
484
485
486
        if not isinstance(y, Key):
            raise ValueError("can only compare two Keys")
        if self.activate is not None and y.activate is not None:
            return self.activate < y.activate
        elif self.publish is not None and y.publish is not None:
            return self.publish < y.publish
        else:
            return self.created < y.created
Valentin Samir's avatar
Valentin Samir committed
487
488
489
490
491
492

    def __eq__(self, y):
        return isinstance(y, Key) and y._path == self._path

if __name__ == '__main__':
    try:
Valentin Samir's avatar
Valentin Samir committed
493
494
        parser = argparse.ArgumentParser()
        parser.add_argument('zone', nargs='*', help='zone name')
Valentin Samir's avatar
style    
Valentin Samir committed
495
496
497
498
499
500
501
502
503
504
505
506
507
        parser.add_argument(
            '--make', '-m',
            action='store_true',
            help='Create keys for each supplied zone'
        )
        parser.add_argument(
            '--cron', '-c',
            action='store_true',
            help='Perform maintenance for each supplied zone or for all zones if no zone supplied'
        )
        parser.add_argument(
            '-ds',
            action='store_true',
508
            help='Show KSK DS for each supplied zone or for all zones if no zone supplied'
Valentin Samir's avatar
style    
Valentin Samir committed
509
510
511
512
513
514
        )
        parser.add_argument(
            '-key',
            action='store_true',
            help='Show DNSKEY for each zone supplied zone or for all zones if no zone supplied'
        )
515
516
517
518
519
520
521
522
523
524
        parser.add_argument(
            '-KSK',
            action='store_true',
            help='Show KSK DNSKEY for each zone supplied zone or for all zones if no zone supplied'
        )
        parser.add_argument(
            '-ZSK',
            action='store_true',
            help='Show ZSK DNSKEY for each zone supplied zone or for all zones if no zone supplied'
        )
Valentin Samir's avatar
style    
Valentin Samir committed
525
526
527
528
529
530
531
532
533
534
535
536
537
538
        parser.add_argument(
            '--ds-seen',
            metavar='KEYID',
            type=int,
            help=(
                'To call with the ID of a new KSK published in the parent zone. '
                'Programs old KSK removal'
            )
        )
        parser.add_argument(
            '--nsec3',
            action='store_true',
            help='Enable NSEC3 for the zones, using a random salt'
        )
Valentin Samir's avatar
Valentin Samir committed
539
540
541
        args = parser.parse_args()
        zones = args.zone
        if args.make:
Valentin Samir's avatar
Valentin Samir committed
542
543
544
            for zone in zones:
                Zone.create(zone)
        zones = get_zones(zones if zones else None)
Valentin Samir's avatar
Valentin Samir committed
545
546
547
        if args.nsec3:
            for zone in zones:
                nsec3(zone.name, os.urandom(24).encode("hex"))
548
549
550
551
552
553
        if args.ds_seen:
            if len(zones) != 1:
                sys.stderr.write("Please specify exactly ONE zone name\n")
                sys.exit(1)
            for zone in zones:
                zone.ds_seen(args.ds_seen)
Valentin Samir's avatar
Valentin Samir committed
554
        if args.cron:
Valentin Samir's avatar
Valentin Samir committed
555
556
            for zone in zones:
                zone.do_zsk()
557
558
                zone.do_ksk()
                zone.remove_deleted()
Valentin Samir's avatar
Valentin Samir committed
559
        if args.ds:
Valentin Samir's avatar
Valentin Samir committed
560
561
            for zone in zones:
                zone.ds()
Valentin Samir's avatar
Valentin Samir committed
562
        if args.key:
Valentin Samir's avatar
Valentin Samir committed
563
            for zone in zones:
564
565
566
567
568
569
570
571
572
                zone.key(show_ksk=True, show_zsk=True)
        else:
            if args.KSK:
                for zone in zones:
                    zone.key(show_ksk=True)
            if args.ZSK:
                for zone in zones:
                    zone.key(show_zsk=True)
        if not any([args.make, args.cron, args.ds, args.key, args.ds_seen, args.nsec3, args.KSK, args.ZSK]):
Valentin Samir's avatar
Valentin Samir committed
573
            parser.print_help()
Valentin Samir's avatar
Valentin Samir committed
574
575
576
    except ValueError as error:
        sys.stderr.write("%s\n" % error)
        sys.exit(1)