feat: point gitlab-cli at a self-hosted host via GITLAB_HOST - #35
feat: point gitlab-cli at a self-hosted host via GITLAB_HOST#35sgraband wants to merge 1 commit into
Conversation
Reaching a self-hosted GitLab needed two manual steps: declare GITLAB_HOST so it reached the container, then edit the mixin spec to put the host in serviceAuth.hosts. Both are now unnecessary. serviceAuth entries gain hostsFromCredential, naming a credential whose resolved value is a host. When it resolves, its value replaces that service's declared hosts. Replacement rather than addition is deliberate: an instance-specific token must not be released to gitlab.com as well. The override is resolved during auth setup, before the effective policy is resolved and cached, so the host flows into the allow set through the existing ReleaseHosts union and stays reachable. gitlab-cli declares GITLAB_HOST as a non-apiKey credential and wires all three tokens to it. Closes eclipse-enclave#4
EclipseSourceAI
left a comment
There was a problem hiding this comment.
Note
Autonomous AI review. Generated automatically, so it may contain mistakes. Feel free to ignore any comment you disagree with (noting why helps future reviews).
This review does not approve the change. A human still needs to review it and sign off on the overall architecture and design.
To get an updated review after pushing changes, re-request a review from this account.
Submitted via review-guard-mcp
Adds network.serviceAuth.<id>.hostsFromCredential so a service's HTTP-release hosts can be selected at runtime from another declared credential, and wires gitlab-cli's three tokens to a new gitlab-host (GITLAB_HOST) credential. The plumbing fits the existing shape well: spec validation sits next to the existing serviceAuth cross-reference check, the runtime reuses resolveActiveSecretValue and domainpattern, and releaseHosts clones the secret map so the loaded spec is not mutated. Build, tests, make lint and make generate are all clean.
Two areas deserve a human look. First, replacement narrows the DNS allow set as well as the token release targets, so for tools whose built-in allowlist lacks the gitlab fragment (everything except claude) setting GITLAB_HOST makes gitlab.com unreachable for the whole session, which the stated rationale does not require. Second, the override lands in a mutable Runtime field whose correctness depends on running before the memoized effective-policy resolution, enforced only by a comment.
The rest are smaller: an unredacted credential value in a warning, a wrong per-project secrets path in the feature README, network apply recomputing the bundle without the override, and the host sticking via the persisted env store. Details inline.
| host, err = domainpattern.Normalize(host) | ||
| } | ||
| if err != nil { | ||
| logx.Warnf("Secret %s: value %q is not a usable host (%v); keeping declared hosts.", secretID, value, err) |
There was a problem hiding this comment.
This prints the credential value verbatim at warn level. If hostsFromCredential ever points at a token (or GITLAB_HOST gets a token value by mistake), the secret lands in the terminal. Everywhere else secret-derived values go through util.RedactSecret, and at debug level:
enclave/internal/runtime/auth_manager.go
Line 208 in 5e80820
| // serviceAuth.hostsFromCredential replacement applied, so a host selected at | ||
| // runtime becomes resolvable exactly like a declared one. The loaded spec is | ||
| // left untouched. | ||
| func (r *Runtime) releaseHosts(secrets map[string]model.SecretConfig) []string { |
There was a problem hiding this comment.
The replacement narrows the DNS allow set too, not just the token release targets. Only claude's built-in allowlist pulls in the gitlab fragment (
codex/opencode/theia the feature's release hosts are the only source of gitlab.com and setting GITLAB_HOST makes gitlab.com unresolvable for the whole session. The rationale in the PR description only needs the release hosts narrowed, so releaseHosts could keep the declared hosts while releaseHostsFor uses only the override.
| } | ||
| // Must precede shouldUseSecretReleases: that call resolves and caches the | ||
| // effective policy, which unions the release hosts into the allow set. | ||
| m.releaseHostOverrides = resolveReleaseHostOverrides(eligibleSecrets, m.host.Home, layeredSecrets, persistedEnv) |
There was a problem hiding this comment.
The invariant is enforced only by this comment, and resolveEffectivePolicy memoizes on the Runtime (
enclave/internal/runtime/runtime.go
Lines 1507 to 1511 in 5e80820
prepareMounts (which runs before prepareVolumes) would silently cache an allow set without the override while the token still targets it. Either reset policyResolved here when overrides are non-empty, or fail loudly if the policy is already resolved.
|
|
||
| To set it persistently, put it in a secrets file rather than the shell — global | ||
| in `~/.local/state/enclave/secrets/global.env`, or per project under | ||
| `~/.local/state/enclave/projects/<hash>/`. |
There was a problem hiding this comment.
Wrong path. Per-project secrets live at ~/.local/state/enclave/secrets/projects/<hash>/<tool>.env (
Lines 136 to 138 in 5e80820
claude.env), not by the feature, which is worth spelling out since this is a mixin.
| ``` | ||
|
|
||
| That single env var selects the instance for `glab`, retargets token injection, | ||
| and joins the network allowlist — no spec edit or `allow_domains` entry. See |
There was a problem hiding this comment.
True at session start, but enclave network apply recomputes the desired bundle from the tool profile only, with no features and no runtime overrides (
enclave/internal/policy/effective_resolver.go
Lines 134 to 143 in 5e80820
enclave/internal/app/network_cmd.go
Lines 291 to 295 in 5e80820
network status reports drift and an apply cuts the self-hosted host out of a running session. The gap already exists for gitlab.com, but a follow-up issue would be good since the override makes it the only host the token can reach.
| - `network.serviceAuth.<service-id>.hostsFromCredential` names another | ||
| `credentials.sources` id whose resolved value is a host, for services that | ||
| can point at a self-hosted instance. When that credential resolves, its value | ||
| **replaces** the declared `hosts` for this service — it is not additive, |
There was a problem hiding this comment.
It replaces the union of hosts and the serviceDomains-derived hosts, not just the declared hosts: both go into the same list in buildSecrets (
enclave/internal/config/spec_map.go
Lines 115 to 124 in 5e80820
github-cli that express hosts via serviceDomains.
| gitlab-job-token: { env: [JOB_TOKEN, CI_JOB_TOKEN] } | ||
| # Not a credential: glab reads GITLAB_HOST to pick the instance, and | ||
| # hostsFromCredential below retargets token injection at it. | ||
| gitlab-host: { env: [GITLAB_HOST], apiKey: false } |
There was a problem hiding this comment.
Because this is a declared credential, the resolved GITLAB_HOST is written to the persisted env store under the default --persist, so later runs without the env var keep the self-hosted override (and keep gitlab.com out of the release hosts). That contradicts "An unset credential keeps the declared hosts" in the docs, so either document how to clear it or skip persistence for non-apiKey host credentials.
| continue | ||
| } | ||
| for _, id := range secretIDs { | ||
| overrides[id] = []string{host} |
There was a problem hiding this comment.
A successful override is completely silent, and the injection debug log redacts the value, so there is no way to see which host was picked. An info line here would help, similar to how ad-hoc allowlisted domains are reported:
| // otherwise become a bogus allowlist entry. | ||
| host, err := domainpattern.NormalizeHost(trimmed) | ||
| if err == nil { | ||
| host, err = domainpattern.Normalize(host) |
There was a problem hiding this comment.
domainpattern.Normalize accepts wildcard patterns, so GITLAB_HOST=*.example.com becomes a wildcard release target and allowlist entry. That works against the "names the one instance the token belongs to" rationale, so rejecting * here seems right.
What it does
Reaching a self-hosted GitLab needed two manual steps: declare GITLAB_HOST so it reached the container, then edit the mixin spec to put the host in serviceAuth.hosts. Both are now unnecessary.
serviceAuth entries gain hostsFromCredential, naming a credential whose resolved value is a host. When it resolves, its value replaces that service's declared hosts. Replacement rather than addition is deliberate: an instance-specific token must not be released to gitlab.com as well.
The override is resolved during auth setup, before the effective policy is resolved and cached, so the host flows into the allow set through the existing ReleaseHosts union and stays reachable. gitlab-cli declares GITLAB_HOST as a non-apiKey credential and wires all three tokens to it.
Closes #4
How to test
Follow-ups
Breaking changes
Review checklist