Display SSL expiry warnings (#2925)

This commit is contained in:
Mateusz Gajewski
2016-10-14 13:48:08 +02:00
committed by Harshavardhana
parent 0320a77dc0
commit c03ce0f74a
11 changed files with 752 additions and 6 deletions

View File

@@ -17,8 +17,13 @@
package cmd
import (
"crypto/x509"
"encoding/pem"
"io/ioutil"
"os"
"path/filepath"
"github.com/pkg/errors"
)
// createCertsPath create certs path.
@@ -87,3 +92,47 @@ func isSSL() bool {
}
return false
}
func readCertificateChain() ([]*x509.Certificate, error) {
certPath := filepath.Join(mustGetCertsPath(), globalMinioCertFile)
file, err := os.Open(certPath)
if err != nil {
return nil, errors.Wrapf(err, "Could not open certificate for reading")
}
defer file.Close()
bytes, err2 := ioutil.ReadAll(file)
if err2 != nil {
return nil, errors.Wrapf(err2, "Could not read certificate contents")
}
return parseCertificateChain(bytes)
}
// Parses certificate chain
func parseCertificateChain(bytes []byte) ([]*x509.Certificate, error) {
var certs []*x509.Certificate
var block *pem.Block
current := bytes
for len(current) > 0 {
block, current = pem.Decode(current)
if block == nil {
return nil, errors.New("Could not PEM block")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, errors.Wrapf(err, "Could not parse certficiate")
}
certs = append(certs, cert)
}
return certs, nil
}

View File

@@ -58,3 +58,54 @@ func TestGetFiles(t *testing.T) {
t.Errorf("KeyFile does not contain %s", globalMinioKeyFile)
}
}
// Parses .crt file contents
func TestParseCertificateChain(t *testing.T) {
// given
cert := `-----BEGIN CERTIFICATE-----
MIICdTCCAd4CCQCO5G/W1xcE9TANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJa
WTEOMAwGA1UECBMFTWluaW8xETAPBgNVBAcTCEludGVybmV0MQ4wDAYDVQQKEwVN
aW5pbzEOMAwGA1UECxMFTWluaW8xDjAMBgNVBAMTBU1pbmlvMR0wGwYJKoZIhvcN
AQkBFg50ZXN0c0BtaW5pby5pbzAeFw0xNjEwMTQxMTM0MjJaFw0xNzEwMTQxMTM0
MjJaMH8xCzAJBgNVBAYTAlpZMQ4wDAYDVQQIEwVNaW5pbzERMA8GA1UEBxMISW50
ZXJuZXQxDjAMBgNVBAoTBU1pbmlvMQ4wDAYDVQQLEwVNaW5pbzEOMAwGA1UEAxMF
TWluaW8xHTAbBgkqhkiG9w0BCQEWDnRlc3RzQG1pbmlvLmlvMIGfMA0GCSqGSIb3
DQEBAQUAA4GNADCBiQKBgQDwNUYB/Sj79WsUE8qnXzzh2glSzWxUE79sCOpQYK83
HWkrl5WxlG8ZxDR1IQV9Ex/lzigJu8G+KXahon6a+3n5GhNrYRe5kIXHQHz0qvv4
aMulqlnYpvSfC83aaO9GVBtwXS/O4Nykd7QBg4nZlazVmsGk7POOjhpjGShRsqpU
JwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALqjOA6bD8BEl7hkQ8XwX/owSAL0URDe
nUfCOsXgIIAqgw4uTCLOfCJVZNKmRT+KguvPAQ6Z80vau2UxPX5Q2Q+OHXDRrEnK
FjqSBgLP06Qw7a++bshlWGTt5bHWOneW3EQikedckVuIKPkOCib9yGi4VmBBjdFE
M9ofSEt/bdRD
-----END CERTIFICATE-----`
// when
certs, err := parseCertificateChain([]byte(cert))
// then
if err != nil {
t.Fatalf("Could not parse certificate: %s", err)
}
if len(certs) != 1 {
t.Fatalf("Expected number of certificates in chain was 1, actual: %d", len(certs))
}
if certs[0].Subject.CommonName != "Minio" {
t.Fatalf("Expected Subject.CommonName was Minio, actual: %s", certs[0].Subject.CommonName)
}
}
// Parses invalid .crt file contents and returns error
func TestParseInvalidCertificateChain(t *testing.T) {
// given
cert := `This is now valid certificate`
// when
_, err := parseCertificateChain([]byte(cert))
// then
if err == nil {
t.Fatalf("Expected error but none occured")
}
}

View File

@@ -30,12 +30,13 @@ const (
// minio configuration related constants.
const (
globalMinioConfigVersion = "9"
globalMinioConfigDir = ".minio"
globalMinioCertsDir = "certs"
globalMinioCertFile = "public.crt"
globalMinioKeyFile = "private.key"
globalMinioConfigFile = "config.json"
globalMinioConfigVersion = "9"
globalMinioConfigDir = ".minio"
globalMinioCertsDir = "certs"
globalMinioCertFile = "public.crt"
globalMinioKeyFile = "private.key"
globalMinioConfigFile = "config.json"
globalMinioCertExpireWarnDays = 30
// Add new global values here.
)

View File

@@ -17,10 +17,12 @@
package cmd
import (
"crypto/x509"
"fmt"
"os"
"runtime"
"strings"
"time"
humanize "github.com/dustin/go-humanize"
"github.com/minio/mc/pkg/console"
@@ -46,10 +48,15 @@ func printStartupMessage(endPoints []string) {
printServerCommonMsg(endPoints)
printCLIAccessMsg(endPoints[0])
printObjectAPIMsg()
objAPI := newObjectLayerFn()
if objAPI != nil {
printStorageInfo(objAPI.StorageInfo())
}
if certs, err := readCertificateChain(); err == nil {
printCertificateMsg(certs)
}
}
// Prints common server startup message. Prints credential, region and browser access.
@@ -147,3 +154,28 @@ func printStorageInfo(storageInfo StorageInfo) {
console.Println()
console.Println(getStorageInfoMsg(storageInfo))
}
// Prints certificate expiry date warning
func getCertificateChainMsg(certs []*x509.Certificate) string {
msg := colorBlue("\nCertificate expiry info:\n")
totalCerts := len(certs)
var expiringCerts int
for i := totalCerts - 1; i >= 0; i-- {
cert := certs[i]
if cert.NotAfter.Before(time.Now().Add(time.Hour * 24 * globalMinioCertExpireWarnDays)) {
expiringCerts++
msg += fmt.Sprintf(colorBold("#%d %s will expire on %s\n"), expiringCerts, cert.Subject.CommonName, cert.NotAfter)
}
}
if expiringCerts > 0 {
return msg
}
return ""
}
func printCertificateMsg(certs []*x509.Certificate) {
console.Println(getCertificateChainMsg(certs))
}

View File

@@ -17,8 +17,12 @@
package cmd
import (
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"strings"
"testing"
"time"
)
// Tests if we generate storage info.
@@ -39,3 +43,52 @@ func TestStorageInfoMsg(t *testing.T) {
t.Fatal("Empty message string is not implemented", msg)
}
}
// Tests if certificate expiry warning will be printed
func TestCertificateExpiryInfo(t *testing.T) {
// given
var expiredDate = time.Now().Add(time.Hour * 24 * (globalMinioCertExpireWarnDays - 1))
var fakeCerts = []*x509.Certificate{
&x509.Certificate{
NotAfter: expiredDate,
Subject: pkix.Name{
CommonName: "Test cert",
},
},
}
expectedMsg := colorBlue("\nCertificate expiry info:\n") +
colorBold(fmt.Sprintf("#1 Test cert will expire on %s\n", expiredDate))
// when
msg := getCertificateChainMsg(fakeCerts)
// then
if msg != expectedMsg {
t.Fatalf("Expected message was: %s, got: %s", expectedMsg, msg)
}
}
// Tests if certificate expiry warning will not be printed if certificate not expired
func TestCertificateNotExpired(t *testing.T) {
// given
var expiredDate = time.Now().Add(time.Hour * 24 * (globalMinioCertExpireWarnDays + 1))
var fakeCerts = []*x509.Certificate{
&x509.Certificate{
NotAfter: expiredDate,
Subject: pkix.Name{
CommonName: "Test cert",
},
},
}
// when
msg := getCertificateChainMsg(fakeCerts)
// then
if msg != "" {
t.Fatalf("Expected empty message was: %s", msg)
}
}