Skip to content

feat: support trusted remote auth proxy#1163

Open
zhexuany wants to merge 1 commit into
larksuite:mainfrom
zhexuany:feat/remote-auth-proxy
Open

feat: support trusted remote auth proxy#1163
zhexuany wants to merge 1 commit into
larksuite:mainfrom
zhexuany:feat/remote-auth-proxy

Conversation

@zhexuany
Copy link
Copy Markdown

@zhexuany zhexuany commented May 28, 2026

Summary

Adds a trusted remote HTTPS auth proxy path for environments where lark-cli cannot receive app secrets directly.

Changes

  • Add lark-cli config auth-proxy trust|untrust|list to store trusted remote proxy hosts in local config instead of env.
  • Allow LARKSUITE_CLI_AUTH_PROXY=https://host[:port] only when the host is locally trusted, while keeping HTTP constrained to same-host sidecar mode.
  • Split remote proxy session from request signing: LARKSUITE_CLI_PROXY_SESSION is sent to the proxy, while LARKSUITE_CLI_PROXY_KEY signs requests and is not transmitted.
  • Harden proxy endpoint and X-Lark-Proxy-Target parsing, and share target validation with the demo servers.

Test Plan

  • go test ./sidecar -count=1
  • go test -tags authsidecar ./extension/credential/sidecar ./extension/transport/sidecar -count=1
  • go test -tags authsidecar_demo ./sidecar/server-demo -count=1
  • go test -tags authsidecar_multi_tenant_demo ./sidecar/server-multi-tenant-demo -count=1
  • go test ./cmd/... ./internal/... ./shortcuts/... ./extension/... -count=1
  • go test -tags authsidecar ./cmd/... ./internal/... ./shortcuts/... ./extension/... ./sidecar -count=1
  • go vet ./...
  • gofmt -l .
  • go mod tidy (no go.mod / go.sum diff)
  • git diff --check
  • go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 run --new-from-rev=origin/main
  • make unit-test blocked locally: Go go1.26.0 darwin/arm64 crashes in runtime.systemstack_switch under -race -gcflags="all=-N -l"; observed as runtime SIGSEGV before package assertions run.

Related Issues

  • None

Summary by CodeRabbit

  • New Features

    • Added auth-proxy CLI commands to trust, untrust, and list remote proxy hosts for authentication.
    • Enabled support for remote managed HTTPS proxies alongside local HTTP sidecars.
    • Introduced new environment variable for proxy session credentials.
  • Chores

    • Updated proxy request handling and validation for remote proxy authentication.

Review Change Stack

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 28, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

📝 Walkthrough

Walkthrough

This PR extends the sidecar authentication architecture to support remote managed HTTPS auth proxies alongside existing local HTTP sidecars. It introduces trust management via CLI commands, persists trusted host configuration, implements mode-aware credential resolution and request interception, and consolidates protocol parsing logic across demo servers.

Changes

Remote Managed Auth Proxy Support with Trust Management

Layer / File(s) Summary
Auth Proxy Configuration Foundation
internal/core/config.go, internal/core/config_test.go, internal/envvars/envvars.go
New AuthProxyConfig type with TrustedHosts field persists remote proxy trust decisions independently of app/profile config. LoadAuthProxyConfig and UpdateAuthProxyConfig manage the trusted host list. New CliProxySession environment variable defined. Tests verify config load/save/roundtrip behavior with missing files and app-less configurations.
Wire Protocol Types and Validators
sidecar/protocol.go, sidecar/hmac_test.go
ProxyMode (local HTTP vs remote HTTPS), ProxyEndpoint struct, and HeaderProxySession constant added. ParseProxyEndpoint validates and classifies proxy addresses; NormalizeRemoteProxyTrustHost and IsTrustedRemoteProxyHost canonicalize and match trusted hosts; ParseProxyTarget validates remote target URLs. ValidateProxyAddr refactored to delegate to ParseProxyEndpoint. Comprehensive test coverage includes endpoint parsing, trust-host normalization, target validation, and reserved-host rejection.
CLI Commands for Trust Management
cmd/config/auth_proxy.go, cmd/config/auth_proxy_test.go, cmd/config/config.go
New auth-proxy trust, auth-proxy untrust, and auth-proxy list commands allow users to manage trusted remote proxy hosts. Commands normalize host values, update persisted config via core.UpdateAuthProxyConfig, and emit JSON results. Tests verify idempotence, HTTP rejection, and untrust removal.
Credential Provider Mode Handling
extension/credential/sidecar/provider.go, extension/credential/sidecar/provider_test.go
ResolveAccount now parses LARKSUITE_CLI_AUTH_PROXY into a ProxyEndpoint and switches on proxy mode: local mode requires CliProxyKey only; remote mode validates trust via requireTrustedRemoteProxy and requires both CliProxyKey and CliProxySession. New test helper trustRemoteProxy and four new tests validate HTTPS remote proxy resolution, trust enforcement, and missing-credential failures.
Transport Interceptor Remote Proxy Support
extension/transport/sidecar/interceptor.go, extension/transport/sidecar/interceptor_test.go
ResolveInterceptor parses endpoint, validates remote trust, and derives signing key/session per mode. Interceptor struct extended with proxyScheme, proxyMode, and proxySession fields. PreRoundTrip conditionally injects HeaderProxySession for remote mode and rewrites URL scheme. Package init() validates endpoint on startup. New remote HTTPS interceptor tests verify request rewriting, header manipulation, and signature verification.
Demo Server Protocol Consolidation
sidecar/server-demo/forward.go, sidecar/server-demo/handler.go, sidecar/server-demo/handler_test.go, sidecar/server-multi-tenant-demo/forward.go, sidecar/server-multi-tenant-demo/handler.go, sidecar/server-multi-tenant-demo/handler_test.go, sidecar/server-demo/README.md
Both demo handlers refactored to use centralized sidecar.ParseProxyTarget instead of local parseTarget function; isProxyHeader recognizes HeaderProxySession. README updated to document local HTTP (loopback/same-host, key only) vs remote HTTPS (managed proxy, session + key) mode requirements and constraints.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • larksuite/cli#532: Both PRs modify auth-sidecar core components (extension/credential/sidecar/provider.go, extension/transport/sidecar/interceptor.go, sidecar/protocol.go) to evolve the sidecar proxy protocol handling; this PR adds remote managed auth-proxy trust validation and session/header support.
  • larksuite/cli#934: Overlaps directly on multi-tenant demo handler target parsing logic; main PR refactors both demo handlers to use centralized sidecar.ParseProxyTarget whereas the retrieved PR adds multi-tenant parseTarget logic.

Suggested labels

size/XL, feature

Suggested reviewers

  • sang-neo03
  • liangshuo-1
  • liuxinyanglxy

Poem

🐰 A bunny hops through config doors,
Trust for proxies—remote and more!
Session headers dance on HTTPS winds,
Mode-aware interceptors spin their spins,
Local or distant, the auth flows through—
Feishu's CLI dreams now come true! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately captures the main change: adding support for trusted remote authentication proxies, which is the primary feature introduced across all modified files.
Description check ✅ Passed The PR description follows the template structure with Summary, Changes, and Test Plan sections. It clearly explains the feature, implementation details, and comprehensive testing that was performed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the size/L Large or sensitive change across domains or core paths label May 28, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
sidecar/protocol.go (1)

115-122: ⚡ Quick win

Reserved host check bypassed by non-default ports.

The reservedRemoteProxyHosts map contains hostnames without ports (e.g., "open.feishu.cn"), but isReservedRemoteProxyHost canonicalizes the authority which preserves non-default ports (e.g., "open.feishu.cn:8443"). This means https://open.feishu.cn:8443 would not be rejected.

While this edge case is unlikely in practice, the defense-in-depth intent suggests blocking these hostnames regardless of port.

Suggested fix to check hostname without port
 func isReservedRemoteProxyHost(authority string) bool {
-	host, err := canonicalHTTPSAuthority(authority)
+	u, err := url.Parse("https://" + authority)
 	if err != nil {
 		return false
 	}
-	return reservedRemoteProxyHosts[host]
+	return reservedRemoteProxyHosts[strings.ToLower(u.Hostname())]
 }

Also applies to: 363-369

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sidecar/protocol.go` around lines 115 - 122, reservedRemoteProxyHosts
currently maps hostnames without ports but isReservedRemoteProxyHost performs
its lookup against the canonicalized authority which can include non-default
ports (e.g., "open.feishu.cn:8443"), allowing bypass; change the lookup to strip
any port and use only the hostname when checking reservedRemoteProxyHosts: in
isReservedRemoteProxyHost extract the hostname portion (e.g., call
urlObj.Hostname() or use net.SplitHostPort with a fallback) and then check
reservedRemoteProxyHosts[hostname], and apply the same hostname-only check to
the other reserved-host check mentioned alongside reservedRemoteProxyHosts so
ports are ignored for matching.
extension/transport/sidecar/interceptor_test.go (1)

187-196: 💤 Low value

Consider adding a test for missing PROXY_SESSION in remote mode.

The credential provider test file has TestResolveAccount_RemoteHTTPSMissingProxySession, but there's no equivalent test here for ResolveInterceptor. Adding TestProviderResolveInterceptor_RemoteHTTPSMissingProxySession would ensure symmetric coverage between the credential and transport layers.

📝 Suggested test
func TestProviderResolveInterceptor_RemoteHTTPSMissingProxySession(t *testing.T) {
	trustRemoteProxy(t, "auth-proxy.example.com")
	t.Setenv(envvars.CliAuthProxy, "https://auth-proxy.example.com")
	t.Setenv(envvars.CliProxyKey, "proxy-signing-key")
	t.Setenv(envvars.CliProxySession, "")

	if interceptor := (&Provider{}).ResolveInterceptor(context.Background()); interceptor != nil {
		t.Fatal("expected nil interceptor when remote proxy session is missing")
	}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extension/transport/sidecar/interceptor_test.go` around lines 187 - 196, Add
a symmetric test to cover the missing PROXY_SESSION case for the transport layer
by creating TestProviderResolveInterceptor_RemoteHTTPSMissingProxySession that
mirrors TestResolveAccount_RemoteHTTPSMissingProxySession: set
trustRemoteProxy(t, "auth-proxy.example.com"), set envvars.CliAuthProxy to
"https://auth-proxy.example.com", set envvars.CliProxyKey to a non-empty value
and envvars.CliProxySession to an empty string, then assert that
(&Provider{}).ResolveInterceptor(context.Background()) returns nil (fail the
test if it is non-nil); reference the Provider type and its ResolveInterceptor
method when locating where to add the test.
extension/credential/sidecar/provider.go (1)

132-144: ⚡ Quick win

Duplicate requireTrustedRemoteProxy implementation.

This function is duplicated verbatim in extension/transport/sidecar/interceptor.go (lines 220-232). Consider extracting it to a shared location (e.g., the sidecar package or a shared internal helper) to avoid divergence and reduce maintenance burden.

♻️ Suggested refactor

Move requireTrustedRemoteProxy to a shared location, for example in the sidecar package:

// In sidecar/protocol.go or a new sidecar/trust.go
func RequireTrustedRemoteProxy(endpoint ProxyEndpoint, loadConfig func() ([]string, error)) error {
	if endpoint.Mode != ProxyModeRemote {
		return nil
	}
	trustedHosts, err := loadConfig()
	if err != nil {
		return fmt.Errorf("failed to load auth proxy trust config: %w", err)
	}
	if IsTrustedRemoteProxyHost(endpoint.Host, trustedHosts) {
		return nil
	}
	return fmt.Errorf("remote auth proxy host %q is not trusted; run `lark-cli config auth-proxy trust https://%s` outside the agent environment", endpoint.Host, endpoint.Host)
}

Then both provider.go and interceptor.go can call this shared function.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extension/credential/sidecar/provider.go` around lines 132 - 144, Extract the
duplicated requireTrustedRemoteProxy logic into a single shared function in the
sidecar package (e.g., sidecar.RequireTrustedRemoteProxy) that accepts a
sidecar.ProxyEndpoint and either calls core.LoadAuthProxyConfig internally or
accepts a loader func for testability; implement the same behavior (check
endpoint.Mode == sidecar.ProxyModeRemote, load trusted hosts, call
sidecar.IsTrustedRemoteProxyHost and return the same formatted error). Replace
both existing implementations (the requireTrustedRemoteProxy in provider.go and
the one in interceptor.go) with calls to sidecar.RequireTrustedRemoteProxy and
remove the duplicated code, updating imports accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@extension/credential/sidecar/provider.go`:
- Around line 132-144: Extract the duplicated requireTrustedRemoteProxy logic
into a single shared function in the sidecar package (e.g.,
sidecar.RequireTrustedRemoteProxy) that accepts a sidecar.ProxyEndpoint and
either calls core.LoadAuthProxyConfig internally or accepts a loader func for
testability; implement the same behavior (check endpoint.Mode ==
sidecar.ProxyModeRemote, load trusted hosts, call
sidecar.IsTrustedRemoteProxyHost and return the same formatted error). Replace
both existing implementations (the requireTrustedRemoteProxy in provider.go and
the one in interceptor.go) with calls to sidecar.RequireTrustedRemoteProxy and
remove the duplicated code, updating imports accordingly.

In `@extension/transport/sidecar/interceptor_test.go`:
- Around line 187-196: Add a symmetric test to cover the missing PROXY_SESSION
case for the transport layer by creating
TestProviderResolveInterceptor_RemoteHTTPSMissingProxySession that mirrors
TestResolveAccount_RemoteHTTPSMissingProxySession: set trustRemoteProxy(t,
"auth-proxy.example.com"), set envvars.CliAuthProxy to
"https://auth-proxy.example.com", set envvars.CliProxyKey to a non-empty value
and envvars.CliProxySession to an empty string, then assert that
(&Provider{}).ResolveInterceptor(context.Background()) returns nil (fail the
test if it is non-nil); reference the Provider type and its ResolveInterceptor
method when locating where to add the test.

In `@sidecar/protocol.go`:
- Around line 115-122: reservedRemoteProxyHosts currently maps hostnames without
ports but isReservedRemoteProxyHost performs its lookup against the
canonicalized authority which can include non-default ports (e.g.,
"open.feishu.cn:8443"), allowing bypass; change the lookup to strip any port and
use only the hostname when checking reservedRemoteProxyHosts: in
isReservedRemoteProxyHost extract the hostname portion (e.g., call
urlObj.Hostname() or use net.SplitHostPort with a fallback) and then check
reservedRemoteProxyHosts[hostname], and apply the same hostname-only check to
the other reserved-host check mentioned alongside reservedRemoteProxyHosts so
ports are ignored for matching.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ecda802e-d59c-4611-99f8-a628e9b71172

📥 Commits

Reviewing files that changed from the base of the PR and between a2cc5e1 and 56cacf1.

📒 Files selected for processing (19)
  • cmd/config/auth_proxy.go
  • cmd/config/auth_proxy_test.go
  • cmd/config/config.go
  • extension/credential/sidecar/provider.go
  • extension/credential/sidecar/provider_test.go
  • extension/transport/sidecar/interceptor.go
  • extension/transport/sidecar/interceptor_test.go
  • internal/core/config.go
  • internal/core/config_test.go
  • internal/envvars/envvars.go
  • sidecar/hmac_test.go
  • sidecar/protocol.go
  • sidecar/server-demo/README.md
  • sidecar/server-demo/forward.go
  • sidecar/server-demo/handler.go
  • sidecar/server-demo/handler_test.go
  • sidecar/server-multi-tenant-demo/forward.go
  • sidecar/server-multi-tenant-demo/handler.go
  • sidecar/server-multi-tenant-demo/handler_test.go

@zhexuany
Copy link
Copy Markdown
Author

zhexuany commented May 29, 2026

@liangshuo-1 PTAL

We want to manage internal user login via a single gate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants