Skip to content
This repository was archived by the owner on Feb 5, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions installer/cmd/tectonic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var (

clusterInstallCommand = kingpin.Command("install", "Create a new Tectonic cluster")
clusterInstallTLSCommand = clusterInstallCommand.Command("tls", "Generate TLS Certificates.")
clusterInstallTLSNewCommand = clusterInstallCommand.Command("newtls", "Generate TLS Certificates, using a new engine (experimental)")
clusterInstallAssetsCommand = clusterInstallCommand.Command("assets", "Generate Tectonic assets.")
clusterInstallBootstrapCommand = clusterInstallCommand.Command("bootstrap", "Create a single bootstrap node Tectonic cluster.")
clusterInstallFullCommand = clusterInstallCommand.Command("full", "Create a new Tectonic cluster").Default()
Expand All @@ -40,6 +41,8 @@ func main() {
w = workflow.InstallFullWorkflow(*clusterInstallDirFlag)
case clusterInstallTLSCommand.FullCommand():
w = workflow.InstallTLSWorkflow(*clusterInstallDirFlag)
case clusterInstallTLSNewCommand.FullCommand():
w = workflow.InstallTLSNewWorkflow(*clusterInstallDirFlag)
case clusterInstallAssetsCommand.FullCommand():
w = workflow.InstallAssetsWorkflow(*clusterInstallDirFlag)
case clusterInstallBootstrapCommand.FullCommand():
Expand Down
4 changes: 4 additions & 0 deletions installer/pkg/config-generator/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ go_library(
srcs = [
"generator.go",
"ignition.go",
"tls.go",
"utils.go",
],
importpath = "github.com/coreos/tectonic-installer/installer/pkg/config-generator",
visibility = ["//visibility:public"],
deps = [
"//installer/pkg/config:go_default_library",
"//installer/pkg/tls:go_default_library",
"//installer/pkg/validate:go_default_library",
"//vendor/github.com/apparentlymart/go-cidr/cidr:go_default_library",
"//vendor/github.com/coreos/ignition/config/v2_2:go_default_library",
"//vendor/github.com/coreos/ignition/config/v2_2/types:go_default_library",
Expand Down
18 changes: 0 additions & 18 deletions installer/pkg/config-generator/ignition.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package configgenerator

import (
"bufio"
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"

ignconfig "github.com/coreos/ignition/config/v2_2"
Expand Down Expand Up @@ -162,19 +160,3 @@ func ignCfgToFile(ignCfg ignconfigtypes.Config, filePath string) error {

return writeFile(filePath, string(data))
}

func writeFile(path, content string) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()

w := bufio.NewWriter(f)
if _, err := fmt.Fprintln(w, content); err != nil {
return err
}
w.Flush()

return nil
}
70 changes: 70 additions & 0 deletions installer/pkg/config-generator/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package configgenerator

import (
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"path/filepath"

"github.com/coreos/tectonic-installer/installer/pkg/tls"
)

const (
rootCACertPath = "generated/newTLS/root-ca.crt"
rootCAKeyPath = "generated/newTLS/root-ca.key"
kubeCACertPath = "generated/newTLS/kube-ca.key"
kubeCAKeyPath = "generated/newTLS/kube-ca.crt"
aggregatorCAKeyPath = "generated/newTLS/aggregator-ca.key"
aggregatorCACertPath = "generated/newTLS/aggregator-ca.crt"
serviceServiceCAKeyPath = "generated/newTLS/service-serving-ca.key"
serviceServiceCACertPath = "generated/newTLS/service-serving-ca.crt"
etcdClientKeyPath = "generated/newTLS/etcd-client-ca.key"
etcdClientCertPath = "generated/newTLS/etcd-client-ca.crt"
)

// GenerateTLSConfig fetches and validates the TLS cert files
// If no file paths were provided, the certs will be auto-generated
func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error {
if c.CA.RootCAKeyPath == "" && c.CA.RootCACertPath == "" {
// generate key and certificate
key, err := generatePrivateKey(clusterDir, rootCAKeyPath)
if err != nil {
return fmt.Errorf("failed to generate private key: %v", err)
}
if _, err := generateRootCA(clusterDir, key); err != nil {
return fmt.Errorf("failed to create a certificate: %v", err)
}
} else {
// copy key and certificates
keyDst := filepath.Join(clusterDir, rootCAKeyPath)
if err := copyFile(c.CA.RootCAKeyPath, keyDst); err != nil {
return fmt.Errorf("failed to write file: %v", err)
}

certDst := filepath.Join(clusterDir, rootCACertPath)
if err := copyFile(c.CA.RootCACertPath, certDst); err != nil {
return fmt.Errorf("failed to write file: %v", err)
}
}
return nil
}

func generateRootCA(path string, key *rsa.PrivateKey) (*x509.Certificate, error) {
fileTargetPath := filepath.Join(path, rootCACertPath)
cfg := &tls.CertCfg{
Subject: pkix.Name{
CommonName: "root-ca",
OrganizationalUnit: []string{"openshift"},
},
KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
}
cert, err := tls.SelfSignedCACert(cfg, key)
if err != nil {
return nil, fmt.Errorf("error generating self signed certificate: %v", err)
}
if err := writeFile(fileTargetPath, certToPem(cert)); err != nil {
return nil, err
}
return cert, nil
}
79 changes: 79 additions & 0 deletions installer/pkg/config-generator/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package configgenerator

import (
"bufio"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"os"
"path/filepath"

"github.com/coreos/tectonic-installer/installer/pkg/tls"
)

func writeFile(path, content string) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
w := bufio.NewWriter(f)
if _, err := f.WriteString(content); err != nil {
return err
}
w.Flush()

return nil
}

func copyFile(fromFilePath, toFilePath string) error {
from, err := os.Open(fromFilePath)
if err != nil {
return err
}
defer from.Close()
to, err := os.OpenFile(toFilePath, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
defer to.Close()
_, err = io.Copy(to, from)
return err
}

// privateKeyToPem gets the content of the private key and returns a pem string
func privateKeyToPem(key *rsa.PrivateKey) string {
keyInBytes := x509.MarshalPKCS1PrivateKey(key)
keyinPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: keyInBytes,
},
)
return string(keyinPem)
}

func certToPem(cert *x509.Certificate) string {
certInPem := pem.EncodeToMemory(
&pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
},
)
return string(certInPem)
}

// generatePrivateKey generates and returns an *rsa.Privatekey object
func generatePrivateKey(clusterDir string, path string) (*rsa.PrivateKey, error) {
fileTargetPath := filepath.Join(clusterDir, path)
key, err := tls.GeneratePrivateKey()
if err != nil {
return nil, fmt.Errorf("error generating private key: %v", err)
}
if err := writeFile(fileTargetPath, privateKeyToPem(key)); err != nil {
return nil, err
}
return key, nil
}
15 changes: 9 additions & 6 deletions installer/pkg/config/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ var defaultCluster = Cluster{
Region: aws.DefaultRegion,
VPCCIDRBlock: aws.DefaultVPCCIDRBlock,
},
CA: CA{
RootCAKeyAlg: "RSA",
},
ContainerLinux: ContainerLinux{
Channel: ContainerLinuxChannelStable,
Version: ContainerLinuxVersionLatest,
Expand All @@ -73,11 +76,16 @@ var defaultCluster = Cluster{
// Cluster defines the config for a cluster.
type Cluster struct {
Admin `json:",inline" yaml:"admin,omitempty"`
aws.AWS `json:",inline" yaml:"aws,omitempty"`
BaseDomain string `json:"tectonic_base_domain,omitempty" yaml:"baseDomain,omitempty"`
CA `json:",inline" yaml:"ca,omitempty"`
CA `json:",inline" yaml:"CA,omitempty"`
ContainerLinux `json:",inline" yaml:"containerLinux,omitempty"`
Etcd `json:",inline" yaml:"etcd,omitempty"`
IgnitionEtcd string `json:"tectonic_ignition_etcd,omitempty" yaml:"-"`
IgnitionMaster string `json:"tectonic_ignition_master,omitempty" yaml:"-"`
IgnitionWorker string `json:"tectonic_ignition_worker,omitempty" yaml:"-"`
Internal `json:",inline" yaml:"-"`
libvirt.Libvirt `json:",inline" yaml:"libvirt,omitempty"`
LicensePath string `json:"tectonic_license_path,omitempty" yaml:"licensePath,omitempty"`
Master `json:",inline" yaml:"master,omitempty"`
Name string `json:"tectonic_cluster_name,omitempty" yaml:"name,omitempty"`
Expand All @@ -86,11 +94,6 @@ type Cluster struct {
Platform Platform `json:"tectonic_platform" yaml:"platform,omitempty"`
PullSecretPath string `json:"tectonic_pull_secret_path,omitempty" yaml:"pullSecretPath,omitempty"`
Worker `json:",inline" yaml:"worker,omitempty"`
aws.AWS `json:",inline" yaml:"aws,omitempty"`
libvirt.Libvirt `json:",inline" yaml:"libvirt,omitempty"`
IgnitionMaster string `json:"tectonic_ignition_master,omitempty" yaml:"-"`
IgnitionWorker string `json:"tectonic_ignition_worker,omitempty" yaml:"-"`
IgnitionEtcd string `json:"tectonic_ignition_etcd,omitempty" yaml:"-"`
}

// NodeCount will return the number of nodes specified in NodePools with matching names.
Expand Down
8 changes: 4 additions & 4 deletions installer/pkg/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ type Admin struct {
Password string `json:"tectonic_admin_password" yaml:"password,omitempty"`
}

// CA converts ca related config.
// CA related config
type CA struct {
Cert string `json:"tectonic_ca_cert,omitempty" yaml:"cert,omitempty"`
Key string `json:"tectonic_ca_key,omitempty" yaml:"key,omitempty"`
KeyAlg string `json:"tectonic_ca_key_alg,omitempty" yaml:"keyAlg,omitempty"`
RootCACertPath string `json:"-" yaml:"rootCACertPath,omitempty"`
RootCAKeyPath string `json:"-" yaml:"rootCAKeyPath,omitempty"`
RootCAKeyAlg string `json:"-" yaml:"rootCAKeyAlg,omitempty"`
}

// ContainerLinux converts container linux related config.
Expand Down
56 changes: 56 additions & 0 deletions installer/pkg/config/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func (c *Cluster) Validate() []error {
errs = append(errs, c.validateCL()...)
errs = append(errs, c.validateTectonicFiles()...)
errs = append(errs, c.validateLibvirt()...)
errs = append(errs, c.validateCA()...)
if err := validate.PrefixError("cluster name", validate.ClusterName(c.Name)); err != nil {
errs = append(errs, err)
}
Expand Down Expand Up @@ -410,3 +411,58 @@ func (c *Cluster) validateNoSharedNodePools() []error {
}
return errs
}

func (c *Cluster) validateCA() []error {
var errs []error

switch {
case (c.CA.RootCACertPath == "") != (c.CA.RootCAKeyPath == ""):
errs = append(errs, fmt.Errorf("rootCACertPath and rootCAKeyPath must both be set or empty"))
case c.CA.RootCAKeyPath != "":
if err := validate.FileExists(c.CA.RootCAKeyPath); err != nil {
errs = append(errs, err)
}
if err := validateCAKey(c.CA.RootCAKeyPath); err != nil {
errs = append(errs, err)
}
fallthrough
case c.CA.RootCACertPath != "":
if err := validate.FileExists(c.CA.RootCACertPath); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the validation of the certificate file and private key file should occur here in the validation phase rather than (or maybe in addition to) the workflow stage. The reason for this is that we want to catch user errors as early as possible so that a user can't even initialize an installer config if it is invalid. Currently, a user could point their CA cert to an arbitrary file, run the install command, and then get an error halfway through. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errs = append(errs, err)
}
if err := validateCACert(c.CA.RootCACertPath); err != nil {
errs = append(errs, err)
}
}
return errs
}

// validateCAKey validates ֿthe content of the private key file
func validateCAKey(path string) error {
data, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read private key file: %v", err)
}
pem := string(data)

// Validate that file content is a valid Key
if err := validate.PrivateKey(pem); err != nil {
return fmt.Errorf("invalid private key (%s): %v", path, err)
}
return nil
}

// validateCACert validates the content of the certificate file
func validateCACert(path string) error {
data, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read certificate file: %v", err)
}
pem := string(data)

// Validate that file content is a valid certificate
if err := validate.Certificate(pem); err != nil {
return fmt.Errorf("invalid certificate (%s): %v", path, err)
}
return nil
}
2 changes: 1 addition & 1 deletion installer/pkg/tls/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func SelfSignedCACert(cfg *CertCfg, key *rsa.PrivateKey) (*x509.Certificate, err

certBytes, err := x509.CreateCertificate(rand.Reader, &cert, &cert, key.Public(), key)
if err != nil {
return nil, fmt.Errorf("error creating certificate: %s", err)
return nil, fmt.Errorf("error creating certificate: %v", err)
}
return x509.ParseCertificate(certBytes)
}
Expand Down
5 changes: 4 additions & 1 deletion installer/pkg/validate/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ go_test(
size = "small",
srcs = ["validate_test.go"],
embed = [":go_default_library"],
deps = ["//vendor/gopkg.in/square/go-jose.v2:go_default_library"],
deps = [
"//installer/pkg/tls:go_default_library",
"//vendor/gopkg.in/square/go-jose.v2:go_default_library",
],
)

go_library(
Expand Down
Loading