Skip to content

Commit

Permalink
add TLS configuration server tests
Browse files Browse the repository at this point in the history
This change adds non-functional tests to check whether a
minio endpoint (TLS) is configured properly.

This includes:
 - SSL/TLS version checks
 - Cipher suite checks

To separate TLS tests from functional tests this change adds a new subdirectory `/run/tls`.

Fixes #253
  • Loading branch information
Andreas Auernhammer committed Jan 17, 2018
1 parent a2e835b commit 7b84511
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 22 deletions.
4 changes: 4 additions & 0 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ RUN apt-get --yes update && apt-get --yes upgrade && apt-get --yes --quiet insta

ENV MINT_ROOT_DIR /mint
ENV MINT_RUN_CORE_DIR $MINT_ROOT_DIR/run/core
ENV MINT_RUN_SECURITY_DIR $MINT_ROOT_DIR/run/security
ENV WGET "wget --quiet --no-check-certificate"

COPY create-data-files.sh /mint
Expand Down Expand Up @@ -60,6 +61,9 @@ RUN build/s3cmd/install.sh
COPY build/minio-dotnet/ /mint/build/minio-dotnet/
RUN /mint/build/minio-dotnet/install.sh

COPY build/security /mint/build/security
RUN build/security/install.sh

COPY remove-packages.list /mint
COPY postinstall.sh /mint
RUN /mint/postinstall.sh
Expand Down
20 changes: 20 additions & 0 deletions build/security/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash -e
#
# Mint (C) 2018 Minio, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

test_run_dir="$MINT_RUN_SECURITY_DIR/tls"
go get -u github.com/sirupsen/logrus/...
go build -o "$test_run_dir/server-tests" "$test_run_dir/server-tests.go"
64 changes: 42 additions & 22 deletions mint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ if [ -z "$SERVER_ENDPOINT" ]; then
fi

ROOT_DIR="$PWD"
TESTS_DIR="$ROOT_DIR/run/core"
CORE_TESTS_DIR="$ROOT_DIR/run/core"
SECURITY_TESTS_DIR="$ROOT_DIR/run/security"

BASE_LOG_DIR="$ROOT_DIR/log"
LOG_FILE="log.json"
Expand Down Expand Up @@ -88,6 +89,32 @@ function run_test()
return $rv
}

function run_tests()
{
i=0
for sdk_dir in "${run_list[@]}"; do
sdk_name=$(basename "$sdk_dir")
(( i++ ))
if [ ! -d "$sdk_dir" ]; then
echo "Test $sdk_name not found. Exiting Mint."
exit 1
fi
echo -n "($i/$count) Running $sdk_name tests ... "
if ! run_test "$sdk_dir"; then
(( i-- ))
break
fi
done

## Report when all tests in run_list are run
if [ $i -eq "$count" ]; then
echo -e "\nAll tests ran successfully"
else
echo -e "\nExecuted $i out of $count tests successfully."
exit 1
fi
}

function main()
{
export MINT_DATA_DIR
Expand All @@ -113,36 +140,29 @@ function main()
declare -a run_list
## Populate values from command line argument
for sdk in "$@"; do
run_list=( "${run_list[@]}" "$TESTS_DIR/$sdk" )
run_list=( "${run_list[@]}" "$CORE_TESTS_DIR/$sdk" )
done

## On empty command line argument, populate all SDK names from $TESTS_DIR
if [ "${#run_list[@]}" -eq 0 ]; then
run_list=( "$TESTS_DIR"/* )
run_list=( "$CORE_TESTS_DIR"/* )
fi

## Run core tests
echo -e "Running core tests:\n"
count="${#run_list[@]}"
i=0
for sdk_dir in "${run_list[@]}"; do
sdk_name=$(basename "$sdk_dir")
(( i++ ))
if [ ! -d "$sdk_dir" ]; then
echo "Test $sdk_name not found. Exiting Mint."
exit 1
fi
echo -n "($i/$count) Running $sdk_name tests ... "
if ! run_test "$sdk_dir"; then
(( i-- ))
break
fi
done
run_tests "${run_list[@]}"

## Report when all tests in run_list are run
if [ $i -eq "$count" ]; then
echo -e "\nAll tests ran successfully"
else
echo -e "\nExecuted $i out of $count tests successfully."
if [ "$ENABLE_HTTPS" -ne 1 ]; then
echo -e "TLS is disabled. Skipping security tests.\n"
exit 0
fi

## Run security tests
echo -e "Running security tests:\n"
count="${#run_list[@]}"
run_list=( "$SECURITY_TESTS_DIR"/* )
run_tests "${run_list[@]}"
}

main "$@"
1 change: 1 addition & 0 deletions release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

export MINT_ROOT_DIR=${MINT_ROOT_DIR:-/mint}
export MINT_RUN_CORE_DIR="$MINT_ROOT_DIR/run/core"
export MINT_RUN_SECURITY_DIR="$MINT_ROOT_DIR/run/security"
export WGET="wget --quiet --no-check-certificate"

./create-data-files.sh
Expand Down
28 changes: 28 additions & 0 deletions run/security/tls/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/bash
#
# Mint (C) 2018 Minio, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# handle command line arguments
if [ $# -ne 2 ]; then
echo "usage: run.sh <OUTPUT-LOG-FILE> <ERROR-LOG-FILE>"
exit -1
fi

output_log_file="$1"
error_log_file="$2"

# run tests
/mint/run/security/tls/server-tests 1>>"$output_log_file" 2>"$error_log_file"
229 changes: 229 additions & 0 deletions run/security/tls/server-tests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// Mint, (C) 2018 Minio, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software

// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"crypto/tls"
"encoding/json"
"fmt"
"os"
"time"

log "github.com/sirupsen/logrus"
)

const testName = "tls-go"

const (
// PASS indicate that a test passed
PASS = "PASS"
// FAIL indicate that a test failed
FAIL = "FAIL"
// NA indicates that a test is not applicable
NA = "NA"
)

func main() {
log.SetOutput(os.Stdout)
log.SetFormatter(&mintJSONFormatter{})
log.SetLevel(log.InfoLevel)

endpoint := os.Getenv("SERVER_ENDPOINT")
secure := os.Getenv("ENABLE_HTTPS")
if secure != "1" {
log.WithFields(log.Fields{"name:": testName, "status": NA, "message": "TLS is not enabled"}).Info()
return
}

testTLSVersions(endpoint)
testTLSCiphers(endpoint)
}

// Tests whether the endpoint accepts SSL3.0, TLS1.0 or TLS1.1 connections - fail if so.
// Tests whether the endpoint accepts TLS1.2 connections - fail if not.
func testTLSVersions(endpoint string) {
const function = "TLSVersions"
startTime := time.Now()

// Tests whether the endpoint accepts SSL3.0, TLS1.0 or TLS1.1 connections
args := map[string]interface{}{
"MinVersion": "tls.VersionSSL30",
"MaxVersion": "tls.VersionTLS11",
}
_, err := tls.Dial("tcp", endpoint, &tls.Config{
MinVersion: tls.VersionSSL30,
MaxVersion: tls.VersionTLS11,
})
if err == nil {
failureLog(function, args, startTime, "", "Endpoint accepts insecure connection", err).Error()
return
}

// Tests whether the endpoint accepts TLS1.2 connections
args = map[string]interface{}{
"MinVersion": "tls.VersionTLS12",
}
_, err = tls.Dial("tcp", endpoint, &tls.Config{
MinVersion: tls.VersionTLS12,
})
if err != nil {
failureLog(function, args, startTime, "", "Endpoint rejects secure connection", err).Error()
return
}
successLog(function, args, startTime)
}

// Tests whether the endpoint accepts SSL3.0, TLS1.0 or TLS1.1 connections - fail if so.
// Tests whether the endpoint accepts TLS1.2 connections - fail if not.
func testTLSCiphers(endpoint string) {
const function = "TLSCiphers"
startTime := time.Now()

// Tests whether the endpoint accepts insecure ciphers
args := map[string]interface{}{
"MinVersion": "tls.VersionTLS12",
"CipherSuites": unsupportedCipherSuites,
}
_, err := tls.Dial("tcp", endpoint, &tls.Config{
MinVersion: tls.VersionTLS12,
CipherSuites: unsupportedCipherSuites,
})
if err == nil {
failureLog(function, args, startTime, "", "Endpoint accepts insecure cipher suites", err).Error()
return
}

// Tests whether the endpoint accepts at least one secure cipher
args = map[string]interface{}{
"MinVersion": "tls.VersionTLS12",
"CipherSuites": supportedCipherSuites,
}
_, err = tls.Dial("tcp", endpoint, &tls.Config{
MinVersion: tls.VersionTLS12,
CipherSuites: supportedCipherSuites,
})
if err != nil {
failureLog(function, args, startTime, "", "Endpoint rejects all secure cipher suites", err).Error()
return
}

// Tests whether the endpoint accepts at least one default cipher
args = map[string]interface{}{
"MinVersion": "tls.VersionTLS12",
"CipherSuites": nil,
}
_, err = tls.Dial("tcp", endpoint, &tls.Config{
MinVersion: tls.VersionTLS12,
CipherSuites: nil, // default value
})
if err != nil {
failureLog(function, args, startTime, "", "Endpoint rejects default cipher suites", err).Error()
return
}
successLog(function, args, startTime)
}

func successLog(function string, args map[string]interface{}, startTime time.Time) *log.Entry {
duration := time.Since(startTime).Nanoseconds() / 1000000
return log.WithFields(log.Fields{
"name": testName,
"function": function,
"args": args,
"duration": duration,
"status": PASS,
})
}

func failureLog(function string, args map[string]interface{}, startTime time.Time, alert string, message string, err error) *log.Entry {
duration := time.Since(startTime).Nanoseconds() / 1000000
fields := log.Fields{
"name": testName,
"function": function,
"args": args,
"duration": duration,
"status": FAIL,
"alert": alert,
"message": message,
}
if err != nil {
fields["error"] = err
}
return log.WithFields(fields)
}

type mintJSONFormatter struct {
}

func (f *mintJSONFormatter) Format(entry *log.Entry) ([]byte, error) {
data := make(log.Fields, len(entry.Data))
for k, v := range entry.Data {
switch v := v.(type) {
case error:
// Otherwise errors are ignored by `encoding/json`
// https://github.com/sirupsen/logrus/issues/137
data[k] = v.Error()
default:
data[k] = v
}
}

serialized, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
}
return append(serialized, '\n'), nil
}

// Secure Go implementations of modern TLS ciphers
// The following ciphers are excluded because:
// - RC4 ciphers: RC4 is broken
// - 3DES ciphers: Because of the 64 bit blocksize of DES (Sweet32)
// - CBC-SHA256 ciphers: No countermeasures against Lucky13 timing attack
// - CBC-SHA ciphers: Legacy ciphers (SHA-1) and non-constant time
// implementation of CBC.
// (CBC-SHA ciphers can be enabled again if required)
// - RSA key exchange ciphers: Disabled because of dangerous PKCS1-v1.5 RSA
// padding scheme. See Bleichenbacher attacks.
var supportedCipherSuites = []uint16{
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
}

var unsupportedCipherSuites = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, // Go stack contains (some) countermeasures against timing attacks (Lucky13)
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, // No countermeasures against timing attacks
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, // Go stack contains (some) countermeasures against timing attacks (Lucky13)
tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, // Broken cipher
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, // Sweet32
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, // Go stack contains (some) countermeasures against timing attacks (Lucky13)
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, // No countermeasures against timing attacks
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, // Go stack contains (some) countermeasures against timing attacks (Lucky13)
tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, // Broken cipher

// all RSA-PKCS1-v1.5 ciphers are disabled - danger of Bleichenbacher attack variants
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, // Sweet32
tls.TLS_RSA_WITH_AES_128_CBC_SHA, // Go stack contains (some) countermeasures against timing attacks (Lucky13)
tls.TLS_RSA_WITH_AES_128_CBC_SHA256, // No countermeasures against timing attacks
tls.TLS_RSA_WITH_AES_256_CBC_SHA, // Go stack contains (some) countermeasures against timing attacks (Lucky13)
tls.TLS_RSA_WITH_RC4_128_SHA, // Broken cipher

tls.TLS_RSA_WITH_AES_128_GCM_SHA256, // Disabled because of RSA-PKCS1-v1.5 - AES-GCM is considered secure.
tls.TLS_RSA_WITH_AES_256_GCM_SHA384, // Disabled because of RSA-PKCS1-v1.5 - AES-GCM is considered secure.
}

0 comments on commit 7b84511

Please sign in to comment.