Permalink
Browse files

Signing and validating messages sent.

  • Loading branch information...
1 parent 7e62441 commit e08f184be584c4b4e1c0e1e636683c27af9c6517 @ralphbean committed Jun 21, 2012
View
View
@@ -0,0 +1,19 @@
+import os
+import socket
+
+SEP = os.path.sep
+here = os.getcwd()
+hostname = socket.gethostname()
+
+config = dict(
+ sign_messages=True,
+ validate_signatures=True,
+ ssldir=SEP.join([here, 'dev_certs']),
+
+ certnames={
+ hostname: "test_cert",
+ # In prod/stg, map hostname to the name of the cert in ssldir.
+ # Unfortunately, we can't use socket.getfqdn()
+ #"app01.stg": "app01.stg.phx2.fedoraproject.org",
+ },
+)
@@ -0,0 +1,20 @@
+import fedmsg.crypto
+import moksha.api.hub.consumer
+
+class FedmsgConsumer(moksha.api.hub.consumer.Consumer):
+ validate_signatures = False
+
+ def __init__(self, *args, **kwargs):
+ super(FedmsgConsumer, self).__init__(*args, **kwargs)
+ self.validate_signatures = self.hub.config.get('validate_signatures')
+
+ def validate(self, message):
+ """ This needs to raise an exception, caught by moksha. """
+
+ # If we're not validating, then everything is valid.
+ # If this is turned on globally, our child class can override it.
+ if not self.validate_signatures:
+ return
+
+ if not fedmsg.crypto.validate(message['body'], **self.hub.config):
+ raise RuntimeWarning("Failed to authn message.")
@@ -1,13 +1,13 @@
import fedmsg
from paste.deploy.converters import asbool
-from moksha.api.hub.consumer import Consumer
+from fedmsg.consumers import FedmsgConsumer
import logging
log = logging.getLogger("moksha.hub")
-class DummyConsumer(Consumer):
+class DummyConsumer(FedmsgConsumer):
topic = "org.fedoraproject.*"
def __init__(self, hub):
@@ -14,7 +14,7 @@
import pygments.formatters
from paste.deploy.converters import asbool
-from moksha.api.hub.consumer import Consumer
+from fedmsg.consumers import FedmsgConsumer
from twisted.words.protocols import irc
from twisted.internet import protocol
@@ -97,7 +97,7 @@ def clientConnectionFailed(self, connector, reason):
log.error("Could not connect: %s" % (reason,))
-class IRCBotConsumer(Consumer):
+class IRCBotConsumer(FedmsgConsumer):
topic = "org.fedoraproject.*"
def __init__(self, hub):
View
@@ -1,13 +1,13 @@
import fedmsg
from paste.deploy.converters import asbool
-from moksha.api.hub.consumer import Consumer
+from fedmsg.consumers import FedmsgConsumer
import logging
log = logging.getLogger("moksha.hub")
-class RelayConsumer(Consumer):
+class RelayConsumer(FedmsgConsumer):
topic = "org.fedoraproject.*"
def __init__(self, hub):
@@ -18,7 +18,11 @@ def __init__(self, hub):
log.info('fedmsg.consumers.relay:RelayConsumer disabled.')
return
- return super(RelayConsumer, self).__init__(hub)
+ super(RelayConsumer, self).__init__(hub)
+
+ # TODO -- turn off message validation for the relay
+ # self.validate_messages = False
+
def consume(self, msg):
## FIXME - for some reason twisted is screwing up fedmsg.
View
@@ -9,6 +9,7 @@
from kitchen.text.converters import to_utf8
import fedmsg.json
+import fedmsg.crypto
def _listify(obj):
@@ -19,10 +20,17 @@ def _listify(obj):
class FedMsgContext(object):
+ # A counter for messages sent.
+ _i = 0
+
def __init__(self, **config):
super(FedMsgContext, self).__init__()
self.c = config
+ self.hostname = socket.gethostname().split('.', 1)[0]
+ if self.c.get('sign_messages', False):
+ self.c['certname'] = self.c['certnames'][self.hostname]
+
# Prepare our context and publisher
self.context = zmq.Context(config['io_threads'])
@@ -32,8 +40,7 @@ def __init__(self, **config):
# If no name is provided, use the calling module's __name__ to decide
# which publishing endpoint to use.
if not config.get("name", None):
- hostname = socket.gethostname().split('.', 1)[0]
- config["name"] = self.guess_calling_module() + '.' + hostname
+ config["name"] = self.guess_calling_module() + '.' + self.hostname
if any(map(config["name"].startswith, ['__main__', 'fedmsg'])):
config["name"] = None
@@ -139,7 +146,11 @@ def publish(self, topic=None, msg=None, modname=None):
if type(topic) == unicode:
topic = to_utf8(topic)
- msg = dict(topic=topic, msg=msg, timestamp=time.time())
+ self._i += 1
+ msg = dict(topic=topic, msg=msg, timestamp=time.time(), i=self._i)
+
+ if self.c.get('sign_messages', False):
+ msg = fedmsg.crypto.sign(msg, **self.c)
self.publisher.send_multipart([topic, fedmsg.json.dumps(msg)])
View
@@ -43,26 +43,26 @@
log = logging.getLogger('fedmsg')
-def sign(message, ssldir, fqdn, **config):
+def sign(message, ssldir, certname, **config):
""" Insert two new fields into the message dict and return it.
'signature' - the computed RSA message digest of the JSON repr.
'certificate' - the base64 X509 certificate of the sending host.
"""
certificate = M2Crypto.X509.load_cert(
- "%s/certs/%s.pem" % (ssldir, fqdn)).as_pem()
+ "%s/certs/%s.pem" % (ssldir, certname)).as_pem()
# FIXME ? -- Opening this file requires elevated privileges in stg/prod.
rsa_private = M2Crypto.RSA.load_key(
- "%s/private_keys/%s.pem" % (ssldir, fqdn))
+ "%s/private_keys/%s.pem" % (ssldir, certname))
signature = rsa_private.sign_rsassa_pss(fedmsg.json.dumps(message))
# Return a new dict containing the pairs in the original message as well
# as the new authn fields.
return dict(message.items() + [
- ('signature', signature),
- ('certificate', certificate),
+ ('signature', signature.encode('base64')),
+ ('certificate', certificate.encode('base64')),
])
@@ -93,7 +93,9 @@ def fail(reason):
return fail("No %r field found." % field)
# Peal off the auth datums
- signature, certificate = message['signature'], message['certificate']
+ decode = lambda obj: obj.decode('base64')
+ signature, certificate = map(decode, (
+ message['signature'], message['certificate']))
message = strip_credentials(message)
# Build an X509 object
@@ -1,7 +1,10 @@
""" Test config. """
+import os
import socket
import random
+SEP = os.path.sep
+here = os.getcwd()
hostname = socket.gethostname()
# Pick random ports for the tests so travis-ci doesn't flip out.
@@ -24,4 +27,16 @@
irc=[],
zmq_enabled=True,
zmq_strict=False,
+
+ # SSL stuff.
+ sign_messages=True,
+ validate_signatures=True,
+ ssldir=SEP.join([here, 'dev_certs']),
+
+ certnames={
+ hostname: "test_cert",
+ # In prod/stg, map hostname to the name of the cert in ssldir.
+ # Unfortunately, we can't use socket.getfqdn()
+ #"app01.stg": "app01.stg.phx2.fedoraproject.org",
+ },
)
@@ -14,7 +14,7 @@ def setUp(self):
# Normally this is /var/lib/puppet/ssl
'ssldir': SEP.join((here, 'test_certs')),
# Normally this is 'app01.stg.phx2.fedoraproject.org'
- 'fqdn': 'test_cert',
+ 'certname': 'test_cert',
}
def tearDown(self):
View
@@ -31,6 +31,7 @@
from nose.tools import eq_, assert_true, assert_false, raises
import fedmsg.config
+import fedmsg.consumers
import fedmsg.json
from fedmsg.producers.heartbeat import HeartbeatProducer
@@ -149,7 +150,7 @@ def test_consumer(self):
obj = {'secret': secret}
messages_received = []
- class TestConsumer(moksha.api.hub.consumer.Consumer):
+ class TestConsumer(fedmsg.consumers.FedmsgConsumer):
topic = self.fq_topic
def consume(self, message):
@@ -174,15 +175,15 @@ def test_double_consumers(self):
obj = {'secret': secret}
messages_received = []
- class TestConsumer1(moksha.api.hub.consumer.Consumer):
+ class TestConsumer1(fedmsg.consumers.FedmsgConsumer):
topic = self.fq_topic
def consume(self, message):
messages_received.append(
message['body']['msg']
)
- class TestConsumer2(moksha.api.hub.consumer.Consumer):
+ class TestConsumer2(fedmsg.consumers.FedmsgConsumer):
topic = self.fq_topic
def consume(self, message):
View
@@ -28,7 +28,7 @@
'simplejson',
'fabulous',
'kitchen',
- 'moksha>=0.8.3',
+ 'moksha>=0.8.6',
#'daemon',
'M2Crypto',
'm2ext',

0 comments on commit e08f184

Please sign in to comment.