Skip to content
Draft
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: 10 additions & 10 deletions server/cmd/api/api/browser_location.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
const (
browserLocationApplyTimeout = 5 * time.Second
defaultBrowserLocationStatePath = "/run/kernel/browser-location.json"
trustedControlPlaneHeader = "X-Kernel-Trusted-Control-Plane"
trustedControlPlaneHeaderValue = "1"
)

type browserLocationBundle struct {
Expand Down Expand Up @@ -231,7 +233,7 @@ func (s *ApiService) reconcileBrowserLocation(ctx context.Context, bundle browse
components.TimeZone = true
lastErr = s.withCDPClientTimeout(ctx, time.Second, func(cdpCtx context.Context, client *cdpclient.Client) error {
languages := strings.Join(bundle.Languages, ",")
if err := client.SetBrowserLocation(cdpCtx, bundle.Locale, languages); err != nil {
if err := client.SetBrowserLocation(cdpCtx, bundle.Locale, languages, bundle.TimeZone); err != nil {
return err
}
observed, err := client.GetBrowserLocation(cdpCtx)
Expand Down Expand Up @@ -298,14 +300,9 @@ func resolvedLocalesMatch(expected string, observed cdpclient.BrowserLocation) b
if err != nil {
return false
}
wantBase, _ := want.Base()
for _, value := range []string{observed.DateTimeLocale, observed.NumberLocale, observed.CollatorLocale} {
tag, err := language.Parse(value)
if err != nil {
return false
}
base, _ := tag.Base()
if base != wantBase {
if err != nil || tag.String() != want.String() {
return false
}
}
Expand Down Expand Up @@ -455,12 +452,15 @@ func (s *ApiService) BrowserLocationMetrics() metrics.BrowserLocationSnapshot {
}

// ResetBrowserLocationHTTP is the lease-authoritative epoch transition. Ordinary
// configure requests cannot change epochs. The instance JWT is not exposed on
// customer CDP or session APIs and binds the reset to this VM lease.
// configure requests cannot change epochs. Direct callers authenticate with the
// instance JWT. Metro-api may instead add the trusted control-plane marker after
// it verifies Kernel's internal token and removes any caller-supplied marker.
func (s *ApiService) ResetBrowserLocationHTTP(w http.ResponseWriter, r *http.Request) {
token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
expected := os.Getenv("KERNEL_INSTANCE_JWT")
if expected == "" || subtle.ConstantTimeCompare([]byte(token), []byte(expected)) != 1 {
instanceAuthorized := expected != "" && subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1
controlPlaneAuthorized := subtle.ConstantTimeCompare([]byte(r.Header.Get(trustedControlPlaneHeader)), []byte(trustedControlPlaneHeaderValue)) == 1
if !instanceAuthorized && !controlPlaneAuthorized {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
Expand Down
28 changes: 28 additions & 0 deletions server/cmd/api/api/browser_location_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"
"time"

"github.com/kernel/kernel-images/server/lib/cdpclient"
"github.com/kernel/kernel-images/server/lib/devtoolsproxy"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand All @@ -37,6 +38,19 @@ func TestValidateBrowserLocationBundle(t *testing.T) {
}
}

func TestResolvedLocalesMatchRequiresRegionalResolution(t *testing.T) {
assert.True(t, resolvedLocalesMatch("de-DE", cdpclient.BrowserLocation{
DateTimeLocale: "de-DE",
NumberLocale: "de-DE",
CollatorLocale: "de-DE",
}))
assert.False(t, resolvedLocalesMatch("de-DE", cdpclient.BrowserLocation{
DateTimeLocale: "de",
NumberLocale: "de-DE",
CollatorLocale: "de-DE",
}))
}

func TestBrowserLocationBundlesEqual(t *testing.T) {
a := browserLocationBundle{Epoch: "lease", Generation: 1, TimeZone: "UTC", Locale: "en-US", Languages: []string{"en-US", "en"}}
b := a
Expand Down Expand Up @@ -151,6 +165,20 @@ func TestResetBrowserLocationHTTPRequiresInstanceIdentity(t *testing.T) {
assert.Equal(t, "lease-a", service.browserLocationSnapshot().ActiveEpoch)
}

func TestResetBrowserLocationHTTPAcceptsTrustedControlPlane(t *testing.T) {
service := newBrowserLocationStateService(t)
service.browserLocationValidate = func(context.Context, browserLocationBundle) error { return nil }
body := `{"previous_epoch":"","bundle":{"epoch":"lease-a","generation":1,"timezone":"UTC","locale":"en-US","languages":["en-US","en"]}}`

request := httptest.NewRequest(http.MethodPost, "/internal/browser-location/reset", strings.NewReader(body))
request.Header.Set(trustedControlPlaneHeader, trustedControlPlaneHeaderValue)
response := httptest.NewRecorder()
service.ResetBrowserLocationHTTP(response, request)

assert.Equal(t, http.StatusAccepted, response.Code)
assert.Equal(t, "lease-a", service.browserLocationSnapshot().ActiveEpoch)
}

func TestBrowserLocationPersistenceFailureDoesNotChangeEpoch(t *testing.T) {
service := newBrowserLocationStateService(t)
t.Setenv("KERNEL_BROWSER_LOCATION_STATE_PATH", "/proc/kernel-browser-location-state")
Expand Down
4 changes: 2 additions & 2 deletions server/lib/cdpclient/cdpclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -758,9 +758,9 @@ func (c *Client) ValidateBrowserLocation(ctx context.Context, locale string) (Br
}

// SetBrowserLocation updates Chromium's session-only locale and language defaults.
func (c *Client) SetBrowserLocation(ctx context.Context, locale, acceptLanguages string) error {
func (c *Client) SetBrowserLocation(ctx context.Context, locale, acceptLanguages, timezone string) error {
_, err := c.Send(ctx, "Browser.setKernelBrowserLocation", map[string]string{
"locale": locale, "acceptLanguages": acceptLanguages,
"locale": locale, "acceptLanguages": acceptLanguages, "timezone": timezone,
}, "")
if err != nil {
return fmt.Errorf("Browser.setKernelBrowserLocation: %w", err)
Expand Down
3 changes: 2 additions & 1 deletion server/lib/cdpclient/cdpclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ func (f *fakeCDP) handler(w http.ResponseWriter, r *http.Request) {
_ = json.Unmarshal(req.Params, &params)
f.browserLocale = params["locale"]
f.browserLanguages = params["acceptLanguages"]
f.browserTimezone = params["timezone"]
result = map[string]any{}
case "Browser.getKernelBrowserLocation":
result = map[string]any{"locale": f.browserLocale, "acceptLanguages": f.browserLanguages, "timezone": f.browserTimezone, "dateTimeLocale": f.browserDateTimeLocale, "numberLocale": f.browserNumberLocale, "collatorLocale": f.browserCollatorLocale, "renderersConverged": true, "networkContextsConverged": true}
Expand Down Expand Up @@ -693,7 +694,7 @@ func TestBrowserLocation(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, BrowserLocationResolution{DateTimeLocale: "de", NumberLocale: "de", CollatorLocale: "de"}, resolution)

require.NoError(t, client.SetBrowserLocation(context.Background(), "de-DE", "de-DE,de"))
require.NoError(t, client.SetBrowserLocation(context.Background(), "de-DE", "de-DE,de", "Europe/Berlin"))
location, err := client.GetBrowserLocation(context.Background())
require.NoError(t, err)
assert.Equal(t, BrowserLocation{Locale: "de-DE", AcceptLanguages: "de-DE,de", TimeZone: "Europe/Berlin", DateTimeLocale: "de", NumberLocale: "de", CollatorLocale: "de", RenderersConverged: true, NetworkContextsConverged: true}, location)
Expand Down