mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Make provider MSP evaluation activation attributable
This commit is contained in:
@@ -48,6 +48,11 @@ CP_TRUSTED_PROXY_CIDRS=172.30.0.0/24
|
||||
# PULSE_PROVIDER_MSP_SKIP_EVAL_LICENSE=1 skips the request on an air-gapped
|
||||
# host. The portal and client isolation still work, but client workspaces will
|
||||
# not carry MSP capabilities until a licence is installed.
|
||||
#
|
||||
# To let Pulse match this activation to your setup request, pass
|
||||
# PULSE_PROVIDER_MSP_EVAL_EMAIL when running setup.sh. The setup sends that
|
||||
# address, the public signing key, and a fixed signup-source label; it never
|
||||
# sends client inventory or credentials. Leave it unset for an anonymous eval.
|
||||
CP_PROVIDER_MSP_LICENSE_FILE=
|
||||
# Entitlement lease signing key. setup.sh generates this; the private key
|
||||
# never leaves this host. Your provider MSP license must bind the derived
|
||||
@@ -86,6 +91,7 @@ PULSE_EMAIL_REPLY_TO=support@example.com
|
||||
# Optional one-command setup proof after .env is filled:
|
||||
# sudo -E PULSE_PROVIDER_MSP_ACCOUNT_NAME="Example MSP" \
|
||||
# PULSE_PROVIDER_MSP_OWNER_EMAIL=owner@example.com \
|
||||
# PULSE_PROVIDER_MSP_EVAL_EMAIL=owner@example.com \
|
||||
# ./setup.sh
|
||||
#
|
||||
# First owner bootstrap:
|
||||
|
||||
@@ -20,6 +20,9 @@ PULSE_PROVIDER_MSP_ACCOUNT_NAME="${PULSE_PROVIDER_MSP_ACCOUNT_NAME:-}"
|
||||
PULSE_PROVIDER_MSP_OWNER_EMAIL="${PULSE_PROVIDER_MSP_OWNER_EMAIL:-}"
|
||||
PULSE_PROVIDER_MSP_LICENSE_URL="${PULSE_PROVIDER_MSP_LICENSE_URL:-https://license.pulserelay.pro}"
|
||||
PULSE_PROVIDER_MSP_SKIP_EVAL_LICENSE="${PULSE_PROVIDER_MSP_SKIP_EVAL_LICENSE:-0}"
|
||||
PULSE_PROVIDER_MSP_EVAL_EMAIL="${PULSE_PROVIDER_MSP_EVAL_EMAIL:-}"
|
||||
PULSE_PROVIDER_MSP_SIGNUP_SOURCE="${PULSE_PROVIDER_MSP_SIGNUP_SOURCE:-provider_msp_setup}"
|
||||
PULSE_PROVIDER_MSP_UPGRADE_URL="${PULSE_PROVIDER_MSP_UPGRADE_URL:-https://pulserelay.pro/msp.html}"
|
||||
|
||||
log() {
|
||||
echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] $*"
|
||||
@@ -395,7 +398,13 @@ ensure_eval_license() {
|
||||
|
||||
log "requesting a 2-client evaluation license from ${PULSE_PROVIDER_MSP_LICENSE_URL}"
|
||||
local body response token
|
||||
body="$(printf '{"entitlement_signing_public_key":"%s"}' "${public_key}")"
|
||||
body="$(jq -cn \
|
||||
--arg public_key "${public_key}" \
|
||||
--arg email "${PULSE_PROVIDER_MSP_EVAL_EMAIL}" \
|
||||
--arg signup_source "${PULSE_PROVIDER_MSP_SIGNUP_SOURCE}" \
|
||||
'{entitlement_signing_public_key: $public_key}
|
||||
+ (if $email == "" then {} else {email: $email} end)
|
||||
+ (if $signup_source == "" then {} else {signup_source: $signup_source} end)')"
|
||||
response="$(curl -fsS --max-time 20 \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "${body}" \
|
||||
@@ -419,10 +428,16 @@ ensure_eval_license() {
|
||||
chmod 0600 "${eval_path}"
|
||||
set_env_value CP_PROVIDER_MSP_LICENSE_FILE "./provider-msp-eval-license.jwt" "${env_path}"
|
||||
|
||||
local expires
|
||||
local expires license_id
|
||||
expires="$(printf '%s' "${response}" | jq -r '.expires_at // empty' 2>/dev/null || true)"
|
||||
license_id="$(printf '%s' "${response}" | jq -r '.license_id // empty' 2>/dev/null || true)"
|
||||
log "evaluation license installed: 2 client workspaces${expires:+, expires ${expires}}"
|
||||
log " buy a plan and set CP_PROVIDER_MSP_LICENSE_FILE to lift the cap"
|
||||
if [[ "${license_id}" =~ ^lic_msp_[a-f0-9]+$ ]]; then
|
||||
log " when you need a third client, request an upgrade at:"
|
||||
log " ${PULSE_PROVIDER_MSP_UPGRADE_URL%/}?eval_license_id=${license_id}#request"
|
||||
else
|
||||
log " when you need a third client, request an upgrade at ${PULSE_PROVIDER_MSP_UPGRADE_URL%/}#request"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_generated_secrets() {
|
||||
@@ -798,8 +813,6 @@ main() {
|
||||
ensure_env_file
|
||||
ensure_dns_credentials_file
|
||||
ensure_generated_secrets
|
||||
# After the signing key exists, since the license binds its public half.
|
||||
ensure_eval_license
|
||||
# After install_docker_ce, which provides the buildx used to read the
|
||||
# registry, and before validation, which requires the pins to be set.
|
||||
ensure_image_pins
|
||||
@@ -809,6 +822,10 @@ main() {
|
||||
block_container_metadata_service
|
||||
validate_compose_config
|
||||
pull_provider_images
|
||||
# Issue the evaluation only after the host is configured and the immutable
|
||||
# images are reachable. This makes an issued evaluation a useful activation
|
||||
# signal rather than a record created before setup can succeed.
|
||||
ensure_eval_license
|
||||
run_install_proof_if_requested
|
||||
print_summary
|
||||
}
|
||||
|
||||
+26
-19
@@ -258,26 +258,25 @@ reports or collect report data in the provider control plane.
|
||||
MSP and Enterprise capabilities (`multi_tenant`, `unlimited`, `white_label`)
|
||||
are carried on the licence key. MSP plans are sized by client workspace count
|
||||
(Starter 5, Growth 15, Scale 40); workspace creation is blocked, not billed,
|
||||
when the limit is reached. MSP and Enterprise keys are issued through sales —
|
||||
contact support to get set up or to join the MSP design-partner program.
|
||||
when the limit is reached. The 60-day, two-workspace evaluation is
|
||||
self-service. Paid MSP licences are currently issued through the assisted
|
||||
upgrade path so the recurring licence renewal and key binding are checked
|
||||
before money changes hands.
|
||||
|
||||
### Evaluating without a licence
|
||||
|
||||
Self-service evaluation is available only from an exact release page that
|
||||
includes the evaluation-capable provider MSP bundle and both integrity
|
||||
sidecars described below. If the release you intend to use does not list all
|
||||
three assets, stop: evaluation onboarding for that release remains
|
||||
request-assisted. Do **not** download the moving `main` branch archive or run
|
||||
its `setup.sh` as root; contact support or join the MSP design-partner program
|
||||
instead.
|
||||
Self-service evaluation is available from the signed provider bundle published
|
||||
with Pulse v6.2.1. Use this exact release asset and its integrity sidecars; do
|
||||
**not** download the moving `main` branch archive or run its `setup.sh` as
|
||||
root. For a later release, first confirm its release page contains the
|
||||
versioned provider archive, checksum, and SSH signature before changing the
|
||||
version below.
|
||||
|
||||
Once an exact release publishes
|
||||
`pulse-provider-msp-vX.Y.Z.tar.gz`, download that versioned asset and its
|
||||
integrity sidecars, verify the archive with Pulse's pinned release key, and
|
||||
Download the versioned asset, verify it with Pulse's pinned release key, and
|
||||
only then extract and run the guided setup:
|
||||
|
||||
```bash
|
||||
export PULSE_VERSION=vX.Y.Z
|
||||
export PULSE_VERSION=v6.2.1
|
||||
export PULSE_MSP_BUNDLE="pulse-provider-msp-${PULSE_VERSION}.tar.gz"
|
||||
export PULSE_RELEASE_BASE="https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}"
|
||||
|
||||
@@ -294,9 +293,16 @@ sha256sum -c "${PULSE_MSP_BUNDLE}.sha256"
|
||||
|
||||
tar -xzf "${PULSE_MSP_BUNDLE}"
|
||||
cd "pulse-provider-msp-${PULSE_VERSION}"
|
||||
export PULSE_PROVIDER_MSP_EVAL_EMAIL=you@example.com
|
||||
export PULSE_PROVIDER_MSP_SIGNUP_SOURCE=msp_docs
|
||||
sudo -E bash ./setup.sh
|
||||
```
|
||||
|
||||
`PULSE_PROVIDER_MSP_EVAL_EMAIL` is optional. Set it if you want setup help and
|
||||
want an eventual paid upgrade matched to this deployment; omit it for an
|
||||
anonymous evaluation. The signup-source value is a fixed attribution label,
|
||||
not free-form telemetry.
|
||||
|
||||
The host needs Ubuntu 24.04 or similar, a domain you can point at it, and
|
||||
ports 80 and 443 free. Install `curl`, `openssh-client`, `coreutils`, and `tar`
|
||||
before the verification step. The wildcard certificate is issued over DNS-01
|
||||
@@ -304,12 +310,13 @@ with Cloudflare as the default provider (`CF_DNS_API_TOKEN`); any other
|
||||
Traefik dnsChallenge provider works by setting `ACME_DNS_PROVIDER` in `.env`
|
||||
and putting that provider's credential variables in `dns-credentials.env`.
|
||||
|
||||
In a published evaluation-capable bundle, leave
|
||||
`CP_PROVIDER_MSP_LICENSE_FILE` blank and `setup.sh` self-issues a 2-client
|
||||
evaluation licence. It sends only the public half of the signing key generated
|
||||
on your host, exactly as the paid path does, and the private key never leaves
|
||||
the machine. You can then onboard two real clients and confirm the isolation
|
||||
boundary on your own infrastructure before buying.
|
||||
Leave `CP_PROVIDER_MSP_LICENSE_FILE` blank and `setup.sh` self-issues a
|
||||
2-client evaluation licence after configuration validation succeeds and the
|
||||
immutable images are reachable. It sends only the public half of the signing
|
||||
key generated on your host, the optional contact address above, and the fixed
|
||||
signup-source label. The private key, client inventory, and credentials never
|
||||
leave the machine. You can then onboard two real clients and confirm the
|
||||
isolation boundary on your own infrastructure before buying.
|
||||
|
||||
The evaluation licence lasts 60 days and re-running `setup.sh` reuses the one
|
||||
already on disk. On an air-gapped host set
|
||||
|
||||
@@ -3319,6 +3319,26 @@ caps continue to come only from the signed licence file, never from the
|
||||
environment fallback, and `ProviderMSPPlanSourceEnvFallback` remains the
|
||||
recorded plan source whenever no licence is present.
|
||||
|
||||
The provider-hosted MSP acquisition funnel uses commercial records that already
|
||||
belong to the boundary rather than reviving retired generic browser analytics
|
||||
or adding client-workspace phone-home. An explicit landing-page evaluation
|
||||
start is delivered to the support inbox with the fixed `msp_landing` source; a
|
||||
persisted `msp_eval` licence is the activation stage and is issued only after
|
||||
provider setup validates configuration and resolves the immutable images; a
|
||||
persisted `msp_starter`, `msp_growth`, or `msp_scale` licence is the paid
|
||||
fulfilment stage. The licence server admin surface may aggregate those licence
|
||||
stages over bounded time windows. Optional evaluator email and the public
|
||||
evaluation licence ID may join an assisted upgrade to its activation, but no
|
||||
stage may collect client inventory, credentials, private signing material, or
|
||||
free-form runtime telemetry. Anonymous evaluation must remain available.
|
||||
|
||||
Provider-hosted Starter remains assisted even though the signed evaluation
|
||||
bundle is now public. A generic recurring Stripe checkout is not a valid
|
||||
substitute: the provider licence is a static offline file, so self-service
|
||||
checkout may open only after renewal reissue, delivery, installation, refund,
|
||||
and cancellation behavior are automated and proven end to end. Growth, Scale,
|
||||
and Enterprise remain request-assisted under the existing tier contract.
|
||||
|
||||
|
||||
The capability ceiling for an entitlement lease follows HOSTING, not licensing.
|
||||
`entitlements.Service.SetProviderHosted` bounds a provider-operated control
|
||||
|
||||
@@ -1311,14 +1311,12 @@ upgrade, update, release, or artifact-selection behavior.
|
||||
|
||||
## Current State
|
||||
|
||||
Provider-hosted MSP evaluation source exists, but the active published release
|
||||
line does not yet contain a signed evaluation-capable deploy bundle. Public MSP
|
||||
guidance therefore fails closed instead of running the moving `main` archive.
|
||||
Future release candidates now assemble a dedicated versioned provider MSP
|
||||
bundle, stamp its Pulse image refs to the same exact tag, cover it with the
|
||||
canonical candidate manifest/checksum/signature path, and require the asset at
|
||||
the final publication barrier. The remaining dependency is publication of the
|
||||
first release built from this packaging path.
|
||||
Pulse v6.2.1 is the first active published release with the signed,
|
||||
evaluation-capable provider MSP deploy bundle. Public MSP guidance pins that
|
||||
exact version, verifies its detached SSH signature and checksum before root
|
||||
execution, and continues to reject the moving `main` archive. Future release
|
||||
guidance may advance the pin only after the new exact-version provider asset
|
||||
and sidecars pass the same publication barrier.
|
||||
|
||||
The shell installer's container-runtime discovery prefers a working rootful
|
||||
Docker daemon over any rootless socket (#1647).
|
||||
@@ -3316,6 +3314,17 @@ unlicensed control plane starts, but release-build client runtimes reject its
|
||||
unchained entitlement leases and the client workspaces run without the
|
||||
capabilities being evaluated.
|
||||
|
||||
Evaluation issuance happens only after setup has validated the operator
|
||||
configuration and confirmed that every immutable provider image is reachable.
|
||||
That ordering makes a stored `msp_eval` issue an activation signal rather than
|
||||
a download-intent signal created before the install can succeed. Setup may
|
||||
include an optional evaluator email and a fixed signup-source label so support
|
||||
can match an assisted paid upgrade to the deployment. Those fields must remain
|
||||
optional and bounded; the request must never contain client inventory,
|
||||
credentials, private keys, or free-form runtime telemetry. The returned public
|
||||
evaluation licence ID may be carried in the upgrade URL as a non-secret
|
||||
correlation key.
|
||||
|
||||
Self-issue must degrade rather than block. A missing signing key, an
|
||||
unreachable licence server, or a response carrying no licence leaves the
|
||||
install unlicensed with an explicit warning, and `PULSE_PROVIDER_MSP_SKIP_EVAL_LICENSE`
|
||||
|
||||
@@ -258,26 +258,25 @@ reports or collect report data in the provider control plane.
|
||||
MSP and Enterprise capabilities (`multi_tenant`, `unlimited`, `white_label`)
|
||||
are carried on the licence key. MSP plans are sized by client workspace count
|
||||
(Starter 5, Growth 15, Scale 40); workspace creation is blocked, not billed,
|
||||
when the limit is reached. MSP and Enterprise keys are issued through sales —
|
||||
contact support to get set up or to join the MSP design-partner program.
|
||||
when the limit is reached. The 60-day, two-workspace evaluation is
|
||||
self-service. Paid MSP licences are currently issued through the assisted
|
||||
upgrade path so the recurring licence renewal and key binding are checked
|
||||
before money changes hands.
|
||||
|
||||
### Evaluating without a licence
|
||||
|
||||
Self-service evaluation is available only from an exact release page that
|
||||
includes the evaluation-capable provider MSP bundle and both integrity
|
||||
sidecars described below. If the release you intend to use does not list all
|
||||
three assets, stop: evaluation onboarding for that release remains
|
||||
request-assisted. Do **not** download the moving `main` branch archive or run
|
||||
its `setup.sh` as root; contact support or join the MSP design-partner program
|
||||
instead.
|
||||
Self-service evaluation is available from the signed provider bundle published
|
||||
with Pulse v6.2.1. Use this exact release asset and its integrity sidecars; do
|
||||
**not** download the moving `main` branch archive or run its `setup.sh` as
|
||||
root. For a later release, first confirm its release page contains the
|
||||
versioned provider archive, checksum, and SSH signature before changing the
|
||||
version below.
|
||||
|
||||
Once an exact release publishes
|
||||
`pulse-provider-msp-vX.Y.Z.tar.gz`, download that versioned asset and its
|
||||
integrity sidecars, verify the archive with Pulse's pinned release key, and
|
||||
Download the versioned asset, verify it with Pulse's pinned release key, and
|
||||
only then extract and run the guided setup:
|
||||
|
||||
```bash
|
||||
export PULSE_VERSION=vX.Y.Z
|
||||
export PULSE_VERSION=v6.2.1
|
||||
export PULSE_MSP_BUNDLE="pulse-provider-msp-${PULSE_VERSION}.tar.gz"
|
||||
export PULSE_RELEASE_BASE="https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}"
|
||||
|
||||
@@ -294,9 +293,16 @@ sha256sum -c "${PULSE_MSP_BUNDLE}.sha256"
|
||||
|
||||
tar -xzf "${PULSE_MSP_BUNDLE}"
|
||||
cd "pulse-provider-msp-${PULSE_VERSION}"
|
||||
export PULSE_PROVIDER_MSP_EVAL_EMAIL=you@example.com
|
||||
export PULSE_PROVIDER_MSP_SIGNUP_SOURCE=msp_docs
|
||||
sudo -E bash ./setup.sh
|
||||
```
|
||||
|
||||
`PULSE_PROVIDER_MSP_EVAL_EMAIL` is optional. Set it if you want setup help and
|
||||
want an eventual paid upgrade matched to this deployment; omit it for an
|
||||
anonymous evaluation. The signup-source value is a fixed attribution label,
|
||||
not free-form telemetry.
|
||||
|
||||
The host needs Ubuntu 24.04 or similar, a domain you can point at it, and
|
||||
ports 80 and 443 free. Install `curl`, `openssh-client`, `coreutils`, and `tar`
|
||||
before the verification step. The wildcard certificate is issued over DNS-01
|
||||
@@ -304,12 +310,13 @@ with Cloudflare as the default provider (`CF_DNS_API_TOKEN`); any other
|
||||
Traefik dnsChallenge provider works by setting `ACME_DNS_PROVIDER` in `.env`
|
||||
and putting that provider's credential variables in `dns-credentials.env`.
|
||||
|
||||
In a published evaluation-capable bundle, leave
|
||||
`CP_PROVIDER_MSP_LICENSE_FILE` blank and `setup.sh` self-issues a 2-client
|
||||
evaluation licence. It sends only the public half of the signing key generated
|
||||
on your host, exactly as the paid path does, and the private key never leaves
|
||||
the machine. You can then onboard two real clients and confirm the isolation
|
||||
boundary on your own infrastructure before buying.
|
||||
Leave `CP_PROVIDER_MSP_LICENSE_FILE` blank and `setup.sh` self-issues a
|
||||
2-client evaluation licence after configuration validation succeeds and the
|
||||
immutable images are reachable. It sends only the public half of the signing
|
||||
key generated on your host, the optional contact address above, and the fixed
|
||||
signup-source label. The private key, client inventory, and credentials never
|
||||
leave the machine. You can then onboard two real clients and confirm the
|
||||
isolation boundary on your own infrastructure before buying.
|
||||
|
||||
The evaluation licence lasts 60 days and re-running `setup.sh` reuses the one
|
||||
already on disk. On an air-gapped host set
|
||||
|
||||
@@ -361,7 +361,13 @@ func TestProviderMSPSetupScriptSupportsUnlicensedEvaluation(t *testing.T) {
|
||||
"PULSE_PROVIDER_MSP_SKIP_EVAL_LICENSE",
|
||||
"reusing existing evaluation license",
|
||||
"could not reach the license server",
|
||||
"PULSE_PROVIDER_MSP_EVAL_EMAIL",
|
||||
"PULSE_PROVIDER_MSP_SIGNUP_SOURCE",
|
||||
"eval_license_id=",
|
||||
)
|
||||
if strings.LastIndex(script, "pull_provider_images\n") > strings.LastIndex(script, "ensure_eval_license\n") {
|
||||
t.Fatal("evaluation must be issued only after pinned provider images are reachable")
|
||||
}
|
||||
|
||||
// The install must never abort because an evaluation licence could not be
|
||||
// obtained. `|| true` inside the substitution does not achieve that: the
|
||||
@@ -399,7 +405,7 @@ func TestProviderMSPSetupScriptSupportsUnlicensedEvaluation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderMSPEvaluationDocsFailClosedUntilSignedBundlePublication(t *testing.T) {
|
||||
func TestProviderMSPEvaluationDocsUsePublishedSignedBundle(t *testing.T) {
|
||||
repoDocBytes, err := os.ReadFile(repoFile("docs", "MSP.md"))
|
||||
if err != nil {
|
||||
t.Fatalf("read repo MSP guide: %v", err)
|
||||
@@ -414,14 +420,17 @@ func TestProviderMSPEvaluationDocsFailClosedUntilSignedBundlePublication(t *test
|
||||
|
||||
doc := string(repoDocBytes)
|
||||
assertContainsAll(t, doc,
|
||||
"Self-service evaluation is available only from an exact release page",
|
||||
"three assets, stop: evaluation onboarding for that release remains",
|
||||
"request-assisted. Do **not** download the moving `main` branch archive",
|
||||
"signed provider bundle published",
|
||||
"with Pulse v6.2.1",
|
||||
"**not** download the moving `main` branch archive",
|
||||
`export PULSE_VERSION=v6.2.1`,
|
||||
`PULSE_MSP_BUNDLE="pulse-provider-msp-${PULSE_VERSION}.tar.gz"`,
|
||||
`releases/download/${PULSE_VERSION}`,
|
||||
"ssh-keygen -Y verify",
|
||||
`-s "${PULSE_MSP_BUNDLE}.sshsig" < "${PULSE_MSP_BUNDLE}"`,
|
||||
`sha256sum -c "${PULSE_MSP_BUNDLE}.sha256"`,
|
||||
`PULSE_PROVIDER_MSP_EVAL_EMAIL=you@example.com`,
|
||||
`PULSE_PROVIDER_MSP_SIGNUP_SOURCE=msp_docs`,
|
||||
`sudo -E bash ./setup.sh`,
|
||||
)
|
||||
assertNotContainsAny(t, doc,
|
||||
|
||||
Reference in New Issue
Block a user