Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 19 additions & 1 deletion deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ PORT=3000 HOST=0.0.0.0 node ui/server.js
| `UC_TOKEN_URL` | no | provider's OAuth token endpoint, e.g. `https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token` |
| `UC_CLIENT_ID` | no | client id of the application registered for the UI login; must also be listed in `UC_AUDIENCES` |
| `UC_CLIENT_SECRET` | no | that application's client secret — stays on the server, never sent to the browser |
| `UC_EXTERNAL_URL` | no | browser-facing base URL of UC (`https://uc.example.com`) when a proxy in front does not send `X-Forwarded-Proto`/`X-Forwarded-Host`; blank = derived from the request |
| `UC_EXTERNAL_URL` | no | browser-facing base URL of UC, `scheme://host[:port]` plus the path prefix when a gateway serves UC under one (`https://uc.example.com`, or `https://api.example.com/unitycatalog`). Needed when a proxy in front does not send `X-Forwarded-Proto`/`X-Forwarded-Host`, and whenever a prefix is stripped before the request reaches UC; blank = derived from the request. See [Behind a path prefix](#behind-a-path-prefix) |
| `UC_ADMIN_PASSWORD` | no | password of the built-in `admin` for the UI's administrator sign-in at `<ui>/login/admin`. Blank = that entry point off. See [UI sign-in](#ui-sign-in) |
| `ALIYUN_REGION` | **yes** | e.g. `cn-hangzhou` |
| `ALIYUN_ACCESS_KEY` | **yes** | master RAM user AK (used for STS `AssumeRole`) |
Expand Down Expand Up @@ -372,6 +372,24 @@ Multi-tenant (`common` / `organizations`) endpoints advertise the literal issuer
The UI proxy forwards `X-Forwarded-Proto`/`X-Forwarded-Host` so UC can build the callback URL the
browser actually uses; set `UC_EXTERNAL_URL` if another proxy in front strips them.

##### Behind a path prefix

A gateway may serve UC under a prefix and strip it before forwarding: the browser is on
`https://api.example.com/unitycatalog/...` while the server receives `/...`. Nothing in the request
tells the server about the prefix, yet two things it hands the browser must carry it — the
`redirect_uri` sent to the identity provider (the registered one carries the prefix) and the path
the OAuth state cookie is scoped to (a cookie scoped to the unprefixed path is never sent back to
`/unitycatalog/.../callback`, and the sign-in fails with `Missing login state` after Microsoft has
already authenticated the person). Put the prefix in `UC_EXTERNAL_URL`:

```bash
UC_EXTERNAL_URL=https://api.example.com/unitycatalog
```

and register `https://api.example.com/unitycatalog/api/1.0/unity-control/auth/callback` in Entra.
`deploy-uc.sh` accepts the path; it still rejects a trailing slash, a query or a fragment. Deployments
at the root of their host are unaffected by leaving the path out.

#### 3. Provisioning users

There is no just-in-time provisioning: every employee who should be able to sign in must exist in UC
Expand Down
17 changes: 16 additions & 1 deletion deploy/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ PORT=3000 HOST=0.0.0.0 node ui/server.js
| `UC_TOKEN_URL` | 否 | IdP 的 OAuth 令牌端点,如 `https://login.microsoftonline.com/<租户id>/oauth2/v2.0/token` |
| `UC_CLIENT_ID` | 否 | 为 UI 登录注册的应用的 client id;`deploy-uc.sh` 会自动把它追加进 `UC_AUDIENCES` |
| `UC_CLIENT_SECRET` | 否 | 该应用的 client secret,**只留在服务端**,不下发浏览器。有有效期,到期要轮换 |
| `UC_EXTERNAL_URL` | 否 | 浏览器访问 UC 的基地址(如 `https://uc.example.com`),当前置代理不传 `X-Forwarded-Proto`/`X-Forwarded-Host` 时需要;留空则按请求推导 |
| `UC_EXTERNAL_URL` | 否 | 浏览器访问 UC 的基地址,`scheme://host[:port]`,网关在前缀下提供 UC 时再加上该前缀(如 `https://uc.example.com`,或 `https://api.example.com/unitycatalog`)。前置代理不传 `X-Forwarded-Proto`/`X-Forwarded-Host` 时需要,前缀在到达 UC 前被剥掉时也需要;留空则按请求推导。见[网关加路径前缀](#网关加路径前缀) |
| `UC_ADMIN_PASSWORD` | 否 | 内置 `admin` 在 `<ui>/login/admin` 的登录密码。留空 = 关闭该入口,见 [UI 登录](#ui-登录) |
| `ALIYUN_REGION` | **是** | 如 `cn-hangzhou` |
| `ALIYUN_ACCESS_KEY` | **是** | master RAM 用户 AK(用于 STS `AssumeRole`) |
Expand Down Expand Up @@ -313,6 +313,21 @@ OIDC discovery。
UI 代理会转发 `X-Forwarded-Proto` / `X-Forwarded-Host`,UC 据此拼出浏览器实际使用的回调地址;若前面还有
一层代理把这两个头去掉了,就填 `UC_EXTERNAL_URL`。

##### 网关加路径前缀

网关可能把 UC 挂在一个前缀下并在转发前剥掉它:浏览器访问的是 `https://api.example.com/unitycatalog/...`,
服务端收到的却是 `/...`。请求里没有任何东西能告诉服务端这个前缀,但服务端交给浏览器的两样东西必须带着
它——发给身份提供方的 `redirect_uri`(登记的那个带前缀),以及 OAuth state cookie 的作用域路径(按无前缀
路径设置的 cookie 永远不会被带回 `/unitycatalog/.../callback`,于是在微软已经认证成功之后报
`Missing login state`)。把前缀写进 `UC_EXTERNAL_URL`:

```bash
UC_EXTERNAL_URL=https://api.example.com/unitycatalog
```

并在 Entra 里登记 `https://api.example.com/unitycatalog/api/1.0/unity-control/auth/callback`。
`deploy-uc.sh` 接受路径,仍拒绝结尾斜杠、查询串与片段。位于主机根路径的部署不填路径,行为不变。

#### 3. 建用户

**没有自动建号**:每个要登录的员工必须先在 UC 里存在(`POST /scim2/Users`),邮箱要与令牌里的 `email`
Expand Down
17 changes: 11 additions & 6 deletions deploy/deploy-uc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,17 @@ done
[ "$UC_AUTHORIZATION" = "enable" ] && [ "$login_set" -eq 0 ] && [ -z "$UC_ADMIN_PASSWORD" ] && \
echo "WARN: authorization is on but neither Microsoft sign-in nor UC_ADMIN_PASSWORD is set: nobody can sign in to the UI"

# UC_EXTERNAL_URL overrides what the server would otherwise derive, and two things depend on it:
# the redirect_uri handed to the identity provider, which must equal the registered one byte for
# byte, and whether the session cookie is marked Secure. A path here silently breaks the first; the
# property's own validator only checks that the value parses as a URL, so check the shape.
if [ -n "$UC_EXTERNAL_URL" ] && ! [[ "$UC_EXTERNAL_URL" =~ ^https?://[^/]+$ ]]; then
echo "ERROR: UC_EXTERNAL_URL must be scheme://host[:port] with no path, e.g. https://uc.example.com"
# UC_EXTERNAL_URL overrides what the server would otherwise derive: the redirect_uri handed to the
# identity provider, which must equal the registered one byte for byte, and the path the OAuth
# state cookie is scoped to. A path is legitimate here -- it is required when a gateway serves UC
# under a prefix and strips it before forwarding, since the registered redirect_uri then carries
# that prefix -- so accept scheme://host[:port][/segment...]. What is still rejected is what can
# only be a slip: a trailing slash, an empty segment, a query or a fragment, a missing scheme. The
# Secure flag on the session cookie reads only the scheme, so a path never affects it. The
# property's own validator only checks that the value parses as a URL, hence this shape check.
if [ -n "$UC_EXTERNAL_URL" ] && ! [[ "$UC_EXTERNAL_URL" =~ ^https?://[^/?#]+(/[^/?#]+)*$ ]]; then
echo "ERROR: UC_EXTERNAL_URL must be scheme://host[:port][/path], with no trailing slash, query or fragment,"
echo " e.g. https://uc.example.com or https://api.example.com/unitycatalog"
echo " got: $UC_EXTERNAL_URL"
exit 1
fi
Expand Down
26 changes: 17 additions & 9 deletions deploy/uc.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,25 @@ UC_AUTHORIZATION_URL=
UC_TOKEN_URL=
UC_CLIENT_ID=
UC_CLIENT_SECRET=
# Browser-facing base URL of UC (scheme://host[:port], no path). It builds the OAuth redirect_uri
# and decides whether the session cookie is marked Secure. NORMALLY LEAVE BLANK: the server derives
# the browser-facing origin from X-Forwarded-Proto/Host, which the deployment puts there. Set it
# only when those headers do not reach the server -- a proxy that strips them, or a TLS terminator
# that never adds them. Two ways to get it wrong, both silent:
# Browser-facing base URL of UC (scheme://host[:port], plus a path when UC is served under one). It
# builds the OAuth redirect_uri, scopes the OAuth state cookie, and decides whether the session
# cookie is marked Secure. NORMALLY LEAVE BLANK: the server derives the browser-facing origin from
# X-Forwarded-Proto/Host, which the deployment puts there. Set it in two situations:
# - those headers do not reach the server -- a proxy that strips them, or a TLS terminator that
# never adds them:
# UC_EXTERNAL_URL=https://uc.example.com
# - a gateway serves UC under a path prefix and strips it before forwarding, so the browser is on
# https://api.example.com/unitycatalog/... while the server sees /...; the prefix must be here,
# because the redirect_uri registered with the identity provider carries it and the state
# cookie must be scoped to the path the browser actually uses:
# UC_EXTERNAL_URL=https://api.example.com/unitycatalog
# Ways to get it wrong:
# - a scheme that does not match reality: https here over plain HTTP makes the browser drop the
# session cookie (sign-in appears to succeed and lands back on the login page); http here over
# HTTPS leaves the cookie without Secure
# - anything after the host: the redirect_uri no longer equals the one registered in Entra, which
# answers AADSTS50011 -- deploy-uc.sh rejects a path outright
# UC_EXTERNAL_URL=https://uc.example.com
# HTTPS leaves the cookie without Secure. Both are silent
# - a path the gateway does not actually strip, or a different one: the redirect_uri no longer
# equals the one registered in Entra, which answers AADSTS50011
# - a trailing slash, query or fragment: deploy-uc.sh rejects these at render time
UC_EXTERNAL_URL=

# --- Aliyun OSS (master RAM identity for STS AssumeRole) ---
Expand Down
5 changes: 5 additions & 0 deletions features.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,8 @@ Feature history of this fork, by PR merge date.

- features: —
- bugfix: Behind a reverse proxy that serves this server under a path prefix and strips it before forwarding, hosted login failed at the callback with "Missing login state". `/auth/login` scoped the `UC_OAUTH_STATE` cookie to the mount path the server sees (`/api/1.0/unity-control/auth`), while the browser comes back to `/callback` under the prefix, so the cookie was never sent. The state cookie is now set and cleared on the path the browser actually uses -- the path of `server.external-url` followed by the mount path -- which is the directory of the `redirect_uri` the provider is handed. Deployments whose `server.external-url` carries no path are unaffected, and `UC_TOKEN` stays at `Path=/`. Reaching this through the bundled deploy scripts additionally needs #22, which lifts the host-only check `deploy-uc.sh` applies to `UC_EXTERNAL_URL`.

#24 (https://github.com/relytcloud/unitycatalog/pull/24)

- features: —
- bugfix: `deploy-uc.sh` accepts a path in `UC_EXTERNAL_URL` (`https://api.example.com/unitycatalog`), which the #21 state-cookie fix depends on and which the script rejected outright, so the fix could not be reached through the repo's own deployment path. Trailing slash, empty segment, query, fragment and a missing scheme are still rejected. The sample file and both deployment guides now describe the behind-a-prefix case and the redirect URI to register.
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,11 @@ public enum Property {
CLIENT_ID("server.client-id"),
CLIENT_SECRET("server.client-secret"),
REDIRECT_PORT("server.redirect-port", POSITIVE_INTEGER_VALIDATOR),
// Base URL at which browsers reach this server (scheme + host [+ port]), used to build the
// OAuth redirect_uri for the server-hosted login flow. Optional: when unset it is derived
// from X-Forwarded-Proto / X-Forwarded-Host or the Host header of the incoming request.
// Base URL at which browsers reach this server, used to build the OAuth redirect_uri for the
// server-hosted login flow; its path, if any, also scopes the OAuth state cookie, which is how
// the flow survives a gateway that serves this server under a prefix and strips it. Optional:
// when unset it is derived from X-Forwarded-Proto / X-Forwarded-Host or the Host header of
// the incoming request.
EXTERNAL_URL("server.external-url", URL_VALIDATOR),
// Password for signing the built-in "admin" account into the UI (issue #15). Blank = off.
ADMIN_PASSWORD("server.admin-password"),
Expand Down
Loading