Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('applyAwsMarketplaceContainerLicense', () => {
checkoutRequests.push(command.input)
const answer = answers.length > 1 ? answers.shift() : answers[0]
if (answer !== 'entitled') throw awsError(answer ?? 'ServerInternalException')
return { EntitlementsAllowed: [{ Name: 'enterprise_license', Unit: 'None' }] }
return { EntitlementsAllowed: [{ Name: 'AWS::Marketplace::Usage', Unit: 'None' }] }
})
})

Expand All @@ -68,7 +68,7 @@ describe('applyAwsMarketplaceContainerLicense', () => {
expect(mockSend).not.toHaveBeenCalled()
})

it('turns Enterprise on with an uncounted checkout of the entitlement', async () => {
it('turns Enterprise on with an uncounted checkout of the Marketplace usage entitlement', async () => {
answers = ['entitled']
await applyAwsMarketplaceContainerLicense()
expect(enterpriseEnabled()).toBe(true)
Expand All @@ -77,7 +77,7 @@ describe('applyAwsMarketplaceContainerLicense', () => {
ProductSKU: 'product-placeholder',
CheckoutType: 'PROVISIONAL',
KeyFingerprint: 'aws:294406891311:AWS/Marketplace:issuer-fingerprint',
Entitlements: [{ Name: 'enterprise_license', Unit: 'None' }],
Entitlements: [{ Name: 'AWS::Marketplace::Usage', Unit: 'None' }],
}),
])
})
Expand Down
40 changes: 21 additions & 19 deletions apps/sim/lib/billing/aws-marketplace/container-license.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ import { awsRequestHandler } from '@/lib/core/network/proxy-agent.server'
/**
* Enterprise entitlement for Sim installed from the AWS Marketplace container listing.
*
* The listing sells an annual contract whose one dimension is `enterprise_license`. AWS Marketplace
* grants the buyer a license in AWS License Manager, and the running product checks it the way AWS
* documents for a contract that unlocks a tier of features rather than metering a quantity: a
* provisional `CheckoutLicense` of the entitlement with unit `None`. Such a checkout is not counted
* against the contract, so every replica can check at once, and it needs only the permission AWS's
* managed `AWSLicenseManagerConsumptionPolicy` grants. While the license is active, the Enterprise
* suite is on (`ENTERPRISE_ENABLED`). Without one, Sim runs as the open-source core, so a lapsed
* renewal or an unreachable License Manager never locks a buyer out of their deployment.
* The listing sells an annual contract. The buyer's AWS License Manager license carries the
* contract's counted dimensions plus an uncounted `AWS::Marketplace::Usage` entitlement held while
* the agreement is active. Sim checks the uncounted one with a provisional `CheckoutLicense`, so
* every replica can check at once (a counted unit would go to one replica only), using only the
* permission in AWS's managed `AWSLicenseManagerConsumptionPolicy`. While the license is active, the
* Enterprise suite is on (`ENTERPRISE_ENABLED`). Without one, Sim runs as the open-source core, so a lapsed renewal or
* an unreachable License Manager never locks a buyer out of their deployment.
*
* Opt-in: nothing here runs unless {@link PRODUCT_SKU_ENV} is set, which only the Marketplace
* Helm profile does. Enterprise flags are read once at startup by both the server and the
Expand All @@ -38,8 +37,8 @@ const LICENSE_REGION_ENV = 'AWS_MARKETPLACE_LICENSE_REGION'
/** The issuer every AWS Marketplace license is signed by (a fixed value, not a placeholder). */
const MARKETPLACE_KEY_FINGERPRINT = 'aws:294406891311:AWS/Marketplace:issuer-fingerprint'

/** The listing's contract dimension API name. */
const ENTERPRISE_ENTITLEMENT = 'enterprise_license'
/** The uncounted entitlement every AWS Marketplace license holds while its agreement is active. */
const MARKETPLACE_USAGE_ENTITLEMENT = 'AWS::Marketplace::Usage'

/** AWS recommends re-checking every 15 minutes to pick up renewals, upgrades, and cancellations. */
const RECHECK_INTERVAL_MS = 15 * 60 * 1000
Expand Down Expand Up @@ -68,7 +67,10 @@ const NO_ENTITLEMENT_ERRORS = new Set([
'ResourceNotFoundException',
])

/** The ServiceAccount's IAM role does not allow the check: a setup problem, never a verdict. */
/**
* The check was refused: the ServiceAccount's IAM role lacks the permission, or License Manager's
* service-linked role is missing from the account. A setup problem, never a verdict.
*/
const PERMISSION_ERRORS = new Set(['AccessDeniedException', 'AuthorizationException'])

/** `unknown` is a failed check (network, permissions, throttling), never a verdict on the license. */
Expand All @@ -93,15 +95,15 @@ function classifyFailure(error: unknown): LicenseState {
return 'not-entitled'
}
if (name && PERMISSION_ERRORS.has(name)) {
logger.error(
'The IAM role cannot check the AWS Marketplace license; allow license-manager:CheckoutLicense',
{ error: name }
)
logger.error('AWS License Manager refused the license check', {
errorName: name,
error: getErrorMessage(error, 'Unknown error'),
})
Comment thread
waleedlatif1 marked this conversation as resolved.
Resolved
return 'unknown'
}
logger.warn('Could not check the AWS Marketplace Enterprise license', {
error: name,
message: getErrorMessage(error, 'Unknown error'),
errorName: name,
error: getErrorMessage(error, 'Unknown error'),
})
return 'unknown'
}
Expand All @@ -116,12 +118,12 @@ async function checkLicense(
ProductSKU: productSku,
CheckoutType: 'PROVISIONAL',
KeyFingerprint: MARKETPLACE_KEY_FINGERPRINT,
Entitlements: [{ Name: ENTERPRISE_ENTITLEMENT, Unit: 'None' }],
Entitlements: [{ Name: MARKETPLACE_USAGE_ENTITLEMENT, Unit: 'None' }],
ClientToken: generateId(),
})
)
const allowed = (checkout.EntitlementsAllowed ?? []).some(
(entitlement) => entitlement.Name === ENTERPRISE_ENTITLEMENT
(entitlement) => entitlement.Name === MARKETPLACE_USAGE_ENTITLEMENT
)
return allowed ? 'entitled' : 'not-entitled'
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion helm/sim/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: v2
name: sim
description: A Helm chart for Sim - the open-source AI workspace where teams build, deploy, and manage AI agents
type: application
version: 1.11.17
version: 1.11.18
appVersion: "v0.8.26"
kubeVersion: ">=1.25.0-0"
home: https://sim.ai
Expand Down
3 changes: 2 additions & 1 deletion helm/sim/examples/values-aws-marketplace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
# CloudFormation template creates both in your VPC.
# 3. An IAM role for the ServiceAccount below that allows license-manager:CheckoutLicense (the
# CloudFormation template creates it; AWS's managed AWSLicenseManagerConsumptionPolicy
# grants it too).
# grants it too). License Manager must be set up in the account: open its console once, or run
# aws iam create-service-linked-role --aws-service-name license-manager.amazonaws.com
# 4. An ACM certificate for the two hostnames below.
# 5. Two Secrets in the install namespace (no secret ever goes in this file). The template's
# DatabasePasswordArn and CacheAuthTokenArn outputs are the ARNs of the Secrets Manager
Expand Down
Loading