Skip to content

Commit

Permalink
Add TLS support (#102)
Browse files Browse the repository at this point in the history
* Add TLS support

* Add createHttpServer test

* Return HTTPS OOBI, agent, and controller URLs
  • Loading branch information
kentbull authored Sep 28, 2023
1 parent 15bafe4 commit ea1f913
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 29 deletions.
60 changes: 45 additions & 15 deletions src/keria/app/agenting.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import json
import os
from dataclasses import asdict
from urllib.parse import urlparse
from urllib.parse import urlparse, urljoin

from keri import kering
from keri.app.notifying import Notifier
Expand All @@ -16,7 +16,7 @@
import falcon
from falcon import media
from hio.base import doing
from hio.core import http
from hio.core import http, tcp
from hio.help import decking
from keri.app import configing, keeping, habbing, storing, signaling, oobiing, agenting, delegating, \
forwarding, querying, connecting, grouping
Expand Down Expand Up @@ -46,15 +46,17 @@
logger = ogler.getLogger()


def setup(name, bran, adminPort, bootPort, base='', httpPort=None, configFile=None, configDir=None):
def setup(name, bran, adminPort, bootPort, base='', httpPort=None, configFile=None, configDir=None,
keypath=None, certpath=None, cafilepath=None):
""" Set up an ahab in Signify mode """

agency = Agency(name=name, base=base, bran=bran, configFile=configFile, configDir=configDir)
bootApp = falcon.App(middleware=falcon.CORSMiddleware(
allow_origins='*', allow_credentials='*',
expose_headers=['cesr-attachment', 'cesr-date', 'content-type', 'signature', 'signature-input',
'signify-resource', 'signify-timestamp']))
bootServer = http.Server(port=bootPort, app=bootApp)

bootServer = createHttpServer(bootPort, bootApp, keypath, certpath, cafilepath)
bootServerDoer = http.ServerDoer(server=bootServer)
bootEnd = BootEnd(agency)
bootApp.add_route("/boot", bootEnd)
Expand All @@ -72,7 +74,7 @@ def setup(name, bran, adminPort, bootPort, base='', httpPort=None, configFile=No
app.req_options.media_handlers.update(media.Handlers())
app.resp_options.media_handlers.update(media.Handlers())

adminServer = http.Server(port=adminPort, app=app)
adminServer = createHttpServer(adminPort, app, keypath, certpath, cafilepath)
adminServerDoer = http.ServerDoer(server=adminServer)

doers = [agency, bootServerDoer, adminServerDoer]
Expand All @@ -95,7 +97,7 @@ def setup(name, bran, adminPort, bootPort, base='', httpPort=None, configFile=No
ending.loadEnds(agency=agency, app=happ)
indirecting.loadEnds(agency=agency, app=happ)

server = http.Server(port=httpPort, app=happ)
server = createHttpServer(httpPort, happ, keypath, certpath, cafilepath)
httpServerDoer = http.ServerDoer(server=server)
doers.append(httpServerDoer)

Expand All @@ -110,6 +112,31 @@ def setup(name, bran, adminPort, bootPort, base='', httpPort=None, configFile=No
return doers


def createHttpServer(port, app, keypath=None, certpath=None, cafilepath=None):
"""
Create an HTTP or HTTPS server depending on whether TLS key material is present
Parameters:
port (int) : port to listen on for all HTTP(s) server instances
app (falcon.App) : application instance to pass to the http.Server instance
keypath (string) : the file path to the TLS private key
certpath (string) : the file path to the TLS signed certificate (public key)
cafilepath (string): the file path to the TLS CA certificate chain file
Returns:
hio.core.http.Server
"""
if keypath is not None and certpath is not None and cafilepath is not None:
servant = tcp.ServerTls(certify=False,
keypath=keypath,
certpath=certpath,
cafilepath=cafilepath,
port=port)
server = http.Server(port=port, app=app, servant=servant)
else:
server = http.Server(port=port, app=app)
return server


class Agency(doing.DoDoer):
"""
Agency
Expand Down Expand Up @@ -874,31 +901,34 @@ def on_get(req, rep, alias):
if role in (kering.Roles.witness,): # Fetch URL OOBIs for all witnesses
oobis = []
for wit in hab.kever.wits:
urls = hab.fetchUrls(eid=wit, scheme=kering.Schemes.http)
urls = hab.fetchUrls(eid=wit, scheme=kering.Schemes.http) or hab.fetchUrls(eid=wit, scheme=kering.Schemes.https)
if not urls:
raise falcon.HTTPNotFound(description=f"unable to query witness {wit}, no http endpoint")

up = urlparse(urls[kering.Schemes.http])
oobis.append(f"http://{up.hostname}:{up.port}/oobi/{hab.pre}/witness/{wit}")
url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https]
up = urlparse(url)
oobis.append(urljoin(up.geturl(), f"/oobi/{hab.pre}/witness/{wit}"))
res["oobis"] = oobis
elif role in (kering.Roles.controller,): # Fetch any controller URL OOBIs
oobis = []
urls = hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.http)
urls = hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.http) or hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.https)
if not urls:
raise falcon.HTTPNotFound(description=f"unable to query controller {hab.pre}, no http endpoint")

up = urlparse(urls[kering.Schemes.http])
oobis.append(f"http://{up.hostname}:{up.port}/oobi/{hab.pre}/controller")
url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https]
up = urlparse(url)
oobis.append(urljoin(up.geturl(), f"/oobi/{hab.pre}/controller"))
res["oobis"] = oobis
elif role in (kering.Roles.agent,):
oobis = []
roleUrls = hab.fetchRoleUrls(hab.pre, scheme=kering.Schemes.http, role=kering.Roles.agent)
roleUrls = hab.fetchRoleUrls(hab.pre, scheme=kering.Schemes.http, role=kering.Roles.agent) or hab.fetchRoleurls(hab.pre, scheme=kering.Schemes.https, role=kering.Roles.agent)
if not roleUrls:
raise falcon.HTTPNotFound(description=f"unable to query controller {hab.pre}, no http endpoint")

for eid, urls in roleUrls['agent'].items():
up = urlparse(urls[kering.Schemes.http])
oobis.append(f"http://{up.hostname}:{up.port}/oobi/{hab.pre}/agent/{eid}")
url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https]
up = urlparse(url)
oobis.append(urljoin(up.geturl(), f"/oobi/{hab.pre}/agent/{eid}"))
res["oobis"] = oobis
else:
rep.status = falcon.HTTP_404
Expand Down
27 changes: 17 additions & 10 deletions src/keria/app/aiding.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"""
import json
from dataclasses import asdict
from urllib.parse import urlparse
from urllib.parse import urlparse, urljoin

import falcon
from keri import kering
Expand Down Expand Up @@ -641,24 +641,26 @@ def on_get(req, rep, name):
if role in (kering.Roles.witness,): # Fetch URL OOBIs for all witnesses
oobis = []
for wit in hab.kever.wits:
urls = hab.fetchUrls(eid=wit, scheme=kering.Schemes.http)
urls = hab.fetchUrls(eid=wit, scheme=kering.Schemes.http) or hab.fetchUrls(eid=wit, scheme=kering.Schemes.https)
if not urls:
raise falcon.HTTPNotFound(description=f"unable to query witness {wit}, no http endpoint")

up = urlparse(urls[kering.Schemes.http])
oobis.append(f"{kering.Schemes.http}://{up.hostname}:{up.port}/oobi/{hab.pre}/witness/{wit}")
url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https]
up = urlparse(url)
oobis.append(urljoin(up.geturl(), f"/oobi/{hab.pre}/witness/{wit}"))
res["oobis"] = oobis
elif role in (kering.Roles.controller,): # Fetch any controller URL OOBIs
oobis = []
urls = hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.http)
urls = hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.http) or hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.https)
if not urls:
raise falcon.HTTPNotFound(description=f"unable to query controller {hab.pre}, no http endpoint")

up = urlparse(urls[kering.Schemes.http])
oobis.append(f"{kering.Schemes.http}://{up.hostname}:{up.port}/oobi/{hab.pre}/controller")
url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https]
up = urlparse(url)
oobis.append(urljoin(up.geturl(), f"/oobi/{hab.pre}/controller"))
res["oobis"] = oobis
elif role in (kering.Roles.agent,): # Fetch URL OOBIs for all witnesses
roleUrls = hab.fetchRoleUrls(cid=hab.pre, role=kering.Roles.agent, scheme=kering.Schemes.http)
roleUrls = hab.fetchRoleUrls(cid=hab.pre, role=kering.Roles.agent, scheme=kering.Schemes.http) or hab.fetchRoleUrls(cid=hab.pre, role=kering.Roles.agent, scheme=kering.Schemes.https)
if kering.Roles.agent not in roleUrls:
raise falcon.HTTPNotFound(description=f"unable to query agent roles for {hab.pre}, no http endpoint")

Expand All @@ -668,9 +670,14 @@ def on_get(req, rep, name):
for agent in set(aoobis.keys()):
murls = aoobis.naball(agent)
for murl in murls:
for url in murl.naball(kering.Schemes.http):
urls = []
if kering.Schemes.http in murl:
urls.extend(murl.naball(kering.Schemes.http))
if kering.Schemes.https in murl:
urls.extend(murl.naball(kering.Schemes.https))
for url in urls:
up = urlparse(url)
oobis.append(f"{kering.Schemes.http}://{up.hostname}:{up.port}/oobi/{hab.pre}/agent/{agent}")
oobis.append(urljoin(up.geturl(), f"/oobi/{hab.pre}/agent/{agent}"))

res["oobis"] = oobis
else:
Expand Down
20 changes: 16 additions & 4 deletions src/keria/app/cli/commands/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@
action="store",
default=None,
help="directory override for configuration data")
parser.add_argument("--keypath", action="store", required=False, default=None,
help="TLS server private key file")
parser.add_argument("--certpath", action="store", required=False, default=None,
help="TLS server signed certificate (public key) file")
parser.add_argument("--cafilepath", action="store", required=False, default=None,
help="TLS server CA certificate chain")


def launch(args):
Expand All @@ -72,16 +78,19 @@ def launch(args):
http=int(args.http),
boot=int(args.boot),
configFile=args.configFile,
configDir=args.configDir)
configDir=args.configDir,
keypath=args.keypath,
certpath=args.certpath,
cafilepath=args.cafilepath)

logger.info("******* Ended Agent for %s listening: admin/%s, http/%s"
".******", args.name, args.admin, args.http)


def runAgent(name="ahab", base="", bran="", admin=3901, http=3902, boot=3903, configFile=None,
configDir=None, expire=0.0):
configDir=None, keypath=None, certpath=None, cafilepath=None, expire=0.0):
"""
Setup and run one witness
Setup and run a KERIA Agency
"""

doers = []
Expand All @@ -90,6 +99,9 @@ def runAgent(name="ahab", base="", bran="", admin=3901, http=3902, boot=3903, co
httpPort=http,
bootPort=boot,
configFile=configFile,
configDir=configDir))
configDir=configDir,
keypath=keypath,
certpath=certpath,
cafilepath=cafilepath))

directing.runController(doers=doers, expire=expire)
25 changes: 25 additions & 0 deletions tests/app/test_agenting.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
import shutil

import falcon
import hio
from falcon import testing
from hio.base import doing
from hio.core import http, tcp
from hio.help import decking
from keri import kering
from keri.app import habbing, configing, oobiing, querying
Expand Down Expand Up @@ -386,8 +388,31 @@ def test_querier(helpers):
assert isinstance(qryDoer, querying.QueryDoer) is True
assert qryDoer.pre == "EI7AkI40M11MS7lkTCb10JC9-nDt-tXwQh44OHAFlv_9"

class MockServerTls:
def __init__(self, certify, keypath, certpath, cafilepath, port):
pass


class MockHttpServer:
def __init__(self, port, app, servant=None):
self.servant = servant


def test_createHttpServer(monkeypatch):
port = 5632
app = falcon.App()
server = agenting.createHttpServer(port, app)
assert isinstance(server, http.Server)

monkeypatch.setattr(hio.core.tcp, 'ServerTls', MockServerTls)
monkeypatch.setattr(hio.core.http, 'Server', MockHttpServer)

server = agenting.createHttpServer(port, app, keypath='keypath', certpath='certpath',
cafilepath='cafilepath')

assert isinstance(server, MockHttpServer)
assert isinstance(server.servant, MockServerTls)




Expand Down

0 comments on commit ea1f913

Please sign in to comment.