|
| 1 | +package certmagic |
| 2 | + |
| 3 | +import ( |
| 4 | + "hash/fnv" |
| 5 | + "os" |
| 6 | + "strconv" |
| 7 | +) |
| 8 | + |
| 9 | +const ( |
| 10 | + // Storage mode controls the format in which certificates are stored in `Storage`. |
| 11 | + // |
| 12 | + // Formats: |
| 13 | + // - legacy: Store cert, privkey and meta as three separate storage items (.cert, .key, .json). |
| 14 | + // - bundle: Store cert, privkey and meta as a single, bundled storage item (.bundle). |
| 15 | + // |
| 16 | + // Modes: |
| 17 | + // - legacy: Store and load certificates in legacy format. |
| 18 | + // - transition: Store in legacy and bundle format, load as bundle with fallback to legacy format. |
| 19 | + // - bundle: Store and load certificates in bundle format. |
| 20 | + // |
| 21 | + // In the transition mode, failures around reads and writes of the bundle are soft. |
| 22 | + // They should only log errors and try to work with the legacy format as fallback. |
| 23 | + // Operations on the legacy format are hard-failures, implying that errors should be propagated up. |
| 24 | + // |
| 25 | + // The rollout percentage enables a phased migration by controlling which domains |
| 26 | + // enter the transition phase. If a domain's deterministic bucket (0-99) is below |
| 27 | + // the rollout percentage, it uses 'transition' mode (dual-write, bundle-read). |
| 28 | + // Otherwise, it remains in 'legacy' mode. |
| 29 | + // |
| 30 | + // The logic for selection is: |
| 31 | + // if mode == StorageModeTransition: |
| 32 | + // useTransition = hash(domain)%100 < rollout |
| 33 | + // return useTransition ? StorageModeTransition : StorageModeLegacy |
| 34 | + // |
| 35 | + // The storage mode is controlled via the CERTMAGIC_STORAGE_MODE environment variable |
| 36 | + StorageModeEnv = "CERTMAGIC_STORAGE_MODE" |
| 37 | + |
| 38 | + StorageModeLegacy = "legacy" |
| 39 | + StorageModeTransition = "transition" |
| 40 | + StorageModeBundle = "bundle" |
| 41 | + |
| 42 | + // StorageModeRolloutPercentEnv controls the percentage of domains that will use |
| 43 | + // the bundle format when the storage mode is set to "transition". |
| 44 | + // An empty rollout precent is equal to 0%. |
| 45 | + StorageModeRolloutPercentEnv = "CERTMAGIC_STORAGE_MODE_ROLLOUT_PERCENT" |
| 46 | +) |
| 47 | + |
| 48 | +var ( |
| 49 | + StorageMode string |
| 50 | + StorageModeRolloutPercent int |
| 51 | +) |
| 52 | + |
| 53 | +func ConfigureStorageMode(mode string, rolloutPercent int) { |
| 54 | + StorageMode = mode |
| 55 | + StorageModeRolloutPercent = rolloutPercent |
| 56 | +} |
| 57 | + |
| 58 | +func init() { |
| 59 | + mode := os.Getenv(StorageModeEnv) |
| 60 | + |
| 61 | + // rolloutPercent becomes zero if env is unset or malformed |
| 62 | + rolloutPercent, _ := strconv.Atoi(os.Getenv(StorageModeRolloutPercentEnv)) |
| 63 | + |
| 64 | + ConfigureStorageMode(mode, rolloutPercent) |
| 65 | +} |
| 66 | + |
| 67 | +func StorageModeForDomain(domain string) string { |
| 68 | + if StorageMode == StorageModeBundle { |
| 69 | + return StorageModeBundle |
| 70 | + } |
| 71 | + if StorageMode != StorageModeTransition { |
| 72 | + return StorageModeLegacy |
| 73 | + } |
| 74 | + if RolloutBucketForDomain(domain) < StorageModeRolloutPercent { |
| 75 | + return StorageModeTransition |
| 76 | + } else { |
| 77 | + return StorageModeLegacy |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +func RolloutBucketForDomain(domain string) int { |
| 82 | + h := fnv.New32a() |
| 83 | + h.Write([]byte(domain)) |
| 84 | + return int(h.Sum32() % 100) |
| 85 | +} |
0 commit comments