Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
227980a
docs(gateway-access): spec gateway access management
bsquizz Oct 5, 2026
11d5d29
docs(gateway-access): grantable owners with delete; last-owner protec…
bsquizz Oct 6, 2026
6f153bc
docs(gateway-access): address PR review feedback
bsquizz Oct 6, 2026
821ccd7
feat(gateway-access): gateway access management (owner/admin/viewer t…
bsquizz Oct 6, 2026
1ba870d
fix(gateway-access): require gateway ownership to grant gateway:admin
bsquizz Oct 6, 2026
9d00c91
fix(ci): update openapi op count for gateway-access; gofmt controller…
bsquizz Oct 6, 2026
5122d45
Merge origin/main into user_management
bsquizz Oct 7, 2026
4bf3bb1
fix(roles): avoid duplicate migration ID after merge with main
bsquizz Oct 7, 2026
f7dc792
fix(gateway-access): require owner tier to grant over an existing own…
bsquizz Oct 7, 2026
bc1d7e4
Merge origin/main into user_management
bsquizz Oct 7, 2026
cab58f8
fix(e2e): relabel openshell CLI config mount for SELinux hosts
bsquizz Oct 7, 2026
ca122c8
Merge remote-tracking branch 'origin/main' into user_management
bsquizz Oct 7, 2026
b4c6c5a
feat(e2e): gateway access management e2e with Keycloak directory pickup
bsquizz Oct 7, 2026
ec2e019
fix(gateways): gate console delete on per-caller can_delete capability
bsquizz Oct 7, 2026
aaa13a0
fix(gateways): render disabled delete action gray, not danger-red
bsquizz Oct 7, 2026
1222afa
feat(access): platform admins get owner-equivalent gateway access man…
bsquizz Oct 7, 2026
57d6008
test(e2e): platform admin manages access on a gateway it does not own
bsquizz Oct 8, 2026
5e87aec
feat(access): tooltip on disabled last-owner role and remove controls
bsquizz Oct 8, 2026
1f952e1
feat(access): rename access table "User name" column to "Name"
bsquizz Oct 8, 2026
e094746
feat(access): confirm before a user changes their own role
bsquizz Oct 8, 2026
493635f
feat(gateways): gray out "Rename gateway" for users who cannot edit
bsquizz Oct 8, 2026
41e0a9a
fix(access): fast-retry directory projection while empty at startup
bsquizz Oct 8, 2026
0908480
feat(access): replace creator marker with workspace-access command
bsquizz Oct 8, 2026
bd04c80
Merge origin/main into user_management
bsquizz Oct 8, 2026
f5948bb
docs(gateway-access): drop is_creator from specs to match implementation
bsquizz Oct 9, 2026
ab6224e
fix(sdk-generator): emit absolute paths for scoped SDK clients
bsquizz Oct 9, 2026
4d2043d
fix(e2e): send placement intent in access-management gateway creates
bsquizz Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/api-server/cmd/hypershell/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

_ "github.com/openshift-online/hypershell/components/api-server/cmd/hypershell/environments"
_ "github.com/openshift-online/hypershell/components/api-server/plugins/agentRuntimes"
_ "github.com/openshift-online/hypershell/components/api-server/plugins/gatewayAccess"
_ "github.com/openshift-online/hypershell/components/api-server/plugins/gateways"
_ "github.com/openshift-online/hypershell/components/api-server/plugins/inferenceRoutes"
_ "github.com/openshift-online/hypershell/components/api-server/plugins/managedClusters"
Expand Down
317 changes: 317 additions & 0 deletions components/api-server/openapi/openapi.gatewayAccess.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
paths:
/api/hypershell/v1/gateways/{gateway_id}/access:
parameters:
- $ref: '#/components/parameters/gatewayId'
get:
operationId: listGatewayAccess
summary: List access grants on a gateway
parameters:
- $ref: '#/components/parameters/page'
- $ref: '#/components/parameters/size'
- $ref: '#/components/parameters/role'
- $ref: '#/components/parameters/search'
- $ref: '#/components/parameters/sort'
- $ref: '#/components/parameters/order'
responses:
'200':
description: Access grants and caller capabilities
content:
application/json:
schema:
$ref: '#/components/schemas/GatewayAccessList'
'400':
$ref: '#/components/responses/BadRequestError'
'401':
$ref: 'openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '#/components/responses/NotFoundError'
'500':
$ref: 'openapi.yaml#/components/responses/InternalServerError'
post:
operationId: grantGatewayAccess
summary: Grant a user access to a gateway
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/GatewayAccessGrantRequest'
responses:
'201':
description: Access granted
content:
application/json:
schema:
$ref: '#/components/schemas/GatewayAccessGrantResponse'
'400':
$ref: '#/components/responses/BadRequestError'
'401':
$ref: 'openapi.yaml#/components/responses/UnauthorizedError'
'403':
$ref: 'openapi.yaml#/components/responses/ForbiddenError'
'404':
$ref: '#/components/responses/NotFoundError'
'409':
$ref: '#/components/responses/ConflictError'
'503':
$ref: '#/components/responses/ServiceUnavailableError'
/api/hypershell/v1/gateways/{gateway_id}/access/directory:
parameters:
- $ref: '#/components/parameters/gatewayId'
get:
operationId: searchGatewayDirectory
summary: Search the Keycloak realm directory for candidate users
parameters:
- $ref: '#/components/parameters/search'
responses:
'200':
description: Candidate realm users
content:
application/json:
schema:
$ref: '#/components/schemas/GatewayDirectoryUserList'
'401':
$ref: 'openapi.yaml#/components/responses/UnauthorizedError'
'403':
$ref: 'openapi.yaml#/components/responses/ForbiddenError'
'404':
$ref: '#/components/responses/NotFoundError'
'503':
$ref: '#/components/responses/ServiceUnavailableError'
/api/hypershell/v1/gateways/{gateway_id}/access/{user_id}:
parameters:
- $ref: '#/components/parameters/gatewayId'
- $ref: '#/components/parameters/userId'
patch:
operationId: changeGatewayAccessRole
summary: Change a user's access role on a gateway
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/GatewayAccessChangeRoleRequest'
responses:
'200':
description: Role changed
content:
application/json:
schema:
$ref: '#/components/schemas/GatewayAccessGrantResponse'
'400':
$ref: '#/components/responses/BadRequestError'
'401':
$ref: 'openapi.yaml#/components/responses/UnauthorizedError'
'403':
$ref: 'openapi.yaml#/components/responses/ForbiddenError'
'404':
$ref: '#/components/responses/NotFoundError'
'409':
$ref: '#/components/responses/ConflictError'
delete:
operationId: revokeGatewayAccess
summary: Revoke a user's access to a gateway
responses:
'204':
description: Access revoked
'401':
$ref: 'openapi.yaml#/components/responses/UnauthorizedError'
'403':
$ref: 'openapi.yaml#/components/responses/ForbiddenError'
'404':
$ref: '#/components/responses/NotFoundError'
'409':
$ref: '#/components/responses/ConflictError'

components:
parameters:
gatewayId:
name: gateway_id
in: path
required: true
description: Selected Gateway ID
schema:
type: string
userId:
name: user_id
in: path
required: true
description: HyperShell User ID
schema:
type: string
page:
name: page
in: query
required: false
schema:
type: integer
minimum: 1
default: 1
size:
name: size
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 100
default: 20
role:
name: role
in: query
required: false
description: Filter by console role tier
schema:
$ref: '#/components/schemas/GatewayAccessRole'
search:
name: search
in: query
required: false
description: Case-insensitive literal substring over username and name
schema:
type: string
sort:
name: sort
in: query
required: false
schema:
type: string
enum: [username, name, role, granted_at]
default: granted_at
order:
name: order
in: query
required: false
schema:
type: string
enum: [asc, desc]
default: desc

responses:
BadRequestError:
description: Invalid request or query value
content:
application/json:
schema:
$ref: 'openapi.yaml#/components/schemas/Error'
NotFoundError:
description: Gateway or user was not found or is not visible
content:
application/json:
schema:
$ref: 'openapi.yaml#/components/schemas/Error'
ConflictError:
description: The operation would remove the last owner (last-owner protection)
content:
application/json:
schema:
$ref: 'openapi.yaml#/components/schemas/Error'
ServiceUnavailableError:
description: The Keycloak directory projection is unavailable
content:
application/json:
schema:
$ref: 'openapi.yaml#/components/schemas/Error'

schemas:
GatewayAccessRole:
type: string
enum: [owner, admin, user]
GatewayAccessGrantRequest:
type: object
additionalProperties: false
required: [username, role]
properties:
username:
type: string
minLength: 1
subject:
type: string
description: Optional Keycloak subject reference returned by the directory search
role:
$ref: '#/components/schemas/GatewayAccessRole'
GatewayAccessChangeRoleRequest:
type: object
additionalProperties: false
required: [role]
properties:
role:
$ref: '#/components/schemas/GatewayAccessRole'
GatewayAccessListItem:
type: object
required:
- role_binding_id
- user_id
- username
- role
- granted_at
properties:
role_binding_id:
type: string
user_id:
type: string
username:
type: string
name:
type: string
nullable: true
email:
type: string
nullable: true
role:
$ref: '#/components/schemas/GatewayAccessRole'
granted_at:
type: string
format: date-time
GatewayAccessGrantResponse:
allOf:
- $ref: '#/components/schemas/GatewayAccessListItem'
GatewayAccessCapabilities:
type: object
required: [can_manage_access, can_manage_owners]
properties:
caller_role:
$ref: '#/components/schemas/GatewayAccessRole'
can_manage_access:
type: boolean
can_manage_owners:
type: boolean
GatewayAccessList:
type: object
required: [page, size, total, capabilities, items]
properties:
page:
type: integer
size:
type: integer
total:
type: integer
capabilities:
$ref: '#/components/schemas/GatewayAccessCapabilities'
items:
type: array
items:
$ref: '#/components/schemas/GatewayAccessListItem'
GatewayDirectoryUser:
type: object
required: [username]
properties:
username:
type: string
name:
type: string
nullable: true
email:
type: string
nullable: true
subject:
type: string
nullable: true
GatewayDirectoryUserList:
type: object
required: [items]
properties:
items:
type: array
items:
$ref: '#/components/schemas/GatewayDirectoryUser'
8 changes: 8 additions & 0 deletions components/api-server/openapi/openapi.gateways.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,14 @@ components:
format: int64
readOnly: true
description: Generation the control plane last successfully applied; converged when equal to generation
can_delete:
type: boolean
readOnly: true
description: Whether the authenticated caller is authorized to delete this gateway (gateway owner or platform admin). Advertised so clients can disable a delete affordance the API would reject; the server remains authoritative.
can_edit:
type: boolean
readOnly: true
description: Whether the authenticated caller is authorized to modify this gateway (PATCH, e.g. rename) -- gateway owner or admin. Advertised so clients can disable an edit affordance the API would reject; the server remains authoritative.
GatewayCreateRequest:
type: object
required:
Expand Down
24 changes: 24 additions & 0 deletions components/api-server/openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ paths:
$ref: 'openapi.users.yaml#/paths/~1api~1hypershell~1v1~1users'
/api/hypershell/v1/users/{id}:
$ref: 'openapi.users.yaml#/paths/~1api~1hypershell~1v1~1users~1{id}'
/api/hypershell/v1/gateways/{gateway_id}/access:
$ref: 'openapi.gatewayAccess.yaml#/paths/~1api~1hypershell~1v1~1gateways~1{gateway_id}~1access'
/api/hypershell/v1/gateways/{gateway_id}/access/directory:
$ref: 'openapi.gatewayAccess.yaml#/paths/~1api~1hypershell~1v1~1gateways~1{gateway_id}~1access~1directory'
/api/hypershell/v1/gateways/{gateway_id}/access/{user_id}:
$ref: 'openapi.gatewayAccess.yaml#/paths/~1api~1hypershell~1v1~1gateways~1{gateway_id}~1access~1{user_id}'
/api/hypershell/ext/agent_runtimes:
$ref: 'openapi.agentRuntimes.yaml#/paths/~1api~1hypershell~1ext~1agent_runtimes'
/api/hypershell/ext/agent_runtimes/{id}:
Expand Down Expand Up @@ -176,6 +182,24 @@ components:
$ref: 'openapi.users.yaml#/components/schemas/User'
UserList:
$ref: 'openapi.users.yaml#/components/schemas/UserList'
GatewayAccessRole:
$ref: 'openapi.gatewayAccess.yaml#/components/schemas/GatewayAccessRole'
GatewayAccessGrantRequest:
$ref: 'openapi.gatewayAccess.yaml#/components/schemas/GatewayAccessGrantRequest'
GatewayAccessChangeRoleRequest:
$ref: 'openapi.gatewayAccess.yaml#/components/schemas/GatewayAccessChangeRoleRequest'
GatewayAccessListItem:
$ref: 'openapi.gatewayAccess.yaml#/components/schemas/GatewayAccessListItem'
GatewayAccessGrantResponse:
$ref: 'openapi.gatewayAccess.yaml#/components/schemas/GatewayAccessGrantResponse'
GatewayAccessCapabilities:
$ref: 'openapi.gatewayAccess.yaml#/components/schemas/GatewayAccessCapabilities'
GatewayAccessList:
$ref: 'openapi.gatewayAccess.yaml#/components/schemas/GatewayAccessList'
GatewayDirectoryUser:
$ref: 'openapi.gatewayAccess.yaml#/components/schemas/GatewayDirectoryUser'
GatewayDirectoryUserList:
$ref: 'openapi.gatewayAccess.yaml#/components/schemas/GatewayDirectoryUserList'
AgentRuntime:
$ref: 'openapi.agentRuntimes.yaml#/components/schemas/AgentRuntime'
AgentRuntimeList:
Expand Down
Loading
Loading