From d99713cd08d9d41059bc7ea744e7b6de80d641c2 Mon Sep 17 00:00:00 2001 From: Michel Vocks Date: Wed, 25 Jul 2018 16:44:38 +0200 Subject: [PATCH] Refactored security package --- cmd/gaia/main.go | 16 ++++ gaia.go | 1 + security/{tls.go => ca.go} | 122 ++++++++++++++++++++++----- security/{tls_test.go => ca_test.go} | 56 +++++++++--- 4 files changed, 163 insertions(+), 32 deletions(-) rename security/{tls.go => ca.go} (52%) rename security/{tls_test.go => ca_test.go} (63%) diff --git a/cmd/gaia/main.go b/cmd/gaia/main.go index d304e4bd..9f94dbed 100644 --- a/cmd/gaia/main.go +++ b/cmd/gaia/main.go @@ -15,6 +15,7 @@ import ( "github.com/gaia-pipeline/gaia/pipeline" "github.com/gaia-pipeline/gaia/plugin" scheduler "github.com/gaia-pipeline/gaia/scheduler" + "github.com/gaia-pipeline/gaia/security" "github.com/gaia-pipeline/gaia/store" hclog "github.com/hashicorp/go-hclog" "github.com/labstack/echo" @@ -41,10 +42,12 @@ func init() { flag.StringVar(&gaia.Cfg.HomePath, "homepath", "", "Path to the gaia home folder") flag.StringVar(&gaia.Cfg.Worker, "worker", "2", "Number of worker gaia will use to execute pipelines in parallel") flag.StringVar(&gaia.Cfg.JwtPrivateKeyPath, "jwtPrivateKeyPath", "", "A RSA private key used to sign JWT tokens") + flag.StringVar(&gaia.Cfg.CAPath, "capath", "", "Folder path where the generated CA certificate files will be saved") flag.BoolVar(&gaia.Cfg.DevMode, "dev", false, "If true, gaia will be started in development mode. Don't use this in production!") flag.BoolVar(&gaia.Cfg.VersionSwitch, "version", false, "If true, will print the version and immediately exit") flag.BoolVar(&gaia.Cfg.Poll, "poll", false, "Instead of using a Webhook, keep polling git for changes on pipelines") flag.IntVar(&gaia.Cfg.PVal, "pval", 1, "The interval in minutes in which to poll vcs for changes") + // Default values gaia.Cfg.Bolt.Mode = 0600 } @@ -123,6 +126,19 @@ func main() { os.Exit(1) } + // Check CA path + if gaia.Cfg.CAPath == "" { + // Set default to data folder + gaia.Cfg.CAPath = gaia.Cfg.DataPath + } + + // Setup CA for cerificate signing + _, err = security.InitCA() + if err != nil { + gaia.Cfg.Logger.Error("cannot create CA", "error", err.Error()) + os.Exit(1) + } + // Initialize echo instance echoInstance = echo.New() diff --git a/gaia.go b/gaia.go index d6ad2092..86a4ec8f 100644 --- a/gaia.go +++ b/gaia.go @@ -162,6 +162,7 @@ type Config struct { JwtPrivateKeyPath string JWTKey interface{} Logger hclog.Logger + CAPath string Bolt struct { Mode os.FileMode diff --git a/security/tls.go b/security/ca.go similarity index 52% rename from security/tls.go rename to security/ca.go index e6ba2a4d..a3c98de5 100644 --- a/security/tls.go +++ b/security/ca.go @@ -7,6 +7,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "errors" "io/ioutil" "math/big" "os" @@ -26,16 +27,66 @@ const ( // CA key name certName = "ca.crt" keyName = "ca.key" + + // Hostname used by go-plugin + goPluginHostname = "unused" +) + +var ( + // errCertNotAppended is thrown when the root CA cert cannot be appended to the pool. + errCertNotAppended = errors.New("cannot append root CA cert to cert pool") ) -// GenerateCA generates the CA and puts it into the data folder. -// The CA will be always overwritten on startup. -func GenerateCA() error { - // Cleanup old certs if existing. - // We ignore the error here cause files might be non existend. - caCertPath := filepath.Join(gaia.Cfg.DataPath, certName) - caKeyPath := filepath.Join(gaia.Cfg.DataPath, keyName) - cleanupCerts(caCertPath, caKeyPath) +// CA represents one generated CA. +type CA struct { + caCertPath string + caKeyPath string +} + +// CAAPI represents the interface used to handle certificates. +type CAAPI interface { + // CreateSignedCert creates a new signed certificate. + // First return param is the public cert. + // Second return param is the private key. + CreateSignedCert() (string, string, error) + + // GenerateTLSConfig generates a TLS config. + // It requires the path to the cert and the key. + GenerateTLSConfig(certPath, keyPath string) (*tls.Config, error) + + // CleanupCerts cleans up the certs at the given path. + CleanupCerts(crt, key string) error + + // GetCACertPath returns the public cert and private key + // of the CA. + GetCACertPath() (string, string) +} + +// InitCA setups a new instance of CA and generates a new CA if not already exists. +func InitCA() (*CA, error) { + t := &CA{ + caCertPath: filepath.Join(gaia.Cfg.DataPath, certName), + caKeyPath: filepath.Join(gaia.Cfg.DataPath, keyName), + } + return t, t.generateCA() +} + +// generateCA generates the CA and puts the certs into the data folder. +// If they are already existing, nothing will be done. +func (c *CA) generateCA() error { + // Check if they are already existing + certExist, keyExist := false, false + if _, err := os.Stat(c.caCertPath); err == nil { + certExist = true + } + if _, err := os.Stat(c.caKeyPath); err == nil { + keyExist = true + } + + // Both exist, skip + if certExist && keyExist { + return nil + } // Set time range for cert validation notBefore := time.Now() @@ -61,7 +112,7 @@ func GenerateCA() error { KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, - DNSNames: []string{orgDNS}, + DNSNames: []string{orgDNS, goPluginHostname}, } // Generate the key @@ -77,7 +128,7 @@ func GenerateCA() error { } // Write out the ca.crt file - certOut, err := os.Create(caCertPath) + certOut, err := os.Create(c.caCertPath) if err != nil { return err } @@ -85,7 +136,7 @@ func GenerateCA() error { certOut.Close() // Write out the ca.key file - keyOut, err := os.OpenFile(caKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + keyOut, err := os.OpenFile(c.caKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err } @@ -95,13 +146,10 @@ func GenerateCA() error { return nil } -// createSignedCert creates a new key pair which is signed by the CA. -func createSignedCert() (string, string, error) { - caCertPath := filepath.Join(gaia.Cfg.DataPath, "ca.crt") - caKeyPath := filepath.Join(gaia.Cfg.DataPath, "ca.key") - +// CreateSignedCert creates a new key pair which is signed by the CA. +func (c *CA) CreateSignedCert() (string, string, error) { // Load CA plain - caPlain, err := tls.LoadX509KeyPair(caCertPath, caKeyPath) + caPlain, err := tls.LoadX509KeyPair(c.caCertPath, c.caKeyPath) if err != nil { return "", "", err } @@ -134,7 +182,7 @@ func createSignedCert() (string, string, error) { SubjectKeyId: []byte{1, 2, 3, 4, 6}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature, - DNSNames: []string{orgDNS}, + DNSNames: []string{orgDNS, goPluginHostname}, } priv, _ := rsa.GenerateKey(rand.Reader, rsaBits) pub := &priv.PublicKey @@ -155,10 +203,44 @@ func createSignedCert() (string, string, error) { return certOut.Name(), keyOut.Name(), nil } -// cleanupCerts removes certificates at the given path. -func cleanupCerts(crt, key string) error { +// GenerateTLSConfig generates a new TLS config based on given +// certificate path and key path. +func (c *CA) GenerateTLSConfig(certPath, keyPath string) (*tls.Config, error) { + // Load certificate + certificate, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, err + } + + // Create certificate pool + certPool := x509.NewCertPool() + caCert, err := ioutil.ReadFile(c.caCertPath) + if err != nil { + return nil, err + } + + // Append cert to cert pool + ok := certPool.AppendCertsFromPEM(caCert) + if !ok { + return nil, errCertNotAppended + } + + return &tls.Config{ + Certificates: []tls.Certificate{certificate}, + ClientCAs: certPool, + RootCAs: certPool, + }, nil +} + +// CleanupCerts removes certificates at the given path. +func (c *CA) CleanupCerts(crt, key string) error { if err := os.Remove(crt); err != nil { return err } return os.Remove(key) } + +// GetCACertPath returns the path to the cert from the root CA. +func (c *CA) GetCACertPath() (string, string) { + return c.caCertPath, c.caKeyPath +} diff --git a/security/tls_test.go b/security/ca_test.go similarity index 63% rename from security/tls_test.go rename to security/ca_test.go index f924a0ee..3ded72a4 100644 --- a/security/tls_test.go +++ b/security/ca_test.go @@ -5,23 +5,22 @@ import ( "crypto/x509" "io/ioutil" "os" - "path/filepath" "testing" "github.com/gaia-pipeline/gaia" ) -func TestGenerateCA(t *testing.T) { +func TestInitCA(t *testing.T) { gaia.Cfg = &gaia.Config{} gaia.Cfg.DataPath = os.TempDir() - err := GenerateCA() + c, err := InitCA() if err != nil { t.Fatal(err) } - caCertPath := filepath.Join(gaia.Cfg.DataPath, "ca.crt") - caKeyPath := filepath.Join(gaia.Cfg.DataPath, "ca.key") + // Get root CA cert path + caCertPath, caKeyPath := c.GetCACertPath() // Load CA plain caPlain, err := tls.LoadX509KeyPair(caCertPath, caKeyPath) @@ -55,7 +54,7 @@ func TestGenerateCA(t *testing.T) { t.Fatal(err) } - err = cleanupCerts(caCertPath, caKeyPath) + err = c.CleanupCerts(caCertPath, caKeyPath) if err != nil { t.Fatal(err) } @@ -65,15 +64,15 @@ func TestCreateSignedCert(t *testing.T) { gaia.Cfg = &gaia.Config{} gaia.Cfg.DataPath = os.TempDir() - err := GenerateCA() + c, err := InitCA() if err != nil { t.Fatal(err) } - caCertPath := filepath.Join(gaia.Cfg.DataPath, "ca.crt") - caKeyPath := filepath.Join(gaia.Cfg.DataPath, "ca.key") + // Get root ca cert path + caCertPath, caKeyPath := c.GetCACertPath() - certPath, keyPath, err := createSignedCert() + certPath, keyPath, err := c.CreateSignedCert() if err != nil { t.Fatal(err) } @@ -110,11 +109,44 @@ func TestCreateSignedCert(t *testing.T) { t.Fatal(err) } - err = cleanupCerts(caCertPath, caKeyPath) + err = c.CleanupCerts(caCertPath, caKeyPath) if err != nil { t.Fatal(err) } - err = cleanupCerts(certPath, keyPath) + err = c.CleanupCerts(certPath, keyPath) + if err != nil { + t.Fatal(err) + } +} + +func TestGenerateTLSConfig(t *testing.T) { + gaia.Cfg = &gaia.Config{} + gaia.Cfg.DataPath = os.TempDir() + + c, err := InitCA() + if err != nil { + t.Fatal(err) + } + + // Get root ca cert path + caCertPath, caKeyPath := c.GetCACertPath() + + certPath, keyPath, err := c.CreateSignedCert() + if err != nil { + t.Fatal(err) + } + + // Generate TLS Config + _, err = c.GenerateTLSConfig(certPath, keyPath) + if err != nil { + t.Fatal(err) + } + + err = c.CleanupCerts(caCertPath, caKeyPath) + if err != nil { + t.Fatal(err) + } + err = c.CleanupCerts(certPath, keyPath) if err != nil { t.Fatal(err) }