fix(server): keep service accounts out of the namespace payload - #7129
Conversation
|
/review |
d03d5b9 to
6d8aff7
Compare
|
/review |
1 similar comment
|
/review |
|
Claude finished @gustavosbarreto's task in 12m 18s —— View job Code Review CompleteReviewed 7 files. 3 inline issues posted. Standards: 0 violations, 1 judgement call. Spec: 1. Correctness: 1. StandardsNo rule violations. One judgement call: the projection in Correctness
SpecThe issue's requirement is met: all six namespace-returning handlers project through One finding, scope creep: Additional notesNo findings were dropped by a cap. Five further candidates were discarded in validation, the notable ones being: a suggestion to also gate on Cross-repo check came back clean otherwise: cloud reuses the core handlers rather than returning To request another review round, comment |
6d8aff7 to
1f56352
Compare
|
/review |
|
Claude finished @gustavosbarreto's task in 12m 34s —— View job Code Review CompleteReviewed 6 files. 2 inline issues posted. Standards: 0 violations, 2 judgement calls. Spec: 0. Correctness: 0. StandardsNo rule violations. Two judgement calls, both on the tests added to close the last round's drift point: the narrative comment at SpecEvery requirement in #7124 is met. Option 2 was taken as written — "Drop service accounts from the members array when the namespace is serialized, leaving the store untouched" — Additional notesNo findings were dropped by a cap. Both of the last round's remaining points were checked and not re-raised: the admin-route consequence you filed as shellhub-io/team#247, and the member-drift gap, which Four further candidates were discarded in validation. The two worth naming: the new member Cross-repo check is clean beyond team#247: To request another review round, comment |
1f56352 to
d2e49db
Compare
|
/review |
|
Claude finished @gustavosbarreto's task in 9m 40s —— View job Code Review CompleteReviewed 6 files on all three axes: Standards (repo rules, language conventions, tests, code smells), Spec (does it implement the issue?) and Correctness (security and runtime defects). No new issues found. The code looks good as-is. Notes on this roundSeven candidates were raised and all seven were dropped — two as repeats of threads you already answered (the shape-vs-behaviour test at
Spec coverage came back complete: all six namespace-returning handlers in One limitation worth stating plainly: the review was static. This container has no Docker daemon and no reachable Go toolchain, so --- • Branch: |
d2e49db to
c73f971
Compare
|
/review |
c73f971 to
6384180
Compare
|
/review |
|
Claude finished @gustavosbarreto's task in 17m 17s —— View job Code Review CompleteReviewed 6 files. 3 inline issues posted. Standards: 0 violations, 2 judgement calls. Spec: 0. Correctness: 1. StandardsNo rule violations. Two judgement calls, both in Correctness
SpecEvery requirement in #7124 is met. Option 2 was taken as written — the store is untouched ( Additional notesNo findings were dropped by a cap. Three candidates were discarded in validation or below the bar:
Cross-repo check is clean beyond the console finding above: To request another review round, comment |
A service account is a non-human principal that exists for SSH, and its membership is only how authorization finds it. Listing it as a member also broke the contract: it carries the role "service", which the member-role enum does not have, so every namespace response holding one failed schema validation. The filter is a response type rather than a store or service change. The store feeds Authorize through FindMember and would drop SSH for every service account, and the SSH session calls GetNamespace in process. Projecting explicitly, the way CreateAPIKeyFromModel already does, also avoids an embedded pointer, which encoding/json skips in silence when nil. The projection descends into the member, which drops its principal type from the response. Once the list is people only, that field has a single possible value and tells a consumer nothing; a caller that wants the service accounts asks GET /api/service-accounts. Nothing here guards the response against the schema. Response validation cannot, because the schema sets no additionalProperties, so a property the server sends and the schema omits passes. A test per response type is the wrong shape for that job, with 203 operations to cover; #7132 tracks turning the whole document strict at load time instead. The member type is still the discriminator for the filter, not the role: the role is a fixed stamp written at creation, so an empty type counts as a person. NamespaceGetMembers already applies the same rule in SQL for the members list. Fixes: #7124
6384180 to
5bb0eec
Compare
|
/review |
Code Review CompleteThe automated review ran but did not post an updated summary — this usually means no new issues were found since the previous review. If you've pushed changes and want a fresh pass, comment |
What
The namespace routes now return only human members, and the member object they return
carries no principal type. A service account is a non-human principal that exists for SSH
and is listed by
GET /api/service-accounts; its membership is only how authorizationfinds it.
Why
Listing a service account as a member broke the contract. It carries the role
service,which the member-role enum does not have, so every namespace response holding one failed
schema validation. The dev log carries both sides of the change on the same route:
Once the list is people only, the member's
typehas a single possible value, so it tellsa consumer nothing and comes out of the response.
Closes #7124
Changes
pkg/api/responses/namespace.go: a response type that projects the stored namespaceexplicitly, the way
CreateAPIKeyFromModelalready does, and descends into the member.An embedded
*models.Namespacewould do the opposite, leaking every new model field onits own, and
encoding/jsonskips a nil embedded pointer in silence, so a nil namespacewould serialize as
{"members":[]}with a 200.Membersis built withmake, nevernil, because the schema declares a non-nullable array.
server/api/routes/nsadm.go: the six responses that carry a namespace. The filterbelongs in the transport layer: the store feeds
AuthorizethroughFindMemberandfiltering there would drop SSH for every service account, and the SSH session calls
GetNamespacein process.openapi/spec/components/schemas/namespace.yaml:membersnow says it holds people.services/namespace_test.goand theAuthorizetable inservices/access-policy_test.gowere left alone on purpose. They still expect a serviceaccount in the member list, which is what proves the SSH path was not touched.
TestNamespaceMemberSchemaMatchesTheResponsepins the projected member against themember object the schema publishes, which response validation cannot do: the schema sets
no
additionalProperties, so a field added to either side passes.The predicate is
Member.Type, not the role. The role a service account carries is a fixedstamp written at creation, so an empty type counts as a person.
NamespaceGetMembersandServiceAccountListalready discriminate by type the same way.Testing
Point a namespace that holds a service account at the running dev stack and read the server
log, not just the payload: the response validator is what caught this, and a regression
shows up there first.
The failure mode worth probing is the opposite direction. A namespace whose only member is
a service account must serialize
"members": [], nevernull, since the schema declares anon-nullable array.
TestNamespaceFromModelNeverMarshalsMembersAsNullpins that, anddropping the
makein the projection is enough to break it.Follow-ups
GetNamespaceand has noservice-account listing to fall back on, so it needs one. Keeping them in that response
is not an option, since
admin@api@namespaces@{tenant}.yamlreferences this same schema.devices_removed_count, found while editing this schema and split out of here.