#+OPTIONS: toc:nil #+LATEX_HEADER: \usepackage{pagecolor} #+LATEX_HEADER: \usepackage{xcolor} #+LATEX_HEADER: \usepackage{upquote} #+LATEX_HEADER: \definecolor{bgcolor}{RGB}{252,253,255} #+LATEX_HEADER: \definecolor{secondary}{RGB}{70,99,114} #+LATEX_HEADER: \hypersetup{colorlinks=true, linkcolor=black, urlcolor=secondary} #+BEGIN_COMMENT https://media.defense.gov/2022/Aug/29/2003066362/-1/-1/0/CTR_KUBERNETES_HARDENING_GUIDANCE_1.2_20220829.PDF https://postgrest.org/en/stable/admin.html https://releases.rancher.com/documents/security/2.4/Rancher_Hardening_Guide.pdf https://developer.hashicorp.com/vault/tutorials/operations/production-hardening https://ranchermanager.docs.rancher.com/v2.0-v2.4/pages-for-subheaders/rancher-security #+END_COMMENT #+BEGIN_EXPORT latex \pagecolor{secondary} \begin{titlepage} \begin{center} \includegraphics[width=.25\linewidth]{./assets/logo_white.png} \end{center} \vspace{1.5cm} \begin{center} \color{white}{\Huge\bf \sffamily Hardening Guide}\\ \vspace{0.3cm} \color{white}{\Huge\bf \sffamily Filestash} \end{center} \vfill \end{titlepage} \pagecolor{bgcolor} \thispagestyle{empty} \tableofcontents \clearpage \setcounter{page}{1} #+END_EXPORT #+BEGIN_COMMENT TODO: - [X] find additional points not already covered? #+END_COMMENT * Introduction #+BEGIN_COMMENT TODO: - [X] content - [X] grammar check #+END_COMMENT Filestash is a self-contained server software that runs on Linux with an amd64 or arm architecture. It can be made available as a docker container or a static binary. This hardening guide will only cover the static build method running in complete isolation as a self-contained standalone software without any runtime dependency. * Architectural Overview #+BEGIN_COMMENT TODO: - [X] content - [X] graphics - [X] grammar check #+END_COMMENT Filestash is made of a core around which are associated modules we refer to as "plugins". [[./assets/architecture.png]] Plugins help change many aspects of Filestash. The scope of plugins encompasses many areas, the most important of which is the triptych storage/authentication/authorisation. The storage plugin implements most file transfer protocols, the authentication plugin connects to an IDP, and the authorisation layer ensure nobody can read/write onto something they aren’t supposed to. The plugins installed in your instance are set at compile time (unless you use the dynamic loader plugin =plg_dlopen= [\ref{proof:plg_dlopen}]) and are visible from the [[https://demo.filestash.app/about][/about ]]page. We can add and remove plugins to only use what's required by your use case, effectively limiting the capabilities of the software and reducing the attack surface. Concretely the list of plugins is located under =/server/plugin/index.go= with a minimal example looking like tihs: #+BEGIN_SRC golang package plugin import ( . "github.com/mickael-kerjean/filestash/server/common" // this is how the server starts, by default using HTTP on // port 8334 but a range of other options are available // we will discuss some of those options later on in this guide _ "github.com/mickael-kerjean/filestash/server/plugin/plg_starter_http" // this is the file transfer protocol our instance can use, // in this case sftp but a range of options are available _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_sftp" ) func init() { Log.Debug("Plugin loader") } #+END_SRC * Hardening of a typical Filestash setup #+BEGIN_COMMENT TODO: - [X] content - [X] graphics - [X] grammar check #+END_COMMENT A typical Filestash implementation has different components: [[./assets/components.png]] Filestash is storage agnostic and doesn't have an opinion on the storage server you want to use. This part is out of scope for this document so we will only focus on the hardening of components A, B, C and D. ** Component A: Browser #+BEGIN_COMMENT TODO: - [X] content - [ ] proof: [proof:cors], [\ref{proof:csrf}] - [X] grammar check #+END_COMMENT By default, Filestash instructs the browser to: - not download or execute untrusted code and or third-party untrusted resources - not make the authentication cookie available to javascript [\ref{proof:httponly}] to reduce the risk of cookie theft if an XSS was discovered - block the application from loading in an iframe [\ref{proof:iframe}] to protect your instance against click-jacking attacks. To run from an iframe, you need to whitelist the domain with the configuration key ="features.protection.iframe"= - disable API calls from third-party domains unless an API key is created and configured to enable access from a specific origin [\ref{proof:cors}] - prevent guessing the effective MIME type of a resource by examining the content of the response through the MIME sniffing mechanism [\ref{proof:nosniff}] - stop pages from loading when they detect reflected cross-site scripting attacks [\ref{proof:xss}] Other features relevant to the browser: - HTTP GET calls can't alter the state of Filestash nor the underlying storage, preventing attackers from carving links that would trick users onto doing unwanted actions [\ref{proof:stateless_get}] - CSRF protection ** Component B: Transport from browser to Filestash #+BEGIN_COMMENT TODO: - [X] content - [X] proof - [X] grammar check #+END_COMMENT - STS can be enabled to instruct the browser to disallow HTTP access altogether [\ref{proof:sts}]. To enable this option, ="general.force_ssl"= should be set to =true= - Filestash can verify the origin of the request it received and deny the ones that are from unknown origins [\ref{proof:origin}]. This option can be enabled either by setting the "general.host" config key or the =APPLICATION_URL= environment variable to your origin - Filestash by default is deployed as a standalone HTTP server that listens on port 8334. That behaviour is wired in the =plg_starter_http= plugin. For hardening, various other =starter= plugins are available: - =plg_starter_httpsfs=: enables HTTPS on the server using your existing SSL certificates - =plg_starter_web=: enables HTTPS on the server using Letsencrypt to generate the SSL certificates ** Component C: Filestash server *** Application security features #+BEGIN_COMMENT TODO: - [X] content - [X] proof - [X] grammar check #+END_COMMENT The [[https://demo.filestash.app/about][/about]] page provides all the information that relates to your build, including: 1. the list of plugins installed which can be OSS plugins, enterprise plugins or custom plugins [\ref{proof:plugin_list}] 2. the commit hash that allows you to audit the actual code which went on a build [\ref{proof:commit_hash}] 3. the hash of the binary to enable automated systems to detect whenever something has changed like during an upgrade or if an intrusion happens [\ref{proof:binary_hash}] 4. the hash of the configuration makes it possible for an automated system to detect any change [\ref{proof:config_hash}] Other features which are relevant to the server: 1. The currently deployed version is visible from the headers in the HTTP responses [\ref{proof:version}] 2. The healthcheck endpoint ensures the server is running correctly [\ref{proof:healthcheck}] 3. The presence of an endpoint instructs security researchers on how to report a security vulnerability [\ref{proof:security_txt}] Companies that need to maintain 24/7 uptime for their services can consider using high-availability clusters. Various standard architectures are possible based on load balancing in pools or at the DNS level, but the details of such architectures are outside the scope of this hardening guide. *** Authentication and authorisation #+BEGIN_COMMENT TODO: - [X] content - [X] proof - [X] grammar check #+END_COMMENT Filestash has two types of users: - Admin user who have the ability to see/change the configuration and perform other administrative tasks. It's possible to disable the entire admin console entirely which is normally available under =/admin= by using the =plg_admin_nil= plugin [\ref{proof:admin_disabled}] - Normal user. In a hardened mode, we will only keep a single authentication middleware (typically either the SAML plugin or the openID plugin) depending of what your IDP supports [\ref{proof:auth_midleware}] By default the endpoints enabling users to authenticate are rate limited [\ref{proof:rate_limit}] and it's possible to provide additional restrictions based on custom rules using custom plugins. *** Audit logging and treat detection #+BEGIN_COMMENT TODO: - [X] content - [X] proof - [X] grammar check #+END_COMMENT Application logs are written in both a log file located in =data/state/log/access.log= and =stdout=. The structure of the logs is detailed in the [[https://www.filestash.app/2022/09/01/anatomy-of-logs/][documentation]] and looks like this: #+BEGIN_SRC 2022/12/08 09:33:55 SYST INFO Filestash v0.5 starting 2022/12/08 09:33:55 SYST INFO [http] starting ... 2022/12/08 09:33:55 SYST INFO [http] listening on :8334 2022/12/08 12:20:58 HTTP 200 GET 0.8ms /files/mickael/ 2022/12/08 12:20:58 HTTP 200 GET 0.2ms /custom.css 2022/12/08 12:20:58 HTTP 200 GET 4.6ms /assets/js/app_be2ce54bbd2cfc50e943.js 2022/12/08 12:20:59 HTTP 200 GET 0.2ms /assets/locales/fr.json 2022/12/08 12:20:59 HTTP 200 GET 1.6ms /api/config 2022/12/08 12:20:59 HTTP 200 GET 11.2ms /api/session 2022/12/08 12:20:59 HTTP 200 GET 0.3ms /favicon.ico 2022/12/08 12:20:59 HTTP 302 GET 0.1ms /manifest.json 2022/12/08 12:20:59 HTTP 500 GET 2.9ms /api/files/ls?path=%2Fmickael%2F 2022/12/08 12:20:59 HTTP 200 GET 0.3ms /assets/logo/android-chrome-192x192.png #+END_SRC If you use a logging service like Splunk, log ingestion can be done directly through the API of your vendor via a plugin. Auditing is also the responsibility of a plugin. By default =plg_audit_log= will log the actions made by users in plain text and provide a graphical way to query that audit data from the admin interface [\ref{proof:audit_gui}]. There are a couple more standard plugins that help in threat detection and remediation: - =plg_security_scanner=: this plugin contains heuristics to detect the use of a scanner [\ref{proof:plg_security_scanner}] - =plg_security_killswitch=: this plugin makes it possible to remotely stop an instance if we were to discover a vulnerability in the like of log4j in the future [\ref{proof:plg_security_killswitch}] *** Configuration management #+BEGIN_COMMENT TODO: - [X] content - [X] grammar check #+END_COMMENT By default, the configuration data is stored on the filesystem. Various other plugins can override this default: - =plg_config_env=: stores the config in the =CONFIG_JSON= environment variable as a base64 string - =plg_config_s3=: stores and retrieves the config from an s3 bucket - =plg_config_vault=: stores the config in an hashicorp vault ** Component D: Transport from Filestash to the storage component #+BEGIN_COMMENT TODO: - [X] content - [X] grammar check #+END_COMMENT The security of the transport layer from the Filestash server to the storage component depends heavily on which file transfer protocols you want to use. As such, the hardening of this component can only be done on a protocol-per-protocol basis: - SFTP: the base =plg_backend_sftp= plugin allows for an empty host key which does shortcut the host verification based on the public key fingerprint of the SSH server. The hardened version of the SFTP backend plugin =plg_backend_sftp_hardened= makes the host key verification mandatory. =plg_backend_sftp_hardened= supports 2 fingerprinting mechanisms based on md5 and sha256, and the authentication can be done either via a password or using a private key. To provide additional restrictions, a custom plugin is required - FTP: the base =plg_backend_ftp= supports both FTP and FTPS connection in implicit and explicit mode. The hardened configuration can be done to block FTP altogether and only enabled FTPS in explicit mode (=plg_backend_ftps_explicit=) or in implicit mode (=plg_backend_ftps_implicit=) depending on what is supported by your server - =plg_backend_webdav_hardened=: same as =plg_backend_webdav= except it won't establish a connection over HTTP but only HTTPS. It's possible to create additional customisations to either restrict the domain and other TLS configurations to provide a hardening based on your specific use case - =plg_backend_s3_hardened=: same as =plg_backend_s3= but enforces usage of an encryption key in which case Filestash will be relying on the AWS SDK to encrypt the incoming/outgoing data using AES256 (refer to SSECustomerKey in the AWS SDK documentation). More variations of this plugin can be offered to restrict the capabilities of the s3 backend plugin (eg: restrict region, role, ...) - =plg_backend_git_hardened=: same as =plg_backend_git= but removes support for git over HTTP and only allows git over ssh using a private key and a valid host key In case your company is using their own root certificate and requires changes in how TLS is handled, we can setup a plugin that implements those particular rules. #+BEGIN_EXPORT latex \clearpage \appendix \addtocontents{toc}{\protect\setcounter{tocdepth}{1}} #+END_EXPORT * Appendix \subsection{}\label{proof:plg_dlopen} #+BEGIN_SRC curl -X GET -s "https://demo.filestash.app/about" | \ grep "plg_dlopen" #+END_SRC \subsection{}\label{proof:csp} #+BEGIN_SRC curl -X GET -o /dev/null -s -D - "https://demo.filestash.app/" | \ grep "Content-Security-Policy: " #+END_SRC \subsection{}\label{proof:httponly} #+BEGIN_SRC curl -sD - "https://demo.filestash.app/api/session" \ --header "X-Requested-With: XmlHttpRequest" \ --data '{"type":"webdav","url":"https://webdav.filestash.app/"}' | \ grep -e "Set-Cookie" -e "HttpOnly" #+END_SRC \subsection{}\label{proof:iframe} #+BEGIN_SRC curl -X GET -o /dev/null -s -D - "https://demo.filestash.app/" | \ grep -e "Content-Security-Policy: " -e "frame-src 'self'" #+END_SRC #+BEGIN_COMMENT \subsection{TODO}\label{proof:cors} #+BEGIN_SRC # in the demo instance, we've enable cors to our website to make # the documentation of the API interactive: curl -X GET "https://demo.filestash.app/api/files/ls?share=shareID&key=foobar&path=/" \ -o /dev/null -s -D - | \ grep "Access-Control-Allow-Origin: " # but by default, browser will block access to the instance curl -X GET -o /dev/null -s -D - "https://demo.filestash.app/api/session" | \ grep "Access-Control-Allow-Origin: " #+END_SRC #+END_COMMENT \subsection{}\label{proof:nosniff} #+BEGIN_SRC curl -X GET -o /dev/null -s -D - "https://demo.filestash.app/" | \ grep "X-Content-Type-Options: nosniff" #+END_SRC \subsection{}\label{proof:xss} #+BEGIN_SRC curl -X GET -o /dev/null -s -D - "https://demo.filestash.app/" | \ grep "X-Xss-Protection: 1;" #+END_SRC \subsection{}\label{proof:stateless_get} see [[https://github.com/mickael-kerjean/filestash/blob/master/server/main.go][server/main.go]] #+BEGIN_COMMENT \subsection{TODO}\label{proof:csrf} # https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html - Samesite Cookie - custom request header - verify the origin #+END_COMMENT \subsection{}\label{proof:sts} #+BEGIN_SRC # precondition: "general.force_ssl" is set to "true" curl -o /dev/null -s -D - "http://127.0.0.1:8334/" | \ grep "Strict-Transport-Security: " #+END_SRC \subsection{}\label{proof:origin} #+BEGIN_SRC # precondition: "general.host" is set to "localhost:8334" curl -s 'http://127.0.0.1:8334/api/session' \ -H 'Content-Type: application/json' -H 'X-Requested-With: XmlHttpRequest' \ --data-raw '{"type":"blackhole"}' | \ grep '{"status":"error"' # wrong origin yield errors curl -s 'http://localhost:8334/api/session' \ -H 'Content-Type: application/json' -H 'X-Requested-With: XmlHttpRequest' \ --data-raw '{"type":"blackhole"}' | \ grep '{"status":"ok"' # correct origin does passthrough #+END_SRC \subsection{}\label{proof:plugin_list} #+BEGIN_SRC curl -s "https://demo.filestash.app/about" | \ grep -e "STANDARD" -e "EXTENDED" -e "CUSTOM" | \ sed 's|<[^>]*>||g' | sed 's|^[[:space:]]*||g' #+END_SRC \subsection{}\label{proof:commit_hash} #+BEGIN_SRC curl -s "https://demo.filestash.app/about" | \ grep "Commit hash" | \ sed 's|<[^>]*>| |g' | sed 's|^\t\s*||g' #+END_SRC \subsection{}\label{proof:binary_hash} #+BEGIN_SRC curl -s "https://demo.filestash.app/about" | \ grep "Binary hash" | \ sed 's|<[^>]*>| |g' | sed 's|^\t\s*||g' #+END_SRC \subsection{}\label{proof:config_hash} #+BEGIN_SRC curl -s "https://demo.filestash.app/about" | \ grep "Config hash" | \ sed 's|<[^>]*>| |g' | sed 's|^\t\s*||g' #+END_SRC \subsection{}\label{proof:version} #+BEGIN_SRC curl -X GET -o /dev/null -s -D - "https://demo.filestash.app/" | \ grep "X-Powered-By: " #+END_SRC \subsection{}\label{proof:healthcheck} #+BEGIN_SRC curl -X GET -s "https://demo.filestash.app/healthz" | \ grep '{"status": "pass"}' #+END_SRC \subsection{}\label{proof:security_txt} #+BEGIN_SRC curl -X GET -s "https://demo.filestash.app/.well-known/security.txt" #+END_SRC \subsection{}\label{proof:admin_disabled} #+BEGIN_SRC # precondition: install plg_admin_nil curl -s -X GET -o /dev/null http://localhost:8334/admin | \ grep "HTTP/1.1 404 Not Found" #+END_SRC \subsection{}\label{proof:auth_midleware} #+BEGIN_SRC curl -s "https://demo.filestash.app/about" | \ grep -e "STANDARD" -e "EXTENDED" -e "CUSTOM" | \ sed 's|<[^>]*>||g' | sed 's|^[[:space:]]*||g' | \ grep "plg_authenticate_" #+END_SRC \subsection{}\label{proof:rate_limit} #+BEGIN_SRC echo '{"type":"blackhole"}' > /tmp/post.json ab -n 5000 -c 1 -p /tmp/post.json \ -H "X-Requested-With: XmlHttpRequest" \ "http://localhost:8334/api/session" 2> /dev/null | \ grep -e "Complete requests:" -e "Non-2xx responses:" #+END_SRC \subsection{}\label{proof:audit_gui} [[./assets/proof_audit_gui.png]] \subsection{}\label{proof:plg_security_scanner} #+BEGIN_SRC curl -X GET -s "https://demo.filestash.app/about" | \ grep -e "STANDARD" -e "EXTENDED" -e "CUSTOM" | \ sed 's|<[^>]*>||g' | sed 's|^[[:space:]]*||g' | \ grep "plg_security_scanner" #+END_SRC \subsection{}\label{proof:plg_security_killswitch} #+BEGIN_SRC curl -X GET -s "https://demo.filestash.app/about" | \ grep -e "STANDARD" -e "EXTENDED" -e "CUSTOM" | \ sed 's|<[^>]*>||g' | sed 's|^[[:space:]]*||g' | \ grep "plg_security_killswitch" #+END_SRC