]>
git.siccegge.de Git - tooling/letool.git/blob - bin/addrecord
3 # ssh-based server to allow hosts to update their own TLS-related RRs
4 # intended to be used like
5 # command="/srv/tls/bin/addrecord hepworth.siccegge.de" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOIlLx3R+Q5LgBZbJ6USuzam/uAEQITl6vzOn/ylk4fq christoph@mitoraj
7 from subprocess
import call
8 from functools
import reduce
17 def __init__(self
, host
):
19 self
._ports
= {'www': [443],
20 'smtp': [25, 587, 465],
25 self
._tlsa
_re
= re
.compile('TLSA 3 1 1 [0-9a-f]{64}')
26 self
._acme
_re
= re
.compile(r
'[a-zA-Z0-9_-]{43}')
27 with
open('config/inventory.yaml') as inv
:
28 self
._inventory
= yaml
.load(inv
)
32 host
= input("Hostname: ")
33 service
= input("Service: ")
34 value
= input("Value: ")
35 if host
not in self
._allowed
_names
(service
):
36 sys
.stderr
.write("Not authorized to update entries for service '%s' on '%s'\n"
40 if re
.fullmatch(self
._tlsa
_re
, value
) is None:
41 sys
.stderr
.write("Not a valid TLSA record: '%s'\n" % value
)
45 for port
in self
._ports
[service
]:
46 records
.append("{0:<35s}\tIN\t{1}.\n".format("_%d._tcp.%s" % (port
, host
),
48 self
._update
_records
('tlsa', host
, records
)
54 host
= input("Hostname: ")
55 value
= input("Value: ")
57 if host
not in self
._allowed
_names
():
58 sys
.stderr
.write("Not authorized to update entries for host '%s'\n" % host
)
61 if re
.fullmatch(self
._acme
_re
, value
) is None:
62 sys
.stderr
.write("Not a valid ACME challenge record: '%s'\n" % value
)
65 records
= [ "{0:<35s}\tIN\tTXT\t\"{1}\"\n".format("_acme-challenge.%s." % host
, value
) ]
66 self
._update
_records
('acme', host
, records
)
71 def _update_records(self
, sort
, host
, records
):
74 candidate
= os
.path
.join(sort
, '.'.join(host
+ ['m4']))
75 if os
.path
.exists(candidate
):
78 return find_zone(host
[1:])
81 to_remove
= [ i
.split()[0] for i
in records
]
83 zone
= find_zone(host
.split('.'))
84 with
open(zone
, 'r') as oldzone
:
85 fcntl
.flock(oldzone
, fcntl
.LOCK_EX
)
86 lines
= oldzone
.readlines()
87 lines
= [ line
for line
in lines
if line
== '\n' or line
.split()[0] not in to_remove
]
90 lines
.append("; Last updated %s by %s\n" % (datetime
.datetime
.utcnow().isoformat(),
92 lines
= lines
+ records
94 with
open('%s.new' % (zone
,), 'w') as newzone
:
95 newtext
= ''.join(lines
)
96 newtext
= re
.sub(r
'\n[\n]+', '\n\n', newtext
)
97 newtext
= re
.sub(r
'\n;.*\n\n;', '\n;', newtext
)
98 newzone
.write(newtext
)
100 os
.rename('%s.new' % (zone
,),
102 fcntl
.flock(oldzone
, fcntl
.LOCK_UN
)
103 call(["ssh", "root@localhost"])
106 def _allowed_names(self
, service
=None):
108 perservice
= self
._inventory
[self
._host
].values()
110 perservice
= self
._inventory
[self
._host
][service
]
113 for entry
in perservice
:
114 if type(entry
) is list:
115 names
= names
.union(entry
)
116 elif type(entry
) is dict:
117 names
= names
.union(reduce(lambda x
, y
: x
+ y
, entry
.values(), []))
119 sys
.stderr
.write("inventory format wrong\n")
124 command
= os
.environ
['SSH_ORIGINAL_COMMAND']
126 addrecord
= Addrecord(host
)
128 if command
== 'acme':
129 return addrecord
.acme()
130 elif command
== 'tlsa':
131 return addrecord
.tlsa()
134 if __name__
== '__main__':