Skip to content

[client] Support user added CAs in android client - #6941

Open
prixeus wants to merge 1 commit into
netbirdio:mainfrom
prixeus:fix-android-ca
Open

[client] Support user added CAs in android client#6941
prixeus wants to merge 1 commit into
netbirdio:mainfrom
prixeus:fix-android-ca

Conversation

@prixeus

@prixeus prixeus commented Jul 28, 2026

Copy link
Copy Markdown

The android stores the Certificate Authorities separately which are installed by the user. The Golang ecosystem does not care about them by default, so there was no possibility to use them in self-hosted environment on android clients with user supplied CA.
As my understanding to fix that the android code should collect the Certificates Authorities with java API, supply them to go code, and consume it combined with the system provided CAs.
By default the system CAs will be used in Golang.

Describe your changes

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)
  • This change does not modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — OR I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See CONTRIBUTING.md.

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Added some in-line comments in go code

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features
    • Added support for importing Android CA certificates to extend trusted TLS roots.
  • Improvements
    • Centralized TLS certificate trust around a shared CA pool for gRPC, OAuth/PKCE flows, relay/WebSocket connections, and management links.
    • When system trust is unavailable, the client still initializes from bundled roots, and the shared pool is used consistently across connections.
    • This reduces certificate-verification inconsistencies across different parts of the client.

@CLAassistant

CLAassistant commented Jul 28, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e02d026-38bd-4c2a-9c9a-71608a8ba30c

📥 Commits

Reviewing files that changed from the base of the PR and between 2032a8c and e609786.

📒 Files selected for processing (10)
  • client/android/certpool.go
  • client/grpc/dialer.go
  • client/internal/auth/device_flow.go
  • client/internal/auth/pkce_flow.go
  • flow/client/client.go
  • proxy/server.go
  • shared/relay/client/dialer/ws/ws.go
  • shared/relay/tls/client_dev.go
  • shared/relay/tls/client_prod.go
  • util/certpool.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • client/android/certpool.go
  • util/certpool.go
  • flow/client/client.go
  • shared/relay/client/dialer/ws/ws.go

📝 Walkthrough

Walkthrough

A global X.509 certificate pool is added with setter and cloned getter APIs. Android PEM certificates can update the pool, while client, authentication, proxy, and relay TLS configurations now consume it.

Changes

Global TLS certificate handling

Layer / File(s) Summary
Global pool and Android configuration
util/certpool.go, client/android/certpool.go
Adds global certificate-pool initialization, replacement, cloning, and Android PEM certificate loading.
Core client and management TLS consumers
client/grpc/dialer.go, flow/client/client.go, proxy/server.go
Updates gRPC, flow-client, and management TLS setup to use the shared pool.
Relay TLS trust configuration
shared/relay/client/dialer/ws/ws.go, shared/relay/tls/client_*.go
Updates WebSocket and QUIC relay TLS configurations to use the shared pool.
Authentication TLS flows
client/internal/auth/device_flow.go, client/internal/auth/pkce_flow.go
Uses the shared pool for device authorization, PKCE callback, mTLS, and OAuth token exchange connections.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Android
  participant CertificatePool
  participant TLSClients
  Android->>CertificatePool: SetAndroidCertificates(pemCerts)
  CertificatePool->>CertificatePool: Append PEM certificates
  CertificatePool-->>Android: Return success or parse error
  TLSClients->>CertificatePool: GetGlobalCertPool()
  CertificatePool-->>TLSClients: Return cloned pool
  TLSClients->>TLSClients: Configure TLS RootCAs
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is mostly a template shell; it lacks the actual change summary, issue link, stack, and required discussion detail. Fill in Describe your changes, add the issue ticket and stack, and either link prior approval or confirm the required checklist item.
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly states the main change: Android client support for user-added CAs.
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.
✨ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
client/internal/auth/pkce_flow.go (1)

202-205: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Remove or wire up this unused TLS configuration.

startServer calls ListenAndServe at Line 256, so this TLSConfig is never used. RootCAs also affects server-side client-certificate verification, not outbound OAuth requests. Use ListenAndServeTLS if an HTTPS callback is intended; otherwise remove this field.

🤖 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 `@client/internal/auth/pkce_flow.go` around lines 202 - 205, Update the server
setup in startServer to remove the unused TLSConfig and its RootCAs
configuration if the callback remains HTTP; alternatively, switch the server
startup from ListenAndServe to ListenAndServeTLS and provide the required
certificate and key when HTTPS callbacks are intended. Keep the implementation
consistent between the server configuration and startup method.
🤖 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.

Inline comments:
In `@client/android/certpool.go`:
- Around line 12-23: Update the certificate-pool initialization around
x509.SystemCertPool so that when the system pool is unavailable or nil, it
starts from embeddedroots.Get() instead of a new empty pool. Keep appending
Android PEM certificates and publishing the resulting pool through
util.SetGlobalCertPool, preserving the existing system-pool path.

In `@client/grpc/dialer.go`:
- Around line 35-37: Make util.GetGlobalCertPool nil-safe by checking
globalCertPool before calling Clone and returning nil when no system pool is
available. Preserve the existing nil-handling and embedded-certificate fallback
behavior at client/grpc/dialer.go:35-37, flow/client/client.go:52-54,
proxy/server.go:790-791, shared/relay/client/dialer/ws/ws.go:87-89,
shared/relay/tls/client_dev.go:15-17, and shared/relay/tls/client_prod.go:15-17;
these callers require no direct changes.

In `@client/internal/auth/pkce_flow.go`:
- Line 236: Update handleRequest’s token-exchange setup to preserve the
mTLS-configured client placed in oauth2.HTTPClient by startServer, rather than
unconditionally replacing it before Exchange. When creating a transport for the
no-client-certificate path, clone http.DefaultTransport so proxy and other
defaults remain intact, then apply RootCAs using util.GetGlobalCertPool.

In `@util/certpool.go`:
- Around line 21-24: Update GetGlobalCertPool to handle a nil globalCertPool
before calling Clone: return nil when no system certificate pool is available,
otherwise preserve the existing cloned-pool behavior so callers can use their
embedded-root fallback.

---

Nitpick comments:
In `@client/internal/auth/pkce_flow.go`:
- Around line 202-205: Update the server setup in startServer to remove the
unused TLSConfig and its RootCAs configuration if the callback remains HTTP;
alternatively, switch the server startup from ListenAndServe to
ListenAndServeTLS and provide the required certificate and key when HTTPS
callbacks are intended. Keep the implementation consistent between the server
configuration and startup method.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a4ef9505-e798-476b-a0ca-e5f5c9cae88c

📥 Commits

Reviewing files that changed from the base of the PR and between 9269b56 and 596b615.

📒 Files selected for processing (10)
  • client/android/certpool.go
  • client/grpc/dialer.go
  • client/internal/auth/device_flow.go
  • client/internal/auth/pkce_flow.go
  • flow/client/client.go
  • proxy/server.go
  • shared/relay/client/dialer/ws/ws.go
  • shared/relay/tls/client_dev.go
  • shared/relay/tls/client_prod.go
  • util/certpool.go

Comment thread client/android/certpool.go Outdated
Comment thread client/grpc/dialer.go Outdated
Comment thread client/internal/auth/pkce_flow.go
Comment thread util/certpool.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@client/android/certpool.go`:
- Around line 10-14: Update SetAndroidCertificates to check whether
util.GetGlobalCertPool() returns nil before calling AppendCertsFromPEM. Add the
appropriate fallback or error return so nil pools are handled safely and invalid
Android certificates cannot trigger a panic, while preserving the existing
parsing error behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f0b31943-eb89-4e1b-a5f6-e849e5d296ff

📥 Commits

Reviewing files that changed from the base of the PR and between 596b615 and 4c7fc56.

📒 Files selected for processing (10)
  • client/android/certpool.go
  • client/grpc/dialer.go
  • client/internal/auth/device_flow.go
  • client/internal/auth/pkce_flow.go
  • flow/client/client.go
  • proxy/server.go
  • shared/relay/client/dialer/ws/ws.go
  • shared/relay/tls/client_dev.go
  • shared/relay/tls/client_prod.go
  • util/certpool.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • util/certpool.go
  • flow/client/client.go

Comment on lines +10 to +14
func SetAndroidCertificates(pemCerts []byte) error {
pool := util.GetGlobalCertPool()

if ok := pool.AppendCertsFromPEM(pemCerts); !ok {
return fmt.Errorf("failed to parse any certificates from android")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
fd -a 'certpool\.go$|util' client | sed 's#^\./##' | head -100

echo "== android certpool =="
if [ -f client/android/certpool.go ]; then
  cat -n client/android/certpool.go
fi

echo "== search GetGlobalCertPool/SetGlobalCertPool/pool usages =="
rg -n "GetGlobalCertPool|SetGlobalCertPool|global.*CertPool|CertPool|SetAndroidCertificates|AppendCertsFromPEM" client -S

echo "== relevant util file snippets =="
rg -n "GetGlobalCertPool|SetGlobalCertPool" -l . | while read -r f; do
  echo "--- $f"
  rg -n -C 8 "GetGlobalCertPool|SetGlobalCertPool" "$f" || true
done

Repository: netbirdio/netbird

Length of output: 9055


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

paths=('client/android/certpool.go','client/util/certpool.go')
for path in paths:
    p=Path(path)
    print(f'FILE {path} exists={p.exists()}')
    if not p.exists():
        continue
    s=p.read_text()
    for name in ["GetGlobalCertPool","SetGlobalCertPool","SetAndroidCertificates"]:
        print(f"\n-- {name} in {path}")
        for i,line in enumerate(s.splitlines(),1):
            if name in line or 'return ' in line or 'AppendCertsFromPEM' in line or 'GetSystemCertificatePool' in line:
                print(f"{i}: {line}")
PY

Repository: netbirdio/netbird

Length of output: 1049


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== util/certpool.go =="
cat -n util/certpool.go

echo "== android/certpool.go =="
cat -n client/android/certpool.go

echo "== SetAndroidCertificates call sites =="
rg -n "SetAndroidCertificates\\(" -S .

Repository: netbirdio/netbird

Length of output: 1929


Guard against GetGlobalCertpool() returning nil.

util.GetGlobalCertPool() can still return nil, and SetAndroidCertificates dereferences it unconditionally on line 13. Add a nil check/fallback before AppendCertsFromPEM so invalid Android certificate handling cannot panic.

🤖 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 `@client/android/certpool.go` around lines 10 - 14, Update
SetAndroidCertificates to check whether util.GetGlobalCertPool() returns nil
before calling AppendCertsFromPEM. Add the appropriate fallback or error return
so nil pools are handled safely and invalid Android certificates cannot trigger
a panic, while preserving the existing parsing error behavior.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@util/certpool.go`:
- Around line 25-26: Update SetGlobalCertPool to validate newCertPool before
assigning globalCertPool; when the input is nil, return without modifying the
existing pool (or report the invalid caller), while preserving normal assignment
for non-nil pools.
- Line 11: Synchronize global certificate-pool access by adding an RWMutex
around the shared pool: update SetGlobalCertPool to lock while replacing the
value, and update GetGlobalCertPool to use a read lock while retrieving or
cloning it. Ensure SetAndroidCertificates cannot mutate or publish the pool
concurrently with TLS consumers such as CreateConnection and the PKCE flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bfc45671-40d9-47b3-a910-53e597582913

📥 Commits

Reviewing files that changed from the base of the PR and between 4c7fc56 and b1fdcab.

📒 Files selected for processing (10)
  • client/android/certpool.go
  • client/grpc/dialer.go
  • client/internal/auth/device_flow.go
  • client/internal/auth/pkce_flow.go
  • flow/client/client.go
  • proxy/server.go
  • shared/relay/client/dialer/ws/ws.go
  • shared/relay/tls/client_dev.go
  • shared/relay/tls/client_prod.go
  • util/certpool.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • client/android/certpool.go
  • flow/client/client.go
  • shared/relay/client/dialer/ws/ws.go

Comment thread util/certpool.go Outdated
Comment thread util/certpool.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
client/internal/auth/pkce_flow.go (1)

202-205: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Remove the ineffective server-side RootCAs configuration.

startServer launches this server with ListenAndServe(), not ListenAndServeTLS(), so TLSConfig.RootCAs is never used. It does not affect the outbound OAuth exchange, which is configured separately at lines 282-295. Remove it, or switch the callback to explicit TLS serving if that is intended.

🤖 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 `@client/internal/auth/pkce_flow.go` around lines 202 - 205, Update the server
construction in startServer to remove the ineffective TLSConfig.RootCAs setting,
since the callback uses ListenAndServe rather than TLS serving; leave the
separate outbound OAuth TLS configuration unchanged.
🤖 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.

Inline comments:
In `@util/certpool.go`:
- Around line 48-50: Update the exported documentation comment immediately above
GetSystemCertPool so it begins with the exact function name and accurately
describes the function’s system CA certificate behavior.
- Around line 32-36: SetGlobalCertPool must clone the provided pool before
publishing it globally. Within SetGlobalCertPool, clone newCertPool after the
nil check, then store the clone under the existing guard lock so later mutations
to the caller’s pool cannot affect globalCertPool.

---

Nitpick comments:
In `@client/internal/auth/pkce_flow.go`:
- Around line 202-205: Update the server construction in startServer to remove
the ineffective TLSConfig.RootCAs setting, since the callback uses
ListenAndServe rather than TLS serving; leave the separate outbound OAuth TLS
configuration unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 576adf74-b8ee-4f83-b79e-a78ec2afc34d

📥 Commits

Reviewing files that changed from the base of the PR and between b1fdcab and 2032a8c.

📒 Files selected for processing (10)
  • client/android/certpool.go
  • client/grpc/dialer.go
  • client/internal/auth/device_flow.go
  • client/internal/auth/pkce_flow.go
  • flow/client/client.go
  • proxy/server.go
  • shared/relay/client/dialer/ws/ws.go
  • shared/relay/tls/client_dev.go
  • shared/relay/tls/client_prod.go
  • util/certpool.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • client/android/certpool.go
  • flow/client/client.go
  • shared/relay/client/dialer/ws/ws.go

Comment thread util/certpool.go
Comment thread util/certpool.go Outdated
The android stores the Certificate Authorities separately which are
installed by the user. The Golang ecosystem does not care about them by
default, so there was no possibility to use them in self-hosted
environment on android clients with user supplied CA.
As my understanding to fix that the android code should collect the
Certificates Authorities with java API, supply them to go code, and
consume it combined with the system provided CAs.
By default the system CAs will be used in Golang.
@sonarqubecloud

Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants