Anti-Virus deprecated

Introduction

Since 7.10.2, the middleware is equipped with the ability to perform anti-virus scanning on mail and PIM attachments as well as drive items upon download (only via the AppSuite UI so far; i.e. synced drive items will NOT be scanned). To enable this feature a few components should be added to your infrastructure.

In a nutshell the middleware is "outsourcing" the anti-virus scans to a remote anti-virus server via requests based on the ICAP protocol. And those are the two components with which your infrastructure needs to be enhanced for the anti-virus feature to work properly: the ICAP server and an anti-virus server/daemon which is ICAP-capable.

In the following example we will use ClamAV as the anti-virus service and SquidClamav as an ICAP service through the c-icap server.

Scan Coverage

The scan is an on-demand gate in front of a single download, not a background or storage-level scan. Uploads are never scanned, and neither is content already stored.

The feature is enabled for a user who has the antivirus capability and com.openexchange.antivirus.enabled set to true. For such a user, the middleware scans a download if

  • the client asks for it via the scan=true URL parameter, or
  • com.openexchange.antivirus.enforce is true for the user. The scan is then enforced: every download is scanned, and the client can neither skip it nor switch it off with scan=false.

The scan covers these requests of the HTTP API, single or zipped, including the viewer's requests for the content of such an item:

  • drive items, including their thumbnails and previews,
  • mail attachments, including inline images,
  • whole mails: the source (.eml), a mail with its source attached (attach_src) and the structure of a mail (get_structure),
  • PIM attachments, including copying one to the drive.

Without enforcement, which of them are scanned depends on the client:

  • App Suite UI. Its download dialog adds scan=true to every download if the user has the antivirus capability. Content the UI loads by other means is not scanned: the viewer and its previews, and downloads from the UI installed as a web app on iOS.
  • Other clients. Third-party clients of the HTTP API usually do not send the parameter, so their downloads are not scanned.
  • Share links. A file downloaded directly through a share link is scanned without any parameter whenever the feature is enabled for the guest user.

Not scanned, enforced or not:

  • synchronization protocols: the Drive synchronization, WebDAV and CalDAV/CardDAV, as well as IMAP and any other access to the mail server that bypasses the middleware,
  • mail attachments downloaded from a compose space, parts of a mail stored as a file (get_ref_attachment), a mail streamed via an emlToken, and images served by the image servlet,
  • drive deltas (documentdelta) and data exports.

Enforcement is therefore a protection of the regular download paths, not a barrier against a user who deliberately fetches content by other means.

Under enforcement, every thumbnail, preview and inline image is scanned as well; an item larger than com.openexchange.antivirus.maxFileSize then cannot be previewed either. If the anti-virus service is not available, for example while the middleware starts, every download of a user with com.openexchange.antivirus.enforce and com.openexchange.antivirus.enabled set is refused; the antivirus capability cannot be checked without the service.

The middleware scans exactly the content it delivers:

  • A zip archive of drive items is checked entry by entry right before each entry is added, and the archive is written to a temporary file and delivered once complete, so that a finding is reported before anything is sent. Its size is bounded by the configured zip thresholds.
  • An item of the internal drive storage is read through its storage location, so that content replaced meanwhile - a new version, or a version updated in place - is neither delivered unscanned nor mistaken for the scanned one. Such a download is delivered from that location also for range requests.
  • Content that could change between scan and delivery - a thumbnail, or a file of an external storage - is buffered, scanned and delivered from that buffer, range requests included. A file known to exceed com.openexchange.antivirus.maxFileSize is refused before it is read, any other one as soon as the limit is passed. With a negative com.openexchange.antivirus.maxFileSize there is no limit, and such content is buffered completely.
  • Several mails downloaded as a zip are fetched once each, spooled up to com.openexchange.antivirus.maxFileSize, scanned and added from the spool; the archive is streamed, so a finding aborts the download.

Since mail content and items of external storages are never taken from the verdict cache, enforcement scans every such view again - each inline image, thumbnail or preview. Size the ICAP service for that load.

Behavior on Detection

If the anti-virus service reports an infection, the middleware refuses to deliver the item in that request and returns the error ANTI-VIRUS-SERVICE-0011 ("The file '...' you are trying to download seems to be infected with '...'"). A download through a share link is answered with HTTP 403 instead. Nothing else happens:

  • The item stays where it is. It is neither deleted, moved, quarantined nor altered, and its owner keeps full access to it.
  • No notification is sent. There is no mail, no push notification and no administrative alert. The error returned to the requesting client is the only feedback.
  • No verdict is persisted. Each middleware node keeps the verdicts of recent scans in memory, keyed by the item and the ISTag of the ICAP service. A repeated download of the same item returns the kept verdict without contacting the service, until the service announces a different ISTag or the node restarts. Verdicts are kept only for content whose identifier pins it: a version of an item in the internal drive storage (its decrypted content apart from its ciphertext), and a stored PIM or appointment attachment. Mail content, items of external storages and transformed content such as thumbnails are scanned every time.

Removing or quarantining infected content is therefore an operator decision; the middleware offers no mechanism for it. What the anti-virus server itself logs or reports on a match is subject to its own configuration.

The App Suite UI lets the user override the verdict. On error ANTI-VIRUS-SERVICE-0011 it offers Download infected file, and if the item could not be scanned it offers Download unscanned file. Either button repeats the download without scan=true. Without enforcement, the middleware serves that download unchecked. With com.openexchange.antivirus.enforce, it scans the repeated download as well and refuses it again, so the verdict cannot be overridden. To tell clients about enforcement, the middleware grants the capability antivirus_enforced to every user for whom com.openexchange.antivirus.enabled and com.openexchange.antivirus.enforce are set; the App Suite UI then offers neither button, only Cancel. The capability does not follow com.openexchange.capability.forced.antivirus: whoever forces the antivirus capability off should also unset com.openexchange.antivirus.enforce.

Unscannable Content

An item cannot be scanned if the ICAP service is unreachable, does not answer in time, returns an error or answers in an unexpected way, or if the item is larger than com.openexchange.antivirus.maxFileSize. In these cases the middleware refuses to deliver the item in that request, the same way as for an infection, but with an error that states the item was not scanned. Encrypted drive items are the one exception, see Encrypted Content and OX Guard.

Without enforcement, whether the user gets the content anyway is up to the client: the App Suite UI offers Download unscanned file, as described above. With com.openexchange.antivirus.enforce, unscannable content is not delivered at all (fail-closed).

Encrypted Content and OX Guard

OX Guard performs no virus scan of its own. The scan always happens in the middleware, and it operates on the stream the middleware is about to serve - which is the stream Guard has already decrypted. So the anti-virus service sees the decrypted content: for a drive item requested with cryptoAction=Decrypt and scan=true, and for a mail attachment requested with decrypt=true and scan=true, it is the plain text that is scanned, not the ciphertext.

The opposite order does not occur for content requested decrypted, and nothing is scanned on upload.

The App Suite UI sets both parameters for Download decrypted on drive items and for attachments of a decrypted mail. Download encrypted on a drive item sets scan=true without decrypting.

Two consequences follow:

  • A download of an encrypted drive item via the HTTP API's drive module that does not decrypt would only hand ciphertext to the scanner, which no signature matches. The middleware therefore does not scan it and records it as not scanned (reason=encrypted, see Logging and Monitoring) instead of reporting it as clean. Without enforcement the item is delivered as before; with com.openexchange.antivirus.enforce it is refused with the error ANTI-VIRUS-SERVICE-0016. An item counts as encrypted if its MIME type is application/pgp-encrypted or its name ends with .pgp; this applies to single and zipped downloads. Elsewhere - a share link, or a PGP mail downloaded without decrypt=true - the ciphertext is scanned and counts as clean.
  • Content the middleware cannot decrypt cannot be scanned at all - wherever the private key never reaches the server, such as end-to-end encrypted mail whose key stays on the client.

ICAP Connection

The middleware talks to the ICAP service as follows:

  • Transport. TCP to com.openexchange.antivirus.server on com.openexchange.antivirus.port, a new connection per scan. With com.openexchange.icap.client.tls, the connection uses TLS. The server certificate and host name are then always verified, against the trust store given by com.openexchange.icap.client.tls.truststore (PKCS#12 or JKS, e.g. holding a custom CA), or against the JDK's default trust store if none is given. This is independent of the middleware's general SSL settings such as com.openexchange.net.ssl.trustlevel.
  • Method. Every scan is a RESPMOD request to the service com.openexchange.antivirus.service. The method is not configurable.
  • OPTIONS. The middleware sends an OPTIONS request before the first scan and keeps the answer for the Options-TTL the service announces, or for com.openexchange.icap.client.optionsTtlSeconds (default 300) if it announces none.
  • Preview and Allow: 204. Both are used only if the service offers them in its OPTIONS answer. With a preview, the rest of the item is sent once the service answers 100 Continue.
  • One request per item. The whole item is streamed in a single request, chunked. It is never split into several requests. Items above com.openexchange.antivirus.maxFileSize are not sent at all.

The answer is evaluated like this:

ICAP answer Result
200 or 204 with X-Infection-Found, a non-empty X-Virus-ID or X-Violations-Found greater than 0 infected
204 No Content clean
200 OK with an encapsulated HTTP status 403 infected
200 OK with an encapsulated 2xx status clean
200 OK with any other encapsulated HTTP status, e.g. a redirect, or without an encapsulated response not scanned (error)
418, 500 or any other status not scanned (error)

The threat name shown to the user is taken from the Threat= part of X-Infection-Found, or from X-Virus-ID; if the service signals a finding without naming it, the name is unknown. SquidClamav sends both headers, also when its redirect option makes it answer a finding with 307, so such findings are detected as well. A service that answers a finding with a redirect and no such header, however, produces a scan error rather than a detection.

Align the size limits. An ICAP service that skips items above its own size limit typically answers 204, which the middleware reports as clean. SquidClamav does so for items of at least its maxsize, which is 5M in the example chart below, while the middleware sends items up to com.openexchange.antivirus.maxFileSize (100 MB by default). Set the limit of the ICAP service and of the anti-virus daemon at least as high as com.openexchange.antivirus.maxFileSize; otherwise items in between are delivered as clean without having been scanned.

Logging and Monitoring

The middleware logs the outcome of every scan as one line of the dedicated logger com.openexchange.antivirus.scan, so that it can be routed to a separate appender or a SIEM:

Anti-virus scan: outcome=infected, reason=none, threat=Eicar-Signature, item=..., size=68, icapStatus=200, cached=false, durationMs=12
Field Meaning
outcome clean, infected or not_scanned
reason why an item was not scanned: too_large, encrypted or error; otherwise none
threat the name of the threat found
item the identifier of the scanned item, e.g. context, id, version and sequence number of a drive item; for content whose verdict is not cached, such as mail content, the request line of the download without credentials
size the size in bytes, -1 if unknown
icapStatus the status code of the ICAP answer, -1 if there was none
cached whether the verdict was taken from the in-memory cache
durationMs the duration of the scan

Infections and items that were refused because they could not be scanned are logged at WARN, encrypted drive items delivered unscanned without enforcement at INFO, clean items at DEBUG. A download refused because the anti-virus service is unavailable is logged as not_scanned with reason=error. User and context are part of the log properties of the request. Errors of the ICAP connection are additionally logged at ERROR with a stack trace.

The middleware exports these metrics:

  • appsuite.antivirus.scans.outcome - counter per outcome, tagged with outcome, reason and cached.
  • appsuite.antivirus.scans.duration - timer per ICAP answer, tagged with status: the ICAP status code or IO_ERROR.
  • appsuite.antivirus.transfer - bytes sent to the ICAP service.
  • The cache metrics of the in-memory verdict cache, tagged cache=antivirus.

Prerequisites

Example Installation

Image Build

First of all you need to build, and afterwards push, the c-icap Docker image to your registry. You can find the respective Dockerfile in the c-icap example repository. Build the image from a checkout of it:

export REGISTRY_URL=<YOUR_REGISTRY_URL>
docker build . -t ${REGISTRY_URL}/c-icap:latest
docker push ${REGISTRY_URL}/c-icap:latest

Configuration

On the middleware side, only a few things need to be configured. Additionally, you have the option to configure other settings, such as the maximum file size allowed for scanning or the timeout of the ICAP client. For more information on configuration, please refer to the documentation.

values.yaml

core-mw:
  packages:
    status:
      open-xchange-antivirus: enabled
  properties:
    com.openexchange.antivirus.enabled: "true"
    com.openexchange.antivirus.server: "${RELEASE}-c-icap-service"
    com.openexchange.antivirus.port: "1344"
    com.openexchange.antivirus.service: "squidclamav"

Once you have enabled and configured the anti-virus feature in the middleware, add a dependency for your example anti-virus chart in the Chart.yaml` file.

Chart.yaml

dependencies:
  - name: anti-virus
    repository: file://../anti-virus # Path to the example anti-virus chart 
    version: "1.0.0"

Finally, set the reference to the c-icap image you have previously pushed.

values.yaml

anti-virus:
  c-icap:
    image:
      repository: <YOUR_REGISTRY_URL>/c-icap:latest
      tag: "latest"
    # Optional
    imagePullSecrets: []

Advanced Configuration

Maximum File Size

The default maximum allowed file size in mega-bytes that is acceptable for the middleware to scan is set to 100. You can increase or decrease that value at your leisure by adjusting the value in the com.openexchange.antivirus.maxFileSize property. If the file size that is to be scanned exceeds the configured amount, then a warning message will be displayed to the user informing him about the fact and whether he still wants to download the content.

Note that at the moment the entire data stream is fetched twice from the underlying file storage; once to send it to the anti-virus service for scanning, and once to deliver it to the user. Mail content and items of external storages are instead buffered once in a temporary file and delivered from there. With that being said, be aware that enabling the anti-virus feature will increase the I/O in your file storage servers and the use of temporary disk space.

ICAP Client Timeouts

Three properties bound how long a scan may take:

  • com.openexchange.icap.client.connectTimeout (default 5000 milliseconds) limits establishing the connection.
  • com.openexchange.icap.client.socketTimeout (default 10000 milliseconds) is a read timeout: it limits how long the middleware waits for data on the connection, applied to every single read, including the wait for the verdict after the item has been sent.
  • com.openexchange.icap.client.requestTimeout (default 120000 milliseconds) limits the whole request once connected, sending included. It also covers a service that stops reading or trickles its answer. A non-positive value imposes no limit; a service that accepts a connection but stops reading can then hold the download for as long as it keeps the connection open. Before 8.54 there was no such limit: a service that needs longer to scan an item of com.openexchange.antivirus.maxFileSize now yields "not scanned", so raise the value accordingly.

If a timeout expires, the item counts as not scanned; see Unscannable Content. Large items need correspondingly more time for the anti-virus daemon to return its verdict, so align the timeouts with com.openexchange.antivirus.maxFileSize.

Helm

Build the chart and install it.

export RELEASE=example
helm repo add truecharts https://charts.truecharts.org/
helm dependency update helm/core-mw/
helm upgrade --install anti-virus-example helm/core-mw/ -namespace ${RELEASE} --create-namespace

Testing

To test that your entire stack is working as expected, you can upload some harmless test viruses on your test user's account (send them either as a mail attachment, or upload them as PIM attachment or in his drive account) and then try downloading them via the OX App Suite UI. Harmless test viruses can be found here.