From 87473aa49dd502f0cc8c0afcc93c954ad0072247 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 10 Jun 2026 14:32:14 +0100 Subject: [PATCH] Fail provider-MSP proof when workspace entitlement leases cannot chain-verify The install proof passed green this morning while every provisioned workspace ran unlicensed; the entitlement gap survived because nothing asserted lease health. The workspace proof now reads each workspace's provisioned billing state and verifies the lease exactly the way a release-build client runtime will: against the hosted entitlement trust root, through the provider MSP license chain, requiring white_label. A present license with an unverifiable lease fails the proof with the specific reason; the environment-fallback plan (dev, no license) reports entitlement_skipped_reason=no_provider_msp_license instead of asserting. Proof and install-proof workspace output lines gain entitlement_lease_checked/verified, entitlement_white_label, and the skip reason. --- .../provider_msp_install_proof.go | 6 +- cmd/pulse-control-plane/provider_msp_proof.go | 79 ++++++++++++++++++- .../provider_msp_proof_test.go | 49 ++++++++++++ 3 files changed, 132 insertions(+), 2 deletions(-) diff --git a/cmd/pulse-control-plane/provider_msp_install_proof.go b/cmd/pulse-control-plane/provider_msp_install_proof.go index e39286b8e..08e37abe0 100644 --- a/cmd/pulse-control-plane/provider_msp_install_proof.go +++ b/cmd/pulse-control-plane/provider_msp_install_proof.go @@ -605,7 +605,7 @@ func printProviderMSPInstallProofReport(report *providerMSPInstallProofReport) { fmt.Printf("final_status_unhealthy_tenants=%d\n", report.FinalStatusUnhealthyTenants) fmt.Printf("final_status_stuck_provisioning_tenants=%d\n", report.FinalStatusStuckProvisioningTenants) for _, workspace := range report.Workspaces { - fmt.Printf("workspace=%s display_name=%q state=%s plan_version=%s container_id=%s public_url=%s install_type=%s install_token_id=%s install_command_generated=%t agent_token_auth_verified=%t setup_facts_token_use_visible=%t agent_report_ingest_verified=%t agent_report_agent_id=%s agent_report_hostname=%s token_rotation_verified=%t rotated_install_token_id=%s old_install_token_rejected=%t rotated_agent_report_verified=%t handoff_exchange_verified=%t handoff_target_path=%s\n", + fmt.Printf("workspace=%s display_name=%q state=%s plan_version=%s container_id=%s public_url=%s install_type=%s install_token_id=%s install_command_generated=%t agent_token_auth_verified=%t setup_facts_token_use_visible=%t agent_report_ingest_verified=%t agent_report_agent_id=%s agent_report_hostname=%s token_rotation_verified=%t rotated_install_token_id=%s old_install_token_rejected=%t rotated_agent_report_verified=%t handoff_exchange_verified=%t handoff_target_path=%s entitlement_lease_checked=%t entitlement_lease_verified=%t entitlement_white_label=%t entitlement_skipped_reason=%s\n", workspace.TenantID, workspace.DisplayName, workspace.State, @@ -626,6 +626,10 @@ func printProviderMSPInstallProofReport(report *providerMSPInstallProofReport) { workspace.RotatedAgentReportVerified, workspace.HandoffExchangeVerified, workspace.HandoffTargetPath, + workspace.EntitlementLeaseChecked, + workspace.EntitlementLeaseVerified, + workspace.EntitlementWhiteLabel, + workspace.EntitlementSkippedReason, ) } for _, failure := range report.Failures { diff --git a/cmd/pulse-control-plane/provider_msp_proof.go b/cmd/pulse-control-plane/provider_msp_proof.go index 25844e6e8..f8c987ae2 100644 --- a/cmd/pulse-control-plane/provider_msp_proof.go +++ b/cmd/pulse-control-plane/provider_msp_proof.go @@ -27,6 +27,7 @@ import ( "github.com/rcourtman/pulse-go-rewrite/internal/monitoring" agentshost "github.com/rcourtman/pulse-go-rewrite/pkg/agents/host" internalauth "github.com/rcourtman/pulse-go-rewrite/pkg/auth" + pkglicensing "github.com/rcourtman/pulse-go-rewrite/pkg/licensing" "github.com/spf13/cobra" ) @@ -76,6 +77,10 @@ type providerMSPProofWorkspace struct { RotatedAgentReportVerified bool HandoffExchangeVerified bool HandoffTargetPath string + EntitlementLeaseChecked bool + EntitlementLeaseVerified bool + EntitlementWhiteLabel bool + EntitlementSkippedReason string } type providerMSPProofReport struct { @@ -451,6 +456,11 @@ func (rt *providerMSPProofRuntime) proveProviderMSPWorkspace(ctx context.Context return providerMSPProofWorkspace{}, err } + entitlement, err := rt.verifyProviderMSPProofEntitlementLease(tenant, tenantDataDir) + if err != nil { + return providerMSPProofWorkspace{}, err + } + return providerMSPProofWorkspace{ TenantID: tenant.ID, DisplayName: tenant.DisplayName, @@ -474,9 +484,72 @@ func (rt *providerMSPProofRuntime) proveProviderMSPWorkspace(ctx context.Context RotatedAgentReportVerified: rotation.RotatedAgentReportVerified, HandoffExchangeVerified: exchangedTargetPath == targetPath, HandoffTargetPath: exchangedTargetPath, + EntitlementLeaseChecked: entitlement.Checked, + EntitlementLeaseVerified: entitlement.Verified, + EntitlementWhiteLabel: entitlement.WhiteLabel, + EntitlementSkippedReason: entitlement.SkippedReason, }, nil } +type providerMSPProofEntitlementLease struct { + Checked bool + Verified bool + WhiteLabel bool + SkippedReason string +} + +// verifyProviderMSPProofEntitlementLease verifies the provisioned workspace's +// hosted entitlement lease exactly the way a release-build client runtime +// will: against the embedded entitlement trust root, through the provider +// MSP license chain. The proof previously passed while every workspace ran +// unlicensed; a present license with an unverifiable lease must fail here, +// not in the client's first branded report. +func (rt *providerMSPProofRuntime) verifyProviderMSPProofEntitlementLease(tenant *registry.Tenant, tenantDataDir string) (providerMSPProofEntitlementLease, error) { + result := providerMSPProofEntitlementLease{} + if tenant == nil { + return result, fmt.Errorf("tenant is required") + } + if !rt.cfg.IsProviderHostedMSP() || strings.TrimSpace(rt.cfg.ProviderMSPLicenseKey) == "" { + result.SkippedReason = "no_provider_msp_license" + return result, nil + } + result.Checked = true + + state, err := runtimeconfig.NewFileBillingStore(tenantDataDir).GetBillingState("default") + if err != nil { + return result, fmt.Errorf("read provisioned billing state for workspace %s: %w", tenant.ID, err) + } + if state == nil || strings.TrimSpace(state.EntitlementJWT) == "" { + return result, fmt.Errorf("workspace %s was provisioned without a hosted entitlement lease", tenant.ID) + } + if strings.TrimSpace(state.EntitlementRefreshToken) == "" { + return result, fmt.Errorf("workspace %s was provisioned without an entitlement refresh token", tenant.ID) + } + + root, err := pkglicensing.HostedEntitlementPublicKey() + if err != nil { + return result, fmt.Errorf("resolve the entitlement verification root a client runtime would use: %w", err) + } + claims, err := pkglicensing.VerifyEntitlementLeaseToken(state.EntitlementJWT, root, "", time.Now().UTC()) + if err != nil { + return result, fmt.Errorf("workspace %s entitlement lease does not verify the way a client runtime would: %w", tenant.ID, err) + } + if strings.TrimSpace(claims.ProviderLicense) == "" { + return result, fmt.Errorf("workspace %s entitlement lease does not chain the provider MSP license; release-build client runtimes would reject it", tenant.ID) + } + result.Verified = true + for _, capability := range claims.Capabilities { + if capability == pkglicensing.FeatureWhiteLabel { + result.WhiteLabel = true + break + } + } + if !result.WhiteLabel { + return result, fmt.Errorf("workspace %s entitlement lease is missing white_label; branded client reports would be locked", tenant.ID) + } + return result, nil +} + type providerMSPProofAgentReportIngest struct { Verified bool AgentID string @@ -1030,7 +1103,7 @@ func printProviderMSPProofReport(report *providerMSPProofReport) { fmt.Printf("token_rotation_verified=%t\n", report.TokenRotationVerified) fmt.Printf("cleanup=%t\n", report.Cleanup) for _, workspace := range report.Workspaces { - fmt.Printf("workspace=%s display_name=%q state=%s plan_version=%s container_id=%s public_url=%s install_type=%s install_token_id=%s install_command_generated=%t agent_token_auth_verified=%t setup_facts_token_use_visible=%t agent_report_ingest_verified=%t agent_report_agent_id=%s agent_report_hostname=%s token_rotation_verified=%t rotated_install_token_id=%s old_install_token_rejected=%t rotated_agent_report_verified=%t handoff_exchange_verified=%t handoff_target_path=%s\n", + fmt.Printf("workspace=%s display_name=%q state=%s plan_version=%s container_id=%s public_url=%s install_type=%s install_token_id=%s install_command_generated=%t agent_token_auth_verified=%t setup_facts_token_use_visible=%t agent_report_ingest_verified=%t agent_report_agent_id=%s agent_report_hostname=%s token_rotation_verified=%t rotated_install_token_id=%s old_install_token_rejected=%t rotated_agent_report_verified=%t handoff_exchange_verified=%t handoff_target_path=%s entitlement_lease_checked=%t entitlement_lease_verified=%t entitlement_white_label=%t entitlement_skipped_reason=%s\n", workspace.TenantID, workspace.DisplayName, workspace.State, @@ -1051,6 +1124,10 @@ func printProviderMSPProofReport(report *providerMSPProofReport) { workspace.RotatedAgentReportVerified, workspace.HandoffExchangeVerified, workspace.HandoffTargetPath, + workspace.EntitlementLeaseChecked, + workspace.EntitlementLeaseVerified, + workspace.EntitlementWhiteLabel, + workspace.EntitlementSkippedReason, ) } } diff --git a/cmd/pulse-control-plane/provider_msp_proof_test.go b/cmd/pulse-control-plane/provider_msp_proof_test.go index 40656f122..1230e220f 100644 --- a/cmd/pulse-control-plane/provider_msp_proof_test.go +++ b/cmd/pulse-control-plane/provider_msp_proof_test.go @@ -193,6 +193,15 @@ func TestProviderMSPProofExercisesWorkspaceInstallHandoffAndIsolation(t *testing if !workspace.HandoffExchangeVerified || workspace.HandoffTargetPath != "/settings/infrastructure?add=linux-host" { t.Fatalf("handoff exchange proof mismatch: %#v", workspace) } + if !workspace.EntitlementLeaseChecked { + t.Fatalf("entitlement lease was not checked for %s despite a provider MSP license being configured", workspace.TenantID) + } + if !workspace.EntitlementLeaseVerified { + t.Fatalf("entitlement lease did not chain-verify for %s", workspace.TenantID) + } + if !workspace.EntitlementWhiteLabel { + t.Fatalf("entitlement lease for %s is missing white_label", workspace.TenantID) + } } } @@ -250,12 +259,47 @@ func TestProviderMSPProofLoadsSignedLicenseFilePlan(t *testing.T) { } } +// mintProviderMSPProofLicense installs a fresh license trust root via env +// (dev builds only) and returns a root-signed MSP license binding the given +// lease signing public key, mirroring what LoadConfig resolves from +// CP_PROVIDER_MSP_LICENSE_FILE in a real deployment. +func mintProviderMSPProofLicense(t *testing.T, planVersion string, leaseSigningPublicKey ed25519.PublicKey) string { + t.Helper() + rootPub, rootPriv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("GenerateKey root: %v", err) + } + t.Setenv("PULSE_LICENSE_PUBLIC_KEY", base64.StdEncoding.EncodeToString(rootPub)) + t.Setenv("PULSE_TRIAL_ACTIVATION_PUBLIC_KEY", base64.StdEncoding.EncodeToString(rootPub)) + t.Cleanup(func() { pkglicensing.SetPublicKey(nil) }) + pkglicensing.InitEmbeddedPublicKey() + + claims := pkglicensing.Claims{ + LicenseID: "lic_provider_msp_test", + Email: "provider@example.com", + Tier: pkglicensing.TierMSP, + IssuedAt: time.Now().Add(-time.Minute).Unix(), + ExpiresAt: time.Now().Add(24 * time.Hour).Unix(), + PlanVersion: planVersion, + EntitlementSigningPublicKey: base64.StdEncoding.EncodeToString(leaseSigningPublicKey), + } + payloadBytes, err := json.Marshal(claims) + if err != nil { + t.Fatalf("Marshal claims: %v", err) + } + header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"EdDSA","typ":"JWT"}`)) + payload := base64.RawURLEncoding.EncodeToString(payloadBytes) + signature := base64.RawURLEncoding.EncodeToString(ed25519.Sign(rootPriv, []byte(header+"."+payload))) + return header + "." + payload + "." + signature +} + func testProviderMSPProofConfig(t *testing.T) *cloudcp.CPConfig { t.Helper() publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatalf("GenerateKey: %v", err) } + licenseKey := mintProviderMSPProofLicense(t, "msp_growth", publicKey) return &cloudcp.CPConfig{ DataDir: t.TempDir(), Environment: "development", @@ -273,6 +317,7 @@ func testProviderMSPProofConfig(t *testing.T) *cloudcp.CPConfig { ProviderMSPPlanSource: cloudcp.ProviderMSPPlanSourceLicenseFile, ProviderMSPLicenseID: "lic_provider_msp_test", ProviderMSPLicenseEmail: "provider@example.com", + ProviderMSPLicenseKey: licenseKey, TrialActivationPrivateKey: base64.StdEncoding.EncodeToString(privateKey), TrialActivationPublicKey: base64.StdEncoding.EncodeToString(publicKey), EmailFrom: "noreply@example.com", @@ -309,6 +354,10 @@ func writeProviderMSPProofLicenseForTest(t *testing.T, licenseID, email, planVer t.Fatalf("GenerateKey: %v", err) } t.Setenv("PULSE_LICENSE_PUBLIC_KEY", base64.StdEncoding.EncodeToString(publicKey)) + // Release binaries embed one key as both the license root and the hosted + // entitlement verification root; mirror that so the proof's lease + // verification can resolve a root in dev test builds. + t.Setenv("PULSE_TRIAL_ACTIVATION_PUBLIC_KEY", base64.StdEncoding.EncodeToString(publicKey)) t.Setenv("PULSE_LICENSE_DEV_MODE", "false") t.Cleanup(func() { pkglicensing.SetPublicKey(nil) })