|
| 1 | +import { |
| 2 | + type GrantedLicense, |
| 3 | + LicenseManagerClient, |
| 4 | + ListReceivedLicensesCommand, |
| 5 | +} from '@aws-sdk/client-license-manager' |
| 6 | +import { createLogger } from '@sim/logger' |
| 7 | +import { getErrorMessage } from '@sim/utils/errors' |
| 8 | +import { awsRequestHandler } from '@/lib/core/network/proxy-agent.server' |
| 9 | + |
| 10 | +/** |
| 11 | + * Enterprise entitlement for Sim installed from the AWS Marketplace container listing. |
| 12 | + * |
| 13 | + * The listing sells an annual contract whose one dimension is `enterprise_license`. AWS Marketplace |
| 14 | + * grants the buyer a license in AWS License Manager, and the running product reads it. While the |
| 15 | + * license is active, the Enterprise suite is on (`ENTERPRISE_ENABLED`). Without one, Sim runs as the |
| 16 | + * open-source core, so a lapsed renewal or an unreachable License Manager never locks a buyer out |
| 17 | + * of their deployment. |
| 18 | + * |
| 19 | + * The check reads the license rather than checking a unit out of it. A contract usually covers the |
| 20 | + * whole deployment with one unit, so checking it out from every replica would make them compete |
| 21 | + * for it, and a unit left checked out (a failed check-in holds it for an hour) would read exactly |
| 22 | + * like an expired license. |
| 23 | + * |
| 24 | + * Opt-in: nothing here runs unless {@link PRODUCT_SKU_ENV} is set, which only the Marketplace |
| 25 | + * Helm profile does. Enterprise flags are read once at startup by both the server and the |
| 26 | + * browser, so instead of flipping them live, a re-check that sees the answer change asks the |
| 27 | + * process to shut down and the orchestrator starts it again in the right mode. |
| 28 | + */ |
| 29 | + |
| 30 | +const logger = createLogger('AwsMarketplaceContainerLicense') |
| 31 | + |
| 32 | +/** Set by the Marketplace Helm profile to the listing's product ID (the License Manager SKU). */ |
| 33 | +const PRODUCT_SKU_ENV = 'AWS_MARKETPLACE_LICENSE_PRODUCT_SKU' |
| 34 | + |
| 35 | +/** |
| 36 | + * Optional override for where the license is read. License Manager replicates a granted license to |
| 37 | + * other Regions, so the default is the pod's own Region, the same chain AWS's EKS walkthrough uses. |
| 38 | + */ |
| 39 | +const LICENSE_REGION_ENV = 'AWS_MARKETPLACE_LICENSE_REGION' |
| 40 | + |
| 41 | +/** The issuer every AWS Marketplace license is signed by (a fixed value, not a placeholder). */ |
| 42 | +const MARKETPLACE_KEY_FINGERPRINT = 'aws:294406891311:AWS/Marketplace:issuer-fingerprint' |
| 43 | + |
| 44 | +/** The listing's contract dimension API name. */ |
| 45 | +const ENTERPRISE_ENTITLEMENT = 'enterprise_license' |
| 46 | + |
| 47 | +/** AWS recommends re-checking every 15 minutes to pick up renewals, upgrades, and cancellations. */ |
| 48 | +const RECHECK_INTERVAL_MS = 15 * 60 * 1000 |
| 49 | + |
| 50 | +const ENTERPRISE_FLAGS = ['ENTERPRISE_ENABLED', 'NEXT_PUBLIC_ENTERPRISE_ENABLED'] as const |
| 51 | + |
| 52 | +/** `unknown` is a failed check (network, permissions, throttling), never a verdict on the license. */ |
| 53 | +type LicenseState = 'entitled' | 'not-entitled' | 'unknown' |
| 54 | + |
| 55 | +function createClient(): LicenseManagerClient { |
| 56 | + const region = |
| 57 | + process.env[LICENSE_REGION_ENV] || |
| 58 | + process.env.AWS_REGION || |
| 59 | + process.env.AWS_DEFAULT_REGION || |
| 60 | + 'us-east-1' |
| 61 | + return new LicenseManagerClient({ |
| 62 | + region, |
| 63 | + requestHandler: awsRequestHandler({ connectionTimeout: 3_000, requestTimeout: 10_000 }), |
| 64 | + }) |
| 65 | +} |
| 66 | + |
| 67 | +/** |
| 68 | + * An AWS Marketplace license this product's buyer holds is current when License Manager reports it |
| 69 | + * `AVAILABLE`, its issuer is AWS Marketplace, and it carries the Enterprise entitlement. License |
| 70 | + * Manager moves an ended, cancelled, or suspended license out of `AVAILABLE` itself. |
| 71 | + */ |
| 72 | +function isActiveEnterpriseLicense(license: GrantedLicense): boolean { |
| 73 | + return ( |
| 74 | + license.Status === 'AVAILABLE' && |
| 75 | + license.Issuer?.KeyFingerprint === MARKETPLACE_KEY_FINGERPRINT && |
| 76 | + (license.Entitlements ?? []).some((entitlement) => entitlement.Name === ENTERPRISE_ENTITLEMENT) |
| 77 | + ) |
| 78 | +} |
| 79 | + |
| 80 | +async function checkLicense( |
| 81 | + client: LicenseManagerClient, |
| 82 | + productSku: string |
| 83 | +): Promise<LicenseState> { |
| 84 | + try { |
| 85 | + let nextToken: string | undefined |
| 86 | + do { |
| 87 | + const page = await client.send( |
| 88 | + new ListReceivedLicensesCommand({ |
| 89 | + Filters: [ |
| 90 | + { Name: 'ProductSKU', Values: [productSku] }, |
| 91 | + { Name: 'Fingerprint', Values: [MARKETPLACE_KEY_FINGERPRINT] }, |
| 92 | + ], |
| 93 | + NextToken: nextToken, |
| 94 | + }) |
| 95 | + ) |
| 96 | + if ((page.Licenses ?? []).some(isActiveEnterpriseLicense)) return 'entitled' |
| 97 | + nextToken = page.NextToken |
| 98 | + } while (nextToken) |
| 99 | + return 'not-entitled' |
| 100 | + } catch (error) { |
| 101 | + logger.warn('Could not check the AWS Marketplace Enterprise license', { |
| 102 | + message: getErrorMessage(error, 'Unknown error'), |
| 103 | + }) |
| 104 | + return 'unknown' |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +function setEnterpriseFlags(enabled: boolean): void { |
| 109 | + for (const flag of ENTERPRISE_FLAGS) process.env[flag] = enabled ? 'true' : 'false' |
| 110 | +} |
| 111 | + |
| 112 | +/** |
| 113 | + * Resolves the Enterprise license before the server loads, so every Enterprise gate reads the |
| 114 | + * answer through `ENTERPRISE_ENABLED`, then keeps re-checking. Returns without doing anything on a |
| 115 | + * deployment that is not a Marketplace install. |
| 116 | + */ |
| 117 | +export async function applyAwsMarketplaceContainerLicense(): Promise<void> { |
| 118 | + const productSku = process.env[PRODUCT_SKU_ENV] |
| 119 | + if (!productSku) return |
| 120 | + |
| 121 | + const client = createClient() |
| 122 | + const bootState = await checkLicense(client, productSku) |
| 123 | + const enterpriseAtBoot = bootState === 'entitled' |
| 124 | + setEnterpriseFlags(enterpriseAtBoot) |
| 125 | + logger.info( |
| 126 | + enterpriseAtBoot |
| 127 | + ? 'AWS Marketplace Enterprise license active; Enterprise features on' |
| 128 | + : 'No active AWS Marketplace Enterprise license; running without Enterprise features', |
| 129 | + { state: bootState } |
| 130 | + ) |
| 131 | + |
| 132 | + const recheck = setInterval(async () => { |
| 133 | + const state = await checkLicense(client, productSku) |
| 134 | + const entitledNow = state === 'entitled' |
| 135 | + if (state === 'unknown' || entitledNow === enterpriseAtBoot) return |
| 136 | + logger.info('AWS Marketplace Enterprise license changed; restarting to apply it', { state }) |
| 137 | + clearInterval(recheck) |
| 138 | + process.kill(process.pid, 'SIGTERM') |
| 139 | + }, RECHECK_INTERVAL_MS) |
| 140 | + recheck.unref() |
| 141 | +} |
0 commit comments