Install instruction for the Enterprise Edition of the Psono server

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 with Docker

  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-server: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: '302650c3c82f7111c2e8ceb660d32173cdc8c3d7717f1d4f982aad5234648fcb'
    PUBLIC_KEY: '02da2ad857321d701d754a7e60d0a147cdbc400ff4465e1f57bc2d9fbfeddf0b'
    
    # The URL of the web client (path to e.g activate.html without the trailing slash)
    # WEB_CLIENT_URL: 'https://www.psono.pw'
    
    # Switch DEBUG to false if you go into production
    DEBUG: False
    
    # Adjust this according to Django Documentation https://docs.djangoproject.com/en/1.10/ref/settings/
    ALLOWED_HOSTS: ['*']
    
    # Should be your domain without "www.". Will be the last part of the username
    ALLOWED_DOMAINS: ['psono.pw']
    
    # 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://www.psono.pw/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: '[email protected]'
    EMAIL_HOST: 'localhost'
    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:
    
    # 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 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'
    
    # Disables Throttling (necessary for unittests to pass) by overriding the cache with a dummy cache
    # https://docs.djangoproject.com/en/1.11/topics/cache/#dummy-caching-for-development
    # THROTTLING: False
    
    # The server will automatically connect to the license server to get a license for 10 users.
    # For paying customers we offer the opportunity to get an offline license code.
    #
    # LICENSE_CODE: |
    #   0abcdefg...
    #   1abcdefg...
    #   2abcdefg...
    #   3abcdefg...
    #   4abcdefg...
    #   5abcdefg...
    #   6abcdefg...
    #   7abcdefg...
    #   8abcdefg...
    
    # Enables the management API, required for the psono-admin-client / admin portal
    # MANAGEMENT_ENABLED: False
    
    # 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
    
    # Configures a system wide DUO connection for all clients
    # DUO_INTEGRATION_KEY: ''
    # DUO_SECRET_KEY: ''
    # DUO_API_HOSTNAME: ''
    
    # 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']
    
    # Enforce the user to setup a second factor
    # COMPLIANCE_ENFORCE_2FA: True
    
    # Disables recovery codes
    # COMPLIANCE_DISABLE_RECOVERY_CODES: True
    
    # Disables file repositories
    # COMPLIANCE_DISABLE_FILE_REPOSITORIES: True
    
    # Disables emergency codes
    # COMPLIANCE_DISABLE_EMERGENCY_CODES: True
    
    # Disables the possibility for a user to delete his own account
    # COMPLIANCE_DISABLE_DELETE_ACCOUNT: True
    
    # Disables the export of passwords
    # COMPLIANCE_DISABLE_EXPORT: True
    
    # Disables API keys
    # COMPLIANCE_DISABLE_API_KEYS: True
    
    # Disables Link shares
    # COMPLIANCE_DISABLE_LINK_SHARES: True
    
    # Prevents the use of the last X passwords. 0 disables it.
    # DISABLE_LAST_PASSWORDS: 0
    
    # If you want to use LDAP, then you can configure it like this
    #
    # 		LDAP_URL: Any valid LDAP string, preferable with ldaps. usual urls are 'ldaps://example.com:636' or 'ldap://192.168.0.1:389'
    #		LDAP_DOMAIN: Your LDAP domain, is added at the end of the username to form the full username
    #		LDAP_BIND_DN: One User that can be used to search your LDAP
    #		LDAP_BIND_PASS: The password of the user specified in LDAP_BIND_DN
    #		LDAP_ATTR_GUID: The uuid attribute. e.g. on Windows 'objectGUID', but common are 'GUID' or 'entryUUID', default 'objectGUID'
    #		LDAP_OBJECT_CLASS_USER: The objectClass value to filter user objects e.g. on Windows 'user', default 'user'
    #		LDAP_OBJECT_CLASS_GROUP: The objectClass value to filter group objects e.g. on Windows 'group', default 'group'
    #		LDAP_SEARCH_USER_DN: The "root" from which downwards we search for the users
    #		LDAP_SEARCH_GROUP_DN: The "root" from which downwards we search for the groups
    #		LDAP_ATTR_USERNAME: The username attribute to try to match against. e.g. on Windows 'sAMAccountName', default 'sAMAccountName'
    #		LDAP_ATTR_EMAIL: The attribute of the user objects that holds the mail address e.g. on Windows 'mail', default 'mail'
    #		LDAP_ATTR_GROUPS: The attribute of the user objects that holds the groups e.g. on Windows 'memberOf', default 'memberOf'
    #		LDAP_REQUIRED_GROUP : The attribute to restrict access / usage. Only members of these groups can connect e.g. ['CN=groupname,OU=something,DC=example,DC=com'], default []
    #		LDAP_CA_CERT_FILE: If you want to use ldaps and don't have a publicly trusted and signed certificate you can specify here the path to your ca certificate
    #
    #		LDAP_MEMBER_OF_OVERLAY: If your server has not this memberOf overlay, you can switch modes with this flag.
    #                               Users will be mapped (based on their LDAP_ATTR_GROUP_MEMBER_ATTRIBUTE attribute) to groups (based on their LDAP_ATTR_MEMBERS attribute), default True
    #		LDAP_ATTR_GROUP_MEMBER_ATTRIBUTE: The user attribute that will be used to map the group memberships, default 'uid'
    #		LDAP_ATTR_MEMBERS: The group attribute that will be used to map the to the users LDAP_ATTR_GROUP_MEMBER_ATTRIBUTE attribute, default 'memberUid'
    #
    # To help you setup LDAP, we have created a small "testldap" command that should make things alot easier. You can execute it like:
    # python3 psono/manage.py testldap [email protected] thePassWord
    #
    # For Windows AD it could look like this:
    #
    # LDAP : [
    #     {
    #         'LDAP_URL': 'ldaps://192.168.0.1:636',
    #         'LDAP_DOMAIN': 'example.com',
    #         'LDAP_BIND_DN': 'CN=LDAPPsono,OU=UsersTech,OU=example.com,DC=example,DC=com',
    #         'LDAP_BIND_PASS': 'hopefully_not_123456',
    #         'LDAP_SEARCH_USER_DN': 'OU=Users,OU=example.com,DC=example,DC=com',
    #         'LDAP_SEARCH_GROUP_DN': 'OU=Groups,OU=example.com,DC=example,DC=com',
    #     },
    # ]
    #
    # For OpenLDAP it could look like this:
    #
    # LDAP : [
    #     {
    #         'LDAP_URL': 'ldaps://192.168.0.1:636',
    #         'LDAP_DOMAIN': 'example.com',
    #         'LDAP_BIND_DN': 'CN=LDAPPsono,OU=UsersTech,OU=example.com,DC=example,DC=com',
    #         'LDAP_BIND_PASS': 'hopefully_not_123456',
    #         'LDAP_SEARCH_USER_DN': 'OU=Users,OU=example.com,DC=example,DC=com',
    #         'LDAP_SEARCH_GROUP_DN': 'OU=Groups,OU=example.com,DC=example,DC=com',
    #         'LDAP_OBJECT_CLASS_USER': 'posixAccount',
    #         'LDAP_OBJECT_CLASS_GROUP': 'posixGroup',
    #         'LDAP_ATTR_USERNAME': 'uid',
    #         'LDAP_ATTR_GUID': 'entryUUID',
    #     },
    # ]
    #
    # If your server does not have the memberOf overlay, then you can also do something like this
    #
    # LDAP : [
    #     {
    #         'LDAP_URL': 'ldaps://192.168.0.1:636',
    #         'LDAP_DOMAIN': 'example.com',
    #         'LDAP_BIND_DN': 'CN=LDAPPsono,OU=UsersTech,OU=example.com,DC=example,DC=com',
    #         'LDAP_BIND_PASS': 'hopefully_not_123456',
    #         'LDAP_SEARCH_USER_DN': 'OU=Users,OU=example.com,DC=example,DC=com',
    #         'LDAP_SEARCH_GROUP_DN': 'OU=Groups,OU=example.com,DC=example,DC=com',
    #         'LDAP_OBJECT_CLASS_USER': 'posixAccount',
    #         'LDAP_OBJECT_CLASS_GROUP': 'posixGroup',
    #         'LDAP_ATTR_USERNAME': 'uid',
    #         'LDAP_ATTR_GUID': 'entryUUID',
    #         'LDAP_MEMBER_OF_OVERLAY': False,
    #         'LDAP_ATTR_GROUP_MEMBER_ATTRIBUTE': 'uid',
    #         'LDAP_ATTR_MEMBERS': 'memberUid',
    #     },
    # ]
    #
    # ATTENTION: API kays currently bypass LDAP authentication, that means API keys can still access secrets even if the
    # user was disabled in LDAP. API keys can be disabled with COMPLIANCE_DISABLE_API_KEYS
    
    # You also have to comment in the line below if you want to use LDAP (default: ['AUTHKEY'])
    # AUTHENTICATION_METHODS: ['AUTHKEY', 'LDAP']
    
    # Enable Audit logging
    # LOGGING_AUDIT: True
    
    # To log to another destination you can specify this here, default '/var/log/psono'
    # Never really necessary, as we will run the psono server in a docker container and can mount /var/log/psono to any
    # location on the underlying docker host.
    # LOGGING_AUDIT_FOLDER: '/var/log/psono'
    
    # If you prefer server time over utc, you can do that like below (default 'time_utc')
    # LOGGING_AUDIT_TIME: 'time_server'
    
    # If the server logs too much for you can either whitelist or blacklist events by their event code. (default: [])
    # LOGGING_AUDIT_WHITELIST: []
    # LOGGING_AUDIT_BLACKLIST: []
    
    # If you want to use SAML, then you can configure it like this as a dictionary.
    #
    # About the parameters:
    #   idp->entityId: Thats the url to the metadata of your IDP
    #   idp->singleLogoutService->url: Thats the url to the logout service of your IDP
    #   idp->singleSignOnService->url: Thats the url to the single sign-on service of your IDP
    #   idp->x509cert: Thats the certificate of your IDP
    #   idp->groups_attribute: The attribute in the SAML response that holds your groups
    #   idp->username_attribute: The attribute in the SAML response that holds the username. If you put here null, then it will use the NameID
    #   idp->email_attribute: The attribute in the SAML response that holds the email address.
    #   idp->username_domain: The domain that is appended to the provided username, if the provided username is not already in email format.
    #   idp->required_group: A list of group names (casesensitive) in order to restrict who can use SAML login with this installation. Leave empty for no restriction.
    #   idp->is_adfs: If you are using ADFS.
    #   idp->honor_multifactors: Multifactor authentication can be bypassed with this flag for all SAML users (e.g. when you already enforce multifactor on the SAML provider).
    #   idp->max_session_lifetime: The time in seconds that a session created throught SAML will live
    #
    #   sp->NameIDFormat: The normal nameformat parameter. (should only be set to transient if you have set a username attribute with username_attribute)
    #   sp->attributeConsumingService: Only necessary if the IDP needs to be told to send some specific attributes
    #   sp->x509cert: The X.509 cert
    #   sp->privateKey: The corresponding private key of the X.509 cert
    #
    # There are a couple of more options next to those required ones below.
    # More information can be found here https://github.com/onelogin/python3-saml
    #
    # A self-signed certificate can be generated with:
    # openssl req -new -newkey rsa:2048 -x509 -days 3650 -nodes -sha256 -out sp_x509cert.crt -keyout sp_private_key.key
    #
    # To help you setup SAML, we have created a small "testsaml" command that should make things easier. You can execute it like:
    # python3 psono/manage.py testsaml
    #
    # The number 1 in line 2 is the provider id. Users are matched by the constructed username.
    #
    # SAML_CONFIGURATIONS:
    #     1:
    #         idp:
    #             entityId: "https://idp.exampple.com/metadata.php"
    #             singleLogoutService:
    #                 binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
    #                 url: "https://idp.exampple.com/SingleLogoutService.php"
    #             singleSignOnService:
    #                 binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
    #                 url: "https://idp.exampple.com/SingleSignOnService.php"
    #             x509cert: "ABC...=="
    #             groups_attribute: "groups"
    #             username_attribute: 'username'
    #             email_attribute: 'email'
    #             username_domain: 'example.com'
    #             required_group: []
    #             is_adfs: false
    #             honor_multifactors: true
    #             max_session_lifetime: 86400
    #         sp:
    #             NameIDFormat: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
    #             assertionConsumerService:
    #                 binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
    #             attributeConsumingService:
    #                 serviceName: "Psono"
    #                 serviceDescription: "Psono password manager"
    #                 requestedAttributes:
    #                     -
    #                         attributeValue: []
    #                         friendlyName: ""
    #                         isRequired: false
    #                         name: "attribute-that-has-to-be-requested-explicitely"
    #                         nameFormat: ""
    #             privateKey: "ABC...=="
    #             singleLogoutService:
    #                 binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
    #             x509cert: "ABC...=="
    #         strict: true
    #
    # You need a couple of urls to configure the IDP correctly. If the server is accessible under https://example.com/server
    # (e.g. https://example.com/server/healthcheck/ shows some json output) and the provider id is 1 as in the example
    # above the folling urls are valid:
    #
    # for metadata :                   https://example.com/server/saml/1/metadata/
    # for assertion consumer service : https://example.com/server/saml/1/acs/
    # for single logout service :      https://example.com/server/saml/1/sls/
    #
    #
    # ATTENTION: API kays currently bypass SAML authentication, that means API keys can still access secrets even if the
    # user was disabled in SAML. API keys can be disabled with COMPLIANCE_DISABLE_API_KEYS
    
    # 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',
                ],
            },
        },
    ]
    
  2. Create the local logging directory for the audit logs

    sudo mkdir -p /var/log/psono
    
  3. 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 [email protected] execute:

    docker run --rm \
      -v /path/to/modified/settings.yaml:/root/.psono_server/settings.yaml \
      -ti psono/psono-server-enterprise:latest python3 ./psono/manage.py sendtestmail [email protected]
    

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

  4. Prepare the database

    docker run --rm \
      -v /path/to/modified/settings.yaml:/root/.psono_server/settings.yaml \
      -ti psono/psono-server-enterprise:latest python3 ./psono/manage.py migrate
    
  5. Run the dockered psono server image and expose the server port

    docker run --name psono-server-enterprise \
        --sysctl net.core.somaxconn=65535 \
        -v /path/to/modified/settings.yaml:/root/.psono_server/settings.yaml \
        -v /path/to/log/folder:/var/log/psono \
        -d --restart=unless-stopped -p 10100:80 psono/psono-server-enterprise:latest
    

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

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

    If not, please make sure you have no firewall on the server blocking you.

  6. Setup cleanup job

    Execute the following command:

    crontab -e
    

    and add the following line:

    30 2 * * * docker run --rm -v /path/to/modified/settings.yaml:/root/.psono_server/settings.yaml -ti psono/psono-server-enterprise:latest python3 ./psono/manage.py cleartoken >> /var/log/cron.log 2>&1
    
  7. 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)
  • Incoming TCP connection (usually 443) from your SAML server (only necessary if you are using SAML)
  • 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.
  • Outgoing TCP 443 connection to api*.yubico.com: Psono validates YubiKey OTP tokens against the official Yubikey Servers. You can use an 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.
  • Outgoing TCP connection (usually 636) to your LDAP server (only necessary if you are using LDAP)
  • Outgoing TCP connection (usually 443) to your SAML server (only necessary if you are using SAML)
Edit me