Prerequisites
Hardware
Here is the minimal recommended server configuration.
Please contact us for more precise recommendations depending on your signature throughput.
- 1 core CPU
- 2 GB RAM
- 20 GB space available
Software
- Java 17 or higher (latest is recommended).
- Latest version of Elasticsearch 7.x.
- ClamAV. Note that the ClamAV daemon must be accessible over a TCP socket.
Security considerations
The following security measures MUST be taken:
-
The application must be accessible over HTTPS through a reverse proxy. The application must not be exposed directly on the Internet.
-
The server must be protected against unauthorized access. Particularly, access to its administration interfaces and its storage devices must be restricted to authorized admins only. In addition, the jar file of the application must never, under any circumstances, be modified.
Deployment
Run the application
This application is a fully executable Spring Boot application.
In most operating system, it can be run with the following command, assuming Java is installed properly:
java -jar webapp.jar
In a Unix/Linux based operating system, you can simply run:
./webapp.jar
Use
java -Xmx1024m -jar webapp.jar
to change the maximum JVM memory usage.
Configuration properties
Configuration properties can be passed to the application as command line arguments starting with --
.
For example, the listening port of the server can be specified as follows:
java -jar webapp.jar --server.port=8080
Or directly:
./webapp.jar --server.port=8080
Jump to the application configuration for the list of available configuration properties.
Systemd service
Most of modern Linux distributions make use of systemd to create background services and have them automatically started at server boot and restarted on unexpected termination.
To create a systemd service, first we create the sunnystamp
user by running the following commands:
groupadd -r sunnystamp
useradd -r -g sunnystamp -d /home/sunnystamp -s /bin/bash sunnystamp
mkdir -p /home/sunnystamp
chown sunnystamp:sunnystamp /home/sunnystamp
Then we create a systemd unit file with name webapp.service
and we place it in /etc/systemd/system
:
[Unit]
Description=Lex Persona web application
After=syslog.target
[Service]
User=sunnystamp
Environment="LC_ALL=C"
ExecStart=/usr/bin/java -XX:MaxRAMPercentage=40.0 -XX:+ExitOnOutOfMemoryError -jar /path/to/webapp.jar --server.port=8080
SuccessExitStatus=143
Restart=on-failure
[Install]
WantedBy=multi-user.target
You can specify different path and parameters for the
ExecStart
field.
Now we can start the service with the following commands:
# Load the service
systemctl daemon-reload
# Start the service now
systemctl start webapp.service
# Start the service automatically at boot
systemctl enable webapp.service
Windows service
On Windows operating system, you can wrap the application as a Windows service by following these steps:
- Download winsw. Make sure to choose the correct version depending on your version of .NET.
- Rename the winsw executable to
webapp.exe
. - Create an XML file named
webapp.xml
with the following content:<?xml version="1.0" encoding="UTF-8"?> <service> <id>webapp</id> <name>webapp</name> <description>Lex Persona web application</description> <executable>java</executable> <arguments>-jar "webapp.jar"</arguments> <logmode>rotate</logmode> </service>
- Create a folder with
webapp.exe
,webapp.jar
andwebapp.xml
inside. - Install the windows service by running the following command inside the folder.
The service should now appear in Windows Service Manager.webapp.exe install
Docker container
To create a docker image for the application, run the following command in the folder where the Dockerfile
and the webapp.jar
reside:
docker build -t sunnystamp/webapp .
To run the application in foreground and expose the listening port on the host:
docker run -p 8080:8080 -it sunnystamp/webapp --server.port=8080
Note that you will need to create volumes for the different folders in the application configuration.
For example, here is how you would mount your client folder in the container:
docker run -p 8080:8080 -v /path/to/clients:/clients -it sunnystamp/webapp \
--server.port=8080 \
--clientFolderPath=/clients
Clustering
For resiliency purpose, you can replicate the application in a cluster environment simply by deploying the executable on different machines, placing them behind a load balancer. For such deployments, the following requirements must be met:
-
The clocks on the different machines must be synced periodically. This can be achieved using an NTP server.
-
Some folders used in the application configuration must be shared across the nodes. This can be achieved using an NFS or a CIFS server.
Note: File attribute caching must be disabled for reads/writes to work properly. In case of an NFS server, the mount option
noac
must be specified. For a CIFS server, the mount optionactimeo=0
must be specified.
As a suggestion, the client folder can be shared across the nodes, with read-only access. This will allow you to manage the client configuration in a centralized way.
Port requirements
The Workflow Manager needs TCP port 8885 to be accessible by the reverse proxy. This port number can be changed via the server.port
property in the application configuration.
The Workflow Manager also needs access to:
- The Elasticsearch server, on the port exposed by Elasticsearch (the default port is TCP 9200).
- The ClamAV daemon, on the port exposed by ClamAV (the default port is TCP 3310).
Furthermore, the application requires both HTTP (TCP, port 80) and HTTPS (TCP, port 443) access to the internet for the following features to work properly:
- Access the timestamp server in order to produce timestamped signatures.
- Download CRLs and access OCSP responders in order to check the revocation status of signer and CA certificates.
Also, in order to customize the consent pages for your clients, the application will make connections over HTTPS (TCP, port 443) to the Evidence Manager URL.
Proxy configuration
If you want the application to connect to the outside world through a proxy, you can specify the host and port of the proxy as command line arguments, as well as the user name and password if your proxy requires an authentication:
java -jar webapp.jar \
--proxyHost=my-proxy.local \
--proxyPort=3128 \
--proxyUser=my_proxy_user \
--proxyPassword=my_proxy_password
Note that the application will create a TCP tunnel through the proxy for any outbound HTTP or HTTPS connections, so you must ensure that the HTTP CONNECT method is allowed. Typically on a Squid proxy, the following rule should be removed, or overridden:
# Deny CONNECT to other than secure SSL ports
http_access deny CONNECT !SSL_ports
HTTPS configuration
You must deploy the application behind a reverse proxy so that it is not exposed directly on the internet. If you also need, you can enable HTTPS in the application itself using the application configuration:
java -jar webapp.jar --server.port=443 --server.ssl.enabled=true --server.ssl.key-store=keystore.p12 --server.ssl.key-store-password=change_me --server.ssl.keyStoreType=PKCS12 --server.ssl.key-alias=mykey
Use
java -jar webapp.jar -Djavax.net.debug=ssl:handshake:verbose
to debug issues with TLS handshakes.
Web application firewall
If you want to use a web application firewall (WAF) in front of the application for security measures, you must ensure that your WAF does not block HTTP requests going to the application for the following methods:
GET
POST
PUT
DELETE
PATCH
OPTIONS
Also, since the application makes use of CORS, your WAF should not suppress or modify the following headers in the HTTP requests going to the application:
Origin
Access-Control-Request-Method
Access-Control-Request-Headers
And the following headers in the HTTP responses coming from the application:
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Access-Control-Max-Age
Backup
The Workflow Manager persistence is composed of:
- Folders in the file system: store workflow documents and other tenant resources.
- Elasticsearch cluster: indexes the workflows, users and tenants metadata.
Folder backup
A folder backup is a backup of the following folders (see the application configuration for their default location):
exportFolderPath
resourceFolderPath
Note that it’s not required to perform atomic backups of your file system. You are free to make snapshots if your file system supports it, or to perform file-level backups of the folders (with the rsync
command for instance).
Elasticsearch snapshot
Please refer to the official Elasticsearch documentation to learn how to snapshot your Elasticsearch cluster.
Disaster recovery
In a disaster recovery scenario, you should restore both a folder backup and an Elasticsearch snapshot.
Note that to prevent inconsistencies, you should always restore an Elasticsearch snapshot that was made before the folder backup you want to restore. Also note that you should not restore an Elasticsearch snapshot that was made too long before the folder backup. The garbageMaxAge
property in the application configuration governs the delay after which files are purged from the folders once they are no longer used. By default, files are purged after 12 hours, which means:
- You have to make sure the delay between your Elasticsearch snapshot and your folder backup is less than 12 hours. It’s recommended to create a folder backup right after each Elasticsearch snapshot.
- You can always restore an Elasticsearch snapshot that was created in the last 12 hours without the need to restore a folder backup.
- You don’t need to keep folder backups that are older than your oldest Elasticsearch snapshot.
Configuration
Application configuration
The application configuration can be changed via configuration properties.
You can specify configuration properties using command line arguments starting with --
as described earlier.
Key | Default value | Description |
---|---|---|
resourceFolderPath |
${user.home}/sgs-wm-data/resources/ | Folder where tenant resources are stored. In a cluster, this folder MUST be shared across the nodes. |
requestFolderPath |
${user.home}/sgs-wm-data/requests/ | Folder where request files are stored. In a cluster, this folder MUST be shared across the nodes. |
exportFolderPath |
${user.home}/sgs-wm-data/exports/ | Folder where exports are stored. In a cluster, this folder MUST be shared across the nodes. |
tempFolderPath |
${user.home}/sgs-wm-data/temp/ | Temporary folder. This folder MUST NOT be shared across the nodes of a cluster. |
signedDataFolderPath |
signed_data/ | Folder used to store signed data into ZIP archive |
logMaxAge |
2592000000 | Maximum age for log entries (30 days by default). |
garbageMaxAge |
43200000 | Delay after which files scheduled for deletion are actually deleted from disk (12 hours by default). |
webhookEventMaxAge |
2592000000 | Maximum age for webhook events (30 days by default). |
accessTokenHashIterations |
10000 | Number of iterations when hashing access tokens. |
wmUrl |
URL of the Workflow Manager, for development. Leave empty to allow tenant resolution based on domain name using the X-Forwarded-Host header. Only localhost URLs are allowed. | |
uiUrl |
URL to the static UI files, for development. Only localhost URLs are allowed. | |
adminsPasswdPath |
${user.home}/sgs-wm-data/admins.passwd | Path to the admins.passwd file. |
confScanRate |
30000 | Configuration change detection frequency (every 30 seconds by default). |
logging.file.name |
${user.home}/sgs-wm-data/logs/wm.log | Path to the log file. |
server.port |
8885 | Listening port of the server. |
smtpServerName |
localhost | Email server hostname |
smtpServerPort |
25 | Email server port |
smtpServerUsername |
Username for email server authentication | |
smtpServerPassword |
Password for email server authentication | |
smtpServerStartTls |
false | Whether or not the email server supports StartTLS |
smtpSenderEmail |
no-reply@sunnystamp.com | The email address to use as sender for all emails |
clamavServerName |
localhost | Clamav server hostname |
clamavServerPort |
3310 | Clamav server port |
proxyHost |
The host name of the proxy server, if any. | |
proxyPort |
80 | The port number of the proxy server. |
proxyUser |
The user name for proxy authentication. | |
proxyPassword |
The user password for proxy authentication. | |
server.ssl.enabled |
false | Whether or not to enable HTTPS. |
server.ssl.keyStoreType |
JKS | Type of keystore to be used to configure HTTPS. Possible values: JKS , PKCS12 . |
server.ssl.key-store |
Path to the keystore to be used to configure HTTPS | |
server.ssl.key-store-password |
Password of the keystore to be used to configure HTTPS | |
server.ssl.key-alias |
Alias of the key to be used to configure HTTPS | |
server.ssl.key-password |
Password of the key to be used to configure HTTPS | |
spring.codec.max-in-memory-size |
2000000 | Maximum size of buffers in RAM |
elasticsearchUrls |
http://localhost:9200 | Comma separated list of endpoint URLs to connect to Elasticsearch |
helios.namespace |
XADES_122 | Helios configuration Namespace |
helios.policy.digestAlgorithm |
SHA256 | The digest algorithm of the XAdES detached signature policy |
helios.policy.id |
1.2.250.1.131.1.5.18.21.1.7 | The OID of the Helios signature policy |
helios.policy.digestValue |
GbP1WjbTrHp6h9zlsz5RN7AqkJbnDNDOAQzgm1qzIJ4= | The base64 encoded digest of the Helios signature policy |
helios.policy.uri |
https://www.collectivites-locales.gouv.fr/files/finances_locales/dematerialisation/ps_helios_dgfip.pdf | The URL of the Helios signature policy |
helios.canonicalizationMethod.keyInfo |
EXCLUSIVE | Canonicalizations |
xadesDetached.namespace |
XADES_132 | XAdES detached configuration Namespace |
xadesDetached.policy.digestAlgorithm |
SHA256 | The digest algorithm of the XAdES detached signature policy |
xadesDetached.policy.id |
1.2.250.1.115.200.300.4 | The OID of the XAdES detached signature policy |
xadesDetached.policy.digestValue |
lLKFDAaR8NxNtrf1cIlmULQgp1GS+7igKN8p7pqy3G0= | The base64 encoded digest of the XAdES detached signature policy |
xadesDetached.policy.uri |
https://sites.banque-france.fr/igc/signature/ps/ps_1_2_250_1_115_200_300_4.pdf | The URL of the XAdES detached signature policy |
xadesDetached.canonicalizationMethod.keyInfo |
EXCLUSIVE | Canonicalizations |
cades.policy.digestAlgorithm |
SHA256 | CAdES configuration The digest algorithm of the CAdES signature policy |
cades.policy.id |
1.2.250.1.115.200.300.4 | The OID of the CAdES signature policy |
cades.policy.digestValue |
lLKFDAaR8NxNtrf1cIlmULQgp1GS+7igKN8p7pqy3G0= | The base64 encoded digest of the CAdES signature policy |
cades.policy.uri |
https://sites.banque-france.fr/igc/signature/ps/ps_1_2_250_1_115_200_300_4.pdf | The URL of the CAdES signature policy |
cadesDetached.policy.digestAlgorithm |
SHA256 | CAdES detached configuration The digest algorithm of the CAdES detached signature policy |
cadesDetached.policy.id |
1.2.250.1.115.200.300.4 | The OID of the CAdES detached signature policy |
cadesDetached.policy.digestValue |
lLKFDAaR8NxNtrf1cIlmULQgp1GS+7igKN8p7pqy3G0= | The base64 encoded digest of the CAdES detached signature policy |
cadesDetached.policy.uri |
https://sites.banque-france.fr/igc/signature/ps/ps_1_2_250_1_115_200_300_4.pdf | The URL of the CAdES detached signature policy |
pdftronCustomKey |
Custom PDFTron licence Key, for development | |
tsaUrlQualified |
https://tsa.sunnystamp.com/tsa | RFC 3161 compliant timestamp server. |
tsaUserName |
User name timestamp server. | |
tsaPassword |
Password timestamp server. | |
email.validation.pattern |
This property sets the regular expression (regex) used for validating email addresses If not specified, the application uses a default regex for email validation “^[_A-Za-z0-9-\+\‘]+(\.[_A-Za-z0-9-\+\’]+)@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)(\.[A-Za-z]{2,})$” |
${user.home}
is the home folder of the user running the application, typically/home/sunnystamp
if you have created a systemd service.Please note that application configuration changes can NOT be applied without restarting the application.
Admin interfaces
The admin interfaces (API and GUI) can be enabled simply by declaring at least one admin user in the admins.passwd
configuration file. The path of the admins.passwd
file is defined in the application configuration.
The admins.passwd
file allows us to declare admin users with their respective password hashes in a Unix fashion way. For example, here is how you would declare two admin users (john
and jack
) in the admins.passwd
file:
john:$6$rounds=100000$GAOcXXTBkR0OcaQk$bnCMTTOWc5jw5fQ7VziaHQEpUVM/KHWKs5RUcA8blRtehYKanOS311GeGdo6TEa9okLw2xF4jw.3doxUPPY6o0
jack:$6$rounds=100000$4DUwSzeHJRevBuKW$3vfincGEEAMCRVfQwRACWgUwTQjokUQcMiCsmxtXlvsQAVApka.GJ8xkfARXQzsjxITUQ16j6YzrdUvMyJcNz/
Use one of the following Unix commands to produce your password hash:
# Using mkpasswd: mkpasswd -m sha-512 # Or using openssl: openssl passwd -6 -salt rounds=100000\$`head -c12 /dev/urandom | openssl base64`
Once admin users are declared, the admin GUI can be accessed from a web browser using the following URL:
https://{hostname}:{port}/admin
Note that it’s highly recommended to:
- disable the admin interfaces on instances that are accessible to regular users,
- enable the admin interfaces on instances that are restricted to admin users, in a limited access network.
Please check the clustering requirements for multiple instances deployment.