You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
HYPERFLEET-1147 - feat: add caller identity support for audit attribution
Inject caller identity headers into all E2E API requests so tests work
against an API with identity enforcement enabled (production default).
- Add IdentityConfig (header/value/token) to config with CLI flags,
env vars (HYPERFLEET_IDENTITY_*), and config.yaml support
- Inject identity via OpenAPI RequestEditorFn in helper.newHelper()
- Add HaveAuditIdentity matcher and ExpectedIdentity() helper
- Add created_by assertion in cluster creation, deleted_by in deletion
- Deploy API with identity_header enabled in local kind setup
- Update AGENTS.md, getting-started, and local-kind-setup docs
Available pollers: `PollCluster`, `PollNodePool`, `PollClusterAdapterStatuses`, `PollNodePoolAdapterStatuses`, `PollClusterHTTPStatus`, `PollNodePoolHTTPStatus`, `PollNamespacesByPrefix`.
89
90
90
-
Available matchers: `HaveResourceCondition`, `HaveAllAdaptersWithCondition`, `HaveAllAdaptersAtGeneration`.
91
+
Available matchers: `HaveResourceCondition`, `HaveAllAdaptersWithCondition`, `HaveAllAdaptersAtGeneration`, `HaveAuditIdentity`.
91
92
92
93
For one-off complex assertions, use `Eventually(func(g Gomega) { ... }).Should(Succeed())` with `g.Expect()` (not bare `Expect()`).
93
94
@@ -118,6 +119,39 @@ Use `ginkgo.By()` for major steps. **IMPORTANT:** Never use `ginkgo.By()` inside
118
119
119
120
Always use config values: `h.Cfg.Timeouts.Cluster.Reconciled`, `h.Cfg.Timeouts.NodePool.Reconciled`, `h.Cfg.Timeouts.Adapter.Processing`, `h.Cfg.Polling.Interval`. Never hardcode durations.
120
121
122
+
### Caller identity (audit attribution)
123
+
124
+
The API enforces caller identity on mutating requests (production default). E2E tests must send identity headers — without them, mutating requests are rejected with `401 Unauthorized`.
HYPERFLEET_IDENTITY_TOKEN=$TOKEN ./bin/hyperfleet-e2e test
139
+
```
140
+
141
+
Identity injection uses `openapi.WithRequestEditorFn` — headers are added to every request from the generated OpenAPI client. The `identity.value` must be email-formatted (the API validates `created_by` as email type).
142
+
143
+
To verify audit fields in tests, use `h.ExpectedIdentity()` and `HaveAuditIdentity`:
When no identity is configured, `ExpectedIdentity()` returns `""` and audit assertions are skipped. However, if the API enforces identity, tests will fail with `401` before reaching any assertions.
152
+
153
+
For local kind setup, `deploy-scripts/lib/api.sh` automatically deploys the API with `config.server.identity_header=X-HyperFleet-Identity` via helm.
154
+
121
155
## Boundaries
122
156
123
157
### DON'T
@@ -136,3 +170,5 @@ Always use config values: `h.Cfg.Timeouts.Cluster.Reconciled`, `h.Cfg.Timeouts.N
- Adapter names come from `h.Cfg.Adapters.Cluster` and `h.Cfg.Adapters.NodePool` at runtime — never hardcode adapter names. Values in `configs/config.yaml` (e.g., `cl-namespace`) override compiled defaults in `pkg/config/defaults.go` (e.g., `clusters-namespace`)
138
172
-`e2e-ci` Makefile target sets `TESTDATA_DIR` to absolute path and writes JUnit XML to `output/`
173
+
- Identity config is optional — when all identity fields are empty, no headers are injected and audit assertions are skipped. The `identity.value` must be email-formatted (e.g., `user@domain.local`) because the API's `created_by` field is typed as `openapi_types.Email`
174
+
- Identity header name must match the API's `server.identity_header` config — conventional value is `X-HyperFleet-Identity`
0 commit comments