Make provider-MSP client runtimes verifiably licensed via chained entitlement leases

Provider-hosted MSP client workspaces previously sat at Community tier
forever: the runtime refreshed leases against the built-in Pulse Cloud URL
(hibernated, 522) instead of the provider control plane, and release-build
images verify leases only against the embedded Pulse key, which an
operator-generated CP_TRIAL_ACTIVATION_PRIVATE_KEY can never satisfy.

- Inject PULSE_PRO_TRIAL_SIGNUP_URL=CP_BASE_URL into client containers so
  lease refresh targets the provider control plane.
- Chain trust through the Pulse-signed provider MSP license: the license
  binds the provider's lease signing public key
  (entitlement_signing_public_key claim); the control plane embeds the
  license in every lease (provider_license claim); release-build runtimes
  verify embedded Pulse root -> provider license -> lease signature.
- Cap chain-verified leases at ProviderChainedLeaseCapabilities: MSP tier
  plus white_label (branded per-client reports), minus Pulse-service-backed
  relay/mobile_app/push_notifications, which otherwise loop doomed
  registrations against Pulse's relay.
- Fail fast at control-plane startup when the license does not bind the
  configured signing key, instead of provisioning silently unlicensed
  client workspaces.

Verified live on a Colima harness: release-tagged tenant image with test
embedded root, Traefik TLS, full provider-msp proof, tenant reports
valid=true plan_version=msp_growth with white_label and zero relay
failures.
This commit is contained in:
rcourtman 2026-06-10 13:58:52 +01:00
parent edd7f001ab
commit d25b99cbcc
17 changed files with 688 additions and 33 deletions

View file

@ -318,6 +318,7 @@ func newMobileProofRuntime(ctx context.Context) (*mobileProofRuntime, error) {
Network: cfg.DockerNetwork,
BaseDomain: mobileProofBaseDomainFromURL(cfg.BaseURL),
TrialActivationPublicKey: cfg.TrialActivationPublicKey,
ControlPlaneBaseURL: cfg.BaseURL,
TrustedProxyCIDRs: cfg.TrustedProxyCIDRs,
MemoryLimit: cfg.TenantMemoryLimit,
CPUShares: cfg.TenantCPUShares,

View file

@ -189,6 +189,7 @@ func providerMSPDockerManagerConfig(cfg *cloudcp.CPConfig) cpDocker.ManagerConfi
IsolateTenantNetworks: true,
BaseDomain: providerMSPBaseDomainFromURL(cfg.BaseURL),
TrialActivationPublicKey: cfg.TrialActivationPublicKey,
ControlPlaneBaseURL: cfg.BaseURL,
TrustedProxyCIDRs: cfg.TrustedProxyCIDRs,
MemoryLimit: cfg.TenantMemoryLimit,
CPUShares: cfg.TenantCPUShares,

View file

@ -179,6 +179,9 @@ func newProviderMSPProofRuntimeFromConfig(cfg *cloudcp.CPConfig) (*providerMSPPr
}
hostedEntitlements := entitlements.NewService(reg, cfg.BaseURL, cfg.TrialActivationPrivateKey)
if cfg.IsProviderHostedMSP() {
hostedEntitlements.SetProviderLicense(cfg.ProviderMSPLicenseKey)
}
provisioner := cpstripe.NewProvisioner(
reg,
cfg.TenantsDir(),

View file

@ -312,13 +312,23 @@ func writeProviderMSPProofLicenseForTest(t *testing.T, licenseID, email, planVer
t.Setenv("PULSE_LICENSE_DEV_MODE", "false")
t.Cleanup(func() { pkglicensing.SetPublicKey(nil) })
// Bind the lease signing key for the fixed CP_TRIAL_ACTIVATION_PRIVATE_KEY
// seed installed by setProviderMSPProofLoadConfigEnv; provider-hosted MSP
// config loading requires the license to bind the control plane's key.
leaseSigningSeed, err := base64.StdEncoding.DecodeString("A8medgdNdm12GXfTXWo6+TMZ2BeHPCLg2kd0znn6ZUk=")
if err != nil {
t.Fatalf("decode lease signing seed: %v", err)
}
leaseSigningPublicKey := ed25519.NewKeyFromSeed(leaseSigningSeed).Public().(ed25519.PublicKey)
claims := pkglicensing.Claims{
LicenseID: licenseID,
Email: email,
Tier: pkglicensing.TierMSP,
IssuedAt: time.Now().Add(-time.Minute).Unix(),
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
PlanVersion: planVersion,
LicenseID: licenseID,
Email: email,
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),
}
header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"EdDSA","typ":"JWT"}`))
payloadBytes, err := json.Marshal(claims)

View file

@ -117,6 +117,7 @@ func AuditCloud(ctx context.Context, cfg *CPConfig) (*CloudAuditReport, error) {
Network: cfg.DockerNetwork,
BaseDomain: baseDomainFromURL(cfg.BaseURL),
TrialActivationPublicKey: cfg.TrialActivationPublicKey,
ControlPlaneBaseURL: cfg.BaseURL,
TrustedProxyCIDRs: cfg.TrustedProxyCIDRs,
TenantReportBrand: cpDocker.TenantReportBrandConfig{
DisplayName: cfg.ReportBrandDisplayName,

View file

@ -83,6 +83,8 @@ type CPConfig struct {
ProviderMSPLicenseFile string // Signed provider MSP license source
ProviderMSPLicenseID string // Validated provider MSP license ID
ProviderMSPLicenseEmail string // Validated provider MSP license holder
ProviderMSPLicenseKey string // Raw validated provider MSP license (chained into tenant leases)
ProviderMSPLeaseSigningPublicKey string // entitlement_signing_public_key claim from the provider MSP license
LicenseServerURL string
LicenseAdminToken string
TrialActivationPrivateKey string
@ -189,15 +191,19 @@ func LoadConfig() (*CPConfig, error) {
providerMSPPlanSource := ProviderMSPPlanSourceEnvFallback
providerMSPLicenseID := ""
providerMSPLicenseEmail := ""
providerMSPLicenseKey := ""
providerMSPLeaseSigningPublicKey := ""
if isMSPControlPlaneMode(controlPlaneMode) && providerMSPLicenseFile != "" {
resolvedPlan, licenseID, licenseEmail, err := resolveProviderMSPPlanFromLicenseFile(providerMSPLicenseFile)
resolved, err := resolveProviderMSPPlanFromLicenseFile(providerMSPLicenseFile)
if err != nil {
return nil, fmt.Errorf("resolve provider MSP license: %w", err)
}
providerMSPPlanVersion = resolvedPlan
providerMSPPlanVersion = resolved.PlanVersion
providerMSPPlanSource = ProviderMSPPlanSourceLicenseFile
providerMSPLicenseID = licenseID
providerMSPLicenseEmail = licenseEmail
providerMSPLicenseID = resolved.LicenseID
providerMSPLicenseEmail = resolved.LicenseEmail
providerMSPLicenseKey = resolved.LicenseKey
providerMSPLeaseSigningPublicKey = resolved.LeaseSigningPublicKey
}
cfg := &CPConfig{
@ -250,6 +256,8 @@ func LoadConfig() (*CPConfig, error) {
ProviderMSPPlanVersion: providerMSPPlanVersion,
ProviderMSPPlanSource: providerMSPPlanSource,
ProviderMSPLicenseFile: providerMSPLicenseFile,
ProviderMSPLicenseKey: providerMSPLicenseKey,
ProviderMSPLeaseSigningPublicKey: providerMSPLeaseSigningPublicKey,
ProviderMSPLicenseID: providerMSPLicenseID,
ProviderMSPLicenseEmail: providerMSPLicenseEmail,
LicenseServerURL: envOrDefault("PULSE_LICENSE_SERVER_URL", "https://license.pulserelay.pro"),
@ -271,9 +279,43 @@ func LoadConfig() (*CPConfig, error) {
if err := cfg.validate(); err != nil {
return nil, fmt.Errorf("validate control plane config: %w", err)
}
if err := cfg.validateProviderMSPLeaseSigningBinding(); err != nil {
return nil, err
}
return cfg, nil
}
// validateProviderMSPLeaseSigningBinding fails fast when a provider-hosted
// MSP control plane cannot mint entitlement leases that client runtimes will
// verify. Release-build tenant runtimes only trust lease signing keys bound
// into the Pulse-signed provider MSP license, so a missing or mismatched
// binding would provision client workspaces that silently run unlicensed.
func (c *CPConfig) validateProviderMSPLeaseSigningBinding() error {
if c == nil || !c.IsProviderHostedMSP() || strings.TrimSpace(c.ProviderMSPLicenseKey) == "" {
// Pulse-hosted modes sign with Pulse's own key, and the env-fallback
// plan path (dev/install-proof without a license file) has nothing
// to bind against.
return nil
}
boundKey := strings.TrimSpace(c.ProviderMSPLeaseSigningPublicKey)
if boundKey == "" {
return fmt.Errorf("CP_PROVIDER_MSP_LICENSE_FILE does not bind an entitlement lease signing key (entitlement_signing_public_key); request an updated provider MSP license bound to this control plane's CP_TRIAL_ACTIVATION_PRIVATE_KEY public key %q", c.TrialActivationPublicKey)
}
bound, err := pkglicensing.DecodePublicKey(boundKey)
if err != nil {
return fmt.Errorf("CP_PROVIDER_MSP_LICENSE_FILE entitlement_signing_public_key is malformed: %w", err)
}
derived, err := pkglicensing.DecodePublicKey(c.TrialActivationPublicKey)
if err != nil {
return fmt.Errorf("derive trial activation public key: %w", err)
}
if !bound.Equal(derived) {
return fmt.Errorf("CP_TRIAL_ACTIVATION_PRIVATE_KEY does not match the entitlement lease signing key bound in CP_PROVIDER_MSP_LICENSE_FILE (license binds %q, control plane derives %q); use the keypair the license was issued for, or request a license bound to this key", boundKey, c.TrialActivationPublicKey)
}
return nil
}
func deriveTrialActivationPublicKey(encodedPrivateKey string) (string, error) {
privateKey, err := pkglicensing.DecodeEd25519PrivateKey(strings.TrimSpace(encodedPrivateKey))
if err != nil {
@ -537,37 +579,51 @@ func isMSPControlPlaneMode(mode ControlPlaneMode) bool {
}
}
func resolveProviderMSPPlanFromLicenseFile(path string) (planVersion, licenseID, licenseEmail string, err error) {
type providerMSPLicenseResolution struct {
PlanVersion string
LicenseID string
LicenseEmail string
LicenseKey string // raw signed license, chained into tenant entitlement leases
LeaseSigningPublicKey string // entitlement_signing_public_key claim, empty when the license predates lease chaining
}
func resolveProviderMSPPlanFromLicenseFile(path string) (*providerMSPLicenseResolution, error) {
path = strings.TrimSpace(path)
if path == "" {
return "", "", "", fmt.Errorf("CP_PROVIDER_MSP_LICENSE_FILE is required")
return nil, fmt.Errorf("CP_PROVIDER_MSP_LICENSE_FILE is required")
}
licenseKey, err := readProviderMSPLicenseFile(path)
if err != nil {
return "", "", "", err
return nil, err
}
pkglicensing.InitEmbeddedPublicKey()
license, err := pkglicensing.ValidateLicense(licenseKey)
if err != nil {
return "", "", "", fmt.Errorf("validate CP_PROVIDER_MSP_LICENSE_FILE: %w", err)
return nil, fmt.Errorf("validate CP_PROVIDER_MSP_LICENSE_FILE: %w", err)
}
if license == nil {
return "", "", "", fmt.Errorf("validate CP_PROVIDER_MSP_LICENSE_FILE: no license returned")
return nil, fmt.Errorf("validate CP_PROVIDER_MSP_LICENSE_FILE: no license returned")
}
if license.Claims.Tier != pkglicensing.TierMSP {
return "", "", "", fmt.Errorf("CP_PROVIDER_MSP_LICENSE_FILE must contain an MSP license tier, got %q", license.Claims.Tier)
return nil, fmt.Errorf("CP_PROVIDER_MSP_LICENSE_FILE must contain an MSP license tier, got %q", license.Claims.Tier)
}
plan := license.Claims.EntitlementPlanVersion()
if strings.TrimSpace(plan) == "" {
return "", "", "", fmt.Errorf("CP_PROVIDER_MSP_LICENSE_FILE must contain plan_version")
return nil, fmt.Errorf("CP_PROVIDER_MSP_LICENSE_FILE must contain plan_version")
}
if !strings.HasPrefix(strings.ToLower(plan), "msp_") {
return "", "", "", fmt.Errorf("CP_PROVIDER_MSP_LICENSE_FILE plan_version must be a canonical MSP plan version, got %q", plan)
return nil, fmt.Errorf("CP_PROVIDER_MSP_LICENSE_FILE plan_version must be a canonical MSP plan version, got %q", plan)
}
if _, known := pkglicensing.WorkspaceLimitForPlan(plan); !known {
return "", "", "", fmt.Errorf("CP_PROVIDER_MSP_LICENSE_FILE plan_version must have a known workspace limit, got %q", plan)
return nil, fmt.Errorf("CP_PROVIDER_MSP_LICENSE_FILE plan_version must have a known workspace limit, got %q", plan)
}
return plan, strings.TrimSpace(license.Claims.LicenseID), strings.ToLower(strings.TrimSpace(license.Claims.Email)), nil
return &providerMSPLicenseResolution{
PlanVersion: plan,
LicenseID: strings.TrimSpace(license.Claims.LicenseID),
LicenseEmail: strings.ToLower(strings.TrimSpace(license.Claims.Email)),
LicenseKey: licenseKey,
LeaseSigningPublicKey: strings.TrimSpace(license.Claims.EntitlementSigningPublicKey),
}, nil
}
func readProviderMSPLicenseFile(path string) (string, error) {

View file

@ -474,6 +474,22 @@ func setPulseHostedMSPEnv(t *testing.T) {
}
func writeProviderMSPLicenseForTest(t *testing.T, tier pkglicensing.Tier, planVersion string) string {
t.Helper()
return writeProviderMSPLicenseForTestWithBinding(t, tier, planVersion, nil)
}
// trialSigningEnvPublicKey derives the public key for the fixed
// CP_TRIAL_ACTIVATION_PRIVATE_KEY seed installed by setTrialSigningEnv.
func trialSigningEnvPublicKey(t *testing.T) ed25519.PublicKey {
t.Helper()
seed, err := base64.StdEncoding.DecodeString("A8medgdNdm12GXfTXWo6+TMZ2BeHPCLg2kd0znn6ZUk=")
if err != nil {
t.Fatalf("decode trial signing seed: %v", err)
}
return ed25519.NewKeyFromSeed(seed).Public().(ed25519.PublicKey)
}
func writeProviderMSPLicenseForTestWithBinding(t *testing.T, tier pkglicensing.Tier, planVersion string, leaseSigningPublicKey ed25519.PublicKey) string {
t.Helper()
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
@ -491,6 +507,9 @@ func writeProviderMSPLicenseForTest(t *testing.T, tier pkglicensing.Tier, planVe
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
PlanVersion: planVersion,
}
if len(leaseSigningPublicKey) > 0 {
claims.EntitlementSigningPublicKey = base64.StdEncoding.EncodeToString(leaseSigningPublicKey)
}
header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"EdDSA","typ":"JWT"}`))
payloadBytes, err := json.Marshal(claims)
if err != nil {
@ -698,7 +717,7 @@ func TestLoadConfig_ProviderHostedMSPUsesSignedLicenseFilePlan(t *testing.T) {
setProviderHostedMSPEnv(t)
t.Setenv("CP_ENV", "production")
t.Setenv("CP_PROVIDER_MSP_PLAN_VERSION", "msp_starter")
t.Setenv("CP_PROVIDER_MSP_LICENSE_FILE", writeProviderMSPLicenseForTest(t, pkglicensing.TierMSP, "msp_growth"))
t.Setenv("CP_PROVIDER_MSP_LICENSE_FILE", writeProviderMSPLicenseForTestWithBinding(t, pkglicensing.TierMSP, "msp_growth", trialSigningEnvPublicKey(t)))
cfg, err := LoadConfig()
if err != nil {
@ -716,6 +735,54 @@ func TestLoadConfig_ProviderHostedMSPUsesSignedLicenseFilePlan(t *testing.T) {
if cfg.ProviderMSPLicenseEmail != "provider@example.com" {
t.Fatalf("ProviderMSPLicenseEmail = %q", cfg.ProviderMSPLicenseEmail)
}
if strings.TrimSpace(cfg.ProviderMSPLicenseKey) == "" {
t.Fatal("ProviderMSPLicenseKey should retain the raw license for lease chaining")
}
if cfg.ProviderMSPLeaseSigningPublicKey != base64.StdEncoding.EncodeToString(trialSigningEnvPublicKey(t)) {
t.Fatalf("ProviderMSPLeaseSigningPublicKey = %q", cfg.ProviderMSPLeaseSigningPublicKey)
}
}
func TestLoadConfig_ProviderHostedMSPRequiresLeaseSigningBinding(t *testing.T) {
setProviderHostedMSPEnv(t)
t.Setenv("CP_ENV", "production")
t.Setenv("CP_PROVIDER_MSP_LICENSE_FILE", writeProviderMSPLicenseForTest(t, pkglicensing.TierMSP, "msp_growth"))
_, err := LoadConfig()
if err == nil {
t.Fatal("expected error when the provider MSP license does not bind a lease signing key")
}
if !strings.Contains(err.Error(), "entitlement_signing_public_key") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestLoadConfig_ProviderHostedMSPRejectsMismatchedLeaseSigningKey(t *testing.T) {
setProviderHostedMSPEnv(t)
t.Setenv("CP_ENV", "production")
otherPub, _, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("GenerateKey: %v", err)
}
t.Setenv("CP_PROVIDER_MSP_LICENSE_FILE", writeProviderMSPLicenseForTestWithBinding(t, pkglicensing.TierMSP, "msp_growth", otherPub))
_, err = LoadConfig()
if err == nil {
t.Fatal("expected error when CP_TRIAL_ACTIVATION_PRIVATE_KEY does not match the license binding")
}
if !strings.Contains(err.Error(), "does not match the entitlement lease signing key") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestLoadConfig_PulseHostedMSPDoesNotRequireLeaseSigningBinding(t *testing.T) {
setPulseHostedMSPEnv(t)
t.Setenv("CP_ENV", "production")
t.Setenv("CP_PROVIDER_MSP_LICENSE_FILE", writeProviderMSPLicenseForTest(t, pkglicensing.TierMSP, "msp_scale"))
if _, err := LoadConfig(); err != nil {
t.Fatalf("Pulse-hosted MSP must accept licenses without a lease signing binding: %v", err)
}
}
func TestLoadConfig_ProviderHostedMSPRequiresLicenseFileInProduction(t *testing.T) {

View file

@ -18,6 +18,8 @@ import (
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
"github.com/rs/zerolog/log"
pkglicensing "github.com/rcourtman/pulse-go-rewrite/pkg/licensing"
)
// ManagerConfig holds Docker manager settings.
@ -29,7 +31,12 @@ type ManagerConfig struct {
SupportContainerLabels []string
BaseDomain string
TrialActivationPublicKey string
TrustedProxyCIDRs []string
// ControlPlaneBaseURL is the control plane's own external base URL
// (CP_BASE_URL). It is injected into client runtimes as the hosted
// commercial base URL so entitlement lease refresh targets this
// control plane instead of the built-in Pulse Cloud default.
ControlPlaneBaseURL string
TrustedProxyCIDRs []string
// TenantDisplayName resolves a tenant's workspace display name at
// container-create time; the value is injected as PULSE_TENANT_NAME so
// alert webhook payloads carry a human-readable workspace label instead
@ -554,7 +561,7 @@ func tenantRuntimeContainerConfig(tenantID string, cfg ManagerConfig, labels map
Image: cfg.Image,
User: tenantRuntimeUserFor(cfg),
Labels: labels,
Env: tenantEnvForRuntime(tenantID, tenantDisplayNameFor(cfg, tenantID), cfg.BaseDomain, cfg.TrialActivationPublicKey, trustedProxyCIDRs, tenantRuntimeUIDFor(cfg), tenantRuntimeGIDFor(cfg), cfg.TenantReportBrand),
Env: tenantEnvForRuntime(tenantID, tenantDisplayNameFor(cfg, tenantID), cfg.BaseDomain, cfg.TrialActivationPublicKey, cfg.ControlPlaneBaseURL, trustedProxyCIDRs, tenantRuntimeUIDFor(cfg), tenantRuntimeGIDFor(cfg), cfg.TenantReportBrand),
}
}
@ -640,11 +647,11 @@ func tenantRuntimeOwnershipPaths() []string {
}
}
func tenantEnv(tenantID, tenantName, baseDomain, trialActivationPublicKey string, trustedProxyCIDRs []string, reportBrand TenantReportBrandConfig) []string {
return tenantEnvForRuntime(tenantID, tenantName, baseDomain, trialActivationPublicKey, trustedProxyCIDRs, tenantRuntimeUID, tenantRuntimeGID, reportBrand)
func tenantEnv(tenantID, tenantName, baseDomain, trialActivationPublicKey, controlPlaneBaseURL string, trustedProxyCIDRs []string, reportBrand TenantReportBrandConfig) []string {
return tenantEnvForRuntime(tenantID, tenantName, baseDomain, trialActivationPublicKey, controlPlaneBaseURL, trustedProxyCIDRs, tenantRuntimeUID, tenantRuntimeGID, reportBrand)
}
func tenantEnvForRuntime(tenantID, tenantName, baseDomain, trialActivationPublicKey string, trustedProxyCIDRs []string, runtimeUID, runtimeGID int, reportBrand TenantReportBrandConfig) []string {
func tenantEnvForRuntime(tenantID, tenantName, baseDomain, trialActivationPublicKey, controlPlaneBaseURL string, trustedProxyCIDRs []string, runtimeUID, runtimeGID int, reportBrand TenantReportBrandConfig) []string {
routing := CanonicalTenantRuntimeRouting(tenantID, baseDomain)
tenantID = strings.TrimSpace(tenantID)
@ -666,6 +673,12 @@ func tenantEnvForRuntime(tenantID, tenantName, baseDomain, trialActivationPublic
if strings.TrimSpace(trialActivationPublicKey) != "" {
env = append(env, "PULSE_TRIAL_ACTIVATION_PUBLIC_KEY="+strings.TrimSpace(trialActivationPublicKey))
}
if value := strings.TrimSpace(controlPlaneBaseURL); value != "" {
// Point hosted entitlement lease refresh at this control plane.
// Without it the runtime falls back to the built-in Pulse Cloud
// URL, which a provider-hosted control plane does not operate.
env = append(env, pkglicensing.ProTrialSignupURLEnvVar+"="+value)
}
if len(trustedProxyCIDRs) > 0 {
env = append(env, "PULSE_TRUSTED_PROXY_CIDRS="+strings.Join(trustedProxyCIDRs, ","))
}

View file

@ -73,7 +73,7 @@ func TestTenantMountsKeepImmutableFilesReadOnly(t *testing.T) {
func TestTenantEnvIncludesImmutableOwnershipContract(t *testing.T) {
t.Parallel()
env := tenantEnv("t-example", "", "cloud.pulserelay.pro", "pubkey-123", []string{"172.18.0.0/16", "127.0.0.1/32"}, TenantReportBrandConfig{})
env := tenantEnv("t-example", "", "cloud.pulserelay.pro", "pubkey-123", "https://cp.example-msp.com", []string{"172.18.0.0/16", "127.0.0.1/32"}, TenantReportBrandConfig{})
want := map[string]bool{
"PULSE_DATA_DIR=/etc/pulse": true,
"PULSE_HOSTED_MODE=true": true,
@ -83,6 +83,7 @@ func TestTenantEnvIncludesImmutableOwnershipContract(t *testing.T) {
"PGID=1000": true,
"PULSE_PUBLIC_URL=https://t-example.cloud.pulserelay.pro": true,
"PULSE_TRIAL_ACTIVATION_PUBLIC_KEY=pubkey-123": true,
"PULSE_PRO_TRIAL_SIGNUP_URL=https://cp.example-msp.com": true,
"PULSE_TRUSTED_PROXY_CIDRS=172.18.0.0/16,127.0.0.1/32": true,
immutableOwnershipPathsEnv + "=/etc/pulse/secrets/handoff.key:/etc/pulse/.cloud_handoff_key": true,
}
@ -96,6 +97,17 @@ func TestTenantEnvIncludesImmutableOwnershipContract(t *testing.T) {
}
}
func TestTenantEnvOmitsEntitlementRefreshURLWhenUnset(t *testing.T) {
t.Parallel()
env := tenantEnv("t-example", "", "cloud.pulserelay.pro", "", "", nil, TenantReportBrandConfig{})
for _, item := range env {
if strings.HasPrefix(item, "PULSE_PRO_TRIAL_SIGNUP_URL=") {
t.Fatalf("PULSE_PRO_TRIAL_SIGNUP_URL should be omitted when no control plane base URL is configured, got %q", item)
}
}
}
func TestCanonicalTenantRuntimeRoutingLowercasesHostedAddressing(t *testing.T) {
t.Parallel()
@ -181,7 +193,7 @@ func TestHealthCheckPrefersProviderMSPIsolatedTenantNetwork(t *testing.T) {
func TestTenantEnvLowercasesPublicURLHost(t *testing.T) {
t.Parallel()
env := tenantEnv("T-AbCd123", "", "Cloud.PulseRelay.Pro", "", nil, TenantReportBrandConfig{})
env := tenantEnv("T-AbCd123", "", "Cloud.PulseRelay.Pro", "", "", nil, TenantReportBrandConfig{})
want := "PULSE_PUBLIC_URL=https://t-abcd123.cloud.pulserelay.pro"
for _, item := range env {
if item == want {
@ -194,7 +206,7 @@ func TestTenantEnvLowercasesPublicURLHost(t *testing.T) {
func TestTenantEnvOmitsPublicURLWithoutTenantContext(t *testing.T) {
t.Parallel()
env := tenantEnv("", "", "", "", nil, TenantReportBrandConfig{})
env := tenantEnv("", "", "", "", "", nil, TenantReportBrandConfig{})
sawTenantID := false
for _, item := range env {
if strings.HasPrefix(item, "PULSE_PUBLIC_URL=") {
@ -212,7 +224,7 @@ func TestTenantEnvOmitsPublicURLWithoutTenantContext(t *testing.T) {
func TestTenantEnvStampsWorkspaceDisplayName(t *testing.T) {
t.Parallel()
env := tenantEnv("t-acme", " Acme Rentals ", "cloud.pulserelay.pro", "", nil, TenantReportBrandConfig{})
env := tenantEnv("t-acme", " Acme Rentals ", "cloud.pulserelay.pro", "", "", nil, TenantReportBrandConfig{})
want := "PULSE_TENANT_NAME=Acme Rentals"
for _, item := range env {
if item == want {
@ -225,7 +237,7 @@ func TestTenantEnvStampsWorkspaceDisplayName(t *testing.T) {
func TestTenantEnvOmitsTenantNameWhenUnresolved(t *testing.T) {
t.Parallel()
env := tenantEnv("t-acme", "", "cloud.pulserelay.pro", "", nil, TenantReportBrandConfig{})
env := tenantEnv("t-acme", "", "cloud.pulserelay.pro", "", "", nil, TenantReportBrandConfig{})
for _, item := range env {
if strings.HasPrefix(item, "PULSE_TENANT_NAME=") {
t.Fatalf("unexpected tenant name env item %q; name must be omitted so payloads fall back to the tenant ID", item)
@ -262,7 +274,7 @@ func TestTenantRuntimeContainerConfigResolvesTenantDisplayName(t *testing.T) {
func TestTenantEnvIncludesProviderReportBrandDefaults(t *testing.T) {
t.Parallel()
env := tenantEnv("t-acme", "", "msp.example.com", "", nil, TenantReportBrandConfig{
env := tenantEnv("t-acme", "", "msp.example.com", "", "", nil, TenantReportBrandConfig{
DisplayName: "Acme Managed IT",
LogoBase64: "iVBORw0KGgo=",
LogoFormat: "png",

View file

@ -27,6 +27,7 @@ type Service struct {
registry *registry.TenantRegistry
baseURL string
trialActivationPrivateKey string
providerLicense string
now func() time.Time
}
@ -50,6 +51,17 @@ func (s *Service) SetNow(now func() time.Time) {
s.now = func() time.Time { return now().UTC() }
}
// SetProviderLicense chains the Pulse-signed provider MSP license into every
// lease this service signs. Release-build client runtimes only trust lease
// signing keys bound into such a license, so provider-hosted control planes
// must set this for their leases to verify.
func (s *Service) SetProviderLicense(licenseKey string) {
if s == nil {
return
}
s.providerLicense = strings.TrimSpace(licenseKey)
}
func (s *Service) IssueTenantBillingState(tenant *registry.Tenant, requestedSubState pkglicensing.SubscriptionState, requestedPlanVersion, stripeCustomerID, stripeSubscriptionID, stripePriceID string) (*pkglicensing.BillingState, error) {
if s == nil || s.registry == nil {
return nil, fmt.Errorf("hosted entitlement service is unavailable")
@ -142,6 +154,7 @@ type tenantLeaseContext struct {
stripeAccount *registry.StripeAccount
subscriptionState pkglicensing.SubscriptionState
planVersion string
providerChained bool
}
type trialEntitlementContext struct {
@ -234,6 +247,7 @@ func (s *Service) resolveTenantLeaseContext(tenant *registry.Tenant, requestedSu
tenant: tenant,
subscriptionState: requestedSubState,
planVersion: pkglicensing.CanonicalizePlanVersion(requestedPlanVersion),
providerChained: strings.TrimSpace(s.providerLicense) != "",
}
if ctx.planVersion == "" {
ctx.planVersion = pkglicensing.CanonicalizePlanVersion(tenant.PlanVersion)
@ -296,6 +310,7 @@ func (s *Service) signPaidLease(ctx *tenantLeaseContext, instanceHost string, no
return "", err
}
claims := buildPaidEntitlementLeaseClaims(ctx, instanceHost, now)
claims.ProviderLicense = s.providerLicense
signed, err := pkglicensing.SignEntitlementLeaseToken(privateKey, claims)
if err != nil {
return "", fmt.Errorf("sign hosted entitlement lease: %w", err)
@ -332,6 +347,10 @@ func randomRefreshToken() (string, error) {
return "etr_hosted_" + hex.EncodeToString(raw), nil
}
func isMSPPlanVersion(planVersion string) bool {
return strings.HasPrefix(strings.ToLower(strings.TrimSpace(planVersion)), "msp_")
}
func tenantSubscriptionState(tenant *registry.Tenant) pkglicensing.SubscriptionState {
if tenant == nil {
return pkglicensing.SubStateExpired
@ -349,7 +368,21 @@ func tenantSubscriptionState(tenant *registry.Tenant) pkglicensing.SubscriptionS
func buildPaidEntitlementLeaseClaims(ctx *tenantLeaseContext, instanceHost string, now time.Time) pkglicensing.EntitlementLeaseClaims {
var capabilities []string
if pkglicensing.ShouldGrantPaidCapabilities(ctx.subscriptionState) {
capabilities = pkglicensing.DeriveCapabilitiesFromTier(pkglicensing.TierCloud, nil)
switch {
case isMSPPlanVersion(ctx.planVersion) && ctx.providerChained:
// Provider-hosted MSP client workspaces lease the chained
// ceiling: MSP tier plus white_label (branded per-client
// reports), minus Pulse-service-backed capabilities (relay,
// mobile, push) the provider deployment cannot grant. Mirrors
// the chain-verification cap in pkg/licensing.
capabilities = pkglicensing.ProviderChainedLeaseCapabilities()
case isMSPPlanVersion(ctx.planVersion):
// Pulse-hosted MSP workspaces run against Pulse-operated
// services, so they keep the full MSP tier set.
capabilities = pkglicensing.DeriveCapabilitiesFromTier(pkglicensing.TierMSP, []string{pkglicensing.FeatureWhiteLabel})
default:
capabilities = pkglicensing.DeriveCapabilitiesFromTier(pkglicensing.TierCloud, nil)
}
}
claims := pkglicensing.EntitlementLeaseClaims{
OrgID: "default",

View file

@ -4,6 +4,7 @@ import (
"crypto/ed25519"
"database/sql"
"encoding/base64"
"encoding/json"
"path/filepath"
"testing"
"time"
@ -420,3 +421,145 @@ func seedLegacyTrialHostedEntitlement(t *testing.T, registryDir string, now time
t.Fatalf("seed legacy trial hosted entitlement: %v", err)
}
}
// mintChainTestProviderLicense installs a test license root and returns a
// Pulse-signed MSP license binding the given lease signing public key, so
// chained lease verification works end-to-end in service tests.
func mintChainTestProviderLicense(t *testing.T, leaseSigningPublicKey ed25519.PublicKey) string {
t.Helper()
rootPub, rootPriv, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatalf("GenerateKey root: %v", err)
}
pkglicensing.SetPublicKey(rootPub)
t.Cleanup(func() { pkglicensing.SetPublicKey(nil) })
claims := pkglicensing.Claims{
LicenseID: "lic_msp_lease_shape_test",
Email: "provider@example-msp.com",
Tier: pkglicensing.TierMSP,
IssuedAt: time.Now().Add(-time.Minute).Unix(),
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
PlanVersion: "msp_starter",
EntitlementSigningPublicKey: base64.StdEncoding.EncodeToString(leaseSigningPublicKey),
}
payloadBytes, err := json.Marshal(claims)
if err != nil {
t.Fatalf("marshal provider license 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 TestIssueTenantBillingStateMSPWorkspaceLeaseShape(t *testing.T) {
svc, pub, reg := newTestService(t)
providerLicense := mintChainTestProviderLicense(t, pub)
svc.SetProviderLicense(providerLicense)
accountID, err := registry.GenerateAccountID()
if err != nil {
t.Fatalf("GenerateAccountID: %v", err)
}
if err := reg.CreateAccount(&registry.Account{
ID: accountID,
Kind: registry.AccountKindMSP,
DisplayName: "Example MSP",
}); err != nil {
t.Fatalf("CreateAccount: %v", err)
}
tenant := &registry.Tenant{
ID: "t-MSPCLIENT1",
AccountID: accountID,
Email: "owner@example-msp.com",
State: registry.TenantStateActive,
PlanVersion: "msp_starter",
}
if err := reg.Create(tenant); err != nil {
t.Fatalf("Create: %v", err)
}
now := time.Unix(1710000000, 0).UTC()
svc.SetNow(func() time.Time { return now })
state, err := svc.IssueTenantBillingState(tenant, pkglicensing.SubStateActive, "msp_starter", "", "", "")
if err != nil {
t.Fatalf("IssueTenantBillingState: %v", err)
}
claims, err := pkglicensing.VerifyEntitlementLeaseToken(state.EntitlementJWT, pub, "t-MSPCLIENT1.cloud.example.com", now)
if err != nil {
t.Fatalf("VerifyEntitlementLeaseToken: %v", err)
}
if claims.ProviderLicense != providerLicense {
t.Fatalf("claims.ProviderLicense = %q, want chained provider license", claims.ProviderLicense)
}
capabilities := map[string]bool{}
for _, capability := range claims.Capabilities {
capabilities[capability] = true
}
if !capabilities[pkglicensing.FeatureWhiteLabel] {
t.Fatalf("MSP workspace lease must carry white_label for branded client reports, got %v", claims.Capabilities)
}
if !capabilities[pkglicensing.FeatureMultiTenant] {
t.Fatalf("MSP workspace lease missing MSP tier capability multi_tenant: %v", claims.Capabilities)
}
if capabilities[pkglicensing.FeatureMultiUser] {
t.Fatalf("MSP workspace lease must not carry enterprise-only multi_user: %v", claims.Capabilities)
}
for _, serviceBacked := range []string{pkglicensing.FeatureRelay, pkglicensing.FeatureMobileApp, pkglicensing.FeaturePushNotifications} {
if capabilities[serviceBacked] {
t.Fatalf("provider-chained MSP lease must not grant Pulse-service-backed capability %q: %v", serviceBacked, claims.Capabilities)
}
}
}
func TestIssueTenantBillingStateCloudPlanOmitsProviderLicenseWhenUnset(t *testing.T) {
svc, pub, reg := newTestService(t)
accountID, err := registry.GenerateAccountID()
if err != nil {
t.Fatalf("GenerateAccountID: %v", err)
}
if err := reg.CreateAccount(&registry.Account{
ID: accountID,
Kind: registry.AccountKindIndividual,
DisplayName: "Pulse Labs",
}); err != nil {
t.Fatalf("CreateAccount: %v", err)
}
tenant := &registry.Tenant{
ID: "t-CLOUD01",
AccountID: accountID,
Email: "owner@example.com",
State: registry.TenantStateActive,
PlanVersion: "cloud_starter",
}
if err := reg.Create(tenant); err != nil {
t.Fatalf("Create: %v", err)
}
now := time.Unix(1710000000, 0).UTC()
svc.SetNow(func() time.Time { return now })
state, err := svc.IssueTenantBillingState(tenant, pkglicensing.SubStateActive, "cloud_starter", "", "", "")
if err != nil {
t.Fatalf("IssueTenantBillingState: %v", err)
}
claims, err := pkglicensing.VerifyEntitlementLeaseToken(state.EntitlementJWT, pub, "t-CLOUD01.cloud.example.com", now)
if err != nil {
t.Fatalf("VerifyEntitlementLeaseToken: %v", err)
}
if claims.ProviderLicense != "" {
t.Fatalf("cloud lease should not chain a provider license, got %q", claims.ProviderLicense)
}
capabilities := map[string]bool{}
for _, capability := range claims.Capabilities {
capabilities[capability] = true
}
if capabilities[pkglicensing.FeatureWhiteLabel] {
t.Fatalf("cloud lease should not carry white_label, got %v", claims.Capabilities)
}
}

View file

@ -138,6 +138,9 @@ func RegisterRoutes(mux *http.ServeMux, deps *Deps) {
hostedEntitlements := deps.HostedEntitlements
if hostedEntitlements == nil {
hostedEntitlements = entitlements.NewService(deps.Registry, deps.Config.BaseURL, deps.Config.TrialActivationPrivateKey)
if deps.Config.IsProviderHostedMSP() {
hostedEntitlements.SetProviderLicense(deps.Config.ProviderMSPLicenseKey)
}
}
provisioner := deps.Provisioner
if provisioner == nil {

View file

@ -59,6 +59,7 @@ func Run(ctx context.Context, version string) error {
IsolateTenantNetworks: cfg.IsMSPControlPlane(),
BaseDomain: baseDomainFromURL(cfg.BaseURL),
TrialActivationPublicKey: cfg.TrialActivationPublicKey,
ControlPlaneBaseURL: cfg.BaseURL,
TrustedProxyCIDRs: cfg.TrustedProxyCIDRs,
TenantDisplayName: tenantDisplayNameResolver(reg),
TenantReportBrand: cpDocker.TenantReportBrandConfig{
@ -112,6 +113,9 @@ func Run(ctx context.Context, version string) error {
// Build HTTP routes
mux := http.NewServeMux()
hostedEntitlements := entitlements.NewService(reg, cfg.BaseURL, cfg.TrialActivationPrivateKey)
if cfg.IsProviderHostedMSP() {
hostedEntitlements.SetProviderLicense(cfg.ProviderMSPLicenseKey)
}
provisioner := cpstripe.NewProvisioner(
reg,
cfg.TenantsDir(),

View file

@ -221,6 +221,7 @@ func newTenantRuntimeRolloutServiceFromConfig(
IsolateTenantNetworks: cfg.IsMSPControlPlane(),
BaseDomain: baseDomainFromURL(cfg.BaseURL),
TrialActivationPublicKey: cfg.TrialActivationPublicKey,
ControlPlaneBaseURL: cfg.BaseURL,
TrustedProxyCIDRs: cfg.TrustedProxyCIDRs,
TenantDisplayName: tenantDisplayNameResolver(reg),
TenantReportBrand: cpDocker.TenantReportBrandConfig{

View file

@ -45,6 +45,15 @@ type EntitlementLeaseClaims struct {
MetersEnabled []string `json:"meters_enabled,omitempty"`
TrialStartedAt *int64 `json:"trial_started_at,omitempty"`
TrialEndsAt *int64 `json:"trial_ends_at,omitempty"`
// ProviderLicense carries the Pulse-signed provider MSP license whose
// entitlement_signing_public_key claim binds the key this lease was
// signed with. Release builds verify the chain (embedded Pulse root →
// provider license → lease signature), so provider-hosted control
// planes can mint verifiable leases for the client runtimes they
// operate without holding Pulse's signing key.
ProviderLicense string `json:"provider_license,omitempty"`
jwt.RegisteredClaims
}
@ -135,6 +144,7 @@ func parseEntitlementLeaseToken(token string, publicKey ed25519.PublicKey, expec
if skipTimeValidation {
parseOpts = append(parseOpts, jwt.WithoutClaimsValidation())
}
chained := false
parsed, err := jwt.ParseWithClaims(
token,
claims,
@ -142,6 +152,21 @@ func parseEntitlementLeaseToken(token string, publicKey ed25519.PublicKey, expec
if t.Method.Alg() != jwt.SigningMethodEdDSA.Alg() {
return nil, fmt.Errorf("unexpected signing method: %s", t.Method.Alg())
}
// A lease carrying a provider license selects the provider's
// bound signing key instead of the direct trust root. The
// provider license itself must verify against the embedded
// Pulse root before its bound key is trusted, so the chain
// never widens beyond Pulse-issued MSP licenses.
if leaseClaims, ok := t.Claims.(*EntitlementLeaseClaims); ok {
if providerLicense := strings.TrimSpace(leaseClaims.ProviderLicense); providerLicense != "" {
providerKey, err := providerLeaseSigningKeyFromLicense(providerLicense)
if err != nil {
return nil, err
}
chained = true
return providerKey, nil
}
}
return publicKey, nil
},
parseOpts...,
@ -152,6 +177,9 @@ func parseEntitlementLeaseToken(token string, publicKey ed25519.PublicKey, expec
if !parsed.Valid {
return nil, errors.New("entitlement lease token is invalid")
}
if chained {
capProviderChainedLeaseClaims(claims)
}
claims.OrgID = strings.TrimSpace(claims.OrgID)
if claims.OrgID == "" {
@ -169,6 +197,78 @@ func parseEntitlementLeaseToken(token string, publicKey ed25519.PublicKey, expec
return claims, nil
}
// providerLeaseSigningKeyFromLicense validates a Pulse-signed provider MSP
// license against the runtime license trust root and returns the entitlement
// lease signing key it binds. Every failure is terminal for the lease: an
// unverifiable provider license must never fall back to the direct root,
// or a forged chain would downgrade into a confusing signature error.
func providerLeaseSigningKeyFromLicense(licenseKey string) (ed25519.PublicKey, error) {
license, err := ValidateLicense(licenseKey)
if err != nil {
return nil, fmt.Errorf("provider license in entitlement lease is invalid: %w", err)
}
if license == nil {
return nil, errors.New("provider license in entitlement lease is invalid")
}
if license.Claims.Tier != TierMSP {
return nil, fmt.Errorf("provider license tier %q cannot bind an entitlement lease signing key", license.Claims.Tier)
}
encoded := strings.TrimSpace(license.Claims.EntitlementSigningPublicKey)
if encoded == "" {
return nil, errors.New("provider license does not bind an entitlement lease signing key")
}
key, err := DecodePublicKey(encoded)
if err != nil {
return nil, fmt.Errorf("provider license entitlement signing key is malformed: %w", err)
}
return key, nil
}
// ProviderChainedLeaseCapabilities returns the capability ceiling for
// entitlement leases verified through a provider MSP license chain: the MSP
// tier set plus white_label (branded per-client reports are the hosted MSP
// packaging of that feature), minus capabilities backed by Pulse-operated
// services (relay, mobile app pairing, push routing). A provider-hosted
// deployment cannot grant those — client runtimes holding them would loop
// doomed registrations against Pulse's relay with credentials it can never
// verify.
func ProviderChainedLeaseCapabilities() []string {
excluded := map[string]struct{}{
FeatureRelay: {},
FeatureMobileApp: {},
FeaturePushNotifications: {},
}
capabilities := make([]string, 0)
for _, capability := range DeriveCapabilitiesFromTier(TierMSP, []string{FeatureWhiteLabel}) {
if _, drop := excluded[capability]; drop {
continue
}
capabilities = append(capabilities, capability)
}
return capabilities
}
// capProviderChainedLeaseClaims bounds a chain-verified lease to
// ProviderChainedLeaseCapabilities. A provider key can therefore never mint
// enterprise-only, Pulse-service-backed, or future capabilities, regardless
// of what the lease claims.
func capProviderChainedLeaseClaims(claims *EntitlementLeaseClaims) {
if claims == nil {
return
}
allowed := make(map[string]struct{})
for _, capability := range ProviderChainedLeaseCapabilities() {
allowed[capability] = struct{}{}
}
kept := make([]string, 0, len(claims.Capabilities))
for _, capability := range claims.Capabilities {
if _, ok := allowed[capability]; ok {
kept = append(kept, capability)
}
}
claims.Capabilities = kept
}
// ResolveEntitlementLeaseBillingState applies a signed hosted entitlement lease
// to a locally cached billing state. Invalid or expired leases fail closed.
func ResolveEntitlementLeaseBillingState(state BillingState, expectedInstanceHost string, now time.Time) BillingState {

View file

@ -36,6 +36,14 @@ type Claims struct {
PlanVersion string `json:"plan_version,omitempty"`
SubState SubscriptionState `json:"subscription_state,omitempty"`
// EntitlementSigningPublicKey binds a provider-operated control plane's
// entitlement lease signing key (base64 Ed25519 public key) to this
// license. Hosted entitlement leases signed by the bound key chain back
// to the embedded Pulse trust root through this license, so release
// builds can verify provider-minted leases without trusting environment
// wiring. Only meaningful on MSP-tier licenses.
EntitlementSigningPublicKey string `json:"entitlement_signing_public_key,omitempty"`
// CoreMonitoringUncapped is a local runtime marker used when a self-hosted
// activation/grant should remain uncapped even if stale legacy limit fields
// are still present in the persisted claims. It is intentionally excluded

View file

@ -0,0 +1,199 @@
package licensing
import (
"crypto/ed25519"
"crypto/rand"
"encoding/base64"
"encoding/json"
"strings"
"testing"
"time"
)
// mintTestProviderMSPLicense signs an MSP license with the shared test root
// key, binding the given lease signing public key. setupTestPublicKey must be
// installed for ValidateLicense to accept it.
func mintTestProviderMSPLicense(t *testing.T, tier Tier, leaseSigningPublicKey ed25519.PublicKey, expiresIn time.Duration) string {
t.Helper()
testKeyPairInit()
claims := Claims{
LicenseID: "lic_provider_chain_test",
Email: "provider@example-msp.com",
Tier: tier,
IssuedAt: time.Now().Unix(),
PlanVersion: "msp_starter",
}
if expiresIn != 0 {
claims.ExpiresAt = time.Now().Add(expiresIn).Unix()
}
if len(leaseSigningPublicKey) > 0 {
claims.EntitlementSigningPublicKey = base64.StdEncoding.EncodeToString(leaseSigningPublicKey)
}
payload, err := json.Marshal(claims)
if err != nil {
t.Fatalf("marshal provider license claims: %v", err)
}
return signTestJWT(t, payload, testPrivateKey)
}
func signTestProviderLease(t *testing.T, signingKey ed25519.PrivateKey, providerLicense string, capabilities []string) string {
t.Helper()
claims := EntitlementLeaseClaims{
OrgID: "default",
InstanceHost: "t-acme.pulse.example-msp.com",
PlanVersion: "msp_starter",
SubscriptionState: SubStateActive,
Capabilities: capabilities,
ProviderLicense: providerLicense,
}
token, err := SignEntitlementLeaseToken(signingKey, claims)
if err != nil {
t.Fatalf("sign provider lease: %v", err)
}
return token
}
func TestEntitlementLeaseProviderChainVerifies(t *testing.T) {
setupTestPublicKey(t)
providerPub, providerPriv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("generate provider key pair: %v", err)
}
license := mintTestProviderMSPLicense(t, TierMSP, providerPub, 30*24*time.Hour)
token := signTestProviderLease(t, providerPriv, license, []string{FeatureWhiteLabel, FeatureMultiTenant})
claims, err := VerifyEntitlementLeaseToken(token, testPublicKey, "t-acme.pulse.example-msp.com", time.Now())
if err != nil {
t.Fatalf("VerifyEntitlementLeaseToken() error = %v, want chain-verified lease", err)
}
got := strings.Join(claims.Capabilities, ",")
if !strings.Contains(got, FeatureWhiteLabel) {
t.Fatalf("chain-verified lease lost white_label: capabilities = %q", got)
}
}
func TestEntitlementLeaseProviderChainCapsCapabilities(t *testing.T) {
setupTestPublicKey(t)
providerPub, providerPriv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("generate provider key pair: %v", err)
}
license := mintTestProviderMSPLicense(t, TierMSP, providerPub, 30*24*time.Hour)
// multi_user is enterprise-only and relay is Pulse-service-backed; a
// provider-minted lease must not be able to grant either even though
// the lease signature verifies.
token := signTestProviderLease(t, providerPriv, license, []string{FeatureWhiteLabel, FeatureMultiUser, FeatureRelay})
claims, err := VerifyEntitlementLeaseToken(token, testPublicKey, "t-acme.pulse.example-msp.com", time.Now())
if err != nil {
t.Fatalf("VerifyEntitlementLeaseToken() error = %v", err)
}
for _, capability := range claims.Capabilities {
switch capability {
case FeatureMultiUser:
t.Fatalf("chained lease escaped the MSP capability ceiling: %v", claims.Capabilities)
case FeatureRelay, FeatureMobileApp, FeaturePushNotifications:
t.Fatalf("chained lease must not grant Pulse-service-backed capability %q: %v", capability, claims.Capabilities)
}
}
if got := strings.Join(claims.Capabilities, ","); !strings.Contains(got, FeatureWhiteLabel) {
t.Fatalf("capability cap removed white_label, which MSP leases legitimately carry: %q", got)
}
}
func TestEntitlementLeaseProviderChainRejectsWrongSigner(t *testing.T) {
setupTestPublicKey(t)
providerPub, _, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("generate provider key pair: %v", err)
}
_, otherPriv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("generate other key pair: %v", err)
}
license := mintTestProviderMSPLicense(t, TierMSP, providerPub, 30*24*time.Hour)
token := signTestProviderLease(t, otherPriv, license, []string{FeatureWhiteLabel})
if _, err := VerifyEntitlementLeaseToken(token, testPublicKey, "t-acme.pulse.example-msp.com", time.Now()); err == nil {
t.Fatal("lease signed by a key the provider license does not bind must be rejected")
}
}
func TestEntitlementLeaseProviderChainRejectsNonMSPLicense(t *testing.T) {
setupTestPublicKey(t)
providerPub, providerPriv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("generate provider key pair: %v", err)
}
license := mintTestProviderMSPLicense(t, TierPro, providerPub, 30*24*time.Hour)
token := signTestProviderLease(t, providerPriv, license, []string{FeatureWhiteLabel})
if _, err := VerifyEntitlementLeaseToken(token, testPublicKey, "t-acme.pulse.example-msp.com", time.Now()); err == nil {
t.Fatal("non-MSP license must not bind an entitlement lease signing key")
}
}
func TestEntitlementLeaseProviderChainRejectsUnboundLicense(t *testing.T) {
setupTestPublicKey(t)
_, providerPriv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("generate provider key pair: %v", err)
}
license := mintTestProviderMSPLicense(t, TierMSP, nil, 30*24*time.Hour)
token := signTestProviderLease(t, providerPriv, license, []string{FeatureWhiteLabel})
if _, err := VerifyEntitlementLeaseToken(token, testPublicKey, "t-acme.pulse.example-msp.com", time.Now()); err == nil {
t.Fatal("license without an entitlement signing key binding must not chain")
}
}
func TestEntitlementLeaseProviderChainRejectsForgedLicense(t *testing.T) {
setupTestPublicKey(t)
providerPub, providerPriv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("generate provider key pair: %v", err)
}
// License signed by the provider's own key instead of the Pulse root: a
// self-issued chain must die at license validation.
claims := Claims{
LicenseID: "lic_forged",
Email: "attacker@example.com",
Tier: TierMSP,
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(30 * 24 * time.Hour).Unix(),
PlanVersion: "msp_starter",
EntitlementSigningPublicKey: base64.StdEncoding.EncodeToString(providerPub),
}
payload, err := json.Marshal(claims)
if err != nil {
t.Fatalf("marshal forged license claims: %v", err)
}
forgedLicense := signTestJWT(t, payload, providerPriv)
token := signTestProviderLease(t, providerPriv, forgedLicense, []string{FeatureWhiteLabel})
if _, err := VerifyEntitlementLeaseToken(token, testPublicKey, "t-acme.pulse.example-msp.com", time.Now()); err == nil {
t.Fatal("a provider license not signed by the Pulse root must not chain")
}
}
func TestEntitlementLeaseWithoutProviderLicenseUsesDirectRoot(t *testing.T) {
setupTestPublicKey(t)
token := signTestProviderLease(t, testPrivateKey, "", []string{FeatureWhiteLabel})
claims, err := VerifyEntitlementLeaseToken(token, testPublicKey, "t-acme.pulse.example-msp.com", time.Now())
if err != nil {
t.Fatalf("direct-root lease verification regressed: %v", err)
}
// Direct-root leases are not capability-capped; the signer is Pulse.
if got := strings.Join(claims.Capabilities, ","); !strings.Contains(got, FeatureWhiteLabel) {
t.Fatalf("direct-root lease capabilities = %q", got)
}
}