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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
|
# $Id$
# Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
"""
Global context for rpkid. Probably should be renamed rpkid.py, but
the identifier gctx is scattered all through the code at the moment.
"""
import traceback, os, time, getopt, sys, MySQLdb, lxml.etree
import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509, rpki.sql
import rpki.https, rpki.config, rpki.cms, rpki.exceptions, rpki.relaxng, rpki.log
# This should be wrapped somewhere in rpki.x509 eventually
import POW
class global_context(object):
"""A container for various global parameters."""
def __init__(self, cfg):
self.db = MySQLdb.connect(user = cfg.get("sql-username"),
db = cfg.get("sql-database"),
passwd = cfg.get("sql-password"))
self.cur = self.db.cursor()
self.cms_ta_irdb = rpki.x509.X509(Auto_file = cfg.get("cms-ta-irdb"))
self.cms_ta_irbe = rpki.x509.X509(Auto_file = cfg.get("cms-ta-irbe"))
self.cms_key = rpki.x509.RSA(Auto_file = cfg.get("cms-key"))
self.cms_certs = rpki.x509.X509_chain(Auto_files = cfg.multiget("cms-cert"))
self.https_key = rpki.x509.RSA(Auto_file = cfg.get("https-key"))
self.https_certs = rpki.x509.X509_chain(Auto_files = cfg.multiget("https-cert"))
self.https_ta_irdb = rpki.x509.X509_chain(Auto_files = cfg.multiget("https-ta-irdb"))
self.https_ta_irbe = rpki.x509.X509_chain(Auto_files = cfg.multiget("https-ta-irbe"))
self.irdb_url = cfg.get("irdb-url")
self.https_server_host = cfg.get("server-host", "")
self.https_server_port = int(cfg.get("server-port", "4433"))
self.publication_kludge_base = cfg.get("publication-kludge-base", "publication/")
self.sql_cache = {}
self.sql_dirty = set()
def irdb_query(self, self_id, child_id = None):
"""Perform an IRDB callback query. In the long run this should not
be a blocking routine, it should instead issue a query and set up a
handler to receive the response. For the moment, though, we are
doing simple lock step and damn the torpedos. Not yet doing
anything useful with subject name. Most likely this function should
really be wrapped up in a class that carries both the query result
and also the intermediate state needed for the event-driven code
that this function will need to become.
"""
rpki.log.trace()
q_msg = rpki.left_right.msg()
q_msg.append(rpki.left_right.list_resources_elt())
q_msg[0].type = "query"
q_msg[0].self_id = self_id
q_msg[0].child_id = child_id
q_elt = q_msg.toXML()
rpki.relaxng.left_right.assertValid(q_elt)
q_cms = rpki.cms.xml_sign(q_elt, self.cms_key, self.cms_certs)
r_cms = rpki.https.client(
client_key = self.https_key,
client_certs = self.https_certs,
server_ta = self.https_ta_irdb,
url = self.irdb_url,
msg = q_cms)
r_elt = rpki.cms.xml_verify(r_cms, self.cms_ta_irdb)
rpki.relaxng.left_right.assertValid(r_elt)
r_msg = rpki.left_right.sax_handler.saxify(r_elt)
if len(r_msg) == 0 or not isinstance(r_msg[0], rpki.left_right.list_resources_elt) or r_msg[0].type != "reply":
raise rpki.exceptions.BadIRDBReply, "Unexpected response to IRDB query: %s" % lxml.etree.tostring(r_msg.toXML(), pretty_print = True, encoding = "us-ascii")
return rpki.resource_set.resource_bag(
as = r_msg[0].as,
v4 = r_msg[0].ipv4,
v6 = r_msg[0].ipv6,
valid_until = r_msg[0].valid_until)
def sql_cache_clear(self):
"""Clear the object cache."""
self.sql_cache.clear()
def sql_assert_pristine(self):
"""Assert that there are no dirty objects in the cache."""
assert not self.sql_dirty, "Dirty objects in SQL cache: %s" % self.sql_dirty
def sql_sweep(self):
"""Write any dirty objects out to SQL."""
for s in self.sql_dirty.copy():
rpki.log.debug("Sweeping %s" % repr(s))
if s.sql_deleted:
s.sql_delete()
else:
s.sql_store()
self.sql_assert_pristine()
def left_right_handler(self, query, path):
"""Process one left-right PDU."""
rpki.log.trace()
try:
q_elt = rpki.cms.xml_verify(query, self.cms_ta_irbe)
rpki.relaxng.left_right.assertValid(q_elt)
q_msg = rpki.left_right.sax_handler.saxify(q_elt)
r_msg = q_msg.serve_top_level(self)
r_elt = r_msg.toXML()
rpki.relaxng.left_right.assertValid(r_elt)
reply = rpki.cms.xml_sign(r_elt, self.cms_key, self.cms_certs)
self.sql_sweep()
return 200, reply
except lxml.etree.DocumentInvalid:
rpki.log.warn("Received reply document does not pass schema check: " + lxml.etree.tostring(r_elt, pretty_print = True))
rpki.log.warn(traceback.format_exc())
return 500, "Schema violation"
except Exception, data:
rpki.log.error(traceback.format_exc())
return 500, "Unhandled exception %s" % data
def up_down_handler(self, query, path):
"""Process one up-down PDU."""
rpki.log.trace()
try:
child_id = path.partition("/up-down/")[2]
if not child_id.isdigit():
raise rpki.exceptions.BadContactURL, "Bad path: %s" % path
child = rpki.left_right.child_elt.sql_fetch(self, long(child_id))
if child is None:
raise rpki.exceptions.ChildNotFound, "Could not find child %s" % child_id
reply = child.serve_up_down(query)
self.sql_sweep()
return 200, reply
except Exception, data:
rpki.log.error(traceback.format_exc())
return 400, "Could not process PDU: %s" % data
def cronjob_handler(self, query, path):
"""Periodic tasks. As simple as possible for now, may need to break
this up into separate handlers later.
"""
rpki.log.trace()
for s in rpki.left_right.self_elt.sql_fetch_all(self):
s.client_poll()
s.update_children()
s.generate_roas()
s.regenerate_crls_and_manifests()
self.sql_sweep()
return 200, "OK"
## @var https_ta_cache
# HTTPS trust anchor cache, to avoid regenerating it for every TLS connection.
https_ta_cache = None
def clear_https_ta_cache(self):
"""Clear cached HTTPS trust anchor X509Store."""
if self.https_ta_cache is not None:
rpki.log.debug("Clearing HTTPS trust anchor cache")
self.https_ta_cache = None
def build_x509store(self):
"""Build a dynamic x509store object.
This probably should be refactored to do the real work in the
rpki.https module so that this module can treat the x509store as a
black box. This method's jobs would then be just to identify
certs that need to be added and to cache an opaque object.
"""
if self.https_ta_cache is None:
store = POW.X509Store()
children = rpki.left_right.child_elt.sql_fetch_all(self)
certs = [c.peer_biz_cert for c in children if c.peer_biz_cert is not None] + \
[c.peer_biz_glue for c in children if c.peer_biz_glue is not None] + \
self.https_ta_irbe
for x in certs:
if rpki.https.debug_tls_certs:
rpki.log.debug("HTTPS dynamic trust anchor %s" % x.getSubject())
store.addTrust(x.get_POW())
self.https_ta_cache = store
return self.https_ta_cache
|