From 8a9852220d5949d307a465e87e2cb3d3f88d0d9c Mon Sep 17 00:00:00 2001 From: Bala FA Date: Thu, 9 Mar 2017 08:50:01 +0530 Subject: [PATCH] Make unit testable cert parsing functions. (#3863) --- cmd/admin-handlers.go | 2 +- cmd/certs.go | 160 ++++++++++------------- cmd/certs_test.go | 253 +++++++++++++++++++++++++++++-------- cmd/config-dir.go | 139 ++++++++++++++++++++ cmd/config-migrate_test.go | 6 +- cmd/config.go | 78 ------------ cmd/globals.go | 6 - cmd/server-main.go | 11 +- cmd/server-mux_test.go | 10 +- 9 files changed, 420 insertions(+), 245 deletions(-) create mode 100644 cmd/config-dir.go delete mode 100644 cmd/config.go diff --git a/cmd/admin-handlers.go b/cmd/admin-handlers.go index 98ede6b53..c5231ba68 100644 --- a/cmd/admin-handlers.go +++ b/cmd/admin-handlers.go @@ -803,7 +803,7 @@ func (adminAPI adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http // Take a lock on minio/config.json. NB minio is a reserved // bucket name and wouldn't conflict with normal object // operations. - configLock := globalNSMutex.NewNSLock(minioReservedBucket, globalMinioConfigFile) + configLock := globalNSMutex.NewNSLock(minioReservedBucket, minioConfigFile) configLock.Lock() defer configLock.Unlock() diff --git a/cmd/certs.go b/cmd/certs.go index 4c8311122..af2696de8 100644 --- a/cmd/certs.go +++ b/cmd/certs.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,120 +19,90 @@ package cmd import ( "crypto/x509" "encoding/pem" - "errors" + "fmt" "io/ioutil" "path/filepath" ) -// getCertsPath get certs path. -func getCertsPath() string { - return filepath.Join(getConfigDir(), globalMinioCertsDir) -} - -// getCertFile must get cert file. -func getCertFile() string { - return filepath.Join(getCertsPath(), globalMinioCertFile) -} - -// getKeyFile must get key file. -func getKeyFile() string { - return filepath.Join(getCertsPath(), globalMinioKeyFile) -} - -// createCertsPath create certs path. -func createCertsPath() error { - rootCAsPath := filepath.Join(getCertsPath(), globalMinioCertsCADir) - return mkdirAll(rootCAsPath, 0700) -} - -// getCAFiles must get the list of the CA certificates stored in minio config dir -func getCAFiles() (caCerts []string) { - CAsDir := filepath.Join(getCertsPath(), globalMinioCertsCADir) - if caFiles, err := ioutil.ReadDir(CAsDir); err == nil { - // Ignore any error. - for _, cert := range caFiles { - caCerts = append(caCerts, filepath.Join(CAsDir, cert.Name())) - } - } - return caCerts -} - -// getSystemCertPool returns empty cert pool in case of error (windows) -func getSystemCertPool() *x509.CertPool { - pool, err := x509.SystemCertPool() - if err != nil { - pool = x509.NewCertPool() - } - - return pool -} - -// isCertFileExists verifies if cert file exists, returns true if -// found, false otherwise. -func isCertFileExists() bool { - return isFile(getCertFile()) -} - -// isKeyFileExists verifies if key file exists, returns true if found, -// false otherwise. -func isKeyFileExists() bool { - return isFile(getKeyFile()) -} - // isSSL - returns true with both cert and key exists. func isSSL() bool { - return isCertFileExists() && isKeyFileExists() + return isFile(getPublicCertFile()) && isFile(getPrivateKeyFile()) } -// Reads certificated file and returns a list of parsed certificates. +func parsePublicCertFile(certFile string) (certs []*x509.Certificate, err error) { + var bytes []byte + + if bytes, err = ioutil.ReadFile(certFile); err != nil { + return certs, err + } + + // Parse all certs in the chain. + var block *pem.Block + var cert *x509.Certificate + current := bytes + for len(current) > 0 { + if block, current = pem.Decode(current); block == nil { + err = fmt.Errorf("Could not read PEM block from file %s", certFile) + return certs, err + } + + if cert, err = x509.ParseCertificate(block.Bytes); err != nil { + return certs, err + } + + certs = append(certs, cert) + } + + if len(certs) == 0 { + err = fmt.Errorf("Empty public certificate file %s", certFile) + } + + return certs, err +} + +// Reads certificate file and returns a list of parsed certificates. func readCertificateChain() ([]*x509.Certificate, error) { - bytes, err := ioutil.ReadFile(getCertFile()) + return parsePublicCertFile(getPublicCertFile()) +} + +func getRootCAs(certsCAsDir string) (*x509.CertPool, error) { + // Get all CA file names. + var caFiles []string + fis, err := ioutil.ReadDir(certsCAsDir) if err != nil { return nil, err } - - // Proceed to parse the certificates. - return parseCertificateChain(bytes) -} - -// Parses certificate chain, returns a list of parsed certificates. -func parseCertificateChain(bytes []byte) ([]*x509.Certificate, error) { - var certs []*x509.Certificate - var block *pem.Block - current := bytes - - // Parse all certs in the chain. - for len(current) > 0 { - block, current = pem.Decode(current) - if block == nil { - return nil, errors.New("Could not PEM block") - } - // Parse the decoded certificate. - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, err - } - certs = append(certs, cert) - + for _, fi := range fis { + caFiles = append(caFiles, filepath.Join(certsCAsDir, fi.Name())) } - return certs, nil -} -// loadRootCAs fetches CA files provided in minio config and adds them to globalRootCAs -// Currently under Windows, there is no way to load system + user CAs at the same time -func loadRootCAs() { - caFiles := getCAFiles() if len(caFiles) == 0 { - return + return nil, nil } - // Get system cert pool, and empty cert pool under Windows because it is not supported - globalRootCAs = getSystemCertPool() + + rootCAs, err := x509.SystemCertPool() + if err != nil { + // In some systems like Windows, system cert pool is not supported. + // Hence we create a new cert pool. + rootCAs = x509.NewCertPool() + } + // Load custom root CAs for client requests for _, caFile := range caFiles { caCert, err := ioutil.ReadFile(caFile) if err != nil { - fatalIf(err, "Unable to load a CA file") + return rootCAs, err } - globalRootCAs.AppendCertsFromPEM(caCert) + + rootCAs.AppendCertsFromPEM(caCert) } + + return rootCAs, nil +} + +// loadRootCAs fetches CA files provided in minio config and adds them to globalRootCAs +// Currently under Windows, there is no way to load system + user CAs at the same time +func loadRootCAs() (err error) { + globalRootCAs, err = getRootCAs(getCADir()) + return err } diff --git a/cmd/certs_test.go b/cmd/certs_test.go index 5d8c649a9..730f311ff 100644 --- a/cmd/certs_test.go +++ b/cmd/certs_test.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc. + * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,49 +17,86 @@ package cmd import ( + "fmt" + "io/ioutil" "os" "path/filepath" - "strings" + "runtime" "testing" ) -// Make sure we have a valid certs path. -func TestGetCertsPath(t *testing.T) { - path := getCertsPath() - if path == "" { - t.Errorf("expected path to not be an empty string, got: '%s'", path) - } - // Ensure it contains some sort of path separator. - if !strings.ContainsRune(path, os.PathSeparator) { - t.Errorf("expected path to contain file separator") - } - // It should also be an absolute path. - if !filepath.IsAbs(path) { - t.Errorf("expected path to be an absolute path") +func createTempFile(prefix, content string) (tempFile string, err error) { + var tmpfile *os.File + + if tmpfile, err = ioutil.TempFile("", prefix); err != nil { + return tempFile, err } - // This will error if something goes wrong, so just call it. - getCertsPath() + if _, err = tmpfile.Write([]byte(content)); err != nil { + return tempFile, err + } + + if err = tmpfile.Close(); err != nil { + return tempFile, err + } + + tempFile = tmpfile.Name() + return tempFile, err } -// Ensure that the certificate and key file getters contain their respective -// file name and endings. -func TestGetFiles(t *testing.T) { - file := getCertFile() - if !strings.Contains(file, globalMinioCertFile) { - t.Errorf("CertFile does not contain %s", globalMinioCertFile) +func TestParsePublicCertFile(t *testing.T) { + tempFile1, err := createTempFile("public-cert-file", "") + if err != nil { + t.Fatalf("Unable to create temporary file. %v", err) } + defer os.Remove(tempFile1) - file = getKeyFile() - if !strings.Contains(file, globalMinioKeyFile) { - t.Errorf("KeyFile does not contain %s", globalMinioKeyFile) + tempFile2, err := createTempFile("public-cert-file", + `-----BEGIN CERTIFICATE----- +MIICdTCCAd4CCQCO5G/W1xcE9TANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJa +WTEOMAwGA1UECBMFTWluaW8xETAPBgNVBAcTCEludGVybmV0MQ4wDAYDVQQKEwVN +aW5pbzEOMAwGA1UECxMFTWluaW8xDjAMBgNVBAMTBU1pbmlvMR0wGwYJKoZIhvcN +AQkBFg50ZXN0c0BtaW5pby5pbzAeFw0xNjEwMTQxMTM0MjJaFw0xNzEwMTQxMTM0 +MjJaMH8xCzAJBgNVBAYTAlpZMQ4wDAYDVQQIEwVNaW5pbzERMA8GA1UEBxMISW50 +ZXJuZXQxDjAMBgNVBA-some-junk-Q4wDAYDVQQLEwVNaW5pbzEOMAwGA1UEAxMF +TWluaW8xHTAbBgkqhkiG9w0BCQEWDnRlc3RzQG1pbmlvLmlvMIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQDwNUYB/Sj79WsUE8qnXzzh2glSzWxUE79sCOpQYK83 +HWkrl5WxlG8ZxDR1IQV9Ex/lzigJu8G+KXahon6a+3n5GhNrYRe5kIXHQHz0qvv4 +aMulqlnYpvSfC83aaO9GVBtwXS/O4Nykd7QBg4nZlazVmsGk7POOjhpjGShRsqpU +JwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALqjOA6bD8BEl7hkQ8XwX/owSAL0URDe +nUfCOsXgIIAqgw4uTCLOfCJVZNKmRT+KguvPAQ6Z80vau2UxPX5Q2Q+OHXDRrEnK +FjqSBgLP06Qw7a++bshlWGTt5bHWOneW3EQikedckVuIKPkOCib9yGi4VmBBjdFE +M9ofSEt/bdRD +-----END CERTIFICATE-----`) + if err != nil { + t.Fatalf("Unable to create temporary file. %v", err) } -} + defer os.Remove(tempFile2) -// Parses .crt file contents -func TestParseCertificateChain(t *testing.T) { - // given - cert := `-----BEGIN CERTIFICATE----- + tempFile3, err := createTempFile("public-cert-file", + `-----BEGIN CERTIFICATE----- +MIICdTCCAd4CCQCO5G/W1xcE9TANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJa +WTEOMAwGA1UECBMFTWluaW8xETAPBgNVBAcTCEludGVybmV0MQ4wDAYDVQQKEwVN +aW5pbzEOMAwGA1UECxMFTWluaW8xDjAMBgNVBAMTBU1pbmlvMR0wGwYJKoZIhvcN +AQkBFg50ZXN0c0BtaW5pby5pbzAeFw0xNjEwMTQxMTM0MjJaFw0xNzEwMTQxMTM0 +MjJaMH8xCzAJBgNVBAYTAlpZMQ4wDAYDVQQIEwVNaW5pbzERMA8GA1UEBxMISW50 +ZXJuZXQxDjAMBgNVBAabababababaQ4wDAYDVQQLEwVNaW5pbzEOMAwGA1UEAxMF +TWluaW8xHTAbBgkqhkiG9w0BCQEWDnRlc3RzQG1pbmlvLmlvMIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQDwNUYB/Sj79WsUE8qnXzzh2glSzWxUE79sCOpQYK83 +HWkrl5WxlG8ZxDR1IQV9Ex/lzigJu8G+KXahon6a+3n5GhNrYRe5kIXHQHz0qvv4 +aMulqlnYpvSfC83aaO9GVBtwXS/O4Nykd7QBg4nZlazVmsGk7POOjhpjGShRsqpU +JwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALqjOA6bD8BEl7hkQ8XwX/owSAL0URDe +nUfCOsXgIIAqgw4uTCLOfCJVZNKmRT+KguvPAQ6Z80vau2UxPX5Q2Q+OHXDRrEnK +FjqSBgLP06Qw7a++bshlWGTt5bHWOneW3EQikedckVuIKPkOCib9yGi4VmBBjdFE +M9ofSEt/bdRD +-----END CERTIFICATE-----`) + if err != nil { + t.Fatalf("Unable to create temporary file. %v", err) + } + defer os.Remove(tempFile3) + + tempFile4, err := createTempFile("public-cert-file", + `-----BEGIN CERTIFICATE----- MIICdTCCAd4CCQCO5G/W1xcE9TANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJa WTEOMAwGA1UECBMFTWluaW8xETAPBgNVBAcTCEludGVybmV0MQ4wDAYDVQQKEwVN aW5pbzEOMAwGA1UECxMFTWluaW8xDjAMBgNVBAMTBU1pbmlvMR0wGwYJKoZIhvcN @@ -74,35 +111,149 @@ JwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALqjOA6bD8BEl7hkQ8XwX/owSAL0URDe nUfCOsXgIIAqgw4uTCLOfCJVZNKmRT+KguvPAQ6Z80vau2UxPX5Q2Q+OHXDRrEnK FjqSBgLP06Qw7a++bshlWGTt5bHWOneW3EQikedckVuIKPkOCib9yGi4VmBBjdFE M9ofSEt/bdRD ------END CERTIFICATE-----` - - // when - certs, err := parseCertificateChain([]byte(cert)) - - // then +-----END CERTIFICATE-----`) if err != nil { - t.Fatalf("Could not parse certificate: %s", err) + t.Fatalf("Unable to create temporary file. %v", err) + } + defer os.Remove(tempFile4) + + tempFile5, err := createTempFile("public-cert-file", + `-----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----- +-----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-----`) + if err != nil { + t.Fatalf("Unable to create temporary file. %v", err) + } + defer os.Remove(tempFile5) + + nonexistentErr := fmt.Errorf("open nonexistent-file: no such file or directory") + if runtime.GOOS == "windows" { + // Below concatenation is done to get rid of goline error + // "error strings should not be capitalized or end with punctuation or a newline" + nonexistentErr = fmt.Errorf("open nonexistent-file:" + " The system cannot find the file specified.") } - if len(certs) != 1 { - t.Fatalf("Expected number of certificates in chain was 1, actual: %d", len(certs)) + testCases := []struct { + certFile string + expectedResultLen int + expectedErr error + }{ + {"nonexistent-file", 0, nonexistentErr}, + {tempFile1, 0, fmt.Errorf("Empty public certificate file %s", tempFile1)}, + {tempFile2, 0, fmt.Errorf("Could not read PEM block from file %s", tempFile2)}, + {tempFile3, 0, fmt.Errorf("asn1: structure error: sequence tag mismatch")}, + {tempFile4, 1, nil}, + {tempFile5, 2, nil}, } - if certs[0].Subject.CommonName != "Minio" { - t.Fatalf("Expected Subject.CommonName was Minio, actual: %s", certs[0].Subject.CommonName) + for _, testCase := range testCases { + certs, err := parsePublicCertFile(testCase.certFile) + + if testCase.expectedErr == nil { + if err != nil { + t.Fatalf("error: expected = , got = %v", err) + } + } else if err == nil { + t.Fatalf("error: expected = %v, got = ", testCase.expectedErr) + } else if testCase.expectedErr.Error() != err.Error() { + t.Fatalf("error: expected = %v, got = %v", testCase.expectedErr, err) + } + + if len(certs) != testCase.expectedResultLen { + t.Fatalf("certs: expected = %v, got = %v", testCase.expectedResultLen, len(certs)) + } } } -// Parses invalid .crt file contents and returns error -func TestParseInvalidCertificateChain(t *testing.T) { - // given - cert := `This is now valid certificate` +func TestGetRootCAs(t *testing.T) { + emptydir, err := ioutil.TempDir("", "test-get-root-cas") + if err != nil { + t.Fatalf("Unable create temp directory. %v", emptydir) + } + defer os.RemoveAll(emptydir) - // when - _, err := parseCertificateChain([]byte(cert)) + dir1, err := ioutil.TempDir("", "test-get-root-cas") + if err != nil { + t.Fatalf("Unable create temp directory. %v", dir1) + } + defer os.RemoveAll(dir1) + if err = os.Mkdir(filepath.Join(dir1, "empty-dir"), 0755); err != nil { + t.Fatalf("Unable create empty dir. %v", err) + } - // then - if err == nil { - t.Fatalf("Expected error but none occurred") + dir2, err := ioutil.TempDir("", "test-get-root-cas") + if err != nil { + t.Fatalf("Unable create temp directory. %v", dir2) + } + defer os.RemoveAll(dir2) + if err = ioutil.WriteFile(filepath.Join(dir2, "empty-file"), []byte{}, 0644); err != nil { + t.Fatalf("Unable create test file. %v", err) + } + + nonexistentErr := fmt.Errorf("open nonexistent-dir: no such file or directory") + if runtime.GOOS == "windows" { + // Below concatenation is done to get rid of goline error + // "error strings should not be capitalized or end with punctuation or a newline" + nonexistentErr = fmt.Errorf("open nonexistent-dir:" + " The system cannot find the file specified.") + } + + err1 := fmt.Errorf("read %s: is a directory", filepath.Join(dir1, "empty-dir")) + if runtime.GOOS == "windows" { + // Below concatenation is done to get rid of goline error + // "error strings should not be capitalized or end with punctuation or a newline" + err1 = fmt.Errorf("read %s:"+" The handle is invalid.", filepath.Join(dir1, "empty-dir")) + } + + testCases := []struct { + certCAsDir string + expectedErr error + }{ + {"nonexistent-dir", nonexistentErr}, + {dir1, err1}, + {emptydir, nil}, + {dir2, nil}, + } + + for _, testCase := range testCases { + _, err := getRootCAs(testCase.certCAsDir) + + if testCase.expectedErr == nil { + if err != nil { + t.Fatalf("error: expected = , got = %v", err) + } + } else if err == nil { + t.Fatalf("error: expected = %v, got = ", testCase.expectedErr) + } else if testCase.expectedErr.Error() != err.Error() { + t.Fatalf("error: expected = %v, got = %v", testCase.expectedErr, err) + } } } diff --git a/cmd/config-dir.go b/cmd/config-dir.go new file mode 100644 index 000000000..fc46a8b33 --- /dev/null +++ b/cmd/config-dir.go @@ -0,0 +1,139 @@ +/* + * Minio Cloud Storage, (C) 2015, 2016, 2017 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 cmd + +import ( + "path/filepath" + "sync" + + homedir "github.com/minio/go-homedir" + "github.com/minio/mc/pkg/console" +) + +const ( + // Default minio configuration directory where below configuration files/directories are stored. + defaultMinioConfigDir = ".minio" + + // Minio configuration file. + minioConfigFile = "config.json" + + // Directory contains below files/directories for HTTPS configuration. + certsDir = "certs" + + // Directory contains all CA certificates other than system defaults for HTTPS. + certsCADir = "CAs" + + // Public certificate file for HTTPS. + publicCertFile = "public.crt" + + // Private key file for HTTPS. + privateKeyFile = "private.key" +) + +// ConfigDir - configuration directory with locking. +type ConfigDir struct { + sync.Mutex + dir string +} + +// Set - saves given directory as configuration directory. +func (config *ConfigDir) Set(dir string) { + config.Lock() + defer config.Unlock() + + config.dir = dir +} + +// Get - returns current configuration directory. +func (config *ConfigDir) Get() string { + config.Lock() + defer config.Unlock() + + return config.dir +} + +func (config *ConfigDir) getCertsDir() string { + return filepath.Join(config.Get(), certsDir) +} + +// GetCADir - returns certificate CA directory. +func (config *ConfigDir) GetCADir() string { + return filepath.Join(config.getCertsDir(), certsCADir) +} + +// Create - creates configuration directory tree. +func (config *ConfigDir) Create() error { + return mkdirAll(config.GetCADir(), 0700) +} + +// GetMinioConfigFile - returns absolute path of config.json file. +func (config *ConfigDir) GetMinioConfigFile() string { + return filepath.Join(config.Get(), minioConfigFile) +} + +// GetPublicCertFile - returns absolute path of public.crt file. +func (config *ConfigDir) GetPublicCertFile() string { + return filepath.Join(config.getCertsDir(), publicCertFile) +} + +// GetPrivateKeyFile - returns absolute path of private.key file. +func (config *ConfigDir) GetPrivateKeyFile() string { + return filepath.Join(config.getCertsDir(), privateKeyFile) +} + +func mustGetDefaultConfigDir() string { + homeDir, err := homedir.Dir() + if err != nil { + console.Fatalln("Unable to get home directory.", err) + } + + return filepath.Join(homeDir, defaultMinioConfigDir) +} + +var configDir = &ConfigDir{dir: mustGetDefaultConfigDir()} + +func setConfigDir(dir string) { + configDir.Set(dir) +} + +func getConfigDir() string { + return configDir.Get() +} + +func getCADir() string { + return configDir.GetCADir() +} + +func createConfigDir() error { + return configDir.Create() +} + +func getConfigFile() string { + return configDir.GetMinioConfigFile() +} + +func getPublicCertFile() string { + return configDir.GetPublicCertFile() +} + +func getPrivateKeyFile() string { + return configDir.GetPrivateKeyFile() +} + +func isConfigFileExists() bool { + return isFile(getConfigFile()) +} diff --git a/cmd/config-migrate_test.go b/cmd/config-migrate_test.go index 359936be3..e30818dd5 100644 --- a/cmd/config-migrate_test.go +++ b/cmd/config-migrate_test.go @@ -66,7 +66,7 @@ func TestServerConfigMigrateInexistentConfig(t *testing.T) { defer removeAll(rootPath) setConfigDir(rootPath) - configPath := rootPath + "/" + globalMinioConfigFile + configPath := rootPath + "/" + minioConfigFile // Remove config file if err := os.Remove(configPath); err != nil { @@ -121,7 +121,7 @@ func TestServerConfigMigrateV2toV14(t *testing.T) { defer removeAll(rootPath) setConfigDir(rootPath) - configPath := rootPath + "/" + globalMinioConfigFile + configPath := rootPath + "/" + minioConfigFile // Create a corrupted config file if err := ioutil.WriteFile(configPath, []byte("{ \"version\":\"2\","), 0644); err != nil { @@ -175,7 +175,7 @@ func TestServerConfigMigrateFaultyConfig(t *testing.T) { defer removeAll(rootPath) setConfigDir(rootPath) - configPath := rootPath + "/" + globalMinioConfigFile + configPath := rootPath + "/" + minioConfigFile // Create a corrupted config file if err := ioutil.WriteFile(configPath, []byte("{ \"version\":\""), 0644); err != nil { diff --git a/cmd/config.go b/cmd/config.go deleted file mode 100644 index 59dd831ab..000000000 --- a/cmd/config.go +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2015, 2016, 2017 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 cmd - -import ( - "path/filepath" - "sync" - - homedir "github.com/minio/go-homedir" - "github.com/minio/mc/pkg/console" -) - -// ConfigDir - configuration directory with locking. -type ConfigDir struct { - sync.Mutex - dir string -} - -// Set - saves given directory as configuration directory. -func (config *ConfigDir) Set(dir string) { - config.Lock() - defer config.Unlock() - - config.dir = dir -} - -// Get - returns current configuration directory. -func (config *ConfigDir) Get() string { - config.Lock() - defer config.Unlock() - - return config.dir -} - -func mustGetDefaultConfigDir() string { - homeDir, err := homedir.Dir() - if err != nil { - console.Fatalln("Unable to get home directory.", err) - } - - return filepath.Join(homeDir, globalMinioConfigDir) -} - -var configDir = &ConfigDir{dir: mustGetDefaultConfigDir()} - -func setConfigDir(dir string) { - configDir.Set(dir) -} - -func getConfigDir() string { - return configDir.Get() -} - -func createConfigDir() error { - return mkdirAll(getConfigDir(), 0700) -} - -func getConfigFile() string { - return filepath.Join(getConfigDir(), globalMinioConfigFile) -} - -func isConfigFileExists() bool { - return isFile(getConfigFile()) -} diff --git a/cmd/globals.go b/cmd/globals.go index 4f04ce0e9..13f8253ec 100644 --- a/cmd/globals.go +++ b/cmd/globals.go @@ -28,12 +28,6 @@ import ( // minio configuration related constants. const ( - globalMinioConfigDir = ".minio" - globalMinioCertsDir = "certs" - globalMinioCertsCADir = "CAs" - globalMinioCertFile = "public.crt" - globalMinioKeyFile = "private.key" - globalMinioConfigFile = "config.json" globalMinioCertExpireWarnDays = time.Hour * 24 * 30 // 30 days. globalMinioDefaultRegion = "us-east-1" diff --git a/cmd/server-main.go b/cmd/server-main.go index df07a3225..6f44624ca 100644 --- a/cmd/server-main.go +++ b/cmd/server-main.go @@ -144,6 +144,9 @@ func initConfig() { // Generic Minio initialization to create/load config, prepare loggers, etc.. func minioInit(ctx *cli.Context) { + // Create certs path. + fatalIf(createConfigDir(), "Unable to create \"certs\" directory.") + // Is TLS configured?. globalIsSSL = isSSL() @@ -155,7 +158,6 @@ func minioInit(ctx *cli.Context) { // Init the error tracing module. initError() - } type serverCmdConfig struct { @@ -208,11 +210,8 @@ func initServerConfig(c *cli.Context) { // Initialization such as config generating/loading config, enable logging, .. minioInit(c) - // Create certs path. - fatalIf(createCertsPath(), "Unable to create \"certs\" directory.") - // Load user supplied root CAs - loadRootCAs() + fatalIf(loadRootCAs(), "Unable to load a CA files") // Set system resources to maximum. errorIf(setMaxResources(), "Unable to change resource limit") @@ -540,7 +539,7 @@ func serverMain(c *cli.Context) { go func() { cert, key := "", "" if globalIsSSL { - cert, key = getCertFile(), getKeyFile() + cert, key = getPublicCertFile(), getPrivateKeyFile() } fatalIf(apiServer.ListenAndServe(cert, key), "Failed to start minio server.") }() diff --git a/cmd/server-mux_test.go b/cmd/server-mux_test.go index c7c39278e..81985c174 100644 --- a/cmd/server-mux_test.go +++ b/cmd/server-mux_test.go @@ -354,12 +354,12 @@ func TestServerListenAndServeTLS(t *testing.T) { })) // Create a cert - err := createCertsPath() + err := createConfigDir() if err != nil { t.Fatal(err) } - certFile := getCertFile() - keyFile := getKeyFile() + certFile := getPublicCertFile() + keyFile := getPrivateKeyFile() defer os.RemoveAll(certFile) defer os.RemoveAll(keyFile) @@ -420,8 +420,8 @@ func TestServerListenAndServeTLS(t *testing.T) { // generateTestCert creates a cert and a key used for testing only func generateTestCert(host string) error { - certPath := getCertFile() - keyPath := getKeyFile() + certPath := getPublicCertFile() + keyPath := getPrivateKeyFile() priv, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return err