license-go is the centralized, secure Go licensing framework and CLI for the Divmora organization. It eliminates duplicated licensing implementations across products such as gitlab-fleet-governor and otel-aws-log-processor, providing a unified claims schema, Ed25519 cryptographic signing and verification, and an embeddable client SDK.
- Asymmetric Cryptography (Ed25519): Fast, compact, tamper-proof license generation and verification.
- Zero External Crypto Dependencies: Built purely on Go standard library (
crypto/ed25519,crypto/x509,encoding/pem). No CGo or heavy third-party bloat. - Dual Representation:
- Armored Text Blocks: Human-friendly files with boundary headers (
-----BEGIN DIVMORA LICENSE KEY-----). - Compact Tokens: Single-line
DIV1.<payload>.<signature>strings for environment variables and CLI arguments.
- Armored Text Blocks: Human-friendly files with boundary headers (
- Rich Claims Schema: Supports customer details, product targeting, subscription tiers (
community,starter,pro,enterprise,trial), expiration dates, feature flags, numerical quotas/limits, and custom key-value metadata. - Offline Revocation Lists (CRL): Support cryptographically signed revocation lists (
crl.divcrl) to invalidate compromised, leaked, or refunded licenses in air-gapped environments without network access. - Cryptographic Release Attestation: Cryptographically seal release binaries with Ed25519 signatures, commit hashes, build dates, and SHA-256 digests (
release.sig). - Background Daemon Manager: Built-in
Managerfor long-running services with hot-reloading from disk and automated expiration warnings. - CLI Included:
cmd/license-cliprovides instantkeygen,issue,verify,inspect,crl, and release attestation subcommands for CI/CD and administration.
go get github.com/divmora/license-goTo install the CLI tool:
go install github.com/divmora/license-go/cmd/license-cli@latestIn your product (e.g. gitlab-fleet-governor or otel-aws-log-processor):
package main
import (
"fmt"
"log"
license "github.com/divmora/license-go/pkg/license"
)
// Divmora public key (can be embedded or loaded from config/secret)
const publicKeyPEM = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA...
-----END PUBLIC KEY-----`
func main() {
// 1. Create a validator configured for your product
validator, err := license.NewValidatorFromPEM(
[]byte(publicKeyPEM),
license.WithProduct("gitlab-fleet-governor"),
)
if err != nil {
log.Fatalf("failed to initialize validator: %v", err)
}
// 2. Verify license automatically from environment (DIVMORA_LICENSE_KEY / DIVMORA_LICENSE_FILE / /etc/divmora/license.key)
// Or explicitly pass a file path: validator.VerifyFromFile("/etc/divmora/license.key")
claims, err := validator.VerifyEnv()
if err != nil {
log.Fatalf("License verification failed: %v", err)
}
fmt.Printf("Licensed to: %s (Plan: %s)\n", claims.Customer.Name, claims.Plan)
fmt.Printf("Days remaining: %d\n", claims.DaysRemaining())
// 3. Check feature entitlements
if claims.HasFeature("ha") {
fmt.Println("High Availability mode enabled")
}
// 4. Enforce quota limits
currentRunners := int64(120)
if err := claims.CheckLimit("max_runners", currentRunners); err != nil {
log.Fatalf("Quota exceeded: %v", err)
}
}license-go provides standardized resolution hierarchies eliminating custom loading boilerplate for both licenses and verification public keys:
| Priority | Source | Description |
|---|---|---|
| 1 | explicitSource argument |
Direct file path or inline token string passed to ResolveToken(), VerifyResolved(), or -license CLI flag. |
| 2 | DIVMORA_LICENSE_KEY |
Environment variable containing raw compact token (DIV1...) or armored PEM block text. Ideal for Docker, Lambda, and 12-factor apps. |
| 3 | DIVMORA_LICENSE_FILE |
Environment variable containing filesystem path to license file. Ideal for Kubernetes Secrets & ConfigMap mounts. |
| 4 | /etc/divmora/license.key |
Default Linux/container filesystem location if file exists. |
| Priority | Source | Description |
|---|---|---|
| 0 | Programmatic Override | In-memory override via SetVerificationPublicKey(key) or SetVerificationKeyRing(ring) (ideal for automated unit/integration tests). |
| 1 | Explicit Embedded Keys (Default) | Embedded fallback public keys passed to NewValidatorWithFallbackKey() or ResolveKeyRing() act as an immutable root of trust to prevent environment-based trust root spoofing. |
| 2 | DIVMORA_PUBLIC_KEYS_PEM |
Multi-key or single-key PKIX PEM bundle text string or filesystem path (active when no explicit keys are passed, or when WithAllowEnvKeyOverride(true) is configured). |
| 3 | DIVMORA_PUBLIC_KEY |
Single Ed25519 public key (base64 raw 32-byte, base64 PKIX DER, inline PEM, or filesystem path). |
| 4 | DIVMORA_PUBLIC_KEY_FILE |
Filesystem path to public key file on disk. |
| 5 | /etc/divmora/public.pem |
Default Linux/container filesystem location if file exists. |
| 6 | Fallback Keys (Opt-In Override) | Used when WithAllowEnvKeyOverride(true) or SetAllowEnvKeyOverride(true) is enabled and no environment variables are set. |
| Priority | Source | Description |
|---|---|---|
| 1 | Explicit Argument / Option | Direct path or raw token passed to WithRevocationList(...), WithRevocationListFile(...), ResolveRevocationList(path), or -crl CLI flag. |
| 2 | DIVMORA_CRL |
Environment variable containing raw compact token (DIVCRL1...) or armored PEM block text. Ideal for Docker, Lambda, and air-gapped environments. |
| 3 | DIVMORA_CRL_FILE |
Environment variable containing filesystem path to revocation list file (.divcrl). |
| 4 | /etc/divmora/crl.divcrl |
Default Linux/container filesystem location if file exists. |
Important
Trust Root Spoofing Defense: To prevent attackers in untrusted container/Kubernetes environments from replacing the vendor's public key with a forged key via DIVMORA_PUBLIC_KEY, NewValidatorWithFallbackKey(vendorKey) and ResolveKeyRing(vendorKey) treat explicit embedded keys as authoritative by default. Use WithAllowEnvKeyOverride(true) only if you intentionally wish to allow environment variables to override embedded keys (e.g. in staging/dev environments).
// Direct resolution helpers:
token, err := license.ResolveToken() // Returns license token string from env/file
resolved, err := license.ResolveLicense() // Returns content + FilePath for hot reloading
// Offline Certificate Revocation List (CRL) resolution:
crl, err := license.ResolveRevocationList("") // Auto-resolves from DIVMORA_CRL / DIVMORA_CRL_FILE / /etc/divmora/crl.divcrl
// Public verification KeyRing resolution:
ring, err := license.ResolveKeyRing(embeddedFallbackKey) // Resolves trusted KeyRing
pubKey, err := license.ResolvePublicKey(embeddedFallbackKey) // Resolves primary public key
// Zero-boilerplate validator initialization with auto-resolved CRL:
validator, err := license.NewValidatorFromEnv(
license.WithProduct("gitlab-fleet-governor"),
license.WithAutoResolvedRevocationList(),
)
// Or compile-time //go:embed helper:
// //go:embed public.pem
// var embeddedPublicKey []byte
validator, err := license.NewValidatorFromEmbeddedPEM(embeddedPublicKey,
license.WithProduct("gitlab-fleet-governor"),
license.WithAutoResolvedRevocationList(),
)For long-running microservices and daemons, use license.NewManager. If LicenseFile and LicenseString are omitted, Manager automatically resolves from DIVMORA_LICENSE_FILE (enabling hot-reloading) or DIVMORA_LICENSE_KEY:
package main
import (
"context"
"log"
"time"
license "github.com/divmora/license-go/pkg/license"
)
func main() {
validator, err := license.NewValidatorFromPEMFile(
"/etc/divmora/public.pem",
license.WithProduct("otel-aws-log-processor"),
)
if err != nil {
log.Fatal(err)
}
// Auto-resolves from DIVMORA_LICENSE_FILE or DIVMORA_LICENSE_KEY if LicenseFile is omitted:
mgr, err := license.NewManager(license.ManagerConfig{
Validator: validator,
CheckInterval: 1 * time.Hour,
ExpiryWarningDays: 14,
OnExpiringSoon: func(c *license.Claims, daysRemaining int) {
log.Printf("WARNING: License expires in %d days!", daysRemaining)
},
OnGracePeriod: func(c *license.Claims, graceDaysRemaining int) {
log.Printf("NOTICE: License operating in grace period (%d days left)!", graceDaysRemaining)
},
OnExpired: func(c *license.Claims) {
log.Printf("CRITICAL: License has expired!")
},
OnReloaded: func(newClaims, oldClaims *license.Claims) {
log.Printf("INFO: License reloaded! New tier: %s", newClaims.Plan)
},
OnError: func(err error) {
log.Printf("License check error: %v", err)
},
})
if err != nil {
log.Fatalf("License initialization failed: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Start background monitoring and file watching
mgr.Start(ctx)
defer mgr.Stop()
// Thread-safe queries anytime in your service:
if mgr.HasFeature("s3_export") {
// enable S3 exporter...
}
}For products governed under Business Source License 1.1 (BSL 1.1), license-go provides first-class modeling of Additional Use Grants (e.g. unlimited non-production/staging exemptions and free community tiers). Prior to the Change Date, the software dynamically evaluates operational context against configured grants. Once the Change Date arrives, the software converts autonomously to open source (Apache-2.0), granting unrestricted access:
policy := license.BSLPolicy{
ReleaseDate: officialReleaseDate, // Anchored by attestation
ChangePeriodYears: 3, // Converts to Apache-2.0 in 3 years
AdditionalUseGrants: []license.BSLAdditionalUseGrant{
// 1. Unlimited non-production exemption (staging, test, dev, demo):
license.NewNonProductionGrant("Non-Production Exemption"),
// 2. Free community tier in production up to 10 nodes (SSO requires commercial license):
license.NewFreeTierGrant("Community Free Tier", map[string]int64{"max_nodes": 10}, "sso"),
},
}
validator, err := license.NewValidator(pubKey,
license.WithProduct("gitlab-fleet-governor"),
license.WithBSLPolicy(policy),
license.WithCurrentEnvironment(os.Getenv("ENV")),
license.WithCurrentUsage(map[string]int64{"max_nodes": currentNodes}),
)
// Zero-license resolution: succeeds if usage satisfies Additional Use Grants or if Change Date reached!
claims, err := validator.VerifyEnv()
if err != nil {
// Fails with *CommercialLicenseRequiredError if usage exceeds free bounds
log.Fatalf("Commercial license required: %v", err)
}type Customer struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
OrgID string `json:"org_id,omitempty"`
}
type Scope struct {
Environments []string `json:"environments,omitempty"` // ["production", "staging"]
Accounts []string `json:"accounts,omitempty"` // AWS Account IDs, cloud tenant IDs
Regions []string `json:"regions,omitempty"` // Cloud regions ["us-east-1", "eu-west-*"]
Clusters []string `json:"clusters,omitempty"` // Kubernetes / ECS cluster IDs
Namespaces []string `json:"namespaces,omitempty"` // GitLab groups/projects ["acme-corp/*"]
Hosts []string `json:"hosts,omitempty"` // Hostnames, FQDNs, domains ["*.acme.corp"]
Resources []string `json:"resources,omitempty"` // Monitored resources (ARNs, wildcards, names)
Custom map[string][]string `json:"custom,omitempty"` // Arbitrary product scope dimensions
}
type Claims struct {
ID string `json:"id"`
Customer Customer `json:"customer"`
Product string `json:"product"` // Product name, suite ("divmora-suite"), or wildcard ("*")
Plan string `json:"plan"` // "community", "starter", "pro", "enterprise", "trial"
IssuedAt time.Time `json:"issued_at"`
NotBefore time.Time `json:"not_before,omitempty"`
ExpiresAt time.Time `json:"expires_at"` // zero time = perpetual
GracePeriodDays int `json:"grace_period_days,omitempty"` // Buffer days after ExpiresAt
Features []string `json:"features,omitempty"`
Limits map[string]int64 `json:"limits,omitempty"` // -1 = unlimited
Scope *Scope `json:"scope,omitempty"` // Scoping constraints
Environment string `json:"environment,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}claims.IsValidForProduct(name string) bool(supports exact match, wildcards*/all, suite bundlesdivmora-suite/suite, multi-product lists, and glob patterns)claims.HasFeature(name string) bool(supports exact match, wildcards*/all, and glob patterns likeaudit:*)claims.IsInGracePeriod() bool(true if passedExpiresAtbut withinGracePeriodDays)claims.GraceDaysRemaining() int(remaining grace buffer days before hard cutoff)claims.EffectiveExpiration() time.Time(final cutoff:ExpiresAt + GracePeriodDays)claims.Status() Status(returnsACTIVE,GRACE_PERIOD,EXPIRED, orNOT_YET_VALID)claims.IsBoundToFingerprint() bool(true if license is bound to a specific node/cluster)claims.MatchesFingerprint(hostFingerprint string) bool(case-insensitive fingerprint evaluation)claims.IsInScope(dimension, target string) bool(checks authorization against any scope dimension)claims.IsEnvironmentAllowed(env string) boolclaims.IsAccountAllowed(account string) boolclaims.IsRegionAllowed(region string) boolclaims.IsClusterAllowed(cluster string) boolclaims.IsNamespaceAllowed(namespace string) boolclaims.IsHostAllowed(host string) boolclaims.IsResourceAllowed(resource string) bool(supports exact match, glob patterns, short IDs, and AWS ARN suffix extraction)claims.AssertResource(resource string) errorclaims.CheckResourceLimit(currentUsage int64) error(checks quota againstLimits["max_resources"])license.GetMaxResources(claims) int(helper extractingmax_resourcesquota limit)license.GetMaxAccounts(claims) int(helper extractingmax_accountsquota limit)license.GetAllowedResources(claims) []string(helper extracting allowed resources list)license.MatchResourcePattern(pattern, resource string) bool(pattern matching engine for resource IDs and ARNs)claims.AssertScope(dimension, target string) errorclaims.AssertFeature(name string) errorclaims.CheckLimit(name string, currentUsage int64) errorclaims.IsPerpetual() boolclaims.IsExpired() boolclaims.DaysRemaining() intclaims.GetMetadata(key string) (string, bool)
license-cli keygen -out-dir ./keysOutputs:
./keys/private.pem(mode 0600 - keep secure, Divmora internal only)./keys/public.pem(mode 0644 - embed or distribute with products)
license-cli issue \
-private-key ./keys/private.pem \
-customer "Acme Corp" \
-email "admin@acme.corp" \
-org-id "org_acme_corp_01" \
-product "gitlab-fleet-governor" \
-plan enterprise \
-valid-days 365 \
-grace-days 14 \
-features "ha,audit-logs,auto-scaling" \
-limits "max_runners=200,max_nodes=10" \
-scope-envs "production" \
-scope-accounts "123456789012" \
-scope-regions "us-east-1,eu-west-1" \
-scope-clusters "prod-eks-01" \
-scope-namespaces "gitlab.com/acme-corp/*" \
-scope-hosts "*.acme.corp" \
-scope-resources "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/prod-*/*,arn:aws:cloudfront::*:distribution/*" \
-scope-custom "tier=platinum,gold;datacenter=dc-east,dc-west" \
-meta "billing_id=inv-9981,contact=admin@acme.corp" \
-fingerprint "node-cluster-01" \
-out ./acme.license.key# Verify explicit license with optional scope assertion flags:
license-cli verify \
-public-key ./keys/public.pem \
-product "gitlab-fleet-governor" \
-env "production" \
-namespace "gitlab.com/acme-corp/fleet" \
-host "runner-01.acme.corp" \
-resource "arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/prod-alb/1234567890abcdef" \
-custom-scope "tier=platinum,datacenter=dc-east" \
-fingerprint "node-cluster-01" \
-license ./acme.license.key
# Or verify automatically with zero configuration (resolves license from DIVMORA_LICENSE_KEY/FILE
# and public key from DIVMORA_PUBLIC_KEY / DIVMORA_PUBLIC_KEYS_PEM / /etc/divmora/public.pem):
license-cli verify -product "gitlab-fleet-governor"
# Verify license enforced with release attestation sidecar and binary checksum:
license-cli verify \
-product "gitlab-fleet-governor" \
-license ./acme.license.key \
-release-attestation ./release.sig \
-binary ./bin/gitlab-fleet-governor \
-require-release-attestationDisplay a visual status card with active tier, countdown, BSL 1.1 open-source transition, and resource quota utilization table:
# Display status with live runtime usage counts:
license-cli status \
-product "gitlab-fleet-governor" \
-license ./acme.license.key \
-usage "max_runners=142,max_nodes=6"
# Or display compact status card from environment:
license-cli status -compact# Direct file / token inspect:
license-cli inspect -license ./acme.license.key
# Or inspect automatically from $DIVMORA_LICENSE_KEY or $DIVMORA_LICENSE_FILE:
license-cli inspectEvaluate deployment operational context against BSL 1.1 terms and Additional Use Grants to determine whether commercial licensing is required:
# Evaluate entitlement for staging environment:
license-cli bsl-eval \
-release-date 2025-01-01 \
-years 3 \
-env staging \
-product "gitlab-fleet-governor" \
-usage "max_nodes=100,max_runners=500"
# Evaluate production free tier quota with custom limits and feature exclusions:
license-cli bsl-eval \
-release-date 2025-01-01 \
-env production \
-free-limits "max_nodes=10,max_runners=50" \
-usage "max_nodes=6,max_runners=20" \
-excluded-features "sso,audit-logs"
# Or output machine-readable JSON for automated CI/CD gating:
license-cli bsl-eval -release-date 2025-01-01 -env staging -jsonMint an Ed25519 cryptographic release attestation token or armored PEM sidecar (release.sig) in your CI/CD release build step to certify binary build authenticity, official Git commit, SemVer version, authoritative BSL 1.1 release date, and binary SHA-256 digest:
license-cli sign-release \
-private-key ./keys/private.pem \
-product "gitlab-fleet-governor" \
-version "v2.5.0" \
-git-commit "${CI_COMMIT_SHA}" \
-binary "./bin/gitlab-fleet-governor" \
-authority "divmora.com/release" \
-out "./bin/release.sig" \
-armoredVerify an official release binary against a cryptographic release attestation:
license-cli verify-release \
-public-key ./keys/public.pem \
-attestation ./bin/release.sig \
-product "gitlab-fleet-governor" \
-version "v2.5.0" \
-git-commit "${CI_COMMIT_SHA}" \
-binary "./bin/gitlab-fleet-governor"Inspect the unverified release claims embedded within an armored .sig file or compact token:
# Standard formatted inspection:
license-cli inspect-release -attestation ./bin/release.sig
# Or output raw JSON:
license-cli inspect-release -attestation ./bin/release.sig -jsonManage and enforce cryptographically signed Revocation Lists (DIVCRL1) for air-gapped environments without network connectivity:
# 1. Mint a signed CRL (inline entries or via JSON entries file):
license-cli crl sign \
-private-key ./keys/private.pem \
-entries "lic-corp-1234=compromised,lic-corp-5678=refunded" \
-next-update "30d" \
-product "gitlab-fleet-governor" \
-out ./crl.divcrl
# 2. Inspect revoked entries in a CRL (no key required):
license-cli crl inspect -crl ./crl.divcrl
# 3. Check whether a specific license ID is revoked:
license-cli crl check -crl ./crl.divcrl -id "lic-corp-1234"
# 4. Verify CRL cryptographic signature against KeyRing:
license-cli crl verify -crl ./crl.divcrl -public-key ./keys/public.pem
# 5. Fetch, verify, and cache CRL from a remote distribution point:
license-cli crl sync \
-url "https://crl.divmora.com/gitlab-fleet-governor.divcrl" \
-public-key ./keys/public.pem \
-cache-file /var/lib/divmora/crl.cache \
-out ./crl.divcrl \
-verbose
# 6. Enforce local CRL during license verification:
license-cli verify \
-product "gitlab-fleet-governor" \
-license ./acme.license.key \
-crl ./crl.divcrl
# 7. Dynamically fetch, verify, and enforce CRL with strict non-revocation requirement:
license-cli verify \
-product "gitlab-fleet-governor" \
-license ./acme.license.key \
-crl-url "https://crl.divmora.com/gitlab-fleet-governor.divcrl" \
-require-crlThis repository provides standardized make targets:
make build # Compile license-cli binary into bin/
make test # Run all unit and integration tests
make test-race # Run tests with race detector
make test-coverage # Generate coverage report
make fmt # Format code
make lint # Run golangci-lint
make clean # Remove temporary build artifacts- Protocol Specification (
SPEC.md): Formal RFC specification coveringDIV1envelope encoding, Ed25519 cryptography, canonical data signing, JSON claims schema, BSL 1.1 state machine, operational policies, and strict 10-step verification algorithm. - Living Product Roadmap (
ROADMAP.md): Upcoming capabilities and delivered enterprise features. - Contributing Guide: Development setup, make targets, and Conventional Commits guidelines.
- Code of Conduct: Community standards and expectations.
- Security Policy: Responsible vulnerability disclosure and 48-hour response SLA.
This software is licensed under the Apache License, Version 2.0. See the LICENSE file for complete terms and conditions.
Copyright (c) 2026 DIVMORA Technologies.