# Install Psono EE

Install instruction for the Enterprise 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'
    
    # 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 (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']
    
    # 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 a lot easier. You can execute it like:
    # docker run --rm \
    #  -v /opt/docker/psono/settings.yaml:/root/.psono_server/settings.yaml \
    #  -ti psono/psono-combo-enterprise:latest python3 psono/manage.py testldap username@something.com 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',
    #     },
    # ]
    #
    # 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'])
    # For SAML authentication, you also have to add 'SAML' to the array.
    # 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 are having Splunk and don't have a Splunk forwarder that can ship the logs, you can use Psono's native Splunk
    # implementation to ship the logs for you. In order for that to work you need a Splunk HTTP EVent Collector to be
    # configured as explained here https://dev.splunk.com/enterprise/docs/devtools/httpeventcollector/
    # Afterwards configure the following variables:
    # 
    # SPLUNK_HOST The host, e.g. an ip or a domain
    # SPLUNK_PORT The port, e.g. 8088 that you configured in the splunk http event collector
    # SPLUNK_TOKEN The token of your splunk http event collector
    # SPLUNK_INDEX The splunk index that you want the events to end up in By default 'main'
    # SPLUNK_PROTOCOL 'http' or 'https' to indicate the protocol. By default 'https'
    # SPLUNK_VERIFY True or False to indicate whether to verify certificates. By default True
    # SPLUNK_SOURCETYPE The source type. By default 'psono:auditLog' (that one is compatible with the provided splunk addons)
    #
    # More infos can be found here https://github.com/zach-taylor/splunk_handler
    
    # If you are having Logstash running and no way to ship logs with an external agent, you can use Psono's native Logstash
    # implementation to ship the logs for you. In order for that to work you need a Splunk HTTP EVent Collector to be
    # configured as explained here https://dev.splunk.com/enterprise/docs/devtools/httpeventcollector/
    # Afterwards configure the following variables:
    # 
    # LOGSTASH_HANDLER Shipping logs either async (logstash_async.handler.AsynchronousLogstashHandler) or in sync (logstash_async.handler.SynchronousLogstashHandler). By default 'logstash_async.handler.SynchronousLogstashHandler'
    # LOGSTASH_TRANSPORT The transport to use. TCP: logstash_async.transport.TcpTransport or UDP: logstash_async.transport.UdpTransport or Beats logstash_async.transport.BeatsTransport or HTTP logstash_async.transport.HttpTransport. Defaults to 'logstash_async.transport.TcpTransport'
    # LOGSTASH_HOST The host, e.g. an ip or a domain
    # LOGSTASH_PORT The port, e.g. 5959 that you configured the. By default 5959
    # LOGSTASH_SSL_ENABLED Wether you want to use SSL or not. By default True
    # LOGSTASH_SSL_VERIFY True or False whether to verify certificates. By default True
    # LOGSTASH_CA_CERTS If you want a custom CA, you can specify here a path to the file with the certs
    # LOGSTASH_CERFILE The path to the cert file
    # LOGSTASH_KEYFILE The path to the key file
    #
    # More infos can be found here https://python-logstash-async.readthedocs.io/en/stable/index.html
    
    # 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:
    # docker run --rm \
    #  -v /opt/docker/psono/settings.yaml:/root/.psono_server/settings.yaml \
    #  -ti psono/psono-combo-enterprise:latest 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
    
    # If you want to use OIDC, then you can configure it like this as a dictionary.
    # OIDC_CONFIGURATIONS:
    #     1:
    #         OIDC_RP_SIGN_ALGO: 'RS256'
    #         OIDC_RP_CLIENT_ID: 'whatever client id was provided'
    #         OIDC_RP_CLIENT_SECRET: 'whatever secret was provided'
    #         OIDC_OP_JWKS_ENDPOINT: 'https://example.com/jwks'
    #         OIDC_OP_AUTHORIZATION_ENDPOINT: 'https://example.com/authorize'
    #         OIDC_OP_TOKEN_ENDPOINT: 'https://example.com/token'
    #         OIDC_OP_USER_ENDPOINT: 'https://example.com/userinfo'
    #
    # Standard parameters explained:
    # OIDC_RP_SIGN_ALGO defaults to HS256 and needs to match the algo of your IDP
    # OIDC_RP_CLIENT_ID the client id that is provided by your IDP
    # OIDC_RP_CLIENT_SECRET the secret that is provided by your IDP
    # OIDC_OP_JWKS_ENDPOINT The JWKS endpoint of your IDP
    # OIDC_OP_AUTHORIZATION_ENDPOINT The authorization endpoint of your IDP
    # OIDC_OP_TOKEN_ENDPOINT The token endpoint of your IDP
    # 
    # other parameters are:
    # OIDC_VERIFY_JWT defaults to true, Controls whether Psono verifies the signature of the JWT tokens
    # OIDC_USE_NONCE defaults to true, Controls whether Psono uses nonce verification
    # OIDC_VERIFY_SSL defaults to true, Controls whether Psono verifies the SSL certificate of the IDP responses
    # OIDC_TIMEOUT defaults to 10, Defines a timeout for all requests in seconds to the IDP (fetch JWS, retrieve JWT tokens, userinfo endpoint))
    # OIDC_PROXY defaults to None, Defines a proxy for all requests to the IDP (fetch JWS, retrieve JWT tokens, Userinfo Endpoint). More infos can be found here https://requests.readthedocs.io/en/master/user/advanced/#proxies
    # OIDC_RP_SCOPES defaults to 'openid email', The OpenID Connect scopes to request during login.
    # OIDC_AUTH_REQUEST_EXTRA_PARAMS defaults to {}, Additional parameters to include in the initial authorization request.
    # OIDC_RP_IDP_SIGN_KEY defaults to None, Sets the key the IDP uses to sign ID tokens in the case of an RSA sign algorithm. Should be the signing key in PEM or DER format.
    # OIDC_ALLOW_UNSECURED_JWT defaults to False, Controls whether the Psono is going to allow unsecured JWT tokens (tokens with header {"alg":"none"}). This needs to be set to True if the IDP is returning unsecured JWT tokens and you want to accept them. See also https://tools.ietf.org/html/rfc7519#section-6
    # OIDC_TOKEN_USE_BASIC_AUTH defaults to False, Use HTTP Basic Authentication instead of sending the client secret in token request POST body.
    
    # 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. 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 something@something.com execute:

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

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

  4. Prepare the database

    docker run --rm \
      -v /opt/docker/psono/settings.yaml:/root/.psono_server/settings.yaml \
      -ti psono/psono-combo-enterprise:latest python3 ./psono/manage.py migrate
    
  5. 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.

  6. (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",
        ...
      }],
      ...
    }
    
  7. Run the Psono server container and expose the server port

    docker run --name psono-combo-enterprise \
        --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 \
        -v /path/to/log/folder:/var/log/psono \
        -d --restart=unless-stopped -p 10200:80 psono/psono-combo-enterprise: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.

  8. 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-enterprise:latest python3 ./psono/manage.py cleartoken >> /var/log/cron.log 2>&1
    
  9. 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. 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 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)
  • Outgoing TCP connection (usually 25, 465, 587 or 2525) to your email server