- 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
468 lines
22 KiB
Markdown
468 lines
22 KiB
Markdown
# MVP-Beta Tenant Path Architecture
|
|
|
|
## Overview
|
|
|
|
This document describes the architecture of the PlasmaCloud MVP-Beta tenant path, which enables end-to-end multi-tenant cloud infrastructure provisioning with complete isolation between tenants.
|
|
|
|
The tenant path spans three core components:
|
|
1. **IAM** (Identity and Access Management): User authentication, RBAC, and tenant scoping
|
|
2. **PrismNET**: Network virtualization with VPC overlay and tenant isolation
|
|
3. **PlasmaVMC**: Virtual machine provisioning and lifecycle management
|
|
|
|
## Architecture Diagram
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ User / API Client │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
│
|
|
↓ Authentication Request
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ IAM (Identity & Access) │
|
|
├─────────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌────────────────────┐ ┌──────────────────┐ │
|
|
│ │ IamTokenService │────────▶│ IamAuthzService │ │
|
|
│ │ │ │ │ │
|
|
│ │ • Authenticate │ │ • RBAC Eval │ │
|
|
│ │ • Issue JWT Token │ │ • Permission │ │
|
|
│ │ • Scope: org+proj │ │ Check │ │
|
|
│ └────────────────────┘ └──────────────────┘ │
|
|
│ │
|
|
│ Data Stores: │
|
|
│ • PrincipalStore (users, service accounts) │
|
|
│ • RoleStore (system, org, project roles) │
|
|
│ • BindingStore (principal → role assignments) │
|
|
│ │
|
|
│ Tenant Scoping: │
|
|
│ • Principals belong to org_id │
|
|
│ • Tokens include org_id + project_id │
|
|
│ • RBAC enforces resource.org_id == token.org_id │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
│
|
|
↓ JWT Token {org_id, project_id, permissions}
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ API Gateway / Service Layer │
|
|
│ • Validates JWT token │
|
|
│ • Extracts org_id, project_id from token │
|
|
│ • Passes tenant context to downstream services │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
│
|
|
┌───────────────┴───────────────┐
|
|
↓ ↓
|
|
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
|
|
│ PrismNET │ │ PlasmaVMC │
|
|
│ (Network Virtualization) │ │ (VM Provisioning) │
|
|
├─────────────────────────────────┤ ├─────────────────────────────────┤
|
|
│ │ │ │
|
|
│ ┌────────────────────────┐ │ │ ┌────────────────────────┐ │
|
|
│ │ VpcServiceImpl │ │ │ │ VmServiceImpl │ │
|
|
│ │ • Create VPC │ │ │ │ • Create VM │ │
|
|
│ │ • Scope: org_id │ │ │ │ • Scope: org_id, │ │
|
|
│ │ • VPC ID generation │ │ │ │ project_id │ │
|
|
│ └────────────────────────┘ │ │ │ • Network attach │ │
|
|
│ ↓ │ │ └────────────────────────┘ │
|
|
│ ┌────────────────────────┐ │ │ │ │
|
|
│ │ SubnetServiceImpl │ │ │ │ │
|
|
│ │ • Create Subnet │ │ │ ┌────────────────────────┐ │
|
|
│ │ • CIDR allocation │ │ │ │ NetworkAttachment │ │
|
|
│ │ • DHCP config │ │ │ │ • Attach port to VM │ │
|
|
│ │ • Gateway config │ │ │ │ • Update port.device │ │
|
|
│ └────────────────────────┘ │ │ │ • TAP interface │ │
|
|
│ ↓ │ │ └────────────────────────┘ │
|
|
│ ┌────────────────────────┐ │ │ ↑ │
|
|
│ │ PortServiceImpl │◀────┼───┼──────────────┘ │
|
|
│ │ • Create Port │ │ │ port_id in NetworkSpec │
|
|
│ │ • IP allocation │ │ │ │
|
|
│ │ • MAC generation │ │ │ Hypervisor: │
|
|
│ │ • Port status │ │ │ • KvmBackend │
|
|
│ │ • device_id tracking │ │ │ • FirecrackerBackend │
|
|
│ └────────────────────────┘ │ │ │
|
|
│ │ │ Storage: │
|
|
│ Metadata Store: │ │ • NetworkMetadataStore │
|
|
│ • NetworkMetadataStore │ │ • ChainFire (planned) │
|
|
│ • In-memory (dev) │ │ │
|
|
│ • FlareDB (production) │ └─────────────────────────────────┘
|
|
│ │
|
|
│ Data Plane (OVN): │
|
|
│ • Logical switches per VPC │
|
|
│ • Logical routers per subnet │
|
|
│ • Security groups │
|
|
│ • DHCP server │
|
|
│ │
|
|
└─────────────────────────────────┘
|
|
```
|
|
|
|
## Component Boundaries
|
|
|
|
### IAM: Tenant Isolation + RBAC Enforcement
|
|
|
|
**Responsibilities**:
|
|
- User authentication and token issuance
|
|
- Organization and project hierarchy management
|
|
- Role-based access control (RBAC) enforcement
|
|
- Cross-tenant access denial
|
|
|
|
**Tenant Scoping**:
|
|
- Each `Principal` (user/service account) belongs to an `org_id`
|
|
- Tokens include both `org_id` and `project_id` claims
|
|
- Resources are scoped as: `org/{org_id}/project/{project_id}/{resource_type}/{id}`
|
|
|
|
**Key Types**:
|
|
```rust
|
|
struct Principal {
|
|
id: String,
|
|
org_id: Option<String>, // Primary tenant boundary
|
|
project_id: Option<String>, // Sub-tenant boundary
|
|
// ...
|
|
}
|
|
|
|
struct Scope {
|
|
System, // Global access
|
|
Org(String), // Organization-level
|
|
Project { org, project }, // Project-level
|
|
}
|
|
|
|
struct Permission {
|
|
action: String, // e.g., "compute:instances:create"
|
|
resource_pattern: String, // e.g., "org/acme-corp/project/*/instance/*"
|
|
conditions: Vec<Condition>, // e.g., resource.owner == principal.id
|
|
}
|
|
```
|
|
|
|
**Integration Points**:
|
|
- Issues JWT tokens consumed by all services
|
|
- Validates authorization before resource creation
|
|
- Enforces `resource.org_id == token.org_id` at policy evaluation time
|
|
|
|
### PrismNET: Network Isolation per Tenant VPC
|
|
|
|
**Responsibilities**:
|
|
- VPC (Virtual Private Cloud) provisioning
|
|
- Subnet management with CIDR allocation
|
|
- Port creation and IP/MAC assignment
|
|
- Security group enforcement
|
|
- Port lifecycle management (attach/detach)
|
|
|
|
**Tenant Scoping**:
|
|
- Each VPC is scoped to an `org_id`
|
|
- VPC provides network isolation boundary
|
|
- Subnets and ports inherit VPC tenant scope
|
|
- Port device tracking links to VM IDs
|
|
|
|
**Key Types**:
|
|
```rust
|
|
struct Vpc {
|
|
id: String,
|
|
org_id: String, // Tenant boundary
|
|
project_id: String,
|
|
cidr: String, // e.g., "10.0.0.0/16"
|
|
// ...
|
|
}
|
|
|
|
struct Subnet {
|
|
id: String,
|
|
vpc_id: String, // Parent VPC (inherits tenant)
|
|
cidr: String, // e.g., "10.0.1.0/24"
|
|
gateway: String,
|
|
dhcp_enabled: bool,
|
|
// ...
|
|
}
|
|
|
|
struct Port {
|
|
id: String,
|
|
subnet_id: String, // Parent subnet (inherits tenant)
|
|
ip_address: String,
|
|
mac_address: String,
|
|
device_id: String, // VM ID when attached
|
|
device_type: DeviceType, // Vm, LoadBalancer, etc.
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Integration Points**:
|
|
- Accepts org_id/project_id from API tokens
|
|
- Provides port IDs to PlasmaVMC for VM attachment
|
|
- Receives port attachment/detachment events from PlasmaVMC
|
|
- Uses OVN (Open Virtual Network) for overlay networking data plane
|
|
|
|
### PlasmaVMC: VM Scoping by org_id/project_id
|
|
|
|
**Responsibilities**:
|
|
- Virtual machine lifecycle management (create, start, stop, delete)
|
|
- Hypervisor abstraction (KVM, Firecracker)
|
|
- Network interface attachment to PrismNET ports
|
|
- VM metadata persistence (ChainFire)
|
|
|
|
**Tenant Scoping**:
|
|
- Each VM belongs to an `org_id` and `project_id`
|
|
- VM metadata includes tenant identifiers
|
|
- Network attachments validated against tenant scope
|
|
|
|
**Key Types**:
|
|
```rust
|
|
struct Vm {
|
|
id: String,
|
|
name: String,
|
|
org_id: String, // Tenant boundary
|
|
project_id: String,
|
|
spec: VmSpec,
|
|
state: VmState,
|
|
// ...
|
|
}
|
|
|
|
struct NetworkSpec {
|
|
id: String, // Interface name (e.g., "eth0")
|
|
network_id: String, // VPC ID from PrismNET
|
|
subnet_id: String, // Subnet ID from PrismNET
|
|
port_id: String, // Port ID from PrismNET
|
|
mac_address: String,
|
|
ip_address: String,
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Integration Points**:
|
|
- Accepts org_id/project_id from API tokens
|
|
- Fetches port details from PrismNET using port_id
|
|
- Notifies PrismNET when VM is created (port attach)
|
|
- Notifies PrismNET when VM is deleted (port detach)
|
|
- Uses hypervisor backends (KVM, Firecracker) for VM execution
|
|
|
|
## Data Flow: Complete Tenant Path
|
|
|
|
### Scenario: User Creates VM with Network
|
|
|
|
```
|
|
Step 1: User Authentication
|
|
──────────────────────────────────────────────────────────────
|
|
User IAM
|
|
│ │
|
|
├──── Login ──────────▶│
|
|
│ ├─ Validate credentials
|
|
│ ├─ Lookup Principal (org_id="acme")
|
|
│ ├─ Generate JWT token
|
|
│◀─── JWT Token ───────┤ {org_id: "acme", project_id: "proj-1"}
|
|
│ │
|
|
|
|
|
|
Step 2: Create Network Resources
|
|
──────────────────────────────────────────────────────────────
|
|
User PrismNET
|
|
│ │
|
|
├── CreateVPC ────────▶│ (JWT token in headers)
|
|
│ {org: acme, ├─ Validate token
|
|
│ project: proj-1, ├─ Extract org_id="acme"
|
|
│ cidr: 10.0.0.0/16} ├─ Create VPC(id="vpc-123", org="acme")
|
|
│◀─── VPC ─────────────┤ {id: "vpc-123"}
|
|
│ │
|
|
├── CreateSubnet ─────▶│
|
|
│ {vpc: vpc-123, ├─ Validate VPC belongs to token.org_id
|
|
│ cidr: 10.0.1.0/24} ├─ Create Subnet(id="sub-456")
|
|
│◀─── Subnet ──────────┤ {id: "sub-456"}
|
|
│ │
|
|
├── CreatePort ───────▶│
|
|
│ {subnet: sub-456, ├─ Allocate IP: 10.0.1.10
|
|
│ ip: 10.0.1.10} ├─ Generate MAC: fa:16:3e:...
|
|
│◀─── Port ────────────┤ {id: "port-789", device_id: ""}
|
|
│ │
|
|
|
|
|
|
Step 3: Create VM with Network Attachment
|
|
──────────────────────────────────────────────────────────────
|
|
User PlasmaVMC PrismNET
|
|
│ │ │
|
|
├─ CreateVM ──────▶│ (JWT token) │
|
|
│ {name: "web-1", ├─ Validate token │
|
|
│ network: [ ├─ Extract org/project │
|
|
│ {port_id: │ │
|
|
│ "port-789"} ├─ GetPort ─────────────▶│
|
|
│ ]} │ ├─ Verify port.subnet.vpc.org_id
|
|
│ │ │ == token.org_id
|
|
│ │◀─── Port ──────────────┤ {ip: 10.0.1.10, mac: fa:...}
|
|
│ │ │
|
|
│ ├─ Create VM │
|
|
│ ├─ Attach network: │
|
|
│ │ TAP device → port │
|
|
│ │ │
|
|
│ ├─ AttachPort ──────────▶│
|
|
│ │ {device_id: "vm-001"}│
|
|
│ │ ├─ Update port.device_id="vm-001"
|
|
│ │ ├─ Update port.device_type=Vm
|
|
│ │◀─── Success ───────────┤
|
|
│ │ │
|
|
│◀─── VM ──────────┤ {id: "vm-001", state: "running"}
|
|
│ │
|
|
|
|
|
|
Step 4: Cross-Tenant Access Denied
|
|
──────────────────────────────────────────────────────────────
|
|
User B PlasmaVMC IAM
|
|
(org: "other") │ │
|
|
│ │ │
|
|
├─ GetVM ────────▶│ (JWT token: org="other")
|
|
│ {vm_id: ├─ Authorize ─────────▶│
|
|
│ "vm-001"} │ {action: "vm:read", ├─ Evaluate RBAC
|
|
│ │ resource: "org/acme/..."}
|
|
│ │ ├─ Check resource.org_id="acme"
|
|
│ │ ├─ Check token.org_id="other"
|
|
│ │ ├─ DENY: org mismatch
|
|
│ │◀─── Deny ────────────┤
|
|
│◀── 403 Forbidden ┤
|
|
│ │
|
|
```
|
|
|
|
## Tenant Isolation Mechanisms
|
|
|
|
### Layer 1: IAM Policy Enforcement
|
|
|
|
**Mechanism**: Resource path matching with org_id validation
|
|
|
|
**Example**:
|
|
```
|
|
Resource: org/acme-corp/project/proj-1/instance/vm-001
|
|
Token: {org_id: "acme-corp", project_id: "proj-1"}
|
|
Policy: Permission {action: "compute:*", resource: "org/acme-corp/*"}
|
|
|
|
Result: ALLOW (org_id matches)
|
|
```
|
|
|
|
**Cross-Tenant Denial**:
|
|
```
|
|
Resource: org/acme-corp/project/proj-1/instance/vm-001
|
|
Token: {org_id: "other-corp", project_id: "proj-2"}
|
|
|
|
Result: DENY (org_id mismatch)
|
|
```
|
|
|
|
### Layer 2: Network VPC Isolation
|
|
|
|
**Mechanism**: VPC provides logical network boundary
|
|
|
|
- Each VPC has a unique overlay network (OVN logical switch)
|
|
- Subnets within VPC can communicate
|
|
- Cross-VPC traffic requires explicit routing (not implemented in MVP-Beta)
|
|
- VPC membership enforced by org_id
|
|
|
|
**Isolation Properties**:
|
|
- Tenant A's VPC (10.0.0.0/16) is isolated from Tenant B's VPC (10.0.0.0/16)
|
|
- Even with overlapping CIDRs, VPCs are completely isolated
|
|
- MAC addresses are unique per VPC (no collision)
|
|
|
|
### Layer 3: VM Scoping
|
|
|
|
**Mechanism**: VMs are scoped to org_id and project_id
|
|
|
|
- VM metadata includes org_id and project_id
|
|
- VM list operations filter by token.org_id
|
|
- VM operations validated against token scope
|
|
- Network attachments validated against VPC tenant scope
|
|
|
|
## Service Communication
|
|
|
|
### gRPC APIs
|
|
|
|
All inter-service communication uses gRPC with Protocol Buffers:
|
|
|
|
```
|
|
IAM: :50080 (IamAdminService, IamAuthzService)
|
|
PrismNET: :50081 (VpcService, SubnetService, PortService, SecurityGroupService)
|
|
PlasmaVMC: :50082 (VmService)
|
|
FlashDNS: :50083 (DnsService) [Future]
|
|
FiberLB: :50084 (LoadBalancerService) [Future]
|
|
LightningStor: :50085 (StorageService) [Future]
|
|
```
|
|
|
|
### Environment Configuration
|
|
|
|
Services discover each other via environment variables:
|
|
|
|
```bash
|
|
# PlasmaVMC configuration
|
|
NOVANET_ENDPOINT=http://prismnet:50081
|
|
IAM_ENDPOINT=http://iam:50080
|
|
|
|
# PrismNET configuration
|
|
IAM_ENDPOINT=http://iam:50080
|
|
FLAREDB_ENDPOINT=http://flaredb:50090 # Metadata persistence
|
|
```
|
|
|
|
## Metadata Persistence
|
|
|
|
### Development: In-Memory Stores
|
|
|
|
```rust
|
|
// NetworkMetadataStore (PrismNET)
|
|
let store = NetworkMetadataStore::new_in_memory();
|
|
|
|
// Backend (IAM)
|
|
let backend = Backend::memory();
|
|
```
|
|
|
|
### Production: FlareDB
|
|
|
|
```
|
|
IAM: PrincipalStore, RoleStore, BindingStore → FlareDB
|
|
PrismNET: NetworkMetadataStore → FlareDB
|
|
PlasmaVMC: VmMetadata → ChainFire (immutable log) + FlareDB (mutable state)
|
|
```
|
|
|
|
## Future Extensions (Post MVP-Beta)
|
|
|
|
### S3: FlashDNS Integration
|
|
|
|
```
|
|
User creates VM → PlasmaVMC creates DNS record in tenant zone
|
|
VM hostname: web-1.proj-1.acme-corp.cloud.internal
|
|
DNS resolution within VPC
|
|
```
|
|
|
|
### S4: FiberLB Integration
|
|
|
|
```
|
|
User creates LoadBalancer → FiberLB provisions LB in tenant VPC
|
|
LB backend pool: [vm-1, vm-2, vm-3] (all in same project)
|
|
LB VIP: 10.0.1.100 (allocated from subnet)
|
|
```
|
|
|
|
### S5: LightningStor Integration
|
|
|
|
```
|
|
User creates Volume → LightningStor allocates block device
|
|
Volume attachment to VM → PlasmaVMC attaches virtio-blk
|
|
Snapshot management → LightningStor + ChainFire
|
|
```
|
|
|
|
## Testing & Validation
|
|
|
|
**Integration Tests**: 8 tests validating complete E2E flow
|
|
|
|
| Test Suite | Location | Tests | Coverage |
|
|
|------------|----------|-------|----------|
|
|
| IAM Tenant Path | iam/.../tenant_path_integration.rs | 6 | Auth, RBAC, isolation |
|
|
| Network + VM | plasmavmc/.../prismnet_integration.rs | 2 | VPC lifecycle, VM attach |
|
|
|
|
**Key Validations**:
|
|
- ✅ User authentication and token issuance
|
|
- ✅ Organization and project scoping
|
|
- ✅ RBAC policy evaluation
|
|
- ✅ Cross-tenant access denial
|
|
- ✅ VPC, subnet, and port creation
|
|
- ✅ Port attachment to VMs
|
|
- ✅ Port detachment on VM deletion
|
|
- ✅ Tenant-isolated networking
|
|
|
|
See [E2E Test Documentation](../por/T023-e2e-tenant-path/e2e_test.md) for detailed test descriptions.
|
|
|
|
## Conclusion
|
|
|
|
The MVP-Beta tenant path provides a complete, production-ready foundation for multi-tenant cloud infrastructure:
|
|
|
|
- **Strong tenant isolation** at IAM, network, and compute layers
|
|
- **Flexible RBAC** with hierarchical scopes (System → Org → Project)
|
|
- **Network virtualization** with VPC overlay using OVN
|
|
- **VM provisioning** with seamless network attachment
|
|
- **Comprehensive testing** validating all integration points
|
|
|
|
This architecture enables secure, isolated cloud deployments for multiple tenants on shared infrastructure, with clear boundaries and well-defined integration points for future extensions (DNS, load balancing, storage).
|