Set Custom Headers for Content-Security

I’m trying to set some custom Content-Security headers (below) to Mattermost Server. We have the Mattermost server listening on port 443 directly, without any reverse proxy in front of it. The headers I am trying to set are: (this is Apache format)

Header always set X-Frame-Options “GOFORIT”
Header always set X-XSS-Protection “1; mode=block”
Header always set X-Content-Type-Options: nosniff
Header always set Strict-Transport-Security “max-age=31536000; includeSubDomains”

In the past, I was using a reverse proxy to rewrite these headers however that caused session issues where mobile devices would be logged out weekly, disregarding the session length I had configured in Mattermost Server. Are there any options natively to set these headers?

As far as I am aware, you must have some form of a server running in order to set headers in the method that you are talking about.

For example, with Apache, which I use with Mattermost, you can set specific headers from within the virtual host configuration file, and/or with a .htaccess file. However, without a web server, there is no engine to process the information in the .htaccess file, so it would virtually render it a nice text file otherwise.

I’m honestly a bit interested to hear a bit further about the issues you experienced with previous reverse proxy configurations and what may have been causing those issues, because that as well sounds like an issue with headers overriding those set by Mattermost, causing users to be logged out.

Below was my apache config. I’ve replaced some specifics with variables for the purposes of putting this out there. .

<VirtualHost *:443>
ServerName {{ FQDN }}
DocumentRoot /var/www/html
ErrorLog /var/log/httpd/{{ HOSTNAME -s }}.err
CustomLog /var/log/httpd/{{ HOSTNAME -s }}.log combined
CustomLog “|/usr/bin/logger -p local6.info -t apache-access” combined
SSLEngine on
SSLProxyVerify none
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off
SSLProxyCheckPeerExpire off

SSLHonorCipherOrder on
SSLProtocol TLSv1.2
SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4:!3DES

 SSLCertificateFile {{ ssl_cert_path }}
 SSLCertificateKeyFile {{ ssl_key_path }}
 SSLCACertificateFile {{ ssl_bundle_path }}

<Proxy *>
Order deny,allow
Allow from all

SSLProxyEngine on
ProxyRequests Off
ProxyPreserveHost On
ProxyVia On

RewriteEngine on
RewriteCond %{REQUEST_URI} ^/api/v1/websocket [NC,OR]
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC,OR]
RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
RewriteRule .* ws://127.0.0.1:8065%{REQUEST_URI} [P,QSA,L]
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule .* https://127.0.0.1:8065%{REQUEST_URI} [P,QSA,L]
RequestHeader set X-Forwarded-Proto “https”

RequestHeader set X-Forwarded-SSL on
Header always set Strict-Transport-Security “max-age=31536000; includeSubDomains”
Header always set X-Xss-Protection “1; mode=block”
Header always set X-Content-Type-Options “nosniff”
Header always append X-Frame-Options SAMEORIGIN

RequestHeader unset If-Modified-Since
RequestHeader unset If-None-Match
Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure

<Location /api/v1/websocket>
Require all granted
ProxyPassReverse https://127.0.0.1:8065
ProxyPassReverseCookieDomain 127.0.0.1 {{ FQDN }}

Require all granted ProxyPassReverse https://127.0.0.1:8065 ProxyPassReverseCookieDomain 127.0.0.1 {{ FQDN }}

IMPORTANT NOTICE: The information contained within this message and any attachment is intended only for the use of the individual or entity to whom it is addressed and may contain information that is privileged, confidential and exempt from disclosure under applicable law. If you have received this communication in error, please notify the sender by reply e-mail and delete the message and any attachments immediately. Statements or opinions in this message and any attachment not related to the official business of Novantas are those of the author, and are not necessarily agreed or endorsed by Novantas, Inc. We reserve the right to monitor emails sent or received for operational or business reasons as permitted by law. No representation is made that this message or its attachments are without defect.

Can you code format the example configuration fully? You can do that by putting ``` on the first line before you paste the configuration and on a new line after the end of the configuration file. I’m not sure if some things that would cause issues are because of how it pasted without the right formatting since its not code formatted or if there really are issues. Thanks!

<VirtualHost *:443>
    ServerName {{ FQDN }}
    DocumentRoot /var/www/html
    ErrorLog /var/log/httpd/{{ HOSTNAME -s }}.err
    CustomLog /var/log/httpd/{{ HOSTNAME -s }}.log combined
    CustomLog "|/usr/bin/logger -p local6.info -t apache-access" combined
    SSLEngine on
    SSLProxyVerify none
    SSLProxyCheckPeerCN off
    SSLProxyCheckPeerName off
    SSLProxyCheckPeerExpire off

    SSLHonorCipherOrder on
    SSLProtocol TLSv1.2
    SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4:!3DES

     SSLCertificateFile {{ ssl_cert_path }}
     SSLCertificateKeyFile {{ ssl_key_path }}
     SSLCACertificateFile {{ ssl_bundle_path }}

<Proxy *>
Order deny,allow
Allow from all
</Proxy>

SSLProxyEngine on
ProxyRequests       Off
ProxyPreserveHost On
ProxyVia On

RewriteEngine on
RewriteCond %{REQUEST_URI} ^/api/v1/websocket [NC,OR]
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC,OR]
RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
RewriteRule .* ws://127.0.0.1:8065%{REQUEST_URI} [P,QSA,L]
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule .* https://127.0.0.1:8065%{REQUEST_URI} [P,QSA,L]
RequestHeader set X-Forwarded-Proto “https”

#RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-SSL on
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Xss-Protection "1; mode=block"
Header always set X-Content-Type-Options "nosniff"
Header always append X-Frame-Options SAMEORIGIN


RequestHeader unset If-Modified-Since
RequestHeader unset If-None-Match
Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure

<Location /api/v1/websocket>
Require all granted
ProxyPassReverse https://127.0.0.1:8065
ProxyPassReverseCookieDomain 127.0.0.1 {{ FQDN }} </Location>

<Location />
Require all granted
ProxyPassReverse https://127.0.0.1:8065
ProxyPassReverseCookieDomain 127.0.0.1 {{ FQDN }} </Location>

</VirtualHost>