diff --git a/README.md b/README.md index 1573de38..58746a3f 100644 --- a/README.md +++ b/README.md @@ -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://:3902/ + KERIA_IURLS: http://:5642/oobi/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha;http://: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 diff --git a/images/keria.dockerfile b/images/keria.dockerfile index 5d2f0bb5..ecc6424d 100644 --- a/images/keria.dockerfile +++ b/images/keria.dockerfile @@ -52,4 +52,6 @@ EXPOSE 3903 COPY src/ src/ -ENTRYPOINT ["keria", "start", "--config-file", "demo-witness-oobis", "--config-dir", "./scripts"] +ENTRYPOINT ["keria"] + +CMD [ "start" ] diff --git a/src/keria/app/agenting.py b/src/keria/app/agenting.py index 55ffbd80..3d6f96a4 100644 --- a/src/keria/app/agenting.py +++ b/src/keria/app/agenting.py @@ -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', @@ -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()) @@ -157,7 +157,7 @@ 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 @@ -165,7 +165,11 @@ def __init__(self, name, bran, base="", configFile=None, configDir=None, adb=Non 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, @@ -176,7 +180,7 @@ 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, @@ -184,32 +188,42 @@ def create(self, caid, salt=None): 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)) @@ -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) @@ -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: diff --git a/src/keria/app/cli/commands/start.py b/src/keria/app/cli/commands/start.py index 9a9dc2dd..21272d48 100644 --- a/src/keria/app/cli/commands/start.py +++ b/src/keria/app/cli/commands/start.py @@ -7,6 +7,7 @@ """ import argparse import logging +import os from keri import __version__ from keri import help @@ -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", @@ -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) @@ -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) diff --git a/tests/app/test_agenting.py b/tests/app/test_agenting.py index 2c04f2b3..6f4cf910 100644 --- a/tests/app/test_agenting.py +++ b/tests/app/test_agenting.py @@ -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)