mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-27 10:44:33 +00:00
Started SimpleSantiago.
Now I just need the tests.
This commit is contained in:
parent
2d1e473a9b
commit
24528100cb
199
ugly_hacks/santiago/simple_santiago.py
Normal file
199
ugly_hacks/santiago/simple_santiago.py
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
#! /usr/bin/python -*- mode: auto-fill; fill-column: 80; -*-
|
||||||
|
|
||||||
|
"""A simple Santiago service.
|
||||||
|
|
||||||
|
I'm tired of overanalyzing this, so I'll write something simple and work from
|
||||||
|
there.
|
||||||
|
|
||||||
|
FIXME: add that whole pgp thing.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import cherrypy
|
||||||
|
import gnupg
|
||||||
|
import httplib, urllib
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleSantiago(object):
|
||||||
|
"""This Santiago is a less extensible Santiago.
|
||||||
|
|
||||||
|
The client and server are unified, and it has hardcoded support for
|
||||||
|
protocols.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, listeners, senders, hosting, consuming, me):
|
||||||
|
"""Create a Santiago with the specified parameters.
|
||||||
|
|
||||||
|
listeners and senders are both protocol-specific dictionaries containing
|
||||||
|
relevant settings per protocol:
|
||||||
|
|
||||||
|
{ "http": { "port": 80 } }
|
||||||
|
|
||||||
|
hosting and consuming are service dictionaries, one being an inversion
|
||||||
|
of the other. hosting contains services you host, while consuming lists
|
||||||
|
services you use, as a client.
|
||||||
|
|
||||||
|
hosting: { "someKey": { "someService": ( "http://a.list",
|
||||||
|
"http://of.locations" )}}
|
||||||
|
|
||||||
|
consuming: { "someService": { "someKey": ( "http://a.list",
|
||||||
|
"http://of.locations" )}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.senders = senders
|
||||||
|
self.hosting = hosting
|
||||||
|
self.consuming = consuming
|
||||||
|
|
||||||
|
self._create_listeners(listeners)
|
||||||
|
self.me = me
|
||||||
|
|
||||||
|
def _create_listeners(self, listeners):
|
||||||
|
"""Iterates through each known protocol creating listeners for all.
|
||||||
|
|
||||||
|
Unfortunately, I won't be able to do this for real because this implies
|
||||||
|
a control flow inversion, treating servers as clients to my meta-server,
|
||||||
|
and most servers aren't built to tolerate that very well (or I don't
|
||||||
|
know how to handle it). I'll work on it though.
|
||||||
|
|
||||||
|
"""
|
||||||
|
for protocol in listeners.iterkeys():
|
||||||
|
method = "_create_%s_listener" % protocol
|
||||||
|
|
||||||
|
try:
|
||||||
|
getattr(self, method)(**listeners[protocol])
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def _create_https_listener(self, port=1):
|
||||||
|
"""Registers an HTTPS listener.
|
||||||
|
|
||||||
|
TODO: complete. that cherrypy daemon thing.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.socket_port = port
|
||||||
|
index.exposed = True
|
||||||
|
|
||||||
|
def am_i(self, server):
|
||||||
|
return self.me == server
|
||||||
|
|
||||||
|
def learn_service(self, client, service, locations):
|
||||||
|
"""Learn a service somebody else hosts for me."""
|
||||||
|
|
||||||
|
self.hosting[client][santiago].update(set(locations))
|
||||||
|
|
||||||
|
def get_locations(self, client, service):
|
||||||
|
"""Return where I'm hosting the service for the client.
|
||||||
|
|
||||||
|
Return nothing if the client or service are unrecognized.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.hosting[client][service]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def index(self, **kwargs):
|
||||||
|
"""Process an incoming Santiago request.
|
||||||
|
|
||||||
|
This tag doesn't do any real processing, it just catches and hides
|
||||||
|
errors from the sender, so that every request is met with silence.
|
||||||
|
|
||||||
|
The only data an attacker should be able to pull from a client is:
|
||||||
|
|
||||||
|
- The fact that a server exists and is serving HTTP 200s.
|
||||||
|
- The round-trip time for that response.
|
||||||
|
- Whether the server is up or down.
|
||||||
|
|
||||||
|
Worst case scenario, a client causes the Python interpreter to segfault
|
||||||
|
and the Santiago process comes down, so the system starts rejecting
|
||||||
|
connections by default.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# no matter what happens, the sender will never hear about it.
|
||||||
|
try:
|
||||||
|
request = unpack_request(kwargs)
|
||||||
|
|
||||||
|
handle_request(request["from"], request["to"],
|
||||||
|
request["client"], request["host"],
|
||||||
|
request["service"], request["reply_to"])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def unpack_request(self, kwargs):
|
||||||
|
"""Decrypt and verify the request.
|
||||||
|
|
||||||
|
Give up if it doesn't pass muster.
|
||||||
|
|
||||||
|
TODO: complete.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def handle_request(self, from_, to, client, host, service, reply_to):
|
||||||
|
"""Actually do the request processing.
|
||||||
|
|
||||||
|
#. Verify we're willing to host for both the client and proxy. If we
|
||||||
|
aren't, quit and return nothing.
|
||||||
|
|
||||||
|
#. Forward the request if it's not for me.
|
||||||
|
|
||||||
|
#. Learn new Santiagi if they were sent.
|
||||||
|
|
||||||
|
#. Reply to the client.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.hosting[from_]
|
||||||
|
self.hosting[client]
|
||||||
|
except KeyError:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.am_i(to):
|
||||||
|
self.proxy()
|
||||||
|
|
||||||
|
if reply_to is not None:
|
||||||
|
self.learn_service(client, "santiago", reply_to)
|
||||||
|
|
||||||
|
self.reply(client, self.get_hosting_locations(client, service), service,
|
||||||
|
self.get_serving_locations(client, service))
|
||||||
|
|
||||||
|
def proxy(self):
|
||||||
|
"""Pass off a request to another Santiago.
|
||||||
|
|
||||||
|
TODO: complete.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def reply(self, client, location, service, reply_to):
|
||||||
|
"""Send the reply to each Santiago client.
|
||||||
|
|
||||||
|
Don't queue, just immediately send the reply to each location we know.
|
||||||
|
|
||||||
|
It's both simple and as reliable as possible.
|
||||||
|
|
||||||
|
"""
|
||||||
|
params = urllib.urlencode({ "request": pgp.signencrypt(
|
||||||
|
{"from": self.me, "to": client,
|
||||||
|
"host": self.me, "client": client,
|
||||||
|
"service": service, "locations": location,
|
||||||
|
"reply_to": self.get_hosting_locations(client, "santiago")})})
|
||||||
|
|
||||||
|
for reply in reply_to:
|
||||||
|
connection = httplib.HTTPSConnection(reply)
|
||||||
|
connection.request("POST", "", params)
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
port = 8090
|
||||||
|
|
||||||
|
listeners = { "https": { "port": port } }
|
||||||
|
senders = ({ "protocol": "http",
|
||||||
|
"proxy": "localhost:4030" },)
|
||||||
|
|
||||||
|
hosting = { "a": { "santiago": set( "localhost:%s" % port )}}
|
||||||
|
consuming = { "santiagao": { "a": set( "localhost:4030" )}}
|
||||||
|
|
||||||
|
cherrypy.quickstart(SimpleSantiago(listeners, senders,
|
||||||
|
hosting, consuming, "b"),
|
||||||
|
'/')
|
||||||
Loading…
x
Reference in New Issue
Block a user