Google SSO login returns 405 after setting app.sso_org via CLI

Hi! Thanks for working on Pritunl.

We are configuring our Pritunl VPN host via a CLI as part of the EC2 user-data script after restoring the local MongoDB. When logging into a freshly booted instance using SSO, we’re receiving 405s back from the Pritunl server.

We found that we can resolve the issue by logging in using admin credentials, and hitting “Save” button under “Settings”. After that, the SSO logins succeed. We would like to automate the respawn of the Pritunl VPN server such that it does not require that manual step. I wasn’t able to find a direct root cause in the code, and verified that the Org does exist in MongoDB after restore.

SSO-related configuration:

sudo pritunl set app.sso "google"
sudo pritunl set app.sso_cache true
sudo pritunl set app.sso_match '["example.com"]'
sudo pritunl set app.sso_google_key "$SSO_GOOGLE_KEY"
sudo pritunl set app.sso_google_email "test@example.com"
sudo pritunl set app.server_sso_url "pritunl-vpn-test.example.com"
sudo pritunl set app.sso_org "671b59600c7720650fed44f8"

Server error log from sudo pritunl logs:

[evening-thunder-3082][2024-10-25 12:38:31,644][ERROR] Organization for sso does not exist
  org_id = "671b59600c7720650fed44f8"
Traceback (most recent call last):
  File "/usr/lib/pritunl/usr/lib/python3.9/threading.py", line 937, in _bootstrap
    self._bootstrap_inner()
  File "/usr/lib/pritunl/usr/lib/python3.9/threading.py", line 980, in _bootstrap_inner
    self.run()
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/cheroot/workers/threadpool.py", line 120, in run
    keep_conn_open = conn.communicate()
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/cheroot/server.py", line 1287, in communicate
    req.respond()
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/cheroot/server.py", line 1077, in respond
    self.server.gateway(self).respond()
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/cheroot/wsgi.py", line 136, in respond
    response = self.req.server.wsgi_app(self.env, self.start_response)
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/flask/app.py", line 2213, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/flask/app.py", line 2190, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/flask/app.py", line 1484, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/flask/app.py", line 1469, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/auth/app.py", line 26, in _wrapped
    return call(*args, **kwargs)
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/handlers/sso.py", line 952, in sso_callback_get
    return _validate_user(username, email, sso_mode, org_id, groups,
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/handlers/sso.py", line 29, in _validate_user
    logger.error('Organization for sso does not exist', 'sso',
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/logger/__init__.py", line 55, in error
    kwargs['traceback'] = traceback.format_stack()

Full user-data script:

#!/bin/bash

export AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION

# Adding swap space
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# Setting up apt sources for Pritunl and MongoDB
sudo tee /etc/apt/sources.list.d/pritunl.list << EOF
deb http://repo.pritunl.com/stable/apt jammy main
EOF

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com --recv 7568D9BB55FF9E5287D586017AE645C0CF8E292A
curl https://raw.githubusercontent.com/pritunl/pgp/master/pritunl_repo_pub.asc | sudo apt-key add -

sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list << EOF
deb https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse
EOF

wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -

sudo apt update
sudo apt upgrade -y
sudo apt install awscli wireguard wireguard-tools mongodb-org pritunl -y
sudo sh -c 'echo "* hard nofile 64000" >> /etc/security/limits.conf'
sudo sh -c 'echo "* soft nofile 64000" >> /etc/security/limits.conf'
sudo sh -c 'echo "root hard nofile 64000" >> /etc/security/limits.conf'
sudo sh -c 'echo "root soft nofile 64000" >> /etc/security/limits.conf'
sudo systemctl enable mongod pritunl
sudo systemctl start mongod pritunl

sudo pritunl set-mongodb mongodb://localhost:27017/pritunl
sudo systemctl restart pritunl

# Restore MongoDB
aws s3 cp s3://<REDACTED>/mongodb_backup.gz ./mongodb_backup.gz
mongorestore --gzip --archive=mongodb_backup.gz
rm mongodb_backup.gz

sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:cloudwatch-agent-config.json -s
fi

sudo systemctl start pritunl

AWS_REGION=$(curl -s http://169.254.169.254/latest/meta-data/placement/region)
aws configure set region $AWS_REGION

### LICENSE ###
LICENSE=$(aws ssm get-parameter --name /pritunl/license --with-decryption --query 'Parameter.Value' --output text | sed -e 's/license//i' -e 's/end//i' -e 's/begin//i' -e 's/[^a-zA-Z0-9]//g' | tr -d '\n')
sudo pritunl set app.license "$LICENSE"
sudo pritunl set app.license_plan "enterprise"

### GOOGLE SSO ###
sudo pritunl set app.sso "google"
sudo pritunl set app.sso_cache true
sudo pritunl set app.sso_match '["example.com"]'
if ! command -v "jq" &> /dev/null; then
  wget https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64 -O /usr/local/bin/jq
  chmod +x /usr/local/bin/jq
fi
SSO_GOOGLE_KEY=$(aws ssm get-parameter --name /pritunl/sso_google_key --with-decryption --query 'Parameter.Value' --output text | jq '. | tostring')
sudo pritunl set app.sso_google_key "$SSO_GOOGLE_KEY"
sudo pritunl set app.sso_google_email "test@example.com"
sudo pritunl set app.server_sso_url "pritunl-vpn-test.example.com"
sudo pritunl set app.sso_org "671b59600c7720650fed44f8"

### MISCELANOUS ###
sudo pritunl set app.theme "dark"
sudo pritunl set app.auditting "all"
sudo pritunl set app.acme_domain "pritunl-vpn-test.example.com"
sudo pritunl set-host-id <REDACTED>

sudo systemctl restart pritunl

The single sign-on organization is stored as an ObjectID that command would store it as a string. There isn’t any support in the CLI code for setting ObjectIDs.

Thanks. We resolved the issue by inserting the correct value as ObjectID into MongoDB collection directly:

MONGOSH_ARCHIVE="mongosh-2.3.2-linux-x64"
wget "https://downloads.mongodb.com/compass/$MONGOSH_ARCHIVE.tgz" -qO- | tar -xz
sudo mv "$MONGOSH_ARCHIVE/bin/mongosh" /usr/local/bin
mongosh --eval 'db.settings.updateOne({_id: "app"}, {$set:{sso_org: ObjectId("<REDACTED>")}})' "$(sudo pritunl get-mongodb)"
# Pritunl systemd service must be restarted for the database change to apply.
sudo systemctl restart pritunl

We observed that despite restoring the MongoDB from dump, the app.sso_org gets lost after the initial server startup, requiring us to set it. If left unset, we receive 500s on SSO callbacks. Attaching the observed error logs from sudo pritunl logs:

[evening-thunder-3082][2024-10-27 10:33:47,049][ERROR] Error running task in queue
Traceback (most recent call last):
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/queue/queue.py", line 225, in run
    self.task()
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/queues/init_user.py", line 36, in task
    self.user.initialize()
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/user/user.py", line 272, in initialize
    self.org.write_file(
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/mongo/object.py", line 174, in write_file
    field_file.write(getattr(self, field))
TypeError: write() argument must be str, not None
  queue_id   = "671e170b0e6195367a5e3cc0"
  queue_type = "init_user"
[evening-thunder-3082][2024-10-27 10:33:47,066][ERROR] Exception on /sso/callback [GET]
Traceback (most recent call last):
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/flask/app.py", line 2190, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/flask/app.py", line 1486, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/flask/app.py", line 1484, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/flask/app.py", line 1469, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/auth/app.py", line 26, in _wrapped
    return call(*args, **kwargs)
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/handlers/sso.py", line 952, in sso_callback_get
    return _validate_user(username, email, sso_mode, org_id, groups,
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/handlers/sso.py", line 85, in _validate_user
    usr = org.new_user(name=username, email=email, type=CERT_CLIENT,
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/organization/organization.py", line 384, in new_user
    usr.queue_initialize(block=block,
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/user/user.py", line 345, in queue_initialize
    queue.start('init_user', block=block, org_doc=self.org.export(),
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/queue/utils.py", line 15, in start
    que.start(block=block, block_timeout=block_timeout)
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/queue/queue.py", line 132, in start
    raise QueueTaskError('Error occurred running ' +
pritunl.exceptions.QueueTaskError: Error occurred running queue task. {'queue_id': ObjectId('671e170b0e6195367a5e3cc0'), 'queue_type': 'init_user'}
[evening-thunder-3082][2024-10-27 10:33:47,066][ERROR] Exception on /sso/callback [GET]
Traceback (most recent call last):
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/flask/app.py", line 2190, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/flask/app.py", line 1486, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/flask/app.py", line 1484, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/flask/app.py", line 1469, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/auth/app.py", line 26, in _wrapped
    return call(*args, **kwargs)
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/handlers/sso.py", line 952, in sso_callback_get
    return _validate_user(username, email, sso_mode, org_id, groups,
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/handlers/sso.py", line 85, in _validate_user
    usr = org.new_user(name=username, email=email, type=CERT_CLIENT,
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/organization/organization.py", line 384, in new_user
    usr.queue_initialize(block=block,
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/user/user.py", line 345, in queue_initialize
    queue.start('init_user', block=block, org_doc=self.org.export(),
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/queue/utils.py", line 15, in start
    que.start(block=block, block_timeout=block_timeout)
  File "/usr/lib/pritunl/usr/lib/python3.9/site-packages/pritunl/queue/queue.py", line 132, in start
    raise QueueTaskError('Error occurred running ' +
pritunl.exceptions.QueueTaskError: Error occurred running queue task. {'queue_id': ObjectId('671e170b0e6195367a5e3cc0'), 'queue_type': 'init_user'}

Is there a reason that the SSO configuration gets reset during Pritunl’s first startup, regardless of the state of MongoDB?

The API should be used to automate configurations, the CLI isn’t intended to be used for that and only provides access to internal settings that are not intended to be directly modified. There is also a Pritunl Terraform Provider.