PocketID + Pangolin + Netbird ( work in progress )

I was stuck trying the get Nebird working with Pangolin and traefik.

Writing this post allowed me to get some of the basic stuff to work:

  • Netbird Docker container
  • Pocket Id as the IDP for Netbird (Already working with pangolin)
  • Netbird dashboard with Packet ID authentication
  • Client connection on internal LAN with Packet ID authentication.

https://np.p.example.com now works for the dashboard.

** To Do:**

  • Client relay support through Traefik

  • First Client Peer to Peer config.

  • First Network config.

  • Figure out if the configure.sh script can be modified to support this config.

  • Is it possible to access the netbird container ports without exposing them or redirecting them to other ports.

  • Use the docker hostnames to references the Traefik endpoints internally.

    Example use of Pocket-ID as IDP

    I am running Packet-ID and would love to use it as the IDP. (I think I have it working now)
    I am also using Pangolin as my reverse proxy with authentication is already working with pangolin and Pocket-ID.

    Steps I took

    A couple of attempts of the Self-hosted roll out has been unsuccessful over the last couple of days, with no proper running solution yet.
    I am trying it again as I write this, to get the steps I used and get exact errors.

    1. Used Advanced guide - NetBird Docs
      1.1 Using Docker on Ubuntu 22.04.
      1.2 Pangolin with Traefik for the reverse proxy. I would like run as much as possible through port 443. ( Not all the services are working yet, E.g. Relay not working)
      1.3 I didn’t use the script method, since I don’t have Zitadel.

    2. Setup setup.env with all my settings.
      2.1 Followed the Advanced guide - NetBird Docs for the reverse proxy settings.
      2.2 I edited the management.json file based on a couple of reddit posts to try and get this working. Will post my details below.

    3. Added a OICD client under Pocket-ID.

    • In Pocket-ID Client. Most of the settings are default.

      • Enabled PKCE
      • Set the callback as https://nb.p.example.com/* and http://localhost:53000 (I didn’t understand why there was a requirement for this localhost entry until I setup my first client that needed to authenticate using Pocket ID. The Call back is sent back to the client that is running on local host. PocketID uses the localhost on the client system since this is where the netbird client is listening on port 53000 for the callbacks. )
      • After the config was run, I changed the management.json file one setting at a time and retested until I got it working.
    • OIDC settings in the setup,env file

      • NETBIRD_AUTH_AUDIENCE="Client ID"
      • NETBIRD_AUTH_CLIENT_ID="Client ID"
      • NETBIRD_AUTH_CLIENT_SECRET="Secret"
      • NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT="https://pocket.p.example.com/.well-known/openid-configuration" (tested that the URL returns proper JSON results)
      • NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID="2xxxxxxx-0xxx-4xxx-8xxx-cxxxxxxxxxxx"
      • NETBIRD_AUTH_SUPPORTED_SCOPES="openid profile email"
      • NETBIRD_AUTH_REDIRECT_URI=""
      • NETBIRD_AUTH_SILENT_REDIRECT_URI=""
    1. I did not disable the Single account mode.

    I ran the configure.sh and got the following result

    Letsencrypt was disabled, the Https-endpoints cannot be used anymore
     and a reverse-proxy with Https needs to be placed in front of netbird!
    The following forwards have to be setup:
    - https://nb.p.example.com:443 -http-> dashboard:80
    - https://nb.p.example.com:443/api -http-> management:443
    - https://nb.p.example.com:443/management.ManagementService/ -grpc-> management:443
    - https://nb.p.example.com:443/signalexchange.SignalExchange/ -grpc-> signal:80
    - rels://nb.p.example.com:33080/relay/ -http-> relay:33080
    You most likely also have to change NETBIRD_MGMT_API_ENDPOINT in base.setup.env and port-mappings in docker-compose.yml.tmpl and rerun this script.
     The target of the forwards depends on your setup. Beware of the gRPC protocol instead of http for management and signal!
    You are also free to remove any occurrences of the Letsencrypt-volume netbird-letsencrypt
    

    The Resulting docker-compose.yaml file still has ports exposed that are in use by the reverse proxy. The Traefik example config file in the infrastructure_files folder has all the ports removed. That example uses the Traefik lables that I don’t use. I used the Configuration files for the middleware and routers.

    These ports were exposed after the config generation.

    • dashboard has ports 80 and 443 exposed.
    • signal has port 443 exposed.
    • management also has pot 443 exposed.
      Since these ports are in use when I try and start the container, I changed them all to available ports. Then I changed the router config to use the exposed ports.

    Expected behavior

    The authentication should work and return to the dashboard. (This now works)

FIXED: Authentication of a client installation works, but I get an error on the return to the Dashboard.
Invalid callback URL, it might be necessary for an admin to fix this.
This is displayed on the Pocket authentication page.
In your Pcket-ID Client config, make sure that the callback is http://localhost:53000. NOT HTTPS.

Are you using NetBird Cloud?
Self-host NetBird’s control plane.
Hosted in a local Docker instance separate from Traefik/Pangolin.

NetBird version

0.51.1

Is any other VPN software installed?

No

Debug output

To help us resolve the problem, please attach the following anonymized status output

netbird status -dA

!! I am still trying to figure out how to do this with the docker compose installation since there is no shell to connect to and I don’t know in which folder the netbird is running from.

Create and upload a debug bundle, and share the returned file key:

netbird debug for 1m -AS -U

!! I am still trying to figure out how to do this with the docker compose installation since there is no shell to connect to and I don’t know in which folder the netbird is running from.

Additional context

I have attempted to follow the instructions:

management.json

RELAY-Secret-Password=32char random value

OICD-Client-ID=PocketID OICD Client ID
OICD-Secret-ID=PocketID OICD Client Password
TURN-Secret-Password=A selfdefines password that also needs to be set int he turnserver.conf. The config will be line this: user=self:TURN-Secret-Password
FIXED1 Relay now working
The below address didn’t match the config in Traefik. It should be:
rels://nb.p.example.com:443/relay. Internally 33080 was used, but since Traefik is now handling the traffic, the external details should be exposed.


    "Relay": {
        "Addresses": [
            "rels://nb.p.example.com:33080"
{
    "Stuns": [
        {
            "Proto": "udp",
            "URI": "stun:nb.p.example.com:3478",
            "Username": "",
            "Password": ""
        }
    ],
    "TURNConfig": {
        "TimeBasedCredentials": false,
        "CredentialsTTL": "12h0m0s",
        "Secret": "secret",
        "Turns": [
            {
                "Proto": "udp",
                "URI": "turn:nb.p.example.com:3478",
                "Username": "self",
                "Password": "TURN-Secret-Password"
            }
        ]
    },
    "Relay": {
        "Addresses": [
            "rels://nb.p.example.com:443/relay"
        ],
        "CredentialsTTL": "24h0m0s",
        "Secret": "RELAY-Secret-Password"
    },
    "Signal": {
        "Proto": "https",
        "URI": "nb.p.example.com:443",
        "Username": "",
        "Password": null
    },
    "Datadir": "/var/lib/netbird/",
    "DataStoreEncryptionKey": "Oxx-ENC-KEY-xk=",
    "HttpConfig": {
        "LetsEncryptDomain": "",
        "CertFile": "",
        "CertKey": "",
        "AuthAudience": "OICD-Client-ID",
        "AuthIssuer": "https://pocket.p.example.com",
        "AuthUserIDClaim": "",
        "AuthKeysLocation": "https://pocket.p.example.com/.well-known/jwks.json",
        "OIDCConfigEndpoint": "https://pocket.p.example.com/.well-known/openid-configuration",
        "IdpSignKeyRefreshEnabled": false,
        "ExtraAuthAudience": ""
    },
    "IdpManagerConfig": {
        "ManagerType": "none",
        "ClientConfig": {
            "Issuer": "https://pocket.p.example.com",
            "TokenEndpoint": "https://pocket.p.example.com/api/oidc/token",
            "ClientID": "OICD-Client-ID",
            "ClientSecret": "",
            "GrantType": "client_credentials"
        },
        "ExtraConfig": {},
        "Auth0ClientCredentials": null,
        "AzureClientCredentials": null,
        "KeycloakClientCredentials": null,
        "ZitadelClientCredentials": null
    },
    "DeviceAuthorizationFlow": {
        "Provider": "hosted",
        "ProviderConfig": {
            "ClientID": "OICD-Client-ID",
            "ClientSecret": "",
            "Domain": "pocket.p.example.com",
            "Audience": "",
            "TokenEndpoint": "https://pocket.p.example.com/api/oidc/token",
            "DeviceAuthEndpoint": "https://pocket.p.example.com/api/oidc/device/authorize",
            "AuthorizationEndpoint": "",
            "Scope": "openid",
            "UseIDToken": false,
            "RedirectURLs": null,
            "DisablePromptLogin": false,
            "LoginFlag": 0
        }
    },
    "PKCEAuthorizationFlow": {
        "ProviderConfig": {
            "ClientID": "OICD-Client-ID",
            "ClientSecret": "OICD-Secret-ID",
            "Domain": "",
            "Audience": "",
            "TokenEndpoint": "https://pocket.p.example.com/api/oidc/token",
            "DeviceAuthEndpoint": "",
            "AuthorizationEndpoint": "https://pocket.p.example.com/authorize",
            "Scope": "openid profile email",
            "UseIDToken": true,
            "RedirectURLs": [
                "http://localhost:53000"
            ],
            "DisablePromptLogin": false,
            "LoginFlag": 0
        }
    },
    "StoreConfig": {
        "Engine": "sqlite"
    },
    "ReverseProxy": {
        "TrustedHTTPProxies": [],
        "TrustedHTTPProxiesCount": 0,
        "TrustedPeers": [
            "0.0.0.0/0"
        ]
    },
    "DisableDefaultPolicy": false
}

compose.yaml

  • I changed all the ports that are 443 and 80 to available ports. Traefik is already running on those ports and I needed to redirect from Traefik to these ports. There probably is a way for Traefik to access the containers without them being exposed. Will look into that.
  • I changed all the volumes to folders that are store in the docker compose folder to allow easier backup and checking files.
  • Lets Encrypt has been disabled since I am using Pangolin that handles the cert management. I talk to all the services internally on HTTP
  • FIXED1: NB_EXPOSED_ADDRESS=rels://nb.p.example.com:33080/relay should match the Traefik and management.json config:
    NB_EXPOSED_ADDRESS=rels://nb.p.example.com:443/relay
services:
  # UI dashboard
  dashboard:
    image: netbirdio/dashboard:latest
    restart: unless-stopped
    ports:
      - 8093:80
      - 8094:443
    environment:
      # Endpoints
      - NETBIRD_MGMT_API_ENDPOINT=https://nb.p.example.com:443
      - NETBIRD_MGMT_GRPC_API_ENDPOINT=https://nb.p.example.com:443
      # OIDC
      - AUTH_AUDIENCE=OICD-Client-ID
      - AUTH_CLIENT_ID=OICD-Client-ID
      - AUTH_CLIENT_SECRET=OICD-Secret-ID
      - AUTH_AUTHORITY=https://pocket.p.example.com
      - USE_AUTH0=false
      - AUTH_SUPPORTED_SCOPES=openid profile email
      - AUTH_REDIRECT_URI=
      - AUTH_SILENT_REDIRECT_URI=
      - NETBIRD_TOKEN_SOURCE=accessToken
      # SSL
      #      - NGINX_SSL_PORT=443
      # Letsencrypt
      - LETSENCRYPT_DOMAIN=
      - LETSENCRYPT_EMAIL=
    volumes:
      #      - ./netbird-letsencrypt:/etc/letsencrypt/
      - ./config/letsencrypt:/etc/letsencrypt/
    logging:
      driver: json-file
      options:
        max-size: 500m
        max-file: "2"
  # Signal
  signal:
    image: netbirdio/signal:latest
    restart: unless-stopped
    volumes:
      #- netbird-signal:/var/lib/netbird
      - ./config/signal:/var/lib/netbird
    ports:
      #      - 443:80
      - 8095:80
    #      # port and command for Let's Encrypt validation
    #      - 443:443
    #    command: ["--letsencrypt-domain", "", "--log-file", "console"]
    logging:
      driver: json-file
      options:
        max-size: 500m
        max-file: "2"
  # Relay
  relay:
    image: netbirdio/relay:latest
    restart: unless-stopped
    environment:
      - NB_LOG_LEVEL=debug
      - NB_LISTEN_ADDRESS=:33080
      - NB_EXPOSED_ADDRESS=rels://nb.p.example.com:443/relay
      - NB_AUTH_SECRET=RELAY-Secret-Password
    ports:
      - 33080:33080
    logging:
      driver: json-file
      options:
        max-size: 500m
        max-file: "2"
  # Management
  management:
    image: netbirdio/management:latest
    restart: unless-stopped
    depends_on:
      - dashboard
    volumes:
      # - netbird-mgmt:/var/lib/netbird
      # - netbird-letsencrypt:/etc/letsencrypt:ro
      # - ./management.json:/etc/netbird/management.json

      - ./config/mgmt:/var/lib/netbird
      - ./config/letsencrypt:/etc/letsencrypt:ro
      - ./config/management.json:/etc/netbird/management.json
    ports:
      - 8096:80 #API port
    #    # command for Let's Encrypt validation without dashboard container
    #    command: ["--letsencrypt-domain", "", "--log-file", "console"]
    command:
      - --port
      - "80"
      - --log-file
      - console
      - --log-level
      - info
      - --disable-anonymous-metrics=true
      - --single-account-mode-domain=nb.p.example.com
      - --dns-domain=netbird.selfhosted
    logging:
      driver: json-file
      options:
        max-size: 500m
        max-file: "2"
    environment:
      - NETBIRD_STORE_ENGINE_POSTGRES_DSN=
      - NETBIRD_STORE_ENGINE_MYSQL_DSN=
  # Coturn
  coturn:
    image: coturn/coturn:latest
    restart: unless-stopped
    #domainname: nb.p.example.com # only needed when TLS is enabled
    volumes:
      - ./config/turnserver.conf:/etc/turnserver.conf:ro
    #      - ./privkey.pem:/etc/coturn/private/privkey.pem:ro
    #      - ./cert.pem:/etc/coturn/certs/cert.pem:ro
    network_mode: host
    command:
      - -c /etc/turnserver.conf
    logging:
      driver: json-file
      options:
        max-size: 500m
        max-file: "2"

# volumes:
#   netbird-mgmt:
#   netbird-signal:
#   netbird-letsencrypt:

Traefik config

  • I added the following router code to handle the direct incoming connections that are not supported by the Pangolin front end.
  • I need to get the internal DNS lookups for the hosts to work so that I don’t have to use the internal IPs of the host.
http:
  routers:
###################################################
# Netbird #########################################
###################################################

    netbird-dashboard:
      rule: "Host(`nb.p.example.com`)"
      service: netbird-dashboard
      entryPoints: 
        - websecure      
      tls:
        certResolver: letsencrypt
    
    # Netbird Relay router 
    netbird-relay-rtr:
      rule: "Host(`nb.p.example.com`) && PathPrefix(`/relay`)"
      service: netbird-relay-service
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

    netbird-signal:
      rule: "Host(`nb.p.example.com`) && PathPrefix(`/signalexchange.SignalExchange/`)"
      service: netbird-signal-service
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

    netbird-api:
      rule: "Host(`nb.p.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/oidc/callback`))"
      service: netbird-api-service
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

    netbird-management:
      rule: "Host(`nb.p.example.com`) && PathPrefix(`/management.ManagementService/`)"
      service: netbird-management-service
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

  services:
    
 # Netbird services
    netbird-dashboard:
      loadBalancer:
        passHostHeader: true
        servers:
#          - url: "http://netbird-dashboard:80"  # Default Netbird service
          - url: "http://192.168.1.9:8093"  # Default Netbird service          

    netbird-relay-service:
      loadBalancer:
        servers:
          #- url: "http://netbird-relay:33080"  # Netbird Relay service 
          - url: "https://192.168.1.9:33080"  # Netbird Relay service 

    netbird-signal-service:
      loadBalancer:
        servers:
          #- url: "h2c://netbird-signal:10000"
          - url: "h2c://192.168.1.9:8095"

    netbird-api-service:
      loadBalancer:
        servers:
          #- url: "http://netbird-management:80"
          - url: "http://192.168.1.9:8096"

    netbird-management-service:
      loadBalancer:
        servers:
          #- url: "h2c://netbird-management:80"
          - url: "h2c://192.168.1.9:8096"

Edit:

FIXED1 : RELAYs fixed. Marked the two locations fixed with FIXED1.

2 Likes

Good luck!
If you ever get the whole feature set working it would be amazing

1 Like

@xXAzazelXx @LeonvanHeerden got it working. will publish share it with @LeonvanHeerden and he will publish it soon

1 Like

Yes, I am running it with Pocket ID at home.

Still want to do some cleanup of the old configs on my side, but it works great.

I can Authenticate on my Windows PC with Pocket-ID, when connecting to Netbird from the Windows Client desktop client.

2 Likes

could you please share a guide on how you got the both running on the same VPS?

2 Likes

Hi, I tried that too, but unfortunately failed miserably. I was hoping I could simply put a haproxy or nginx in front of it and use SNI inspection or http to forward the traffic to the appropriate backends, while letting the rest just pass by, but… well… I’m afraid this is all beyond my capabilities. Do you plan on completing the guide? Otherwise, I’d just try it based on the current description. Thank you very much.

Sorry, have not tried this on a VPS.

this is basically what the Traefik config is doing here.

Based on the FQDN where it connected too, the traffic is sent to the appropriate back-end IP and port.

The Routers in the Traefik config, is the inspection part, that then directs the FQDN and path to a service entry. The Service entry directs the traffic the configured URLs.