iceberg is a file server that uses client certificate authentication and policy-based access control. iceberg requires the use of client certificates verified with the certificate authority chain configured at startup.
Iceberg is built in Go. Iceberg uses the net/http and crypto/tls packages in the Go standard library to secure communication. By default, iceberg supports TLS 1.0 to 1.3 and all the CipherSuites implemented by net/http
, excluding those with known security issues. The TLS configuration can be modified using command line flags.
Iceberg is an alternative to configuring the Apache HTTP Server or NGINX to serve files while requiring client certificates. Iceberg does not attempt to be on parity with other file servers, but is designed to be a file server that is simple to manage and secure by default.
The iceberg
program has 5 sub commands: defaults
, help
, serve
, validate-access-policy
, and version
. Use iceberg serve
to launch the server and iceberg validate-access-policy
to validate a policy file. Use iceberg defaults [tls-cipher-suites|tls-curve-preferences]
to show default configuration. Use iceberg version
to show the current version.
Below is the usage for the iceberg serve
command.
start the iceberg server
Usage:
iceberg serve [flags]
Flags:
-p, --access-policy string path to the policy file.
-f, --access-policy-format string format of the policy file (default "json")
-a, --addr string address that iceberg will listen on (default ":8080")
--behavior-not-found string default behavior when a file is not found. One of: redirect,none (default "none")
--client-ca string path to CA bundle for client authentication
--client-ca-format string format of the CA bundle for client authentication, either pkcs7 or pem (default "pkcs7")
--client-crl string path to CRL bundle for client authentication
--client-crl-format string format of the CRL bundle for client authentication, either der, der.zip, or pem (default "der")
--dry-run exit after checking configuration
-h, --help help for serve
--keylog string path to the key log output. Also requires unsafe flag.
-l, --log string path to the log output. Defaults to stdout. (default "-")
--ocsp-http-timeout duration the maximum amount of time before OCSP http requests timeout (default 30s)
--ocsp-refresh-min duration the minimum amount of time to wait before a refresh can occur (default 5m0s)
--ocsp-refresh-ratio float the amount of time to wait for renewal between OCSP production and next update (default 0.8)
--ocsp-renew-interval duration interval to run OCSP renewal (default 5m0s)
--ocsp-server enable OCSP checking on the server certificate
--public-location string the public location of the server used for redirects
--redirect string address that iceberg will listen to and redirect requests to the public location
-r, --root string path to the document root served
--server-cert string path to server public cert
--server-key string path to server private key
-t, --template string path to the template file used during directory listing
--timeout-idle string maximum amount of time to wait for the next request when keep-alives are enabled (default "5m")
--timeout-read string maximum duration for reading the entire request (default "15m")
--timeout-write string maximum duration before timing out writes of the response (default "5m")
--tls-cipher-suites string list of supported cipher suites for TLS versions up to 1.2 (TLS 1.3 is not configurable)
--tls-curve-preferences string curve preferences (default "X25519,CurveP256,CurveP384,CurveP521")
--tls-max-version string maximum TLS version accepted for requests (default "1.3")
--tls-min-version string minimum TLS version accepted for requests (default "1.0")
--tls-prefer-server-cipher-suites prefer server cipher suites
--unsafe allow unsafe configuration
iceberg requires the use of a server certificate and client certificate authentication. The server certificate is loaded from a PEM-encoded x509 key pair using the LoadX509KeyPair function. The location of the key pair is specified using the --server-cert
and --server-key
command line flags.
The client certificate authorities can be loaded from a PKCS#7 or PEM-encoded file. The Parse function in go.mozilla.org/pkcs7 is used to parse the PKCS#7-encoded data loaded from a .p7b
file. The AppendCertsFromPEM method is used to parse PEM-encoded data loaded from a .pem
file. The location of the client certificate authorities is specified using the client-ca
and client-ca-format
command line flags.
You can use the tls-*
flags to customize the server TLS configuration. Options are very limited for TLS 1.3.
For example, you could configure the server to only support TLS version 1.2
and specific ciphers using --tls-min-version
, --tls-max-version
, and --tls-cipher-suites
.
iceberg serve ... --tls-min-version 1.2 --tls-max-version 1.2 --tls-cipher-suites 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256'
Mozilla keeps a Security/Server Side TLS document up to date on the best practices for configuring a server for TLS.
An access policy document is a list of statements that are evaluate when determining whether a user can access a given file path. By default, users have no privileges. The access policy document can be serialized as a JSON or YAML document.
{
"statements": [...]
}
The policy statements are evaluated sequentially. If the first policy statement allows access, the other statements are still evaluated. If any statement denies access, then the user is denied access.
Each statement requires the effect
, paths
, and users
/not_users
to be set. The id is optional and not used during evaluation. The effect is either allow
or deny
.
{
"id": "DefaultAllow",
"effect": "allow",
"paths": [...],
"users": [...],
"not_users": [],
}
The values for paths
includes an array of paths from the root directory as set during startup, e.g., /shared
. Paths can start or end with a wildcard
, eg., /shared/*
or *.jpg
.
{
"paths": [
"/shared/",
"/shared/*"
]
}
The value for paths
can be set to ["*"]
to apply to all file paths.
{
"paths": ["*"]
}
The values included in users
or not_users
includes an array of distinguished names derived from the the subject of the client certificate provided by the connecting client. The value of not_users
means the statement effect is applied to any user not in the array. For example.
{
"users": [
"/C=US/O=Atlantis/OU=Atlantis Digital Service/OU=CONTRACTOR/CN=LAST.FIRST.MIDDLE.ID"
]
}
The value for users
can be set to ["*"]
to apply to all users.
{
"users": ["*"]
}
The template provided during server startup is used to render directory listings using the native Go template rendering engine in the html/template package. The template is provided the following context.
struct {
Up string
Directory string
Files []struct {
ModTime string
Size int64
Type string
Path string
}
})
In addition to the default functions available, the prefix
and suffix
template functions are also available. For example, the suffix function can be used to conditionally write an html element based on the file extension.
{{if .Path | suffix ".mp4"}}Video{{else}}Other{{end}}
Below are the example commands and files needed to run a server that, by default allows access to all files, but limits access to the /secure
path to a limited set of users identified by their client certificate subject distinguished name.
iceberg serve \
--access-policy examples/conf/example.json \
--client-ca temp/certs.p7b \
--root examples/public \
--server-cert temp/server.crt \
--server-key temp/server.key \
--template examples/conf/template.html \
--behavior-not-found redirect
The below policy statement allows access to any authenticated user and then limits access to /secure
to a limited set of users.
{
"statements": [
{
"id": "DefaultAllow",
"effect": "allow",
"paths": [
"*"
],
"users": [
"*"
]
},
{
"id": "ProtectSecure",
"effect": "deny",
"paths": [
"/secure",
"/secure/*"
],
"not_users": [
"/C=US/O=Atlantis/OU=Atlantis Digital Service/OU=CONTRACTOR/CN=LAST.FIRST.MIDDLE.ID",
]
}
]
}
iceberg is written in pure Go, so the only dependency needed to compile the server is Go. Go can be downloaded from https://golang.org/dl/.
This project uses direnv to manage environment variables and automatically adding the bin
and scripts
folder to the path. Install direnv and hook it into your shell. The use of direnv
is optional as you can always call iceberg directly with bin/iceberg
.
If using macOS
, follow the macOS
instructions below.
To build a binary for your local operating system you can use make bin/iceberg
. To build for a release, you can use make build_release
. Additionally, you can call go build
directly to support specific use cases.
You can install go
on macOS using homebrew with brew install go
.
To install direnv
on macOS
use brew install direnv
. If using bash, then add eval \"$(direnv hook bash)\"
to the ~/.bash_profile
file . If using zsh, then add eval \"$(direnv hook zsh)\"
to the ~/.zshrc
file.
To get started you will need to edit the /etc/hosts
file on your machine to have this entry:
127.0.0.1 iceberglocal
To connect to the server you need a client certificate and key pair, so create that file first:
make temp/client.p12
To run the example included in this repository start by running the server in docker:
make docker_build docker_serve_example
Now connect to the server with cURL to view the index.html
file or the contents of the allowed/
directory:
curl --cacert ./temp/ca.crt --key ./temp/client.key --cert ./temp/client.crt https://iceberglocal:8080/index.html
curl --cacert ./temp/ca.crt --key ./temp/client.key --cert ./temp/client.crt https://iceberglocal:8080/allowed/
To view files based on the contents of the conf/example.json
file:
curl --cacert ./temp/ca.crt --key ./temp/client.key --cert ./temp/client.crt https://iceberglocal:8080/allowed/a.txt
curl --cacert ./temp/ca.crt --key ./temp/client.key --cert ./temp/client.crt https://iceberglocal:8080/allowed/b.txt
Other directories and files will be explicitly denied based on the same example policy:
curl --cacert ./temp/ca.crt --key ./temp/client.key --cert ./temp/client.crt https://iceberglocal:8080/allowed/123.abc
curl --cacert ./temp/ca.crt --key ./temp/client.key --cert ./temp/client.crt https://iceberglocal:8080/denied/
curl --cacert ./temp/ca.crt --key ./temp/client.key --cert ./temp/client.crt https://iceberglocal:8080/denied/a.txt
curl --cacert ./temp/ca.crt --key ./temp/client.key --cert ./temp/client.crt https://iceberglocal:8080/denied/b.txt
curl --cacert ./temp/ca.crt --key ./temp/client.key --cert ./temp/client.crt https://iceberglocal:8080/denied/123.abc
You may consider using a web browser to view the files. In this case you need to load the Certificate Authority and identity file into the firefox Certificate Manager. Browse to "about:preferences#privacy" and search for the section labeled "Certificates". Use the "View Certificates" button to open the Certificate Manager.
In the "Authorities" tab use the "Import..." button to load the temp/ca.crt
file. When asked select
"Trust this CA to identify websites." and then continue by selecting "OK".
In the "Your Certificates" tab use the "Import..." button to load the temp/client.p12
file. There is no password to enter
so select the "OK" button immediately.
Now you may browse to the website at https://iceberglocal:8080.
Key logging should only be used in development since it compromises the security of TLS. You can output TLS master secrets in the NSS Key Log Format by using the --unsafe
and --keylog KEYLOG
flags. The newly created file can be used by Wireshark or other applications to decrypt TLS traffic.
NOTE: At this time this will not work with Docker.
OCSP is the preferred method of managing certificate revocation as certificate revocation lists can get outdated and quite large. OCSP stapling embeds an OCSP response in the TLS Server Hello to remove the need for the client to contact the OCSP responder separately. In order to work with OCSP you first need to create OCSP certificate and key pair in order to run an OCSP responder server with OpenSSL. Additionally you'll need a server or client key to verify.
Start by ensuring all certs are renewed:
rm -rf temp && make temp/ca.crt temp/ocsp.crt temp/client.p12 temp/server.crt
Now turn on the OCSP Responder server in another terminal instance:
make ocsp_responder
Verify the OCSP server information is on the client certificate with:
openssl x509 -in temp/client.crt -text -noout
A section that looks like this should appear:
X509v3 extensions:
Authority Information Access:
OCSP - URI:http://127.0.0.1:9999
Validate the client certificate with:
make ocsp_validate_client
You will see a response like:
...
Response verify OK
temp/client.crt: unknown
This Update: Sep 22 22:06:51 2020 GMT
Next Update: Sep 22 22:11:51 2020 GMT
Validate the server certificate with:
make ocsp_validate_server
You will see a response like:
...
Response verify OK
temp/server.crt: unknown
This Update: Sep 22 22:06:51 2020 GMT
Next Update: Sep 22 22:11:51 2020 GMT
Run the server now:
rm bin/iceberg && make bin/iceberg serve_example_ocsp
Get a file to show things are working and check that the OCSP responder was connected to:
make check_client_response
Check the OCSP response:
make ocsp_check_client_response
Now revoke the server:
make ocsp_revoke_server
Restart the responder and then validate the server certificate was revoked with:
make ocsp_validate_server
Check for output that has the Revocation Time
:
...
Response verify OK
temp/server.crt: revoked
This Update: Sep 22 21:52:29 2020 GMT
Next Update: Sep 22 21:57:29 2020 GMT
Revocation Time: Sep 22 21:52:11 2020 GMT
And run again:
make check_client_response
Since the certificate was revoked, iceberg will not return the staple. Check the response:
make ocsp_check_client_response
CLI
To run CLI testes use make test_cli
, which uses shUnit2. If you recive a shunit2:FATAL Please declare TMPDIR with path on partition with exec permission.
error, you can modify the TMPDIR
environment variable in line or with export TMPDIR=<YOUR TEMP DIRECTORY HERE>
. For example:
TMPDIR="/usr/local/tmp" make test_cli
Go
To run Go tests use make test_go
(or bash scripts/test.sh
), which runs unit tests, go vet
, go vet with shadow
, errcheck, staticcheck, and misspell.
We'd love to have your contributions! Please see CONTRIBUTING.md for more info.
Please see SECURITY.md for more info.
This project constitutes a work of the United States Government and is not subject to domestic copyright protection under 17 USC § 105. However, because the project utilizes code licensed from contributors and other third parties, it therefore is licensed under the MIT License. See LICENSE file for more information.