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
- Create a workload identity pool in their project.
- Grant
roles/iam.workloadIdentityUserto Mnemose’s WIF provider on the service account that Mnemose will impersonate. - Share back the
projectId,serviceAccountEmail,wifPoolId, andregion.
AWS — cross-account IAM role
- Create an IAM role with a trust policy allowing Mnemose’s operator
account to
sts:AssumeRolewith a chosenExternalId. - Attach least-privilege policies for the IaaS surfaces Mnemose will manage (Compute/IAM/Network/Storage/Security).
- Share back the
accountId,roleArn,externalId, andregion.
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
tenantIdfrom verified auth claims (the Cloudflare Access JWT’smnemose_tenant_idclaim, or the dev-token bypass withx-tenant-idin dev). - Every command payload includes
tenantId. - Every domain event row has a
tenant_idcolumn. - Every IaaS call uses a credential minted for that tenant’s cloud project — there is no cross-tenant credential sharing.