@educreds/connector-agent
v1.0.0
Published
EduCreds Connector Agent — on-premises SIS/LMS integration bridge for automated credential issuance
Maintainers
Readme
EduCreds Connector Agent
Package: @educreds/connector-agent
Version: 1.0.0
Deployed by: the institution, on-premises (or in an institution-controlled VPC)
Contract with EduCreds cloud: HTTPS + HMAC + optional mTLS
This is the institution-side half of the Tier 1 (Enterprise Connector) and Tier 2 (Cloud Connector) architecture. It is a small, hardened Fastify server that exposes a fixed contract of read/write endpoints against your SIS. It is the only piece of EduCreds software that lives inside your data centre; nothing else needs to be installed on your side.
Corresponding cloud-side client: cert_backend/src/providers/connector/
(ConnectorHttpClient + EnterpriseConnectorProvider + CloudConnectorProvider).
What it does
- Accepts HMAC-signed HTTPS requests from the EduCreds cloud.
- Optionally enforces mutual TLS (recommended for Tier 1).
- Fetches student, graduation, programme and transcript records from your SIS.
- Writes back status updates (e.g. "credential minted" / "revoked").
- Emits an append-only JSONL audit log for regulatory review.
What it does NOT do
- Never persists EduCreds cloud data locally. All state is your SIS.
- Never transmits raw PII in-clear. Requests are HMAC-signed; mTLS is the transport wrapper. If you use Tier 1, encryption of specific fields at rest before transmission is handled by your SIS export layer, not by this agent.
- Never trusts request bodies without HMAC verification. Anonymous requests are rejected with 401.
Directory layout
connector-agent/
├── src/
│ ├── adapters/ # SIS adapters (Postgres/MySQL/MSSQL/Oracle/SQLite)
│ ├── middleware/ # HMAC guard
│ ├── routes/ # Fastify route bindings
│ ├── utils/ # config loader, audit logger
│ └── index.ts # entrypoint
├── config/
│ └── connector-config.example.yaml
├── Dockerfile # multi-stage, non-root, distroless-shaped
├── docker-compose.yml # reference deployment
├── package.json
├── tsconfig.json
└── README.md # (this file)Quickstart (Docker)
cd connector-agent
cp config/connector-config.example.yaml connector-config.yaml
$EDITOR connector-config.yaml # fill SIS SQL, institution id
cat > .env <<'EOF'
EDUCREDS_CONNECTOR_SHARED_SECRET=<32+ char shared secret from EduCreds onboarding>
EDUCREDS_INSTITUTION_ID=<uuid from EduCreds onboarding>
SIS_DB_HOST=your.sis.internal
SIS_DB_NAME=sis_production
SIS_DB_USER=educreds_reader
SIS_DB_PASSWORD=…
EOF
mkdir -p certs && cp /path/to/institution.crt /path/to/institution.key /path/to/educreds-ca.crt certs/
docker compose up -dVerify:
curl -k https://localhost:8443/healthz
# {"status":"ok","adapter":"postgres","at":"2026-01-…"}Quickstart (local dev without Docker)
yarn install
export CONNECTOR_CONFIG_PATH=$PWD/connector-config.yaml
yarn devHTTP contract
Every non-/healthz//readyz//health//agent-info request MUST carry:
| Header | Value |
|---------------------------|-----------------------------------------------------------------------|
| X-EduCreds-Timestamp | Unix milliseconds. Rejected if >5 min from server clock. |
| X-EduCreds-Signature | HMAC_SHA256(sharedSecret, canonical) in hex. |
| X-Institution-Id | Institution id. When configured, must equal the agent's institutionId. |
This scheme is byte-for-byte identical to the cloud client
(cert_backend/src/providers/connector/connector-http.client.ts::sign) and to
the inbound guard (connector-signature.guard.ts). There is no nonce — the
cloud signs each request uniquely; do not add one or the two sides will fail to
authenticate each other.
Canonical string:
<TIMESTAMP>\n
<METHOD>\n
<PATH_WITHOUT_QUERY>\n
sha256(<RAW_BODY>) # empty string when there is no bodyEndpoints
| Method | Path | Purpose |
|--------|---------------------------------------------|----------------------------------------|
| GET | /students/:studentId | Resolve a student by id |
| GET | /students/:studentId/transcript | Full course-level transcript |
| GET | /graduations/:graduationId | Fetch a single graduation record |
| GET | /programmes/:programmeId | Fetch a programme / degree |
| GET | /institution | Institution identity + signing key |
| POST | /status-updates | Status write-back (body: {entityType, entityId, status, reason?}) |
| POST | /credentials | Record a minted credential (body: ConnectorCredentialPayload) |
| GET | /healthz | Liveness (no auth) |
| GET | /health | Readiness probe used by cloud healthCheck (no auth) |
| GET | /readyz | Readiness incl. adapter ping (no auth) |
| GET | /agent-info | Read-only metadata for the cloud ConnectorManager (no auth) |
Responses match the schemas in cert_backend/src/providers/connector/connector-response.schema.ts. Deviations are rejected by the cloud with ConnectorValidationError.
GET /agent-info (no auth) returns a small JSON object used by the cloud
ConnectorManager to enrich Tier 1 descriptors:
{
"status": "ok",
"institutionId": "inst-123",
"adapterKind": "sqlite",
"agentVersion": "2.0.0",
"mtls": false,
"cloudPushEnabled": false,
"rateLimit": 1000,
"reportedAt": "2026-07-16T00:00:00.000Z"
}If the configured institutionId does not match the agent's own id, the agent
returns 409 institution_id_mismatch (defensive; the cloud treats any missing
or mismatched response as agentInfo: null). The version field is read from
the agent config (version: ${AGENT_VERSION}) and defaults to unknown
when unset.
Adding a new SIS adapter
- Create
src/adapters/<vendor>.adapter.tsimplementingIConnectorAdapter. - Wire the adapter kind in
src/index.tsbootstrap switch. - Extend
config.tsAgentConfig.adapterunion. - Add an example config to
config/connector-config.example.yaml.
Currently shipped (all on-prem, same contract):
| Dialect | Adapter | Placeholder style | Default port |
|---------|---------|------------------|--------------|
| PostgreSQL | PostgresAdapter | $1, $2, … | 5432 |
| MySQL / MariaDB | MysqlAdapter | ? | 3306 |
| SQL Server / Azure SQL | MssqlAdapter | ? (rewritten to @pN) | 1433 |
| Oracle | OracleAdapter | ? (rewritten to :1, :2 …) | 1521 |
| SQLite | SqliteAdapter | ? (Node ≥22.5 node:sqlite) | file |
oracledb is an optional dependency (native build) — install it only on
Oracle institutions. SqliteAdapter uses Node's built-in node:sqlite, so
the published image runs Node 22; on-prem npm installs that only use
Postgres/MySQL/MSSQL/Oracle can stay on Node 20, but SQLite requires ≥22.5.
A new dialect only needs a src/adapters/<vendor>.adapter.ts implementing
IConnectorAdapter — routes and middleware are untouched.
Security posture
- Runs as unprivileged
educredsuser inside a read-only container. - All Linux capabilities dropped (
cap_drop: ALL). - HMAC secret rotation: coordinate with the EduCreds admin
/api/institutions/admin/:id/connector/rotate-secretendpoint. The connector agent MUST be updated with the new secret within 15 minutes of rotation. - mTLS: strongly recommended for Tier 1 (production). See
docker-compose.ymlvolume mount for/etc/educreds/certs. - Rate limiting: 300 req/min per client IP or per mTLS subject CN. Adjust in config.
- Audit log: append-only JSONL, one file per day,
mode: 0o640. Never rotated by the agent — configure logrotate on the host. - Egress depends on the deployment mode:
- Pull-only (no
cloud:block): the cloud polls the agent, so the agent needs no egress beyond the SIS network. - Event-driven / push (recommended, default
cloud.callbackBaseUrlset): the agent does egress tohttps://api.educreds.xyz:443to pushgraduation.detectedand status updates. Allow that hostname in the egress policy.
- Pull-only (no
- Prerequisites that bite people: accurate host clock (NTP) — the HMAC replay window is ±5 min; a drifting clock 401s every request. The agent's mTLS server must trust
educreds-ca.crtor the cloud's client-cert handshake fails.
Health / observability
GET /healthz— liveness (no auth). Returns{ status: "ok" }even if the adapter is offline.GET /readyz— readiness (no auth). Fails 503 if the SIS ping fails.GET /agent-info— read-only metadata (no auth). Surfaced by the cloudConnectorManagerto enrich Tier 1 descriptors with adapter kind, agent version, and capability flags.- Structured JSON logs on stdout via pino. Signature headers are redacted.
- The audit log directory (
/var/log/educreds/audit) is the primary regulatory record; ship it to your SIEM.
FAQ
Q: Why not run this as a Lambda / serverless? A: On-prem SIS databases usually live behind institution firewalls with no public egress. A long-lived host inside the same network segment is the cheapest and safest topology.
Q: What if my SIS is not any of the shipped adapters?
A: Implement IConnectorAdapter (~150 LOC) against your driver of choice. Or expose a thin REST layer in front of your SIS and adapt against that.
Q: How do I test integration end-to-end?
A: Use the EduCreds staging cloud (https://api-staging.educreds.xyz), point your connector agent at a staging SIS replica, and run the documentation/operations/CONNECTOR_INTEGRATION_TEST.md scenario (see the repo docs). The agent's /healthz and /readyz must both be green for the cloud to enable issuance to your institution.
Distribution & install methods (Tier 1 self-install)
The agent is shipped as a single hardened Docker image. Institutions never need a Node toolchain. Three supported install paths:
1. One-line installer (recommended for IT teams)
curl -sSL https://get.educreds.xyz/connector | bashThe script installs Docker if missing, creates /opt/educreds-connector and
/etc/educreds, writes docker-compose.yml + .env from your onboarding
pack, and verifies /healthz. Non-interactive / config-management usage:
EDUCREDS_INSTITUTION_ID=... EDUCREDS_CONNECTOR_SHARED_SECRET=... \
SIS_DB_HOST=... SIS_DB_NAME=... SIS_DB_USER=... SIS_DB_PASSWORD=... \
curl -sSL https://get.educreds.xyz/connector | bash -s -- --systemdAfter install, edit /etc/educreds/connector-config.yaml to match your SIS
schema and drop your mTLS certs into /opt/educreds-connector/certs/.
2. npm CLI (@educreds/connector-agent)
npx @educreds/connector-agent initScaffolds connector-config.yaml, .env, docker-compose.yml, and a
systemd unit from your onboarding answers (or from EDUCREDS_* env vars
when run non-interactively). It only writes files — starting the service is left
to Docker/systemd. Health probe: npx @educreds/connector-agent healthcheck.
3. Pull the image directly
Image: ghcr.io/educreds/connector-agent:1.0.0. Build/push is automated by
.github/workflows/connector-cd.yml (tags v* → :x.y.z + :latest;
manual dispatch → :edge). Institutions just run the bundled
docker-compose.yml against the published image.
All three methods produce the same on-prem topology: a read-only, cap_drop:
ALL, mTLS container listening on 8443, with the audit log volume-mounted to
the host for SIEM shipping.
