-
Notifications
You must be signed in to change notification settings - Fork 282
docs(bh-rfc-6): DogTags (SKU Feature Entitlements) BED-7094 #2226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kpom-specter
wants to merge
2
commits into
main
Choose a base branch
from
bh-rfc-6
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+135
−0
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| --- | ||
| bh-rfc: 6 | ||
| title: Dogtags - SKU-Based Feature Entitlements | ||
| authors: | | ||
| [Pomeroy, Kaleb](kpomeroy@specterops.io) | ||
| status: DRAFT | ||
| created: 2025-01-07 | ||
| --- | ||
|
|
||
| # Dogtags - SKU-Based Feature Entitlements | ||
|
|
||
| ## 1. Overview | ||
|
|
||
| Dogtags is a provider-based system for managing feature entitlements. It abstracts where SKU level configuration comes from, allowing the same application code to work whether entitlements are sourced from a license file, a database, or some future backend we haven't thought of yet. | ||
|
|
||
| ## 2. Motivation & Goals | ||
|
|
||
| BloodHound supports multiple deployment models with different configuration needs. Some deployments read entitlements from signed license files, others from a database. The application shouldn't care where the values come from. | ||
|
|
||
| - **Abstraction** - Decouple feature checks from configuration source | ||
| - **Extensibility** - Add new providers without touching application code | ||
| - **Simplicity** - One interface for all entitlement lookups | ||
|
|
||
| ## 3. Considerations | ||
|
|
||
| ### 3.1 Provider Injection | ||
|
|
||
| Providers are injected at runtime. The application receives a configured provider at startup and uses it for all entitlement lookups. Provider implementation details are intentionally hidden from the application. The FOSS application uses a no-op provider, meaning default values in the SKU lookup field will apply. | ||
|
|
||
| ### 3.2 Startup-Time Loading | ||
|
|
||
| Entitlements are loaded once at startup and cached. Changes require a restart. This is intentional; Runtime updates add complexity and create less predictable behavior. | ||
|
|
||
| ### 3.3 Fail-Fast | ||
|
|
||
| If a provider can't load its configuration, the application fails to start. Silent fallbacks mask misconfigurations and make debugging painful. | ||
|
|
||
| ### 3.4 Public Interfaces, Private Implementations | ||
|
|
||
| The dogtags interfaces and service layer live in this public repository, but the feature itself is enterprise-only. This is a deliberate pattern: CE ships with a `NoopProvider` that returns defaults, effectively disabling SKU-gated functionality. Enterprise builds inject real providers that source actual entitlements. | ||
|
|
||
| This lets us maintain a single codebase where enterprise features are structurally present but functionally inert in CE. Application code doesn't branch on "is this enterprise?" - it just asks dogtags for values and gets sensible defaults in CE. | ||
|
|
||
| ## 4. Architecture | ||
|
|
||
| ### 4.1 Provider Interface | ||
|
|
||
| Providers implement a simple interface: | ||
|
|
||
| ```go | ||
| type Provider interface { | ||
| GetFlagAsBool(key string) (bool, error) | ||
| GetFlagAsString(key string) (string, error) | ||
| GetFlagAsInt(key string) (int64, error) | ||
| } | ||
| ``` | ||
|
|
||
| Providers return errors when a key isn't found. This lets the service layer decide whether to use defaults. | ||
|
|
||
| ### 4.2 Service Layer | ||
|
|
||
| The service wraps providers and handles defaults. If a provider doesn't have a flag, the service falls back to the default defined in the SKU spec. | ||
|
|
||
| ```go | ||
| type Service interface { | ||
| GetFlagAsBool(key BoolDogTag) bool | ||
| GetFlagAsString(key StringDogTag) string | ||
| GetFlagAsInt(key IntDogTag) int64 | ||
| GetAllDogTags() map[string]any | ||
| } | ||
| ``` | ||
|
|
||
| Note the typed keys (`BoolDogTag`, `IntDogTag`, etc.) - this prevents mixing up flag types at compile time. | ||
|
|
||
| ### 4.3 API Endpoint | ||
|
|
||
| Dogtags are exposed via `GET /api/v2/dog-tags`. The response is always a flat map (no nested objects). Keys use dot notation for namespacing (`namespace.key`), but this is purely a naming convention, not structure. | ||
|
|
||
| ```json | ||
| { | ||
| "data": { | ||
| "privilege_zones.tier_limit": 3, | ||
| "privilege_zones.label_limit": 10, | ||
| "privilege_zones.multi_tier_analysis": false | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## 5. Adding a New SKU Flag | ||
|
|
||
| ### 5.1 Define the Flag | ||
|
|
||
| Add to `bhce/cmd/api/src/services/dogtags/sku_flags.go`: | ||
|
|
||
| ```go | ||
| const ( | ||
| MY_NEW_FEATURE BoolDogTag = "my_feature.enabled" | ||
| ) | ||
|
|
||
| var AllBoolDogTags = map[BoolDogTag]BoolDogTagSpec{ | ||
| MY_NEW_FEATURE: {Description: "My New Feature", Default: false}, | ||
| } | ||
| ``` | ||
|
|
||
| Pick a sensible default. This is what the service returns if a provider doesn't have the flag. | ||
|
|
||
| ### 5.2 Configure Providers | ||
|
|
||
| Every provider must be updated to source the new flag's value. Consult provider-specific documentation for configuration details. Missing this step means some deployments will silently fall back to defaults. | ||
|
|
||
| ### 5.3 Use It | ||
|
|
||
| ```go | ||
| if s.DogTags.GetFlagAsBool(dogtags.MY_NEW_FEATURE) { | ||
| // feature enabled | ||
| } | ||
| ``` | ||
|
|
||
| The service handles defaults, so you never need to check errors. | ||
|
|
||
| ## 6. Testing | ||
|
|
||
| Use `NoopProvider` for tests - it always returns errors, forcing the service to use defaults. For testing specific flag values, create a mock provider. | ||
|
|
||
| ```go | ||
| dogtagsService := dogtags.NewDefaultService() // uses NoopProvider | ||
| ``` | ||
|
|
||
| ## 7. File Locations | ||
|
|
||
| | File | Purpose | | ||
| | ------------------------------------------------ | ------------------------------------ | | ||
| | `bhce/cmd/api/src/services/dogtags/sku_flags.go` | Flag definitions and defaults | | ||
| | `bhce/cmd/api/src/services/dogtags/service.go` | Service interface and implementation | | ||
| | `bhce/cmd/api/src/services/dogtags/provider.go` | Provider interface and NoopProvider | | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.