print proper certinfo on console when starting up (#9479)

also potentially fix a race in certs.go implementation
while accessing tls.Certificate concurrently.
This commit is contained in:
Harshavardhana 2020-04-30 16:15:29 -07:00 committed by GitHub
parent 9a547dcbfb
commit 5205c9591f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 118 additions and 86 deletions

92
cmd/config/certsinfo.go Normal file
View File

@ -0,0 +1,92 @@
/*
* MinIO Cloud Storage, (C) 2020 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 config
import (
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"net/http"
"strings"
color "github.com/minio/minio/pkg/color"
)
// Extra ASN1 OIDs that we may need to handle
var (
oidEmailAddress = []int{1, 2, 840, 113549, 1, 9, 1}
)
// printName prints the fields of a distinguished name, which include such
// things as its common name and locality.
func printName(names []pkix.AttributeTypeAndValue, buf *strings.Builder) []string {
values := []string{}
for _, name := range names {
oid := name.Type
if len(oid) == 4 && oid[0] == 2 && oid[1] == 5 && oid[2] == 4 {
switch oid[3] {
case 3:
values = append(values, fmt.Sprintf("CN=%s", name.Value))
case 6:
values = append(values, fmt.Sprintf("C=%s", name.Value))
case 8:
values = append(values, fmt.Sprintf("ST=%s", name.Value))
case 10:
values = append(values, fmt.Sprintf("O=%s", name.Value))
case 11:
values = append(values, fmt.Sprintf("OU=%s", name.Value))
default:
values = append(values, fmt.Sprintf("UnknownOID=%s", name.Type.String()))
}
} else if oid.Equal(oidEmailAddress) {
values = append(values, fmt.Sprintf("emailAddress=%s", name.Value))
} else {
values = append(values, fmt.Sprintf("UnknownOID=%s", name.Type.String()))
}
}
if len(values) > 0 {
buf.WriteString(values[0])
for i := 1; i < len(values); i++ {
buf.WriteString(", " + values[i])
}
buf.WriteString("\n")
}
return values
}
// CertificateText returns a human-readable string representation
// of the certificate cert. The format is similar to the OpenSSL
// way of printing certificates (not identical).
func CertificateText(cert *x509.Certificate) string {
var buf strings.Builder
buf.WriteString(color.Blue("\nCertificate:\n"))
if cert.SignatureAlgorithm != x509.UnknownSignatureAlgorithm {
buf.WriteString(color.Blue("%4sSignature Algorithm: ", "") + color.Bold(fmt.Sprintf("%s\n", cert.SignatureAlgorithm)))
}
// Issuer information
buf.WriteString(color.Blue("%4sIssuer: ", ""))
printName(cert.Issuer.Names, &buf)
// Validity information
buf.WriteString(color.Blue("%4sValidity\n", ""))
buf.WriteString(color.Bold(fmt.Sprintf("%8sNot Before: %s\n", "", cert.NotBefore.Format(http.TimeFormat))))
buf.WriteString(color.Bold(fmt.Sprintf("%8sNot After : %s\n", "", cert.NotAfter.Format(http.TimeFormat))))
return buf.String()
}

View File

@ -206,7 +206,7 @@ func (d *dataUpdateTracker) load(ctx context.Context, drives ...string) {
continue continue
} }
err = d.deserialize(f, d.Saved) err = d.deserialize(f, d.Saved)
if err != nil { if err != nil && err != io.EOF {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
} }
f.Close() f.Close()

View File

@ -43,8 +43,10 @@ func printGatewayStartupMessage(apiEndPoints []string, backendType string) {
// SSL is configured reads certification chain, prints // SSL is configured reads certification chain, prints
// authority and expiry. // authority and expiry.
if globalIsSSL { if color.IsTerminal() && !globalCLIContext.Anonymous {
printCertificateMsg(globalPublicCerts) if globalIsSSL {
printCertificateMsg(globalPublicCerts)
}
} }
} }

View File

@ -24,6 +24,7 @@ import (
"strings" "strings"
humanize "github.com/dustin/go-humanize" humanize "github.com/dustin/go-humanize"
"github.com/minio/minio/cmd/config"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
color "github.com/minio/minio/pkg/color" color "github.com/minio/minio/pkg/color"
xnet "github.com/minio/minio/pkg/net" xnet "github.com/minio/minio/pkg/net"
@ -131,8 +132,10 @@ func printStartupMessage(apiEndpoints []string) {
// SSL is configured reads certification chain, prints // SSL is configured reads certification chain, prints
// authority and expiry. // authority and expiry.
if globalIsSSL { if color.IsTerminal() && !globalCLIContext.Anonymous {
printCertificateMsg(globalPublicCerts) if globalIsSSL {
printCertificateMsg(globalPublicCerts)
}
} }
} }
@ -298,25 +301,9 @@ func printCacheStorageInfo(storageInfo CacheStorageInfo) {
logStartupMessage(msg) logStartupMessage(msg)
} }
// Prints certificate expiry date warning
func getCertificateChainMsg(certs []*x509.Certificate) string {
msg := color.Blue("\nCertificate expiry info:\n")
totalCerts := len(certs)
var expiringCerts int
for i := totalCerts - 1; i >= 0; i-- {
cert := certs[i]
if cert.NotAfter.Before(UTCNow().Add(globalMinioCertExpireWarnDays)) {
expiringCerts++
msg += fmt.Sprintf(color.Bold("#%d %s will expire on %s\n"), expiringCerts, cert.Subject.CommonName, cert.NotAfter)
}
}
if expiringCerts > 0 {
return msg
}
return ""
}
// Prints the certificate expiry message. // Prints the certificate expiry message.
func printCertificateMsg(certs []*x509.Certificate) { func printCertificateMsg(certs []*x509.Certificate) {
logStartupMessage(getCertificateChainMsg(certs)) for _, cert := range certs {
logStartupMessage(config.CertificateText(cert))
}
} }

View File

@ -17,16 +17,11 @@
package cmd package cmd
import ( import (
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"os" "os"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"time"
"github.com/minio/minio/pkg/color"
"github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/madmin"
) )
@ -42,55 +37,6 @@ func TestStorageInfoMsg(t *testing.T) {
} }
} }
// Tests if certificate expiry warning will be printed
func TestCertificateExpiryInfo(t *testing.T) {
// given
var expiredDate = time.Now().Add(time.Hour * 24 * (30 - 1)) // 29 days.
var fakeCerts = []*x509.Certificate{
{
NotAfter: expiredDate,
Subject: pkix.Name{
CommonName: "Test cert",
},
},
}
expectedMsg := color.Blue("\nCertificate expiry info:\n") +
color.Bold(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 * (30 + 1)) // 31 days.
var fakeCerts = []*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)
}
}
// Tests stripping standard ports from apiEndpoints. // Tests stripping standard ports from apiEndpoints.
func TestStripStandardPorts(t *testing.T) { func TestStripStandardPorts(t *testing.T) {
apiEndpoints := []string{"http://127.0.0.1:9000", "http://127.0.0.2:80", "https://127.0.0.3:443"} apiEndpoints := []string{"http://127.0.0.1:9000", "http://127.0.0.2:80", "https://127.0.0.3:443"}

View File

@ -36,7 +36,7 @@ type Certs struct {
loadCert LoadX509KeyPairFunc loadCert LoadX509KeyPairFunc
// points to the latest certificate. // points to the latest certificate.
cert tls.Certificate cert *tls.Certificate
// internal param to track for events, also // internal param to track for events, also
// used to close the watcher. // used to close the watcher.
@ -89,12 +89,13 @@ func checkSymlink(file string) (bool, error) {
// watchSymlinks reloads symlinked files since fsnotify cannot watch // watchSymlinks reloads symlinked files since fsnotify cannot watch
// on symbolic links. // on symbolic links.
func (c *Certs) watchSymlinks() (err error) { func (c *Certs) watchSymlinks() (err error) {
c.Lock() cert, err := c.loadCert(c.certFile, c.keyFile)
c.cert, err = c.loadCert(c.certFile, c.keyFile)
c.Unlock()
if err != nil { if err != nil {
return err return err
} }
c.Lock()
c.cert = &cert
c.Unlock()
go func() { go func() {
for { for {
select { select {
@ -107,7 +108,7 @@ func (c *Certs) watchSymlinks() (err error) {
continue continue
} }
c.Lock() c.Lock()
c.cert = cert c.cert = &cert
c.Unlock() c.Unlock()
} }
} }
@ -138,8 +139,12 @@ func (c *Certs) watch() (err error) {
if err = notify.Watch(filepath.Dir(c.keyFile), c.e, eventWrite...); err != nil { if err = notify.Watch(filepath.Dir(c.keyFile), c.e, eventWrite...); err != nil {
return err return err
} }
cert, err := c.loadCert(c.certFile, c.keyFile)
if err != nil {
return err
}
c.Lock() c.Lock()
c.cert, err = c.loadCert(c.certFile, c.keyFile) c.cert = &cert
c.Unlock() c.Unlock()
if err != nil { if err != nil {
return err return err
@ -162,7 +167,7 @@ func (c *Certs) run() {
continue continue
} }
c.Lock() c.Lock()
c.cert = cert c.cert = &cert
c.Unlock() c.Unlock()
} }
} }
@ -177,7 +182,7 @@ type GetCertificateFunc func(hello *tls.ClientHelloInfo) (*tls.Certificate, erro
func (c *Certs) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { func (c *Certs) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
c.RLock() c.RLock()
defer c.RUnlock() defer c.RUnlock()
return &c.cert, nil return c.cert, nil
} }
// Stop tells loader to stop watching for changes to the // Stop tells loader to stop watching for changes to the