maelvls dev blog

maelvls dev blog

Systems software engineer. I write mostly about Kubernetes and Go. About

17 Apr2024

Logging into Synology NAS with Personal Google Accounts

Introduction

My parents and I share a DS923+. We mainly use it for storing photos. To save energy, the NAS is stopped at night and restarted in the morning. Restarting the NAS means that you need to log in to the UI at least once every day.

Over the past two years, my parents forgot their passwords a couple of times. They can’t reset the password on their own (that’s a limitation with Synology’s “local” accounts), and I need to log in and reset the password for them.

Challenges with Local Accounts and SSO

A couple of years ago, Synology introduced SSO (single sign-on). Since 7.2, DSM supports generic OIDC providers, and supports logging into local users (it used to be only possible for LDAP users).

Since my parents are always signed in into their Google account, I figured it would be possible to use OIDC with Google… Except it won’t work with local accounts.

Here is what I tried: I created a My OIDC configuration for Google looked like this:

Credentials page in API and Services in GCP’s Console

Then, I configured my Synology to use Google’s OIDC endpoint:

Configuration of the SSO Client using Google OIDC in DSM

The problem arose with the “Username claim”. I want to log into my local account mael.valais, but none of the claims in Google’s ID tokens contain that username. Here is an example of a Google ID token:

{
  "iss": "https://accounts.google.com",
  "aud": "1234987819200.apps.googleusercontent.com",
  "sub": "10769150350006150715113082367",
  "email": "jsmith@example.com",
  "email_verified": "true",
  "iat": 1353601026,
  "exp": 1353604926
}

Forking Dex to use it as an OIDC middleware for Google OIDC

I figured I could use Dex to act as a middleware between Synology’s OIDC client and Google’s OIDC server. My goal was to “augment” Google’s JWTs with Synology’s usernames by looking up the user by email.

Dex isn’t as flexible as I would have hoped. To make it work, I had to fork it to change the internals of the Google OIDC connector.

Fork: https://github.com/maelvls/dex/tree/google-to-synology-sso

This fork is a fork of the fork presented in https://github.com/dexidp/dex/pull/2954. It builds on the idea of the ExtendPayload interface, which I slightly adjusted to pass the original claims since I needed access to the email contained in the JWT provided by Google.

With this fork, you will need to set three more environment variables:

SYNO_PASSWD=redacted
SYNO_USER=mael.valais
SYNO_URL=http://127.0.0.1:5000

When the OIDC flow with Google is done and before Dex issues its own JWT, I added some code to add the claim username. With this modified Dex, the JWT looks like this:

{
  "at_hash": "-j6HZYvzDaqkQB2KxIgSyw",
  "aud": "caddy",
  "c_hash": "8SK3tobDYgaI3cnDzkmi5g",
  "email": "mael65@gmail.com",
  "email_verified": true,
  "exp": 1713387587,
  "iat": 1713301187,
  "iss": "https://login.mysynodomain.dev/dex",
  "name": "Maël Valais",
  "nonce": "MFZFSkESL1XqdQmbvr0T43Kn7v0CzLap",
  "sub": "ChUxMDAzNjk3OTQzNjg3MDAwOTk5MTISBmdvb2dsZQ",
  "username": "mael.valais"
}

Here is the updated configuration in DSM:

Configuration of the SSO Client using Dex in DSM

Using the fork of Dex

First, install zig and ko. That will allow you to cross-compile Dex to linux/amd64 on macOS without Buildx (cross-compiling is required because Dex’s sqlite library needs CGO)

brew install ko zig

Clone the fork:

git clone https://github.com/maelvls/dex --branch google-to-synology-sso

Then, build the image:

CC="zig cc -target x86_64-linux" CXX="zig c++ -target x86_64-linux" CGO_ENABLED=1 \
  KO_DOCKER_REPO=ghcr.io/maelvls/dex \
  KO_DEFAULTBASEIMAGE=alpine \
  ko build ./cmd/dex --bare --tarball /tmp/out.tar --push=false  

Then, copy the image to your NAS:

ssh yournas /usr/local/bin/docker load </tmp/out.tar

Then, create a file dex.yaml on your NAS:

issuer: https://login.mysynodomain.dev/dex

storage:
  type: sqlite3
  config:
    file: dex.sqlite

web:
  http: 0.0.0.0:5556

logger:
  level: debug

oauth2:
   skipApprovalScreen: true
   alwaysShowLoginScreen: false

staticClients:
- id: synology
  name: 'Synology'
  redirectURIs:
  - 'https://mysynodomain.dev/'
  secret: foo # Use openssl rand -hex 16 to generate this.

connectors:
- type: google
  id: google
  name: Google
  config:
    issuer: https://accounts.google.com
    clientID: $GOOGLE_CLIENT_ID
    clientSecret: $GOOGLE_CLIENT_SECRET
    redirectURI: https://login.mysynodomain.dev/dex/callback

# I have disabled email login.
enablePasswordDB: false

Finally, run Dex:

docker run --name dex -d \
  -v $HOME/dex.yaml:/dex.yaml \
  -v $HOME/dex.sqlite:/dex.sqlite \
  -e SYNO_PASSWD=redacted \
  -e SYNO_USER=mael.valais \
  -e SYNO_URL=http://127.0.0.1:5000 \
  -e GOOGLE_CLIENT_ID=207842732284-l7nhetlsvimmds80fa2knir8fundp3h4.apps.googleusercontent.com \
  -e GOOGLE_CLIENT_SECRET=redacted \
  -p 5556:5556 \
  ghcr.io/maelvls/dex serve /dex.yaml

Conclusion

With this method, my parents can log into the NAS with their Google account and no longer have to remember their Synology username and password.

Although it works, I wish I didn’t have to fork Dex to customize the claims it puts into the JWT payload. I came across a couple of designs that would aim to make Dex more extendable, but none have been implemented yet.

📝 Edit this page