Skip to content

Commit

Permalink
Refactored security package
Browse files Browse the repository at this point in the history
  • Loading branch information
michelvocks committed Jul 25, 2018
1 parent d6a51f1 commit d99713c
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 32 deletions.
16 changes: 16 additions & 0 deletions cmd/gaia/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}
Expand Down Expand Up @@ -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()

Expand Down
1 change: 1 addition & 0 deletions gaia.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ type Config struct {
JwtPrivateKeyPath string
JWTKey interface{}
Logger hclog.Logger
CAPath string

Bolt struct {
Mode os.FileMode
Expand Down
122 changes: 102 additions & 20 deletions security/tls.go → security/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"io/ioutil"
"math/big"
"os"
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -77,15 +128,15 @@ 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
}
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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
}
56 changes: 44 additions & 12 deletions security/tls_test.go → security/ca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down

0 comments on commit d99713c

Please sign in to comment.