Skip to content

Fix URL Prefix Support#1655

Open
neurolag wants to merge 5 commits intomozilla-services:masterfrom
neurolag:public-url
Open

Fix URL Prefix Support#1655
neurolag wants to merge 5 commits intomozilla-services:masterfrom
neurolag:public-url

Conversation

@neurolag
Copy link

@neurolag neurolag commented Mar 12, 2025

Description

Currently, hosting the SyncStorage service under any root URL other than / like, say, /firefox-sync, causes 401 HTTP error codes caused by mismatching Message Authentication Codes (or MACs for short) as pointed out by @ethowitz here.

Changes made in this PR add a new option public_url allowing users to specify the public facing URL to the root of the syncservers services.

This public_url option is used for determining the original request uri and perform the MAC authentication properly.

Things to Note

As explained by @kyz here, the host and port for performing the MAC authentication are taken from the Forwarded or the X-Forwarded-For and X-Forwarded-Scheme etc. headers:

let host_port: Vec<_> = ci.host().splitn(2, ':').collect();
let host = host_port[0];
let port = if host_port.len() == 2 {
host_port[1].parse().map_err(|_| {
ValidationErrorKind::FromDetails(
"Invalid port (hostname:port) specified".to_owned(),
RequestErrorLocation::Header,
None,
label!("request.validate.hawk.invalid_port"),
)
})?
} else if ci.scheme() == "https" {
443
} else {
80
};
let path = uri.path_and_query().ok_or(HawkErrorKind::MissingPath)?;

It might be a good idea to swap this to perform the authentication based on public_url if specified, instead. However, I did not include this in this PR and I would love to hear what other people think about this.

Testing

  1. Spin up a syncserver which is hosted under a root other than /, for example: http://localhost:8080/firefox-sync:
    services:
      web:
        image: nginxproxy/nginx-proxy
        restart: unless-stopped
        ports:
          - 127.0.0.1:8080:80
        environment:
          DEFAULT_HOST: sync.example.com
        volumes:
          - /var/run/docker.sock:/tmp/docker.sock:ro
      sync-server:
        build:
          context: .
          dockerfile_inline: |
            FROM rust AS build
            ARG SYNC_STORAGE_VERSION=0.18.2
            RUN git clone https://github.com/mozilla-services/syncstorage-rs -b $${SYNC_STORAGE_VERSION} /app
            WORKDIR /app
    
            RUN \
              apt-get update \
              && apt-get install -y libpython3-dev \
              && cargo install --path ./syncserver --features mysql --locked \
              && cargo clean \
              && apt-get remove -y libpython3-dev \
              && rm -rf /var/lib/apt/lists \
              && bash -O extglob -c 'rm -rf /usr/local/cargo/!(bin)' \
              && bash -O extglob -c 'rm -rf /usr/local/cargo/bin/!(syncserver)'
    
            FROM python:3.11 AS sync
            COPY --from=build /usr/local/cargo/bin/syncserver /usr/local/bin
            COPY --from=build /app/requirements.txt .
            RUN pip install -r requirements.txt
            CMD [ "/usr/local/bin/syncserver" ]
          target: sync
        restart: unless-stopped
        environment:
          VIRTUAL_PORT: 80
          VIRTUAL_PATH: "/firefox-sync/"
          VIRTUAL_DEST: "/"
          VIRTUAL_HOST: sync.example.com
          RUST_LOG: warn
          SYNC_HUMAN_LOGS: 1
          SYNC_HOST: "0.0.0.0"
          SYNC_PORT: 80
          SYNC_MASTER_SECRET: secret
          SYNC_SYNCSTORAGE__ENABLED: "true"
          SYNC_SYNCSTORAGE__DATABASE_URL: mysql://sync:password@sync-db/SyncStorage
          SYNC_SYNCSTORAGE__ENABLE_QUOTA: 0
          SYNC_TOKENSERVER__ENABLED: "true"
          SYNC_TOKENSERVER__DATABASE_URL: mysql://token:password@token-db/TokenServer
          SYNC_TOKENSERVER__RUN_MIGRATIONS: "true"
          SYNC_TOKENSERVER__FXA_METRICS_HASH_SECRET: secret
          SYNC_TOKENSERVER__FXA_EMAIL_DOMAIN: api.accounts.firefox.com
          SYNC_TOKENSERVER__FXA_OAUTH_SERVER_URL: https://oauth.accounts.firefox.com
          SYNC_TOKENSERVER__FXA_BROWSERID_AUDIENCE: https://token.services.mozilla.com
          SYNC_TOKENSERVER__FXA_BROWSERID_ISSUER: https://api.accounts.firefox.com
          SYNC_TOKENSERVER__FXA_BROWSERID_SERVER_URL: https://verifier.accounts.firefox.com/v2
        expose:
          - 80
      sync-db:
        image: mariadb
        restart: unless-stopped
        environment:
          MARIADB_RANDOM_ROOT_PASSWORD: "yes"
          MARIADB_USER: sync
          MARIADB_PASSWORD: password
          MARIADB_DATABASE: SyncStorage
      token-db:
        build:
          context: .
          dockerfile_inline: |
            FROM rust AS build
            ARG SYNC_STORAGE_VERSION=0.18.2
            RUN git clone https://github.com/mozilla-services/syncstorage-rs -b $${SYNC_STORAGE_VERSION} /app
    
            RUN \
              cargo install diesel_cli --no-default-features --features mysql --locked \
              && bash -O extglob -c 'rm -rf /usr/local/cargo/!(bin)' \
              && bash -O extglob -c 'rm -rf /usr/local/cargo/bin/!(diesel)'
    
            FROM mariadb AS db
            RUN mkdir -p /app/tokenserver-db
            COPY --from=build /app/tokenserver-db/migrations /app/tokenserver-db/migrations
            COPY --from=build /usr/local/cargo/bin/diesel /usr/local/bin
    
            RUN { \
                echo '#!/bin/bash'; \
                echo 'diesel --database-url "mysql://$${MARIADB_USER}:$${MARIADB_PASSWORD}@localhost/$${MARIADB_DATABASE}" migration --migration-dir /app/tokenserver-db/migrations run'; \
                echo 'mariadb -u$$MARIADB_USER -p$$MARIADB_PASSWORD -D $$MARIADB_DATABASE <<EOF'; \
                echo 'INSERT INTO services (service, pattern)'; \
                echo "SELECT 'sync-1.5', '{node}/1.5/{uid}'"; \
                echo "WHERE NOT EXISTS (SELECT 1 FROM services);"; \
                echo ""; \
                echo 'INSERT INTO nodes (\`service\`, node, capacity, available, current_load, downed, backoff)'; \
                echo "SELECT LAST_INSERT_ID(), 'http://localhost:8080/firefox-sync', 1, 1, 0, 0, 0"; \
                echo "WHERE LAST_INSERT_ID() > 0;"; \
                echo "EOF"; \
              } > /docker-entrypoint-initdb.d/tokenserver-db.sh
        restart: unless-stopped
        environment:
          MARIADB_RANDOM_ROOT_PASSWORD: "yes"
          MARIADB_USER: token
          MARIADB_PASSWORD: password
          MARIADB_DATABASE: TokenServer
  2. Try to sync your browser against http://localhost:8080/firefox-sync/1.0/sync/1.5
  3. Take note that any request pointing to http://localhost:8080/firefox-sync/1.5/* fail with a 401 HTTP code

Issue(s)

Closes #1217 and closes #1649.

@neurolag neurolag changed the title Allow Customizing the Public URL Fix URL Prefix Support Mar 21, 2025
@kyz
Copy link

kyz commented Jun 9, 2025

Thanks for submitting this! I hope Mozilla consider accepting it.

It might be a good idea to swap this to perform the authentication based on public_url if specified, instead. However, I did not include this in this PR and I would love to hear what other people think about this.

Personally, yes, I would like it if public_url also sets host, port and scheme if it is defined. If it's defined by config, the service should not need to guess from headers. I believe it's also the behaviour of the previous SyncServer-1.5.

@virgoparna
Copy link

This is now broken by commit 3404150. Tried to use it so that I could get syncserver-rs to work properly.

@neurolag
Copy link
Author

I think at time of writing you can just rebase it
The commits are still perfectly compatible

Gimme a sec

@neurolag neurolag force-pushed the public-url branch 2 times, most recently from c3895d3 to 54b9344 Compare October 17, 2025 18:30
@virgoparna
Copy link

virgoparna commented Nov 7, 2025

Built syncserver from this pull request, but sync still fails (logs from firefox, this is where it switches to localhost again):

1762537347298 Sync.Status DEBUG Status.login: success.status_ok => success.login
1762537347298 Sync.Status DEBUG Status.service: error.login.failed => success.status_ok
1762537347298 Sync.SyncAuthManager DEBUG _findCluster returning http://localhost:8000/1.5/4/

config file in server side has public_url set.

@neurolag
Copy link
Author

neurolag commented Nov 7, 2025

To me, it looks like you're calling the sync server by http://localhost:8000/1.5/4/ instead of its public_url.

Is this, by chance, what's going wrong?

@virgoparna
Copy link

To me, it looks like you're calling the sync server by http://localhost:8000/1.5/4/ instead of its public_url.

No, prefs.js in Firefox has same value, as public_url... And it initially connects public_url according the logs...
I even tried logging out firefox account and then logging back in (which managed reset tokenserver url).

Could it be, that syncserver sends it self url as a part of some response? That /4/ at the end... Configured url in firefox ends with 1.5
And public_url in syncserver config ends with ffsync
And running strings against syncserver binary I get on instance of public_url
And I checked out pull request to local branch with "git fetch origin pull/1655/head:local_branc_name"

1762580651257 Services.Common.RESTRequest DEBUG GET https://public_server/ffsync/1.0/sync/1.5 200
1762580651257 Services.Common.TokenServerClient DEBUG Got token response: 200
1762580651257 Services.Common.TokenServerClient DEBUG Successful token response
1762580651258 Sync.BulkKeyBundle INFO BulkKeyBundle being created for undefined
1762580651258 Sync.Status DEBUG Status.login: success.status_ok => success.login
1762580651258 Sync.Status DEBUG Status.service: error.login.failed => success.status_ok
1762580651258 Sync.SyncAuthManager DEBUG _findCluster returning http://localhost:8000/1.5/4/
1762580651259 Sync.SyncAuthManager DEBUG Cluster value = http://localhost:8000/1.5/4/
1762580651259 Sync.SyncAuthManager DEBUG Setting cluster to http://localhost:8000/1.5/4/

@virgoparna
Copy link

Could it be, that syncserver sends it self url as a part of some response? That /4/ at the end... Configured url in firefox ends with 1.5 And public_url in syncserver config ends with ffsync And running strings against syncserver binary I get on instance of public_url And I checked out pull request to local branch with "git fetch origin pull/1655/head:local_branc_name"

Ok. tokenserver_rs database nodes table had localhost url in node field.. Changing it to public_url removes those http://localhosty:9000 requests....
Sync still fails, but that is probably nothing to do with this patch...
"One of: no meta, no meta storageVersion, or no meta syncID. Fresh start needed."

@virgoparna
Copy link

Sync still fails, but that is probably nothing to do with this patch... "One of: no meta, no meta storageVersion, or no meta syncID. Fresh start needed."

That is probably caused by #1753
current master already has fixes for mariadb support.

When running behind a reverse proxy hosting
the service under a webroot other than `/`
causes 401 error codes due to mismatching
Message Authentication Codes (MACs).

Changes made in this commit allow users
hosting the sync server behind a reverse proxy
to specify the `public_url` of their service
in order to correct this behaviour.

In doing so, changes made in this commit fix mozilla-services#1217, mozilla-services#1649
@neurolag neurolag force-pushed the public-url branch 2 times, most recently from 9448400 to ca2d94c Compare January 29, 2026 23:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants