Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: configure keria with environment variables #333

Merged
merged 2 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,23 @@ All Agent db access is through the associated Agent.
```

#### Run with docker
* Specify an entrypoint with proper configuration, for instance if you want to use the demo-witness-oobis that is under the scripts/keri/cf dir:
```
ENTRYPOINT ["keria", "start", "--config-file", "demo-witness-oobis", "--config-dir", "./scripts"]

* The easiest way to configure a keria container is with environment variables. See below example for a working docker-compose configuration

```yaml
services:
keria:
image: weboftrust/keria:latest
environment:
KERI_AGENT_CORS: 1
KERIA_CURLS: http://<keria-hostname>:3902/
KERIA_IURLS: http://<witness-demo-hostname>:5642/oobi/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha;http://<witness-demo-hostname>:5643/oobi/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM
ports:
- 3901:3901
- 3902:3902
- 3903:3903
```

You can see a [working example here](https://github.com/WebOfTrust/signify-ts/blob/main/docker-compose.yaml).

### Running Tests
Expand Down
4 changes: 3 additions & 1 deletion images/keria.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,6 @@ EXPOSE 3903

COPY src/ src/

ENTRYPOINT ["keria", "start", "--config-file", "demo-witness-oobis", "--config-dir", "./scripts"]
ENTRYPOINT ["keria"]

CMD [ "start" ]
78 changes: 46 additions & 32 deletions src/keria/app/agenting.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@


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

agency = Agency(name=name, base=base, bran=bran, configFile=configFile, configDir=configDir)
agency = Agency(name=name, base=base, bran=bran, configFile=configFile, configDir=configDir, releaseTimeout=releaseTimeout, curls=curls, iurls=iurls, durls=durls)
bootApp = falcon.App(middleware=falcon.CORSMiddleware(
allow_origins='*', allow_credentials='*',
expose_headers=['cesr-attachment', 'cesr-date', 'content-type', 'signature', 'signature-input',
Expand All @@ -77,7 +77,7 @@ def setup(name, bran, adminPort, bootPort, base='', httpPort=None, configFile=No
allow_origins='*', allow_credentials='*',
expose_headers=['cesr-attachment', 'cesr-date', 'content-type', 'signature', 'signature-input',
'signify-resource', 'signify-timestamp']))
if os.getenv("KERI_AGENT_CORS", "false").lower() in ("true", "1"):
if cors:
app.add_middleware(middleware=httping.HandleCORS())
app.add_middleware(authing.SignatureValidationComponent(agency=agency, authn=authn, allowed=["/agent"]))
app.req_options.media_handlers.update(media.Handlers())
Expand Down Expand Up @@ -157,15 +157,19 @@ class Agency(doing.DoDoer):

"""

def __init__(self, name, bran, base="", configFile=None, configDir=None, adb=None, temp=False):
def __init__(self, name, bran, base="", releaseTimeout=None, configFile=None, configDir=None, adb=None, temp=False, curls=None, iurls=None, durls=None):
self.name = name
self.base = base
self.bran = bran
self.temp = temp
self.configFile = configFile
self.configDir = configDir
self.cf = None
if self.configFile is not None: # Load config file if creating database
self.curls = curls
self.iurls = iurls
self.durls = durls

if self.configFile is not None:
self.cf = configing.Configer(name=self.configFile,
base="",
headDirPath=self.configDir,
Expand All @@ -176,40 +180,50 @@ def __init__(self, name, bran, base="", configFile=None, configDir=None, adb=Non
self.agents = dict()

self.adb = adb if adb is not None else basing.AgencyBaser(name="TheAgency", base=base, reopen=True, temp=temp)
super(Agency, self).__init__(doers=[Releaser(self)], always=True)
super(Agency, self).__init__(doers=[Releaser(self, releaseTimeout=releaseTimeout)], always=True)

def create(self, caid, salt=None):
ks = keeping.Keeper(name=caid,
base=self.base,
temp=self.temp,
reopen=True)

cf = None
if self.cf is not None: # Load config file if creating database
data = dict(self.cf.get())
if "keria" in data:
curls = data["keria"]
data[f"agent-{caid}"] = curls
del data["keria"]

cf = configing.Configer(name=f"{caid}",
base="",
human=False,
temp=self.temp,
reopen=True,
clear=False)
cf.put(data)
timestamp = nowIso8601()
data = dict(self.cf.get() if self.cf is not None else { "dt": timestamp })

habName = f"agent-{caid}"
if "keria" in data:
data[habName] = data["keria"]
del data["keria"]

if self.curls is not None and isinstance(self.curls, list):
data[habName] = { "dt": timestamp, "curls": self.curls }

if self.iurls is not None and isinstance(self.iurls, list):
data["iurls"] = self.iurls

if self.durls is not None and isinstance(self.durls, list):
data["durls"] = self.durls

config = configing.Configer(name=f"{caid}",
base="",
human=False,
temp=self.temp,
reopen=True,
clear=False)

config.put(data)

# Create the Hab for the Agent with only 2 AIDs
agentHby = habbing.Habery(name=caid, base=self.base, bran=self.bran, ks=ks, cf=cf, temp=self.temp, salt=salt)
agentHab = agentHby.makeHab(f"agent-{caid}", ns="agent", transferable=True, delpre=caid)
agentHby = habbing.Habery(name=caid, base=self.base, bran=self.bran, ks=ks, cf=config, temp=self.temp, salt=salt)
agentHab = agentHby.makeHab(habName, ns="agent", transferable=True, delpre=caid)
agentRgy = Regery(hby=agentHby, name=agentHab.name, base=self.base, temp=self.temp)

agent = Agent(agentHby, agentRgy, agentHab,
agent = Agent(hby=agentHby,
rgy=agentRgy,
agentHab=agentHab,
caid=caid,
agency=self,
configDir=self.configDir,
configFile=self.configFile)
agency=self)

self.adb.agnt.pin(keys=(caid,),
val=coring.Prefixer(qb64=agent.pre))
Expand Down Expand Up @@ -804,17 +818,17 @@ def recur(self, tyme):
return False

class Releaser(doing.Doer):
KERIAReleaserTimeOut = "KERIA_RELEASER_TIMEOUT"
TimeoutRel = int(os.getenv(KERIAReleaserTimeOut, "86400"))
def __init__(self, agency):
""" Check open agents and close if idle for more than TimeoutRel seconds
def __init__(self, agency: Agency, releaseTimeout=86400):
""" Check open agents and close if idle for more than releaseTimeout seconds
Parameters:
agency (Agency): KERIA agent manager
releaseTimeout (int): Timeout in seconds

"""
self.tock = 60.0
self.agents = agency.agents
self.agency = agency
self.releaseTimeout = releaseTimeout

super(Releaser, self).__init__(tock=self.tock)

Expand All @@ -823,7 +837,7 @@ def recur(self, tyme=None):
idle = []
for caid in self.agents:
now = helping.nowUTC()
if (now - self.agents[caid].last) > datetime.timedelta(seconds=self.TimeoutRel):
if (now - self.agents[caid].last) > datetime.timedelta(seconds=self.releaseTimeout):
idle.append(caid)

for caid in idle:
Expand Down
71 changes: 33 additions & 38 deletions src/keria/app/cli/commands/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"""
import argparse
import logging
import os

from keri import __version__
from keri import help
Expand All @@ -25,29 +26,32 @@
parser.add_argument('-a', '--admin-http-port',
dest="admin",
action='store',
default=3901,
type=int,
default=os.getenv("KERIA_ADMIN_PORT", "3901"),
help="Admin port number the HTTP server listens on. Default is 3901.")
parser.add_argument('-H', '--http',
action='store',
default=3902,
type=int,
default=os.getenv("KERIA_HTTP_PORT", "3902"),
help="Local port number the HTTP server listens on. Default is 3902.")
parser.add_argument('-B', '--boot',
action='store',
default=3903,
type=int,
default=os.getenv("KERIA_BOOT_PORT", "3903"),
help="Boot port number the Boot HTTP server listens on. This port needs to be secured."
" Default is 3903.")
parser.add_argument('-n', '--name',
action='store',
default="keria",
help="Name of controller. Default is agent.")
help="Name of controller. Default is 'keria'.")
parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore',
required=False, default="")
parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)',
dest="bran", default=None) # passcode => bran
dest="bran",
default=os.getenv("KERIA_PASSCODE")) # passcode => bran
parser.add_argument('--config-file',
dest="configFile",
action='store',
default="",
help="configuration filename")
parser.add_argument("--config-dir",
dest="configDir",
Expand All @@ -60,11 +64,14 @@
help="TLS server signed certificate (public key) file")
parser.add_argument("--cafilepath", action="store", required=False, default=None,
help="TLS server CA certificate chain")
parser.add_argument("--loglevel", action="store", required=False, default="CRITICAL",
parser.add_argument("--loglevel", action="store", required=False, default=os.getenv("KERIA_LOG_LEVEL", "CRITICAL"),
help="Set log level to DEBUG | INFO | WARNING | ERROR | CRITICAL. Default is CRITICAL")
parser.add_argument("--logfile", action="store", required=False, default=None,
help="path of the log file. If not defined, logs will not be written to the file.")

def getListVariable(name):
value = os.getenv(name)
return value.split(";") if value else None

def launch(args):
help.ogler.level = logging.getLevelName(args.loglevel)
Expand All @@ -77,37 +84,25 @@ def launch(args):
logger.info("******* Starting Agent for %s listening: admin/%s, http/%s "
".******", args.name, args.admin, args.http)

runAgent(name=args.name,
base=args.base,
bran=args.bran,
admin=int(args.admin),
http=int(args.http),
boot=int(args.boot),
configFile=args.configFile,
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)
agency = agenting.setup(name=args.name or "ahab",
base=args.base or "",
bran=args.bran,
adminPort=args.admin,
httpPort=args.http,
bootPort=args.boot,
configFile=args.configFile,
configDir=args.configDir,
keypath=args.keypath,
certpath=args.certpath,
cafilepath=args.cafilepath,
cors=os.getenv("KERI_AGENT_CORS", "false").lower() in ("true", "1"),
releaseTimeout=int(os.getenv("KERIA_RELEASER_TIMEOUT", "86400")),
curls=getListVariable("KERIA_CURLS"),
iurls=getListVariable("KERIA_IURLS"),
durls=getListVariable("KERIA_DURLS"))

directing.runController(doers=agency, expire=0.0)

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

doers = []
doers.extend(agenting.setup(name=name, base=base, bran=bran,
adminPort=admin,
httpPort=http,
bootPort=boot,
configFile=configFile,
configDir=configDir,
keypath=keypath,
certpath=certpath,
cafilepath=cafilepath))

directing.runController(doers=doers, expire=expire)
logger.info("******* Ended Agent for %s listening: admin/%s, http/%s"
".******", args.name, args.admin, args.http)
46 changes: 46 additions & 0 deletions tests/app/test_agenting.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,52 @@ def test_agency():
assert caid not in agency.agents
assert len(agent.doers) == 0

def test_agency_without_config_file():
salt = b'0123456789abcdef'
salter = core.Salter(raw=salt)
cf = configing.Configer(name="keria", headDirPath=SCRIPTS_DIR, temp=True, reopen=True, clear=False)

with habbing.openHby(name="keria", salt=salter.qb64, temp=True, cf=cf) as hby:
hby.makeHab(name="test")

agency = agenting.Agency(name="agency", base="", bran=None, temp=True, configFile=None, configDir=SCRIPTS_DIR)
assert agency.cf is None

doist = doing.Doist(limit=1.0, tock=0.03125, real=True)
doist.extend(doers=[agency])

# Ensure we can still create agent
caid = "ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose"
agent = agency.create(caid, salt=salter.qb64)
assert agent.pre == "EIAEKYpTygdBtFHBrHKWeh0aYCdx0ZJqZtzQLFnaDB2b"

def test_agency_with_urls_from_arguments():
salt = b'0123456789abcdef'
salter = core.Salter(raw=salt)
cf = configing.Configer(name="keria", headDirPath=SCRIPTS_DIR, temp=True, reopen=True, clear=False)

with habbing.openHby(name="keria", salt=salter.qb64, temp=True, cf=cf) as hby:
hby.makeHab(name="test")

curls = ["http://example.com:3902/"]
iurls = ["http://example.com:5432/oobi"]
durls = ["http://example.com:7723/oobi"]
agency = agenting.Agency(name="agency", base="", bran=None, temp=True, configDir=SCRIPTS_DIR, curls=curls, iurls=iurls, durls=durls)
assert agency.cf is None

doist = doing.Doist(limit=1.0, tock=0.03125, real=True)
doist.extend(doers=[agency])

# Ensure we can still create agent
caid = "ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose"
agent = agency.create(caid, salt=salter.qb64)
assert agent.pre == "EIAEKYpTygdBtFHBrHKWeh0aYCdx0ZJqZtzQLFnaDB2b"

assert agent.hby.cf is not None
assert agent.hby.cf.get()[f"agent-{caid}"]["curls"] == curls
assert agent.hby.cf.get()["iurls"] == iurls
assert agent.hby.cf.get()["durls"] == durls

def test_boot_ends(helpers):
agency = agenting.Agency(name="agency", bran=None, temp=True)
doist = doing.Doist(limit=1.0, tock=0.03125, real=True)
Expand Down
Loading