Skip to content

3. Onboard your first tenant

A tenant is a Mnemose subscription scope: a unit of multi-tenancy that owns cloud project delegations, IAM escalations, kernels, and reports. Every domain row in the database carries a tenant_id column.

Prerequisite: 2. Deploy the platform complete.

Architecture recap

Tenant
├─ CloudProject (one or more — customer cloud accounts)
├─ AgentKernel (zero or more — enrolled remote hosts)
├─ IamEscalation (lifecycle: requested → approved/denied → revoked)
├─ ProvisionJob (resource creation)
├─ RemediationJob (security finding remediation)
└─ Report (XLSX/CSV/PDF)

Register the tenant

Self-registration bootstraps the system tenant plus the platform’s own cloud project in one call (this is the same mutation the installer seed and scripts/seed-dev-tenant.sh use):

mutation RegisterSelf {
registerSelf(
projectId: "<cloudflare-account-id>"
region: "auto"
serviceAccountEmail: "ops@example.com"
) {
tenantId
cloudProjectId
credentialsValidated
}
}

This emits a PlatformSelfRegistered domain event, creates the tenant row, and records the platform’s own cloud project. (Subscription-scoped tenants can also be created via createTenant and the RegisterSubscription command flow.)

Delegate a customer cloud project

The customer must already have:

GCP — Workload Identity Federation

  1. Create a workload identity pool in their project.
  2. Grant roles/iam.workloadIdentityUser to Mnemose’s WIF provider on the service account that Mnemose will impersonate.
  3. Share back the projectId, serviceAccountEmail, wifPoolId, and region.

AWS — cross-account IAM role

  1. Create an IAM role with a trust policy allowing Mnemose’s operator account to sts:AssumeRole with a chosen ExternalId.
  2. Attach least-privilege policies for the IaaS surfaces Mnemose will manage (Compute/IAM/Network/Storage/Security).
  3. Share back the accountId, roleArn, externalId, and region.

Submit the delegation

mutation Delegate {
delegateCloudProject(
displayName: "Acme Production"
projectId: "acme-prod"
provider: "gcp"
region: "us-central1"
serviceAccountEmail: "mnemose-ops@acme-prod.iam.gserviceaccount.com"
wifPoolId: "projects/.../locations/global/workloadIdentityPools/mnemose/providers/gcp"
) {
id
projectId
status
}
}

The handler validates the credentials by attempting cloudCredentials.mintToken before persisting. On success a CloudProjectDelegated domain event is emitted and inventory sync becomes available for that project.

Trigger inventory sync

mutation Sync {
syncResourceInventory(cloudProjectId: "<id>")
}

The SyncResourceInventory command handler enumerates compute, network, storage, IAM, and security resources via the IaaS adapter and writes resource_snapshots rows. The mutation returns Boolean (enqueue result); watch the resources query for the refreshed snapshots.

What “tenant” means at runtime

  • Every GraphQL request resolves tenantId from verified auth claims (the Cloudflare Access JWT’s mnemose_tenant_id claim, or the dev-token bypass with x-tenant-id in dev).
  • Every command payload includes tenantId.
  • Every domain event row has a tenant_id column.
  • Every IaaS call uses a credential minted for that tenant’s cloud project — there is no cross-tenant credential sharing.

Next

→ 4. Provision the platform core