Skip to content

Commit c8d8473

Browse files
committed
Merge remote-tracking branch 'origin/main' into alona/secret-restart-checksum
# Conflicts: # charts/openhands/Chart.yaml
2 parents 3b1ed1e + 22a0204 commit c8d8473

11 files changed

Lines changed: 209 additions & 13 deletions

File tree

charts/openhands-secrets/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ apiVersion: v2
22
name: openhands-secrets
33
description: A Helm chart for OpenHands secrets
44
type: application
5-
version: 0.1.16
5+
version: 0.1.18
66
appVersion: "1.0"

charts/openhands-secrets/templates/bitbucket-data-center-app.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{{- if or .Values.config.bitbucket_data_center_domain .Values.config.bitbucket_data_center_client_id .Values.config.bitbucket_data_center_client_secret }}
1+
{{- if or .Values.config.bitbucket_data_center_domain .Values.config.bitbucket_data_center_client_id .Values.config.bitbucket_data_center_client_secret .Values.config.bitbucket_data_center_bot_token }}
22
apiVersion: v1
33
kind: Secret
44
metadata:
@@ -15,4 +15,7 @@ data:
1515
{{- if .Values.config.bitbucket_data_center_client_secret }}
1616
client-secret: {{ .Values.config.bitbucket_data_center_client_secret | b64enc | quote }}
1717
{{- end }}
18+
{{- if .Values.config.bitbucket_data_center_bot_token }}
19+
bot-token: {{ .Values.config.bitbucket_data_center_bot_token | b64enc | quote }}
20+
{{- end }}
1821
{{- end }}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{{- $server := .Values.config.custom_sandbox_image_registry_server | default "" | trim }}
2+
{{- $user := .Values.config.custom_sandbox_image_registry_username | default "" | trim }}
3+
{{- $pass := .Values.config.custom_sandbox_image_registry_password | default "" | trim }}
4+
{{- if and $server $user $pass }}
5+
{{- $auth := printf "%s:%s" $user $pass | b64enc }}
6+
{{- $entry := dict "username" $user "password" $pass "auth" $auth }}
7+
{{- $cfg := dict "auths" (dict $server $entry) }}
8+
apiVersion: v1
9+
kind: Secret
10+
metadata:
11+
name: sandbox-image-pull-secret
12+
namespace: {{ .Release.Namespace }}
13+
type: kubernetes.io/dockerconfigjson
14+
data:
15+
.dockerconfigjson: {{ $cfg | toJson | b64enc | quote }}
16+
{{- end }}

charts/openhands-secrets/values.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ config:
7373
slack_client_secret: ""
7474
slack_signing_secret: ""
7575

76+
# Custom sandbox image registry credentials
77+
custom_sandbox_image_registry_server: ""
78+
custom_sandbox_image_registry_username: ""
79+
custom_sandbox_image_registry_password: ""
80+
7681
# Plugin Directory secrets
7782
plugin_directory_identity_shared_secret: ""
7883
plugin_directory_session_secret: ""

charts/openhands/templates/_env.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,12 @@
206206
secretKeyRef:
207207
name: bitbucket-data-center-app
208208
key: client-secret
209+
- name: BITBUCKET_DATA_CENTER_BOT_TOKEN
210+
valueFrom:
211+
secretKeyRef:
212+
name: bitbucket-data-center-app
213+
key: bot-token
214+
optional: true
209215
{{- end }}
210216
{{- if and (index .Values "litellm-helm") (index .Values "litellm-helm" "enabled") }}
211217
- name: LITE_LLM_API_URL

docs/assets/github-oauth-flow.mmd

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
%% Validated against OpenHands-Cloud @ 136f63fdc
2+
3+
sequenceDiagram
4+
autonumber
5+
participant Browser
6+
participant OpenHands as OpenHands<br/>(Enterprise Server)
7+
participant DB as SecretsStore
8+
participant Keycloak as Keycloak<br/>(Identity Provider / OAuth Broker)
9+
participant GitHub as GitHub<br/>(GitHub App, OAuth 2.0)
10+
11+
Note over Browser,GitHub: This deployment uses a GitHub APP (not a classic OAuth App).<br/>Evidence: GitHub App client_id has the Iv__ prefix and<br/>the returned user token is prefixed ghu_ (App user-to-server).<br/>GitHub Apps grant capabilities via App-level permissions,<br/>so OAuth scope strings are largely ignored.<br/>User tokens expire (8 h) and the App returns a refresh token<br/>that OpenHands rotates via grant_type=refresh_token.
12+
13+
Note over Browser,Keycloak: OIDC. SPA initiates login client-side.
14+
Note over Browser: SPA at /login builds URL via<br/>generate-auth-url.ts and assigns<br/>window.location.href
15+
16+
Browser->>Keycloak: GET /realms/allhands/protocol/openid-connect/auth<br/>?client_id=allhands<br/>&response_type=code<br/>&kc_idp_hint=github<br/>&redirect_uri=.../oauth/keycloak/callback<br/>&scope=openid+email+profile<br/>&state=base64(redirect_url)
17+
18+
Note over Keycloak: kc_idp_hint=github skips Keycloak login screen.<br/>Keycloak does NOT forward the SPA OIDC scopes.<br/>It sends its own configured defaultScope to GitHub.
19+
20+
Keycloak-->>Browser: 303 See Other<br/>/realms/allhands/broker/github/login?session_code=...
21+
22+
Browser->>Keycloak: GET /realms/allhands/broker/github/login
23+
Keycloak-->>Browser: 303 See Other to github.com
24+
25+
Note over Browser,GitHub: OAuth 2.0 (NOT OIDC). Keycloak<br/>built-in providerId=github broker.
26+
Browser->>GitHub: GET /login/oauth/authorize<br/>?client_id=GITHUB_APP_CLIENT_ID -- Iv__ prefix marks GitHub App<br/>&redirect_uri=.../realms/allhands/broker/github/endpoint<br/>&response_type=code<br/>&scope=openid+email+profile -- OIDC strings, not GitHub OAuth scopes<br/>&state=keycloak-state
27+
28+
Note over GitHub: Scope strings openid/email/profile are<br/>NOT GitHub native scopes (user, user:email, ...).<br/>GitHub Apps ignore them. App permissions are<br/>configured on github.com and apply uniformly.<br/>User authorizes the App (first time) or is auto-redirected.
29+
30+
GitHub-->>Browser: 302 to Keycloak broker endpoint
31+
Browser->>Keycloak: GET /realms/allhands/broker/github/endpoint<br/>?code=github-auth-code<br/>&state=keycloak-state
32+
33+
Note over Keycloak,GitHub: Server-to-server (no browser).
34+
Keycloak->>GitHub: POST /login/oauth/access_token<br/>client_id, client_secret, code, redirect_uri
35+
GitHub-->>Keycloak: access_token=ghu_...<br/>refresh_token=ghr_...<br/>expires_in, refresh_token_expires_in
36+
37+
Keycloak->>GitHub: GET https://api.github.com/user<br/>Authorization: Bearer ghu_...
38+
GitHub-->>Keycloak: id, login, email, ...
39+
40+
Note over Keycloak: Maps github id to user attribute github_id<br/>via github-user-attribute-mapper.<br/>Hardcodes identity_provider=github.<br/>Persists tokens because storeToken=true.
41+
42+
Keycloak-->>Browser: 302 to OpenHands
43+
Browser->>OpenHands: GET /oauth/keycloak/callback<br/>?code=kc-code.session.client<br/>&session_state=...&iss=...
44+
45+
Note over OpenHands,Keycloak: Server-to-server OIDC.
46+
OpenHands->>Keycloak: POST /realms/allhands/protocol/openid-connect/token<br/>grant_type=authorization_code, code,<br/>client_id=allhands, client_secret
47+
Keycloak-->>OpenHands: access_token (JWT), id_token (JWT), refresh_token<br/>NO offline_access on this leg
48+
49+
OpenHands->>Keycloak: GET /realms/allhands/protocol/openid-connect/userinfo<br/>Authorization: Bearer kc_access_token
50+
Keycloak-->>OpenHands: sub, email, preferred_username, github_id, identity_provider
51+
52+
Note over OpenHands: Logs (verbatim):<br/>Logging in user UUID in org UUID<br/>user_logged_in idp=github idp_type=oidc
53+
54+
Note over OpenHands,Keycloak: Fetch broker-stored GitHub token.
55+
OpenHands->>Keycloak: GET /realms/allhands/broker/github/token<br/>Authorization: Bearer kc_access_token
56+
Keycloak-->>OpenHands: access_token=ghu_...<br/>refresh_token=ghr_...<br/>expires_at, refresh_expires_at
57+
OpenHands->>DB: Encrypt and persist tokens (AuthTokenStore)
58+
59+
Note over OpenHands: server/routes/auth.py branches on two<br/>independent flags - valid_offline_token and<br/>has_accepted_tos. First login fails both.<br/>Returning users typically pass both.
60+
61+
alt has_accepted_tos == false (first login)
62+
OpenHands-->>Browser: 302 to /accept-tos<br/>?redirect_url=(offline_auth_url_or_app)
63+
Note over Browser: SPA renders TOS. User clicks accept.
64+
Browser->>OpenHands: POST /api/accept_tos
65+
OpenHands-->>Browser: 200 OK, body has redirect_url<br/>(NOT a 302)
66+
Note over Browser: SPA reads redirect_url and assigns<br/>window.location.href = redirect_url
67+
end
68+
69+
alt valid_offline_token == false (first login, or offline session expired)
70+
Note over Browser,Keycloak: Second OIDC flow. Captures offline (refresh) token.<br/>Keycloak SSO session is live, so no GitHub round-trip.
71+
Browser->>Keycloak: GET /realms/allhands/protocol/openid-connect/auth<br/>scope=openid+email+profile+offline_access<br/>redirect_uri=.../oauth/keycloak/offline/callback
72+
Keycloak-->>Browser: 302 with new authorization code
73+
Browser->>OpenHands: GET /oauth/keycloak/offline/callback?code=...
74+
OpenHands->>Keycloak: POST /protocol/openid-connect/token
75+
Keycloak-->>OpenHands: access_token + LONG-LIVED refresh_token (offline)
76+
end
77+
78+
OpenHands-->>Browser: Set session cookies, redirect to app<br/>(returning users land here directly with no TOS/offline detour)
79+
80+
Note over Browser,GitHub: Subsequent calls. GitHub token from local store.
81+
Browser->>OpenHands: GET /api/v1/git/installations/search?provider=github
82+
OpenHands->>DB: load_tokens(github)
83+
84+
alt access_token near expiry (less than 15 min left) and refresh_token still valid
85+
Note over OpenHands,GitHub: token_manager._refresh_github_token<br/>(gated by AuthTokenStore._is_token_expired<br/>with ACCESS_TOKEN_EXPIRY_BUFFER = 900s)
86+
OpenHands->>GitHub: POST https://github.com/login/oauth/access_token<br/>client_id, client_secret, refresh_token,<br/>grant_type=refresh_token
87+
GitHub-->>OpenHands: new access_token + new refresh_token<br/>expires_in, refresh_token_expires_in
88+
OpenHands->>DB: re-encrypt and persist
89+
else access_token still valid
90+
Note over OpenHands: Use stored ghu_... as-is
91+
end
92+
93+
OpenHands->>GitHub: GET https://api.github.com/...<br/>Authorization: Bearer ghu_...
94+
GitHub-->>OpenHands: Repos / installations / etc.
95+
OpenHands-->>Browser: JSON response

docs/assets/github-oauth-flow.png

212 KB
Loading

docs/build-diagrams.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ which d2 || (echo "d2 command not found, see: https://github.com/terrastruct/d2"
99
export D2_LAYOUT=elk
1010

1111
d2 assets/fig1.d2 assets/fig1.svg
12+
13+
which mmdc || (echo "mmdc command not found, install via: npm install -g @mermaid-js/mermaid-cli" ; exit 1)
14+
15+
mmdc -i assets/github-oauth-flow.mmd -o assets/github-oauth-flow.png

replicated/config.yaml

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -417,27 +417,39 @@ spec:
417417
items:
418418
- name: bitbucket_data_center_auth_enabled
419419
title: Enable Bitbucket Data Center Authentication
420-
help_text: Enable Bitbucket Data Center OAuth for user authentication
420+
help_text: Let users sign in with their Bitbucket Data Center account.
421421
type: bool
422422
default: "0"
423423
- name: bitbucket_data_center_domain
424424
title: Bitbucket Data Center Domain
425-
help_text: Domain of your Bitbucket Data Center instance
425+
help_text: >-
426+
Hostname of your Bitbucket Data Center instance (e.g. bbdc.example.com)
426427
type: text
427428
when: 'repl{{ ConfigOptionEquals "bitbucket_data_center_auth_enabled" "1" }}'
428429
required: true
429430
- name: bitbucket_data_center_client_id
430431
title: Bitbucket Data Center Client ID
431-
help_text: Client ID from your Bitbucket Data Center OAuth App
432+
help_text: >-
433+
Client ID of the OAuth 2.0 Application Link created in Bitbucket
434+
Data Center (Administration → Application Links).
432435
type: text
433436
when: 'repl{{ ConfigOptionEquals "bitbucket_data_center_auth_enabled" "1" }}'
434437
required: true
435438
- name: bitbucket_data_center_client_secret
436439
title: Bitbucket Data Center Client Secret
437-
help_text: Client Secret from your Bitbucket Data Center OAuth App
440+
help_text: Client Secret for that Application Link.
438441
type: password
439442
when: 'repl{{ ConfigOptionEquals "bitbucket_data_center_auth_enabled" "1" }}'
440443
required: true
444+
- name: bitbucket_data_center_bot_token
445+
title: Bitbucket Data Center Bot Token (optional)
446+
help_text: >-
447+
HTTP access token for a dedicated bot user (e.g. openhands-bot)
448+
with repo access. When set, OpenHands posts comments and reactions
449+
as the bot instead of the mentioning user or installer; jobs still
450+
run on the invoking user's workspace.
451+
type: password
452+
when: 'repl{{ ConfigOptionEquals "bitbucket_data_center_auth_enabled" "1" }}'
441453

442454
- name: github_authentication
443455
title: GitHub Authentication
@@ -705,6 +717,53 @@ spec:
705717
regex:
706718
pattern: ^(0|[1-9][0-9]*)$
707719
message: Must be a non-negative integer
720+
- name: custom_sandbox_image_enabled
721+
title: Use a Custom Sandbox Image
722+
help_text: Use this if you bundle a custom agent-server image.
723+
type: bool
724+
default: "0"
725+
- name: custom_sandbox_image_repository
726+
title: Sandbox Image Repository
727+
help_text: 'Full repository path with no tag, e.g. my-registry.example.com/openhands/agent-server'
728+
type: text
729+
when: 'repl{{ ConfigOptionEquals "custom_sandbox_image_enabled" "1" }}'
730+
required: true
731+
- name: custom_sandbox_image_tag
732+
title: Sandbox Image Tag
733+
help_text: Image tag, e.g. 1.21.1-python
734+
type: text
735+
default: "1.21.1-python"
736+
when: 'repl{{ ConfigOptionEquals "custom_sandbox_image_enabled" "1" }}'
737+
required: true
738+
- name: custom_sandbox_image_registry_server
739+
title: Registry Server
740+
help_text: |
741+
Host (and optional port) of the registry, e.g. my-registry.example.com.
742+
Leave blank if the registry is public, or if your cluster authenticates
743+
via a credential provider (EKS IRSA, GKE Workload Identity, AKS managed
744+
identity).
745+
type: text
746+
when: 'repl{{ ConfigOptionEquals "custom_sandbox_image_enabled" "1" }}'
747+
required: false
748+
- name: custom_sandbox_image_registry_username
749+
title: Registry Username
750+
help_text: |
751+
Registry username. Some registries use sentinel values: _json_key
752+
for GCP Artifact Registry, AWS for ECR static creds, $oauthtoken
753+
for Quay robot accounts.
754+
type: text
755+
when: 'repl{{ ConfigOptionEquals "custom_sandbox_image_enabled" "1" }}'
756+
required: false
757+
- name: custom_sandbox_image_registry_password
758+
title: Registry Password or Credentials
759+
help_text: |
760+
Password, PAT, or credentials string. This field is single-line.
761+
For GCP Artifact Registry, minify the service-account JSON first
762+
(e.g. `jq -c . key.json`) and paste the one-line result, with
763+
username set to _json_key.
764+
type: password
765+
when: 'repl{{ ConfigOptionEquals "custom_sandbox_image_enabled" "1" }}'
766+
required: false
708767

709768
- name: proxy_configuration
710769
title: Proxy Configuration

0 commit comments

Comments
 (0)