Configuration système

Ce document décrit les étapes de base pour configurer Odoo en production ou sur un serveur exposé à Internet. Il suit installation, et n’est généralement pas nécessaire pour un système de développement qui n’est pas exposé sur internet.

Avertissement

Si vous configurez un serveur public, assurez-vous de consulter nos recommandations de securité !

dbfilter

Odoo est un système multi-tenant : un seul système Odoo peut fonctionner et servir un certain nombre d’instances de bases de données. Il est également hautement personnalisable, les personnalisations (à partir des modules chargés) dépendant de la « base de données actuelle ».

Ce n’est pas un problème lorsque vous travaillez avec le backend (client web) en tant qu’utilisateur de l’entreprise : la base de données peut être sélectionnée lors de la connexion, et les personnalisations chargées ensuite.

Cependant, c’est un problème pour les utilisateurs non connectés (portail, site web) qui ne sont pas liés à une base de données : Odoo doit savoir quelle base de données doit être utilisée pour charger la page du site web ou effectuer l’opération. Si la multi-location n’est pas utilisée, ce n’est pas un problème, il n’y a qu’une seule base de données à utiliser, mais s’il y a plusieurs bases de données accessibles, Odoo a besoin d’une règle pour savoir laquelle il doit utiliser.

C’est l’un des objectifs de l’option:–db-filter <odoo-bin –db-filter> ` : elle spécifie comment la base de données doit être sélectionnée en fonction du nom d’hôte (domaine) demandé. La valeur est une `expression régulière, incluant éventuellement le nom d’hôte injecté dynamiquement (%h) ou le premier sous-domaine (%d) par lequel le système est accessible.

Pour les serveurs hébergeant plusieurs bases de données en production, en particulier si website est utilisé, dbfilter doit être défini, sinon un certain nombre de fonctionnalités ne fonctionneront pas correctement.

Exemples de configurations

  • Afficher uniquement les bases de données dont le nom commence par “mycompany”

dans /etc/odoo.conf définir:

[options]
dbfilter = ^mycompany.*$
  • Afficher uniquement les bases de données correspondant au premier sous-domaine après www : par exemple, la base de données « mycompany » sera affichée si la requête entrante a été envoyée à www.mycompany.com ou mycompany.co.uk , mais pas pour www2.mycompany.com ou helpdesk.mycompany.com.

dans /etc/odoo.conf définir:

[options]
dbfilter = ^%d$

Note

Setting a proper --db-filter is an important part of securing your deployment. Once it is correctly working and only matching a single database per hostname, it is strongly recommended to block access to the database manager screens, and to use the --no-database-list startup parameter to prevent listing your databases, and to block access to the database management screens. See also security.

PostgreSQL

Par défaut, PostgreSQL n’autorise que les connexions via les sockets UNIX et les connexions loopback (à partir de « localhost », la même machine sur laquelle le serveur PostgreSQL est installé).

UNIX socket est très bien si vous voulez que Odoo et PostgreSQL s’exécutent sur la même machine, et c’est la valeur par défaut quand aucun hôte n’est fourni, mais si vous voulez que Odoo et PostgreSQL s’exécutent sur des machines différentes 1 il devra listen to network interfaces 2, soit :

  • N’acceptez que les connexions loopback et utilisez un tunnel SSH entre la machine sur laquelle fonctionne Odoo et celle sur laquelle fonctionne PostgreSQL, puis configurez Odoo pour qu’il se connecte à son extrémité du tunnel.

  • Accepter les connexions à la machine sur laquelle Odoo est installé, éventuellement via ssl (voir Paramètres de connexion PostgreSQL pour plus de détails), puis configurer Odoo pour qu’il se connecte via le réseau.

Exemple de configuration

  • Autoriser la connexion tcp sur localhost

  • Autoriser la connexion TCP à partir du réseau 192.168.1.x

dans /etc/postgresql/9.5/main/pg_hba.conf définir :

# IPv4 local connections:
host    all             all             127.0.0.1/32            md5
host    all             all             192.168.1.0/24          md5

dans /etc/postgresql/9.5/main/postgresql.conf définir :

listen_addresses = 'localhost,192.168.1.2'
port = 5432
max_connections = 80

Configurer Odoo

Par défaut, Odoo se connecte à un Postgres local sur un socket UNIX via le port 5432. Ceci peut être remplacé par les options de base de données lorsque votre déploiement Postgres n’est pas local et/ou n’utilise pas les valeurs par défaut de l’installation.

The packaged installers will automatically create a new user (odoo) and set it as the database user.

  • The database management screens are protected by the admin_passwd setting. This setting can only be set using configuration files, and is simply checked before performing database alterations. It should be set to a randomly generated value to ensure third parties can not use this interface.

  • All database operations use the database options, including the database management screen. For the database management screen to work requires that the PostgreSQL user have createdb right.

  • Users can always drop databases they own. For the database management screen to be completely non-functional, the PostgreSQL user needs to be created with no-createdb and the database must be owned by a different PostgreSQL user.

    Avertissement

    l’utilisateur PostgreSQL ne doit pas être un superutilisateur

Exemple de configuration

  • connect to a PostgreSQL server on 192.168.1.2

  • port 5432

  • en utilisant un compte utilisateur “odoo”,

  • avec “pwd” comme mot de passe

  • filtering only db with a name beginning with “mycompany”

dans /etc/odoo.conf définir:

[options]
admin_passwd = mysupersecretpassword
db_host = 192.168.1.2
db_port = 5432
db_user = odoo
db_password = pwd
dbfilter = ^mycompany.*$

SSL entre Odoo et PostgreSQL

Depuis Odoo 11.0, vous pouvez appliquer une connexion ssl entre Odoo et PostgreSQL. Dans Odoo, le db_sslmode contrôle la sécurité SSL de la connexion avec une valeur choisie parmi “disable”, “allow”, “prefer”, “require”, “verify-ca” ou “verify-full”

Doc PostgreSQL

Builtin server

Odoo inclut des serveurs HTTP intégrés, en utilisant soit le multithreading soit le multitraitement.

Pour une utilisation en production, il est recommandé d’utiliser le serveur multiprocesseurs car il augmente la stabilité, utilise un peu mieux les ressources informatiques et peut être mieux surveillé avec possibilité de restriction des ressources.

  • Le multitraitement est activé en configurant un nombre non nul de processus de travail, le nombre de processus de travail doit être basé sur le nombre de Cores de la machine (éventuellement avec de l’espace pour les processus de travail Cron en fonction du nombre de travaux Cron prévus)

  • Les limites du processus de travail peuvent être configurées en fonction de la configuration matérielle pour éviter l’épuisement des ressources

Avertissement

Le mode multitraitement n’est pas disponible actuellement sous Windows

Calcul du nombre de processus de travail

  • Règle de base : (#CPU * 2) + 1

  • Cron workers need CPU

  • 1 worker ~= 6 utilisateurs simultanés

calcul de la taille de la mémoire

  • Nous considérons que 20% des demandes sont des demandes lourdes, tandis que 80% sont des demandes plus simples

  • Un gros worker, lorsque tous les champs calculés sont bien conçus, les requêtes SQL sont bien conçues, … est estimé consommer environ 1 Go de RAM

  • On estime qu’un worker plus léger, dans le même scénario, consomme environ 150 Mo de RAM

RAM Nécessaire = #worker * ( (light_worker_ratio * light_worker_ram_estimation) + (heavy_worker_ratio * heavy_worker_ram_estimation) )

LiveChat

In multiprocessing, a dedicated LiveChat worker is automatically started and listening on the longpolling port but the client will not connect to it.

Instead you must have a proxy redirecting requests whose URL starts with /longpolling/ to the longpolling port. Other request should be proxied to the normal HTTP port

Pour réaliser une telle chose, vous devrez déployer un reverse proxy devant Odoo, comme nginx ou apache. En faisant cela, vous devrez transmettre d’autres en-têtes http à Odoo, et activer le proxy_mode dans la configuration de Odoo pour que Odoo lise ces en-têtes.

Exemple de configuration

  • Serveur avec 4 processeurs, 8 threads

  • 60 utilisateurs simultanés

  • 60 users / 6 = 10 <- theorical number of worker needed

  • (4 * 2) + 1 = 9 <- theorical maximal number of worker

  • Nous utiliserons 8 workers + 1 pour la cron. Nous utiliserons également un système de surveillance pour mesurer la charge du processeur et vérifier si elle se situe entre 7 et 7,5.

  • RAM = 9 * ((0.8*150) + (0.2*1024)) ~= 3Go de RAM pour Odoo

dans /etc/odoo.conf:

[options]
limit_memory_hard = 1677721600
limit_memory_soft = 629145600
limit_request = 8192
limit_time_cpu = 600
limit_time_real = 1200
max_cron_threads = 1
workers = 8

HTTPS

Qu’il soit accessible via un site web/un client web ou un service web, Odoo transmet les informations d’authentification en texte clair. Cela signifie qu’un déploiement sécurisé d’Odoo doit utiliser HTTPS3. La terminaison SSL peut être implémentée via à peu près n’importe quel proxy de terminaison SSL, mais nécessite la configuration suivante :

  • Enable Odoo’s proxy mode. This should only be enabled when Odoo is behind a reverse proxy

  • Configurer le proxy de terminaison SSL (Exemple de terminaison Nginx)

  • Configurer le proxy lui-même (Exemple de proxy Nginx)

  • Votre proxy de terminaison SSL doit également rediriger automatiquement les connexions non sécurisées vers le port sécurisé

Exemple de configuration

  • Rediriger les requêtes http vers https

  • Demandes de proxy à odoo

dans /etc/odoo.conf définir:

proxy_mode = True

dans /etc/nginx/sites-enabled/odoo.conf définir :

#odoo server
upstream odoo {
 server 127.0.0.1:8069;
}
upstream odoochat {
 server 127.0.0.1:8072;
}

# http -> https
server {
   listen 80;
   server_name odoo.mycompany.com;
   rewrite ^(.*) https://$host$1 permanent;
}

server {
 listen 443 ssl;
 server_name odoo.mycompany.com;
 proxy_read_timeout 720s;
 proxy_connect_timeout 720s;
 proxy_send_timeout 720s;

 # Add Headers for odoo proxy mode
 proxy_set_header X-Forwarded-Host $host;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header X-Forwarded-Proto $scheme;
 proxy_set_header X-Real-IP $remote_addr;

 # SSL parameters
 ssl_certificate /etc/ssl/nginx/server.crt;
 ssl_certificate_key /etc/ssl/nginx/server.key;
 ssl_session_timeout 30m;
 ssl_protocols TLSv1.2;
 ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
 ssl_prefer_server_ciphers off;

 # log
 access_log /var/log/nginx/odoo.access.log;
 error_log /var/log/nginx/odoo.error.log;

 # Redirect longpoll requests to odoo longpolling port
 location /longpolling {
 proxy_pass http://odoochat;
 }

 # Redirect requests to odoo backend server
 location / {
   proxy_redirect off;
   proxy_pass http://odoo;
 }

 # common gzip
 gzip_types text/css text/scss text/plain text/xml application/xml application/json application/javascript;
 gzip on;
}

Odoo en tant qu’application WSGI

It is also possible to mount Odoo as a standard WSGI application. Odoo provides the base for a WSGI launcher script as odoo-wsgi.example.py. That script should be customized (possibly after copying it from the setup directory) to correctly set the configuration directly in odoo.tools.config rather than through the command-line or a configuration file.

Cependant, le serveur WSGI n’exposera que le point de terminaison HTTP principal pour le client Web, le site web et l’API du service web. Comme Odoo ne contrôle plus la création de workers, il ne peut pas configurer de workers cron ou livechat.

Cron Workers

To run cron jobs for an Odoo deployment as a WSGI application requires

  • Un Odoo classique (exécuté via odoo-bin)

  • Connected to the database in which cron jobs have to be run (via odoo-bin -d)

  • Which should not be exposed to the network. To ensure cron runners are not network-accessible, it is possible to disable the built-in HTTP server entirely with odoo-bin --no-http or setting http_enable = False in the configuration file

LiveChat

The second problematic subsystem for WSGI deployments is the LiveChat: where most HTTP connections are relatively short and quickly free up their worker process for the next request, LiveChat require a long-lived connection for each client in order to implement near-real-time notifications.

This is in conflict with the process-based worker model, as it will tie up worker processes and prevent new users from accessing the system. However, those long-lived connections do very little and mostly stay parked waiting for notifications.

Les solutions pour supporter le livechat/modifications dans une application WSGI sont les suivantes :

  • Deploy a threaded version of Odoo (instead of a process-based preforking one) and redirect only requests to URLs starting with /longpolling/ to that Odoo, this is the simplest and the longpolling URL can double up as the cron instance.

  • Deploy an evented Odoo via odoo-gevent and proxy requests starting with /longpolling/ to the longpolling port.

Serving Static Files

For development convenience, Odoo directly serves all static files in its modules. This may not be ideal when it comes to performances, and static files should generally be served by a static HTTP server.

Odoo static files live in each module’s static/ folder, so static files can be served by intercepting all requests to /MODULE/static/FILE, and looking up the right module (and file) in the various addons paths.

Sécurité

Pour commencer, gardez à l’esprit que la sécurisation d’un système d’information est un processus continu, et non une opération ponctuelle. À tout moment, votre sécurité ne sera que celle du maillon le plus faible de votre environnement.

Ne considérez donc pas cette section comme la liste ultime des mesures qui permettront d’éviter tous les problèmes de sécurité. Il s’agit uniquement d’un résumé des premiers éléments importants que vous devez veiller à inclure dans votre plan d’action de sécurité. Le reste viendra des meilleures pratiques de sécurité pour votre système d’exploitation et votre distribution, des meilleures pratiques en termes d’utilisateurs, de mots de passe et de gestion du contrôle d’accès, etc.

Lors du déploiement d’un serveur tourné vers l’Internet, veillez à prendre en compte les sujets suivants liés à la sécurité :

  • Définissez toujours un mot de passe administrateur super-admin fort, et limitez l’accès aux pages de gestion de la base de données dès que le système est configuré. Voir Database Manager Security.

  • Choisissez des identifiants uniques et des mots de passe forts pour tous les comptes administrateurs de toutes les bases de données. N’utilisez pas « admin » comme identifiant. N’utilisez pas ces identifiants pour les opérations quotidiennes, mais uniquement pour contrôler/gérer l’installation. *N’utilisez jamais de mots de passe par défaut comme admin/admin, même pour les bases de données de test/stage.

  • N’installez pas de données de démonstration sur des serveurs tournés vers l’Internet. Les bases de données contenant des données de démonstration contiennent des identifiants et des mots de passe par défaut qui peuvent être utilisés pour s’introduire dans vos systèmes et causer des problèmes importants, même sur les systèmes de mise en scène/de développement.

  • Utilisez les filtres de base de données appropriés ( --db-filter) pour restreindre la visibilité de vos bases de données en fonction du nom d’hôte. Voir dbfilter. Vous pouvez également utiliser -d pour fournir votre propre liste (séparée par des virgules) de bases de données disponibles à filtrer, au lieu de laisser le système les récupérer toutes depuis le backend de la base de données.

  • Une fois que vos db_name et db_filter sont configurés et ne correspondent qu’à une seule base de données par nom d’hôte, vous devriez mettre l’option de configuration list_db à False, pour empêcher de lister entièrement des bases de données, et pour bloquer l’accès aux écrans de gestion des bases de données (ceci est aussi exposé comme l’option de ligne de commande --no-database-list)

  • Assurez-vous que l’utilisateur PostgreSQL (--db_user) n’est pas un super-utilisateur, et que vos bases de données appartiennent à un autre utilisateur. Par exemple, elles pourraient appartenir au super-utilisateur postgres si vous utilisez un db_user dédié et non privilégié. Voir aussi Configurer Odoo.

  • Maintenez vos installations à jour en installant régulièrement les dernières versions, soit via GitHub, soit en téléchargeant la dernière version sur https://www.odoo.com/page/download ou http://nightly.odoo.com.

  • Configurez votre serveur en mode multi-process avec des limites appropriées correspondant à votre utilisation typique (mémoire/CPU/délais d’attente). Voir aussi Builtin server.

  • Exécutez Odoo derrière un serveur web fournissant une terminaison HTTPS avec un certificat SSL valide, afin d’empêcher l’écoute des communications en clair. Les certificats SSL sont bon marché, et de nombreuses options gratuites existent. Configurez le proxy web pour limiter la taille des requêtes, définissez des délais d’attente appropriés, puis activez l’option mode proxy. Voir aussi HTTPS.

  • Si vous devez autoriser un accès SSH distant à vos serveurs, assurez-vous de définir un mot de passe fort pour tous les comptes, et pas seulement pour root. Il est fortement recommandé de désactiver complètement l’authentification par mot de passe et de n’autoriser que l’authentification par clé publique. Pensez également à restreindre l’accès via un VPN, à n’autoriser que les IP de confiance dans le pare-feu, et/ou à utiliser un système de détection par force brute tel que fail2ban ou équivalent.

  • Pensez à installer une limitation de débit appropriée sur votre proxy ou votre pare-feu, afin d’empêcher les attaques par force brute et les attaques par déni de service. Voir aussi Blocage des attaques par force brute pour des mesures spécifiques.

    De nombreux fournisseurs de réseaux proposent une atténuation automatique des attaques par déni de service distribué (DDOS), mais il s’agit souvent d’un service facultatif, que vous devez donc consulter.

  • Dans la mesure du possible, hébergez vos instances de démonstration/test/staging destinées au public sur des machines différentes de celles de la production. Et appliquez les mêmes précautions de sécurité que pour la production.

  • Si votre serveur Odoo public a accès à des ressources ou des services réseau internes sensibles (par exemple via un VLAN privé), mettez en place des règles de pare-feu appropriées pour protéger ces ressources internes. Cela garantira que le serveur Odoo ne peut pas être utilisé accidentellement (ou à la suite d’actions malveillantes d’utilisateurs) pour accéder à ces ressources internes ou les perturber. Typiquement, cela peut être fait en appliquant une règle DENY par défaut sur le pare-feu, puis en autorisant explicitement l’accès aux ressources internes auxquelles le serveur Odoo doit accéder. Systemd IP traffic access control peut également être utile pour implémenter un contrôle d’accès réseau par processus.

  • Si votre serveur Odoo public se trouve derrière un pare-feu d’application web, un équilibreur de charge, un service de protection DDoS transparent (comme CloudFlare) ou un dispositif similaire au niveau du réseau, vous pouvez souhaiter éviter l’accès direct au système Odoo. Il est généralement difficile de garder secrètes les adresses IP d’extrémité de vos serveurs Odoo. Par exemple, elles peuvent apparaître dans les journaux des serveurs web lors de l’interrogation de systèmes publics, ou dans les en-têtes des courriers électroniques envoyés depuis Odoo. Dans une telle situation, vous pouvez configurer votre pare-feu de sorte que les points d’extrémité ne soient pas accessibles publiquement, sauf à partir des adresses IP spécifiques de votre WAF, de votre équilibreur de charge ou de votre service de proxy. Les fournisseurs de services comme CloudFlare maintiennent généralement une liste publique de leurs plages d’adresses IP à cette fin.

  • Si vous hébergez plusieurs clients, isolez les données et les fichiers des clients les uns des autres en utilisant des conteneurs ou des techniques de « prison » appropriées.

  • Configurez des sauvegardes quotidiennes de vos bases de données et des données de vos dépôts de fichiers/filstore data, et copiez-les sur un serveur d’archivage distant qui n’est pas accessible depuis le serveur lui-même.

Blocage des attaques par force brute

Pour les déploiements orientés vers l’Internet, les attaques par force brute sur les mots de passe des utilisateurs sont très courantes, et cette menace ne doit pas être négligée pour les serveurs Odoo. Odoo émet une entrée de journal chaque fois qu’une tentative de connexion est effectuée, et rapporte le résultat : succès ou échec, ainsi que le login cible et l’IP source.

Les entrées du journal auront la forme suivante.

Échec de la connexion:

2018-07-05 14:56:31,506 24849 INFO db_name odoo.addons.base.res.res_users: Login failed for db:db_name login:admin from 127.0.0.1

Succès de la connexion:

2018-07-05 14:56:31,506 24849 INFO db_name odoo.addons.base.res.res_users: Login successful for db:db_name login:admin from 127.0.0.1

Ces journaux/logs peuvent être facilement analysés par un système de prévention des intrusions tel que fail2ban.

Par exemple, la définition suivante du filtre fail2ban devrait correspondre à un échec de connexion:

[Definition]
failregex = ^ \d+ INFO \S+ \S+ Login failed for db:\S+ login:\S+ from <HOST>
ignoreregex =

Ceci pourrait être utilisé avec une définition de prison pour bloquer l’IP attaquant sur HTTP(S).

Voici à quoi cela pourrait ressembler pour bloquer l’IP pendant 15 minutes lorsque 10 tentatives de connexion échouées sont détectées à partir de la même IP en 1 minute:

[odoo-login]
enabled = true
port = http,https
bantime = 900  ; 15 min ban
maxretry = 10  ; if 10 attempts
findtime = 60  ; within 1 min  /!\ Should be adjusted with the TZ offset
logpath = /var/log/odoo.log  ;  set the actual odoo log path here

Database Manager Security

Configurer Odoo mentioned admin_passwd in passing.

Ce paramètre est utilisé sur tous les écrans de gestion des bases de données (pour créer, supprimer, vider ou restaurer des bases de données).

Si les écrans de gestion ne doivent pas être accessibles du tout, vous devez mettre l’option de configuration list_db à False, pour bloquer l’accès à tous les écrans de sélection et de gestion des bases de données.

Avertissement

Il est fortement recommandé de désactiver le gestionnaire de bases de données pour tout système connecté à Internet ! Il est conçu comme un outil de développement/démonstration, pour faciliter la création et la gestion rapides de bases de données. Il n’est pas conçu pour être utilisé en production, et peut même exposer des fonctionnalités dangereuses aux attaquants. Il n’est pas non plus conçu pour gérer de grandes bases de données, et peut déclencher des limites de mémoire.

Sur les systèmes de production, les opérations de gestion des bases de données doivent toujours être effectuées par l’administrateur système, y compris le provisionnement de nouvelles bases de données et les sauvegardes automatiques.

Assurez-vous de configurer un paramètre approprié db_name (et optionnellement, db_filter aussi) afin que le système puisse déterminer la base de données cible pour chaque requête, sinon les utilisateurs seront bloqués car ils ne seront pas autorisés à choisir la base de données eux-mêmes.

Si les écrans de gestion ne doivent être accessibles qu’à partir d’un ensemble de machines sélectionnées, utilisez les fonctionnalités du serveur proxy pour bloquer l’accès à toutes les routes commençant par /web/database sauf (peut-être) /web/database/selector qui affiche l’écran de sélection de la base de données.

Si l’écran de gestion de la base de données doit rester accessible, le paramètre admin_passwd doit être modifié par rapport à sa valeur par défaut admin : ce mot de passe est vérifié avant d’autoriser les opérations de modification de la base de données.

Il doit être stocké en toute sécurité et doit être généré de manière aléatoire, par exemple

$ python3 -c 'import base64, os; print(base64.b64encode(os.urandom(24)))'

qui générera une chaîne imprimable pseudo aléatoire de 32 caractères.

Navigateurs pris en charge

Odoo prend en charge tous les principaux navigateurs desktop et mobiles disponibles sur le marché, pour autant qu’ils soient pris en charge par leurs éditeurs.

Voici les navigateurs pris en charge :

  • Google Chrome

  • Mozilla Firefox

  • Microsoft Edge

  • Apple Safari

Avertissement

Veuillez vous assurer que votre navigateur est à jour et toujours pris en charge par son éditeur avant de déposer un rapport de bogue.

Note

Depuis Odoo 13.0, ES6 est supporté. Par conséquent, le support d’IE est abandonné.

1

pour que plusieurs installations Odoo utilisent la même base de données PostgreSQL, ou pour fournir plus de ressources informatiques aux deux logiciels.

2

technically a tool like socat can be used to proxy UNIX sockets across networks, but that is mostly for software which can only be used over UNIX sockets

3

or be accessible only over an internal packet-switched network, but that requires secured switches, protections against ARP spoofing and precludes usage of WiFi. Even over secure packet-switched networks, deployment over HTTPS is recommended, and possible costs are lowered as « self-signed » certificates are easier to deploy on a controlled environment than over the internet.