Skip to content

Commit

Permalink
Signing and validating messages sent.
Browse files Browse the repository at this point in the history
  • Loading branch information
ralphbean committed Jun 21, 2012
1 parent 7e62441 commit e08f184
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 21 deletions.
1 change: 1 addition & 0 deletions dev_certs
19 changes: 19 additions & 0 deletions fedmsg.d/ssl.py
Original file line number Original file line Diff line number Diff line change
@@ -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",
},
)
20 changes: 20 additions & 0 deletions fedmsg/consumers/__init__.py
Original file line number Original file line Diff line number Diff line change
@@ -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.")
4 changes: 2 additions & 2 deletions fedmsg/consumers/dummy.py
Original file line number Original file line Diff line number Diff line change
@@ -1,13 +1,13 @@
import fedmsg import fedmsg


from paste.deploy.converters import asbool from paste.deploy.converters import asbool
from moksha.api.hub.consumer import Consumer from fedmsg.consumers import FedmsgConsumer


import logging import logging
log = logging.getLogger("moksha.hub") log = logging.getLogger("moksha.hub")




class DummyConsumer(Consumer): class DummyConsumer(FedmsgConsumer):
topic = "org.fedoraproject.*" topic = "org.fedoraproject.*"


def __init__(self, hub): def __init__(self, hub):
Expand Down
4 changes: 2 additions & 2 deletions fedmsg/consumers/ircbot.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import pygments.formatters import pygments.formatters


from paste.deploy.converters import asbool 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.words.protocols import irc
from twisted.internet import protocol from twisted.internet import protocol
Expand Down Expand Up @@ -97,7 +97,7 @@ def clientConnectionFailed(self, connector, reason):
log.error("Could not connect: %s" % (reason,)) log.error("Could not connect: %s" % (reason,))




class IRCBotConsumer(Consumer): class IRCBotConsumer(FedmsgConsumer):
topic = "org.fedoraproject.*" topic = "org.fedoraproject.*"


def __init__(self, hub): def __init__(self, hub):
Expand Down
10 changes: 7 additions & 3 deletions fedmsg/consumers/relay.py
Original file line number Original file line Diff line number Diff line change
@@ -1,13 +1,13 @@
import fedmsg import fedmsg


from paste.deploy.converters import asbool from paste.deploy.converters import asbool
from moksha.api.hub.consumer import Consumer from fedmsg.consumers import FedmsgConsumer


import logging import logging
log = logging.getLogger("moksha.hub") log = logging.getLogger("moksha.hub")




class RelayConsumer(Consumer): class RelayConsumer(FedmsgConsumer):
topic = "org.fedoraproject.*" topic = "org.fedoraproject.*"


def __init__(self, hub): def __init__(self, hub):
Expand All @@ -18,7 +18,11 @@ def __init__(self, hub):
log.info('fedmsg.consumers.relay:RelayConsumer disabled.') log.info('fedmsg.consumers.relay:RelayConsumer disabled.')
return 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): def consume(self, msg):
## FIXME - for some reason twisted is screwing up fedmsg. ## FIXME - for some reason twisted is screwing up fedmsg.
Expand Down
17 changes: 14 additions & 3 deletions fedmsg/core.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from kitchen.text.converters import to_utf8 from kitchen.text.converters import to_utf8


import fedmsg.json import fedmsg.json
import fedmsg.crypto




def _listify(obj): def _listify(obj):
Expand All @@ -19,10 +20,17 @@ def _listify(obj):




class FedMsgContext(object): class FedMsgContext(object):
# A counter for messages sent.
_i = 0

def __init__(self, **config): def __init__(self, **config):
super(FedMsgContext, self).__init__() super(FedMsgContext, self).__init__()


self.c = config 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 # Prepare our context and publisher
self.context = zmq.Context(config['io_threads']) self.context = zmq.Context(config['io_threads'])
Expand All @@ -32,8 +40,7 @@ def __init__(self, **config):
# If no name is provided, use the calling module's __name__ to decide # If no name is provided, use the calling module's __name__ to decide
# which publishing endpoint to use. # which publishing endpoint to use.
if not config.get("name", None): if not config.get("name", None):
hostname = socket.gethostname().split('.', 1)[0] config["name"] = self.guess_calling_module() + '.' + self.hostname
config["name"] = self.guess_calling_module() + '.' + hostname


if any(map(config["name"].startswith, ['__main__', 'fedmsg'])): if any(map(config["name"].startswith, ['__main__', 'fedmsg'])):
config["name"] = None config["name"] = None
Expand Down Expand Up @@ -139,7 +146,11 @@ def publish(self, topic=None, msg=None, modname=None):
if type(topic) == unicode: if type(topic) == unicode:
topic = to_utf8(topic) 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)]) self.publisher.send_multipart([topic, fedmsg.json.dumps(msg)])


Expand Down
14 changes: 8 additions & 6 deletions fedmsg/crypto.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -43,26 +43,26 @@
log = logging.getLogger('fedmsg') 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. """ Insert two new fields into the message dict and return it.
'signature' - the computed RSA message digest of the JSON repr. 'signature' - the computed RSA message digest of the JSON repr.
'certificate' - the base64 X509 certificate of the sending host. 'certificate' - the base64 X509 certificate of the sending host.
""" """


certificate = M2Crypto.X509.load_cert( 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. # FIXME ? -- Opening this file requires elevated privileges in stg/prod.
rsa_private = M2Crypto.RSA.load_key( 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)) signature = rsa_private.sign_rsassa_pss(fedmsg.json.dumps(message))


# Return a new dict containing the pairs in the original message as well # Return a new dict containing the pairs in the original message as well
# as the new authn fields. # as the new authn fields.
return dict(message.items() + [ return dict(message.items() + [
('signature', signature), ('signature', signature.encode('base64')),
('certificate', certificate), ('certificate', certificate.encode('base64')),
]) ])




Expand Down Expand Up @@ -93,7 +93,9 @@ def fail(reason):
return fail("No %r field found." % field) return fail("No %r field found." % field)


# Peal off the auth datums # 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) message = strip_credentials(message)


# Build an X509 object # Build an X509 object
Expand Down
15 changes: 15 additions & 0 deletions fedmsg/tests/fedmsg-test-config.py
Original file line number Original file line Diff line number Diff line change
@@ -1,7 +1,10 @@
""" Test config. """ """ Test config. """
import os
import socket import socket
import random import random


SEP = os.path.sep
here = os.getcwd()
hostname = socket.gethostname() hostname = socket.gethostname()


# Pick random ports for the tests so travis-ci doesn't flip out. # Pick random ports for the tests so travis-ci doesn't flip out.
Expand All @@ -24,4 +27,16 @@
irc=[], irc=[],
zmq_enabled=True, zmq_enabled=True,
zmq_strict=False, 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",
},
) )
2 changes: 1 addition & 1 deletion fedmsg/tests/test_crypto.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def setUp(self):
# Normally this is /var/lib/puppet/ssl # Normally this is /var/lib/puppet/ssl
'ssldir': SEP.join((here, 'test_certs')), 'ssldir': SEP.join((here, 'test_certs')),
# Normally this is 'app01.stg.phx2.fedoraproject.org' # Normally this is 'app01.stg.phx2.fedoraproject.org'
'fqdn': 'test_cert', 'certname': 'test_cert',
} }


def tearDown(self): def tearDown(self):
Expand Down
7 changes: 4 additions & 3 deletions fedmsg/tests/test_hub.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from nose.tools import eq_, assert_true, assert_false, raises from nose.tools import eq_, assert_true, assert_false, raises


import fedmsg.config import fedmsg.config
import fedmsg.consumers
import fedmsg.json import fedmsg.json
from fedmsg.producers.heartbeat import HeartbeatProducer from fedmsg.producers.heartbeat import HeartbeatProducer


Expand Down Expand Up @@ -149,7 +150,7 @@ def test_consumer(self):
obj = {'secret': secret} obj = {'secret': secret}
messages_received = [] messages_received = []


class TestConsumer(moksha.api.hub.consumer.Consumer): class TestConsumer(fedmsg.consumers.FedmsgConsumer):
topic = self.fq_topic topic = self.fq_topic


def consume(self, message): def consume(self, message):
Expand All @@ -174,15 +175,15 @@ def test_double_consumers(self):
obj = {'secret': secret} obj = {'secret': secret}
messages_received = [] messages_received = []


class TestConsumer1(moksha.api.hub.consumer.Consumer): class TestConsumer1(fedmsg.consumers.FedmsgConsumer):
topic = self.fq_topic topic = self.fq_topic


def consume(self, message): def consume(self, message):
messages_received.append( messages_received.append(
message['body']['msg'] message['body']['msg']
) )


class TestConsumer2(moksha.api.hub.consumer.Consumer): class TestConsumer2(fedmsg.consumers.FedmsgConsumer):
topic = self.fq_topic topic = self.fq_topic


def consume(self, message): def consume(self, message):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
'simplejson', 'simplejson',
'fabulous', 'fabulous',
'kitchen', 'kitchen',
'moksha>=0.8.3', 'moksha>=0.8.6',
#'daemon', #'daemon',
'M2Crypto', 'M2Crypto',
'm2ext', 'm2ext',
Expand Down

0 comments on commit e08f184

Please sign in to comment.