From 5205c9591fee2ede205d2aece24484d19e579841 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Thu, 30 Apr 2020 16:15:29 -0700 Subject: [PATCH] print proper certinfo on console when starting up (#9479) also potentially fix a race in certs.go implementation while accessing tls.Certificate concurrently. --- cmd/config/certsinfo.go | 92 ++++++++++++++++++++++++++++++++++ cmd/data-update-tracker.go | 2 +- cmd/gateway-startup-msg.go | 6 ++- cmd/server-startup-msg.go | 29 +++-------- cmd/server-startup-msg_test.go | 54 -------------------- pkg/certs/certs.go | 21 +++++--- 6 files changed, 118 insertions(+), 86 deletions(-) create mode 100644 cmd/config/certsinfo.go diff --git a/cmd/config/certsinfo.go b/cmd/config/certsinfo.go new file mode 100644 index 000000000..0e013f472 --- /dev/null +++ b/cmd/config/certsinfo.go @@ -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() +} diff --git a/cmd/data-update-tracker.go b/cmd/data-update-tracker.go index 7d73be435..350a1ed0a 100644 --- a/cmd/data-update-tracker.go +++ b/cmd/data-update-tracker.go @@ -206,7 +206,7 @@ func (d *dataUpdateTracker) load(ctx context.Context, drives ...string) { continue } err = d.deserialize(f, d.Saved) - if err != nil { + if err != nil && err != io.EOF { logger.LogIf(ctx, err) } f.Close() diff --git a/cmd/gateway-startup-msg.go b/cmd/gateway-startup-msg.go index 6403518bd..4ac915fee 100644 --- a/cmd/gateway-startup-msg.go +++ b/cmd/gateway-startup-msg.go @@ -43,8 +43,10 @@ func printGatewayStartupMessage(apiEndPoints []string, backendType string) { // SSL is configured reads certification chain, prints // authority and expiry. - if globalIsSSL { - printCertificateMsg(globalPublicCerts) + if color.IsTerminal() && !globalCLIContext.Anonymous { + if globalIsSSL { + printCertificateMsg(globalPublicCerts) + } } } diff --git a/cmd/server-startup-msg.go b/cmd/server-startup-msg.go index ef18b56c2..13454a255 100644 --- a/cmd/server-startup-msg.go +++ b/cmd/server-startup-msg.go @@ -24,6 +24,7 @@ import ( "strings" humanize "github.com/dustin/go-humanize" + "github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/logger" color "github.com/minio/minio/pkg/color" xnet "github.com/minio/minio/pkg/net" @@ -131,8 +132,10 @@ func printStartupMessage(apiEndpoints []string) { // SSL is configured reads certification chain, prints // authority and expiry. - if globalIsSSL { - printCertificateMsg(globalPublicCerts) + if color.IsTerminal() && !globalCLIContext.Anonymous { + if globalIsSSL { + printCertificateMsg(globalPublicCerts) + } } } @@ -298,25 +301,9 @@ func printCacheStorageInfo(storageInfo CacheStorageInfo) { 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. func printCertificateMsg(certs []*x509.Certificate) { - logStartupMessage(getCertificateChainMsg(certs)) + for _, cert := range certs { + logStartupMessage(config.CertificateText(cert)) + } } diff --git a/cmd/server-startup-msg_test.go b/cmd/server-startup-msg_test.go index 3f0f00efd..d0eb14f62 100644 --- a/cmd/server-startup-msg_test.go +++ b/cmd/server-startup-msg_test.go @@ -17,16 +17,11 @@ package cmd import ( - "crypto/x509" - "crypto/x509/pkix" - "fmt" "os" "reflect" "strings" "testing" - "time" - "github.com/minio/minio/pkg/color" "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. 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"} diff --git a/pkg/certs/certs.go b/pkg/certs/certs.go index fd6d00b26..758782b1b 100644 --- a/pkg/certs/certs.go +++ b/pkg/certs/certs.go @@ -36,7 +36,7 @@ type Certs struct { loadCert LoadX509KeyPairFunc // points to the latest certificate. - cert tls.Certificate + cert *tls.Certificate // internal param to track for events, also // used to close the watcher. @@ -89,12 +89,13 @@ func checkSymlink(file string) (bool, error) { // watchSymlinks reloads symlinked files since fsnotify cannot watch // on symbolic links. func (c *Certs) watchSymlinks() (err error) { - c.Lock() - c.cert, err = c.loadCert(c.certFile, c.keyFile) - c.Unlock() + cert, err := c.loadCert(c.certFile, c.keyFile) if err != nil { return err } + c.Lock() + c.cert = &cert + c.Unlock() go func() { for { select { @@ -107,7 +108,7 @@ func (c *Certs) watchSymlinks() (err error) { continue } c.Lock() - c.cert = cert + c.cert = &cert 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 { return err } + cert, err := c.loadCert(c.certFile, c.keyFile) + if err != nil { + return err + } c.Lock() - c.cert, err = c.loadCert(c.certFile, c.keyFile) + c.cert = &cert c.Unlock() if err != nil { return err @@ -162,7 +167,7 @@ func (c *Certs) run() { continue } c.Lock() - c.cert = cert + c.cert = &cert 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) { c.RLock() defer c.RUnlock() - return &c.cert, nil + return c.cert, nil } // Stop tells loader to stop watching for changes to the