# Install Psono CE

Installation instruction for the Community Edition of Psono, using Psono's combo image which bundles the server, web client and admin portal.

# Preamble

At this point we assume that you already have a postgres database running, ready for connections. If not follow this guide to setup postgres.

# Installation

  1. Create a settings.yaml in e.g. /opt/docker/psono/ with the following content

    # generate the following six parameters with the following command
    # docker run --rm -ti psono/psono-combo:latest python3 ./psono/manage.py generateserverkeys
    SECRET_KEY: 'SOME SUPER SECRET KEY THAT SHOULD BE RANDOM AND 32 OR MORE DIGITS LONG'
    ACTIVATION_LINK_SECRET: 'SOME SUPER SECRET ACTIVATION LINK SECRET THAT SHOULD BE RANDOM AND 32 OR MORE DIGITS LONG'
    DB_SECRET: 'SOME SUPER SECRET DB SECRET THAT SHOULD BE RANDOM AND 32 OR MORE DIGITS LONG'
    EMAIL_SECRET_SALT: '$2b$12$XUG.sKxC2jmkUvWQjg53.e'
    PRIVATE_KEY: '02...0b'
    PUBLIC_KEY: '02...0b'
    
    # The URL of the web client (path to e.g activate.html without the trailing slash)
    # WEB_CLIENT_URL: 'https://psono.example.com'
    
    # Switch DEBUG to false if you go into production
    DEBUG: False
    
    # Adjust this according to Django Documentation https://docs.djangoproject.com/en/2.2/ref/settings/
    ALLOWED_HOSTS: ['*']
    
    # Should be your domain without "www.". Will be the last part of the username
    ALLOWED_DOMAINS: ['example.com']
    
    # If you want to disable registration, you can comment in the following line
    # ALLOW_REGISTRATION: False
    
    # If you want to disable the lost password functionality, you can comment in the following line
    # ALLOW_LOST_PASSWORD: False
    
    # If you want to enforce that the email address and username needs to match upon registration
    # ENFORCE_MATCHING_USERNAME_AND_EMAIL: False
    
    # If you want to restrict registration to some email addresses you can specify here a list of domains to filter
    # REGISTRATION_EMAIL_FILTER: ['company1.com', 'company2.com']
    
    # Should be the URL of the host under which the host is reachable
    # If you open the url and append /info/ to it you should have a text similar to {"info":"{\"version\": \"....}
    HOST_URL: 'https://psono.example.com/server'
    
    # The email used to send emails, e.g. for activation
    # ATTENTION: If executed in a docker container, then "localhost" will resolve to the docker container, so
    # "localhost" will not work as host. Use the public IP or DNS record of the server.
    EMAIL_FROM: 'the-mail-for-for-example-useraccount-activations@test.com'
    EMAIL_HOST: 'smtp.example.com'
    EMAIL_HOST_USER: ''
    EMAIL_HOST_PASSWORD : ''
    EMAIL_PORT: 25
    EMAIL_SUBJECT_PREFIX: ''
    EMAIL_USE_TLS: False
    EMAIL_USE_SSL: False
    EMAIL_SSL_CERTFILE:
    EMAIL_SSL_KEYFILE:
    EMAIL_TIMEOUT: 10
    
    # In case one wants to use mailgun, comment in below lines and provide the mailgun access key and server name
    # EMAIL_BACKEND: 'anymail.backends.mailgun.EmailBackend'
    # MAILGUN_ACCESS_KEY: ''
    # MAILGUN_SERVER_NAME: ''
    
    # In case you want to offer Yubikey support, create a pair of credentials here https://upgrade.yubico.com/getapikey/
    # and update the following two lines before commenting them in
    # YUBIKEY_CLIENT_ID: '123456'
    # YUBIKEY_SECRET_KEY: '8I65IA6ASDFIUHGIH5021FKJA='
    
    # If you have your own Yubico servers, you can specify here the urls as a list
    # YUBICO_API_URLS: ['https://api.yubico.com/wsapi/2.0/verify']
    
    # Cache enabled without belows Redis may lead to unexpected behaviour
    
    # Cache with Redis
    # By default you should use something different than database 0 or 1, e.g. 13 (default max is 16, can be configured in
    # redis.conf) possible URLS are:
    #    redis://[:password]@localhost:6379/0
    #    rediss://[:password]@localhost:6379/0
    #    unix://[:password]@/path/to/socket.sock?db=0
    # CACHE_ENABLE: False
    # CACHE_REDIS: False
    # CACHE_REDIS_LOCATION: 'redis://127.0.0.1:6379/13'
    
    # Enables the management API, required for the psono-admin-client / admin portal (Default is set to False)
    MANAGEMENT_ENABLED: True
    
    # Enables the fileserver API, required for the psono-fileserver
    # FILESERVER_HANDLER_ENABLED: False
    
    # Enables files for the client
    # FILES_ENABLED: False
    
    # Allows that users can search for partial usernames
    # ALLOW_USER_SEARCH_BY_USERNAME_PARTIAL: True
    
    # Allows that users can search for email addresses too
    # ALLOW_USER_SEARCH_BY_EMAIL: True
    
    # Disables central security reports
    # DISABLE_CENTRAL_SECURITY_REPORTS: True
    
    # Configures a system wide DUO connection for all clients
    # DUO_INTEGRATION_KEY: ''
    # DUO_SECRET_KEY: ''
    # DUO_API_HOSTNAME: ''
    
    # If you are using the DUO proxy, you can configure here the necessary HTTP proxy
    # DUO_PROXY_HOST: 'the-ip-or-dns-name-goes-here'
    # DUO_PROXY_PORT: 80
    # DUO_PROXY_TYPE: 'CONNECT'
    # If your proxy requires specific headers you can also configure these here
    # DUO_PROXY_HEADERS: ''
    
    # Normally only one of the configured second factors needs to be solved. Setting this to True forces the client to solve all
    # MULTIFACTOR_ENABLED: True
    
    # Allows admins to limit the offered second factors in the client
    # ALLOWED_SECOND_FACTORS: ['yubikey_otp', 'google_authenticator', 'duo', 'webauthn']
    
    # Your Postgres Database credentials
    # ATTENTION: If executed in a docker container, then "localhost" will resolve to the docker container, so
    # "localhost" will not work as host. Use the public IP or DNS record of the server.
    DATABASES:
        default:
            'ENGINE': 'django.db.backends.postgresql_psycopg2'
            'NAME': 'psono'
            'USER': 'psono'
            'PASSWORD': 'password'
            'HOST': 'localhost'
            'PORT': '5432'
    # for master / slave replication setup comment in the following (all reads will be redirected to the slave
    #    slave:
    #        'ENGINE': 'django.db.backends.postgresql_psycopg2'
    #        'NAME': 'YourPostgresDatabase'
    #        'USER': 'YourPostgresUser'
    #        'PASSWORD': 'YourPostgresPassword'
    #        'HOST': 'YourPostgresHost'
    #        'PORT': 'YourPostgresPort'
    
    # The path to the template folder can be "shadowed" if required later
    TEMPLATES: [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': ['/root/psono/templates'],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]
    

    Update database credentials / secrets / paths like described in the comments

  2. Test E-Mail

    The most tedious step is usually for me to get e-mail working. To make this step easier, we offer a small test script which will send a test e-mail.

    To send a test e-mail to something@something.com execute:

    docker run --rm \
      -v /opt/docker/psono/settings.yaml:/root/.psono_server/settings.yaml \
      -ti psono/psono-combo:latest python3 ./psono/manage.py sendtestmail something@something.com
    

    If you receive this test e-mail, it should be configured properly.

  3. Prepare the database

    docker run --rm \
      -v /opt/docker/psono/settings.yaml:/root/.psono_server/settings.yaml \
      -ti psono/psono-combo:latest python3 ./psono/manage.py migrate
    
  4. Create a config.json with the following content in e.g. /opt/docker/psono-client/:

    {
      "backend_servers": [{
        "title": "Psono.pw",
        "url": "https://psono.example.com/server"
      }],
      "base_url": "https://psono.example.com/",
      "allow_custom_server": true,
      "allow_registration": true,
      "allow_lost_password": true,
      "disable_download_bar": false,
      "authentication_methods": ["AUTHKEY", "LDAP"],
      "saml_provider": []
    }
    

    Adjust the title and domain of the URLs accordingly.

    TIP

    If you want to configure your admin portal different, them create a second config.json.

  5. (optional) Change domain for login

    You don't see it yet, but later in the login form your username will be a composition of something that the user defines, and a domain, forming the full username that looks similar to an email address (e.g. something@example.com).

    The client will parse the base_url parameter from the config.json. Sometimes you would want to specify a different domain. You can do that by adjusting the config.json as shown below:

    {
      "backend_servers": [{
        ...
        "domain": "other.com",
        ...
      }],
      ...
    }
    
  6. Run the Psono server container and expose the server port

    docker run --name psono-combo \
        --sysctl net.core.somaxconn=65535 \
        -v /opt/docker/psono/settings.yaml:/root/.psono_server/settings.yaml \
        -v /opt/docker/psono-client/config.json:/usr/share/nginx/html/config.json \
        -v /opt/docker/psono-client/config.json:/usr/share/nginx/html/portal/config.json \
        -d --restart=unless-stopped -p 10200:80 psono/psono-combo:latest
    

    This will start the Psono server on port 10200. If you open now http://your-ip:10200/server/info/ you should see something like this:

    {"info":"{\"version\": \"....}
    

    If you don't, please make sure no firewall is blocking your request.

  7. Setup cleanup job

    Execute the following command:

    crontab -e
    

    and add the following line:

    30 2 * * * docker run --rm -v /opt/docker/psono/settings.yaml:/root/.psono_server/settings.yaml -ti psono/psono-combo:latest python3 ./psono/manage.py cleartoken >> /var/log/cron.log 2>&1
    
  8. Setup Reverse Proxy

    To run the Psono password manager in production, a reverse proxy is needed, to handle the ssl offloading and glue the Psono server and webclient together. Follow the guide to setup reverse proxy as a next step.

# Note: Installation behind Firewall

If you have put your installation behind a firewall, you have to whitelist some ports / adjust some settings, that all features work:

  • Incoming TCP connections (usually Port 443) from clients to servers
  • Incoming TCP connections (usually Port 443) from fileservers to servers
  • Incoming TCP connections (usually Port 80) from clients to servers (will do the redirect to https)
  • Outgoing TCP 5432 to database server
  • Outgoing TCP / UDP 123 connection to time.google.com: Psono requires a synced time for various reasons (Google Authenticator, YubiKey, Throttling, Replay protection, ...) Therefore it has a healthcheck, to compare the local time to a time server (by default time.google.com). You can specify your own timeserver in the settings.yaml with the "TIME_SERVER" parameter. If you are confident that your server always has the correct time you can also disable the healthcheck with "HEALTHCHECK_TIME_SYNC_ENABLED: False".
  • Outgoing TCP 443 connection to api*.yubico.com: Psono validates YubiKey OTP tokens against the official Yubikey Servers. You can use your own YubiKey server with the "YUBICO_API_URLS" parameter in the settings.yaml
  • Outgoing TCP 443 connection to api*.duosecurity.com: Psono initiates connection to the Duo api server, whenever someone uses Duo's 2 Factor authentication. Psono also supports Duo's auth proxy duo.com/docs/authproxy-reference (opens new window) by configuring DUO_PROXY_HOST, DUO_PROXY_PORT and DUO_PROXY_TYPE
  • Outgoing TCP connection (usually 25, 465, 587 or 2525) to your email server