- Replace form_urlencoded with RFC 3986 compliant URI encoding - Implement aws_uri_encode() matching AWS SigV4 spec exactly - Unreserved chars (A-Z,a-z,0-9,-,_,.,~) not encoded - All other chars percent-encoded with uppercase hex - Preserve slashes in paths, encode in query params - Normalize empty paths to '/' per AWS spec - Fix test expectations (body hash, HMAC values) - Add comprehensive SigV4 signature determinism test This fixes the canonicalization mismatch that caused signature validation failures in T047. Auth can now be enabled for production. Refs: T058.S1
15 KiB
Tenant Onboarding Guide
Overview
This guide walks you through the complete process of onboarding your first tenant in PlasmaCloud, from user creation through VM deployment with networking. By the end of this guide, you will have:
- A running PlasmaCloud infrastructure (IAM, PrismNET, PlasmaVMC)
- An authenticated user with proper RBAC permissions
- A complete network setup (VPC, Subnet, Port)
- A virtual machine with network connectivity
Time to Complete: ~15 minutes
Prerequisites
System Requirements
- Operating System: Linux (Ubuntu 20.04+ recommended)
- Rust: 1.70 or later
- Cargo: Latest version (comes with Rust)
- Memory: 4GB minimum (8GB recommended for VM testing)
- Disk: 10GB free space
Optional Components
- OVN (Open Virtual Network): For real overlay networking (not required for basic testing)
- KVM: For actual VM execution (tests can run in mock mode without KVM)
- Docker: If running services in containers
Installation
# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
# Verify installation
rustc --version
cargo --version
Architecture Quick Reference
User → IAM (Auth) → Token {org_id, project_id}
↓
┌────────────┴────────────┐
↓ ↓
PrismNET PlasmaVMC
(VPC/Subnet/Port) (VM)
↓ ↓
└──────── port_id ────────┘
For detailed architecture, see Architecture Documentation.
Step 1: Clone and Build PlasmaCloud
Clone the Repository
# Clone the main repository
cd /home/centra/cloud
git clone https://github.com/your-org/plasmavmc.git
cd plasmavmc
# Initialize submodules (IAM, ChainFire, FlareDB, etc.)
git submodule update --init --recursive
Build All Components
# Build IAM
cd /home/centra/cloud/iam
cargo build --release
# Build PrismNET
cd /home/centra/cloud/prismnet
cargo build --release
# Build PlasmaVMC
cd /home/centra/cloud/plasmavmc
cargo build --release
Build Time: 5-10 minutes (first build)
Step 2: Start PlasmaCloud Services
Open three terminal windows to run the services:
Terminal 1: Start IAM Service
cd /home/centra/cloud/iam
# Run IAM server on port 50080
cargo run --bin iam-server -- --port 50080
# Expected output:
# [INFO] IAM server listening on 0.0.0.0:50080
# [INFO] Principal store initialized (in-memory)
# [INFO] Role store initialized (in-memory)
# [INFO] Binding store initialized (in-memory)
Terminal 2: Start PrismNET Service
cd /home/centra/cloud/prismnet
# Set environment variables
export IAM_ENDPOINT=http://localhost:50080
# Run PrismNET server on port 50081
cargo run --bin prismnet-server -- --port 50081
# Expected output:
# [INFO] PrismNET server listening on 0.0.0.0:50081
# [INFO] NetworkMetadataStore initialized (in-memory)
# [INFO] OVN integration: disabled (mock mode)
Terminal 3: Start PlasmaVMC Service
cd /home/centra/cloud/plasmavmc
# Set environment variables
export NOVANET_ENDPOINT=http://localhost:50081
export IAM_ENDPOINT=http://localhost:50080
export PLASMAVMC_STORAGE_BACKEND=file
# Run PlasmaVMC server on port 50082
cargo run --bin plasmavmc-server -- --port 50082
# Expected output:
# [INFO] PlasmaVMC server listening on 0.0.0.0:50082
# [INFO] Hypervisor registry initialized
# [INFO] KVM backend registered (mock mode)
# [INFO] Connected to PrismNET: http://localhost:50081
Verification: All three services should be running without errors.
Step 3: Create User & Authenticate
Using grpcurl (Recommended)
Install grpcurl if not already installed:
# Install grpcurl
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
# or on Ubuntu:
sudo apt-get install grpcurl
Create Organization Admin User
# Create a principal (user) for your organization
grpcurl -plaintext -d '{
"principal": {
"id": "alice",
"name": "Alice Smith",
"email": "alice@acmecorp.com",
"org_id": "acme-corp",
"principal_type": "USER"
}
}' localhost:50080 iam.v1.IamAdminService/CreatePrincipal
# Expected response:
# {
# "principal": {
# "id": "alice",
# "name": "Alice Smith",
# "email": "alice@acmecorp.com",
# "org_id": "acme-corp",
# "principal_type": "USER",
# "created_at": "2025-12-09T10:00:00Z"
# }
# }
Create OrgAdmin Role
# Create a role that grants full access to the organization
grpcurl -plaintext -d '{
"role": {
"name": "roles/OrgAdmin",
"display_name": "Organization Administrator",
"description": "Full access to all resources in the organization",
"scope": {
"org": "acme-corp"
},
"permissions": [
{
"action": "*",
"resource_pattern": "org/acme-corp/*"
}
]
}
}' localhost:50080 iam.v1.IamAdminService/CreateRole
# Expected response:
# {
# "role": {
# "name": "roles/OrgAdmin",
# "display_name": "Organization Administrator",
# ...
# }
# }
Bind User to Role
# Assign the OrgAdmin role to Alice at org scope
grpcurl -plaintext -d '{
"binding": {
"id": "alice-org-admin",
"principal_ref": {
"type": "USER",
"id": "alice"
},
"role_name": "roles/OrgAdmin",
"scope": {
"org": "acme-corp"
}
}
}' localhost:50080 iam.v1.IamAdminService/CreateBinding
# Expected response:
# {
# "binding": {
# "id": "alice-org-admin",
# ...
# }
# }
Issue Authentication Token
# Issue a token for Alice scoped to project-alpha
grpcurl -plaintext -d '{
"principal_id": "alice",
"org_id": "acme-corp",
"project_id": "project-alpha",
"ttl_seconds": 3600
}' localhost:50080 iam.v1.IamTokenService/IssueToken
# Expected response:
# {
# "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
# "expires_at": "2025-12-09T11:00:00Z"
# }
Save the token: You'll use this token in subsequent API calls.
export TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Step 4: Create Network Resources
Create VPC (Virtual Private Cloud)
grpcurl -plaintext \
-H "Authorization: Bearer $TOKEN" \
-d '{
"org_id": "acme-corp",
"project_id": "project-alpha",
"name": "main-vpc",
"description": "Main VPC for project-alpha",
"cidr": "10.0.0.0/16"
}' localhost:50081 prismnet.v1.VpcService/CreateVpc
# Expected response:
# {
# "vpc": {
# "id": "vpc-1a2b3c4d",
# "org_id": "acme-corp",
# "project_id": "project-alpha",
# "name": "main-vpc",
# "cidr": "10.0.0.0/16",
# ...
# }
# }
Save the VPC ID:
export VPC_ID="vpc-1a2b3c4d"
Create Subnet with DHCP
grpcurl -plaintext \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"org_id\": \"acme-corp\",
\"project_id\": \"project-alpha\",
\"vpc_id\": \"$VPC_ID\",
\"name\": \"web-subnet\",
\"description\": \"Subnet for web tier\",
\"cidr\": \"10.0.1.0/24\",
\"gateway\": \"10.0.1.1\",
\"dhcp_enabled\": true
}" localhost:50081 prismnet.v1.SubnetService/CreateSubnet
# Expected response:
# {
# "subnet": {
# "id": "subnet-5e6f7g8h",
# "vpc_id": "vpc-1a2b3c4d",
# "cidr": "10.0.1.0/24",
# "gateway": "10.0.1.1",
# "dhcp_enabled": true,
# ...
# }
# }
Save the Subnet ID:
export SUBNET_ID="subnet-5e6f7g8h"
Create Port (Network Interface)
grpcurl -plaintext \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"org_id\": \"acme-corp\",
\"project_id\": \"project-alpha\",
\"subnet_id\": \"$SUBNET_ID\",
\"name\": \"web-server-port\",
\"description\": \"Port for web server VM\",
\"ip_address\": \"10.0.1.10\",
\"security_group_ids\": []
}" localhost:50081 prismnet.v1.PortService/CreatePort
# Expected response:
# {
# "port": {
# "id": "port-9i0j1k2l",
# "subnet_id": "subnet-5e6f7g8h",
# "ip_address": "10.0.1.10",
# "mac_address": "fa:16:3e:12:34:56",
# "device_id": "",
# "device_type": "NONE",
# ...
# }
# }
Save the Port ID:
export PORT_ID="port-9i0j1k2l"
Step 5: Deploy Virtual Machine
Create VM with Network Attachment
grpcurl -plaintext \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"name\": \"web-server-1\",
\"org_id\": \"acme-corp\",
\"project_id\": \"project-alpha\",
\"hypervisor\": \"KVM\",
\"spec\": {
\"cpu\": {
\"cores\": 2,
\"threads\": 1
},
\"memory\": {
\"size_mb\": 2048
},
\"network\": [
{
\"id\": \"eth0\",
\"network_id\": \"$VPC_ID\",
\"subnet_id\": \"$SUBNET_ID\",
\"port_id\": \"$PORT_ID\",
\"model\": \"VIRTIO_NET\"
}
]
},
\"metadata\": {
\"environment\": \"production\",
\"tier\": \"web\"
}
}" localhost:50082 plasmavmc.v1.VmService/CreateVm
# Expected response:
# {
# "id": "vm-3m4n5o6p",
# "name": "web-server-1",
# "org_id": "acme-corp",
# "project_id": "project-alpha",
# "state": "RUNNING",
# "spec": {
# "cpu": { "cores": 2, "threads": 1 },
# "memory": { "size_mb": 2048 },
# "network": [
# {
# "id": "eth0",
# "port_id": "port-9i0j1k2l",
# "ip_address": "10.0.1.10",
# "mac_address": "fa:16:3e:12:34:56"
# }
# ]
# },
# ...
# }
Save the VM ID:
export VM_ID="vm-3m4n5o6p"
Step 6: Verification
Verify Port Attachment
# Check that the port is now attached to the VM
grpcurl -plaintext \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"org_id\": \"acme-corp\",
\"project_id\": \"project-alpha\",
\"subnet_id\": \"$SUBNET_ID\",
\"id\": \"$PORT_ID\"
}" localhost:50081 prismnet.v1.PortService/GetPort
# Verify response shows:
# "device_id": "vm-3m4n5o6p"
# "device_type": "VM"
Verify VM Network Configuration
# Get VM details
grpcurl -plaintext \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"org_id\": \"acme-corp\",
\"project_id\": \"project-alpha\",
\"vm_id\": \"$VM_ID\"
}" localhost:50082 plasmavmc.v1.VmService/GetVm
# Verify response shows:
# - state: "RUNNING"
# - network[0].ip_address: "10.0.1.10"
# - network[0].mac_address: "fa:16:3e:12:34:56"
Verify Cross-Tenant Isolation
Try to access the VM with a different tenant's token (should fail):
# Create a second user in a different org
grpcurl -plaintext -d '{
"principal": {
"id": "bob",
"name": "Bob Jones",
"org_id": "other-corp"
}
}' localhost:50080 iam.v1.IamAdminService/CreatePrincipal
# Issue token for Bob
grpcurl -plaintext -d '{
"principal_id": "bob",
"org_id": "other-corp",
"project_id": "project-beta"
}' localhost:50080 iam.v1.IamTokenService/IssueToken
export BOB_TOKEN="<bob's token>"
# Try to get Alice's VM (should fail)
grpcurl -plaintext \
-H "Authorization: Bearer $BOB_TOKEN" \
-d "{
\"org_id\": \"acme-corp\",
\"project_id\": \"project-alpha\",
\"vm_id\": \"$VM_ID\"
}" localhost:50082 plasmavmc.v1.VmService/GetVm
# Expected: 403 Forbidden or "Permission denied"
Step 7: Cleanup (Optional)
Delete VM
grpcurl -plaintext \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"org_id\": \"acme-corp\",
\"project_id\": \"project-alpha\",
\"vm_id\": \"$VM_ID\",
\"force\": true
}" localhost:50082 plasmavmc.v1.VmService/DeleteVm
# Verify port is detached
grpcurl -plaintext \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"org_id\": \"acme-corp\",
\"project_id\": \"project-alpha\",
\"subnet_id\": \"$SUBNET_ID\",
\"id\": \"$PORT_ID\"
}" localhost:50081 prismnet.v1.PortService/GetPort
# Verify: device_id should be empty
Common Issues & Troubleshooting
Issue: "Connection refused" when calling services
Solution: Ensure all three services are running:
# Check if services are listening
netstat -tuln | grep -E '50080|50081|50082'
# Or use lsof
lsof -i :50080
lsof -i :50081
lsof -i :50082
Issue: "Permission denied" when creating resources
Solution: Verify token is valid and has correct scope:
# Decode JWT token to verify claims
echo $TOKEN | cut -d '.' -f 2 | base64 -d | jq .
# Should show:
# {
# "org_id": "acme-corp",
# "project_id": "project-alpha",
# "exp": <expiration timestamp>
# }
Issue: Port not attaching to VM
Solution: Verify port exists and is in the correct tenant scope:
# List all ports in subnet
grpcurl -plaintext \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"org_id\": \"acme-corp\",
\"project_id\": \"project-alpha\",
\"subnet_id\": \"$SUBNET_ID\"
}" localhost:50081 prismnet.v1.PortService/ListPorts
Issue: VM creation fails with "Hypervisor error"
Solution: This is expected if running in mock mode without KVM. The integration tests use mock hypervisors. For real VM execution, ensure KVM is installed:
# Check KVM support
lsmod | grep kvm
# Install KVM (Ubuntu)
sudo apt-get install qemu-kvm libvirt-daemon-system
Next Steps
Run Integration Tests
Verify your setup by running the E2E tests:
# IAM tenant path tests
cd /home/centra/cloud/iam
cargo test --test tenant_path_integration
# Network + VM integration tests
cd /home/centra/cloud/plasmavmc
cargo test --test prismnet_integration -- --ignored
See E2E Test Documentation for detailed test descriptions.
Explore Advanced Features
- RBAC: Create custom roles with fine-grained permissions
- Multi-Project: Create multiple projects within your organization
- Security Groups: Add firewall rules to your ports
- VPC Peering: Connect multiple VPCs (coming in future releases)
Deploy to Production
For production deployments:
- Use FlareDB: Replace in-memory stores with FlareDB for persistence
- Enable OVN: Configure OVN for real overlay networking
- TLS/mTLS: Secure gRPC connections with TLS certificates
- API Gateway: Add authentication gateway for token validation
- Monitoring: Set up Prometheus metrics and logging
See Production Deployment Guide (coming soon).
Architecture & References
- Architecture Overview: MVP-Beta Tenant Path
- E2E Tests: Test Documentation
- T023 Summary: SUMMARY.md
- Component Specs:
Summary
Congratulations! You've successfully onboarded your first tenant in PlasmaCloud. You have:
- ✅ Created a user with organization and project scope
- ✅ Assigned RBAC permissions (OrgAdmin role)
- ✅ Provisioned a complete network stack (VPC → Subnet → Port)
- ✅ Deployed a virtual machine with network attachment
- ✅ Verified tenant isolation works correctly
Your PlasmaCloud deployment is now ready for multi-tenant cloud workloads!
For questions or issues, please file a GitHub issue or consult the Architecture Documentation.