cubecosapi: the agent can read its own cluster - #40
Merged
github-actions[bot] merged 1 commit intoSep 12, 2026
Merged
Conversation
Lab validation on the 1cc R630 found the read catalogue answering "cube-cos-api access is not configured on this agent" for all 21 paths on every cluster. That was two defects: ConfigureCubeCOS had no non-test caller, and nothing in this repository implemented a CubeCOSGetter at all. ADR 0016 slice 4 covers the first; this covers both, because the setting alone would wire nothing. The client authenticates on cube-cos-api's internal node-to-node path, which it checks before the OpenStack and OIDC paths and which is the only one an on-node daemon can satisfy without holding a user's credentials: Node names this host, Authorization carries the token cube-cos-api writes to /var/run/cube-cos-api/node_token at startup. The token is read per request rather than cached, because that file is rewritten whenever the api restarts and a cached value would fail afterwards in a way that looks like misconfiguration and is cured by restarting the agent. The transport is plain HTTP. cube-cos-api serves its management ip on 8082 with no TLS — verified in its own newHttpServer, which passes no certificate, and on a live node where https answers nothing and http answers 200. Talking to the node's own address keeps the token off the wire; the client accepts https so a future api that offers it needs no change here. Three catalogue reads are held back rather than shipped broken. events, events/abstract and events/rank are zero-parameter GETs in the OpenAPI document and 400 on a real cluster, which requires a type query parameter the catalogue cannot express. cube_cos_events shipped that way before the catalogue existed, so this narrows nothing that ever worked; reading a live api is what found it. The remaining 18 return real data. Signed-off-by: Travis Wu <travis.wu@bigstack.co>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What
An authenticated cube-cos-api client, and the setting that wires it — ADR 0016 slice 4 plus the component it configures.
internal/cubecosapiperforms the read;cube-cos-api.jsonin the agent's own directory says where the api is and what to call the cluster. 18 of the 21 catalogue paths now return real data from a live cluster; three that cannot work are held back with the reason.Why
Lab validation on the 1cc R630 found every catalogue path answering
cube-cos-api access is not configured on this agent. Two defects, not one:ConfigureCubeCOShad no non-test caller — the configuration half, which slice 4 covers.CubeCOSGetterat all. ADR 0016 scoped the client out deliberately: "finding 2 is two problems — no configuration surface and no client to configure."Slice 4 alone would have wired nothing, so this does both.
Reviewer notes
Auth: the internal node-to-node path, verified from source and on hardware.
verifyAuthTokenchecks it before the OpenStack and OIDC paths, andisValidInternalTokencompares the bearer token againstnodes.GenToken(<Node header>)=sha512(hostname + oidc client secret). cube-cos-api writes its own value to/var/run/cube-cos-api/node_token(flushDefaultNodeToken, mode 0644). On the R630, a datacenter-scoped read is 401 without those two headers and 200 with them — so this is necessary, not merely sufficient.The token is read per request. That file is rewritten every time the api starts, so a cached value would keep working until the next api restart and then fail in a way that looks like a configuration error and is cured by restarting the agent. Its mode is deliberately not checked: cube-cos-api publishes it 0644 and owns that decision; refusing to read it would disable the agent over something it cannot change. That the token is readable by any local account is cube-cos-api's exposure, not one this introduces — worth someone's attention, but not fixable here.
TLS: there is none, and the note that said otherwise was wrong. cube-cos-api's
newHttpServerpasses no certificate, and the config template has TLS only under the SAML service provider. On the R630 it listens on10.32.1.200:8082,https://answers nothing andhttp://answers 200. Nothing was disabled to make this work — there was nothing to verify. The client acceptshttpsso a future api that offers it needs no change, and refuses any other scheme at construction where an operator sees it in a startup line. Talking to the node's own management address keeps the token off the wire.The setting's mode policy is the instance profile's, not the credential's. Refused if group- or world-writable; readability unconstrained. The file holds no secret — a datacenter name and a URL on the customer's own network — but whoever can write it chooses where the agent sends its node token, and a token posted to an attacker's listener is the whole of the node's api authority. Write access is the escalation.
maxBytes reads
maxBytes+1so truncation is detected rather than inferred from an exactly-full buffer, then returnsErrOutputTruncated— the contract the command runner already follows, sofetchstays the one place that decides what a cut looks like. Verified end to end against a real 42 KB response: capped at 1024 with the marker present.Three reads held back.
events,events/abstractandevents/rankare zero-parameter GETs in the OpenAPI document and return400 'type' can't be null and should be one of 'system', 'host', or 'instance'on a real cluster. The catalogue deliberately cannot express query parameters, so admitting them offers the model three reads that can only ever fail.cube_cos_eventsshipped as a bare GET of the same path before the catalogue existed — this narrows nothing that ever worked, and the vendored-spec conformance check could not have caught it, because the path exists and the method is GET.events/filterConditionsandevents/predefinedneed no parameter and stay admitted.Validated on the 1cc R630, read-only
Nothing was left behind: the probe binary was removed, no
/etc/cube/advisor-agentwas created, andcluster checkreports every groupok.Break and restore
applydroppedStringOne test of my own was wrong before it was right:
TestAnUnknownCubeCOSFieldIsRefuseduseddataCenter, whichencoding/jsonmatches case-insensitively, so the field lands correctly andDisallowUnknownFieldsnever fires. Corrected todatacentreandbaseUrl— genuinely different names — and the tolerated case-variant behaviour is now pinned by its own test rather than left to be discovered.Docs
bigstack-handbook#<n>— the as-built beside slices 1 and 3.