Skip to content
This repository was archived by the owner on Feb 5, 2020. It is now read-only.

installer/pkg/config-generator/tls: generate root CA's with go#3316

Merged
trawler merged 1 commit intocoreos:masterfrom
trawler:move_tls_from_terraform_to_installer
Jul 3, 2018
Merged

installer/pkg/config-generator/tls: generate root CA's with go#3316
trawler merged 1 commit intocoreos:masterfrom
trawler:move_tls_from_terraform_to_installer

Conversation

@trawler
Copy link
Contributor

@trawler trawler commented Jun 28, 2018

Fixes #INST-1106

@trawler trawler requested review from spangenberg and squat June 28, 2018 15:22
@coreosbot
Copy link

Can one of the admins verify this patch?

Copy link
Contributor

@squat squat left a comment

Choose a reason for hiding this comment

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

@trawler this is looking even better now. There are still a few points I'd like to fix.

if c.CA.RootCAKeyPath == "" {
key, err = generatePrivateKey(clusterDir)
if err != nil {
return fmt.Errorf("failed to generate private key: %s", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

as a rule, we like to format errors with %v


keyDst := filepath.Join(clusterDir, rootCAKeyPath)
if err := copyFile(c.CA.RootCAKeyPath, keyDst); err != nil {
return fmt.Errorf("failed to write file (%s)", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

did you mean to pass the file destination for the (%s)? otherwise lets be consistent and wrap the error like : %s


// If RootCACertPath is not provided, generate a cert
if c.CA.RootCACertPath == "" {
_, err := generateRootCA(clusterDir, key)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: since you are ignoring the non-error return value, we can make this a little bit nicer with:

if _, err := generateRootCA(clusterDir, key); err != nil {
...
}

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

Choose a reason for hiding this comment

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

same question here as above concerning the (%s)


// Validate that file content is a valid certificate
if err := validate.Certificate(pem); err != nil {
return "", fmt.Errorf("provided file does not seem to contain a valid certificate (%s): %s", path, err)
Copy link
Contributor

Choose a reason for hiding this comment

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

for consistency please use %v to format errors.

if block == nil {
return errors.New("failed to parse certificate pem")
}
_, err := x509.ParseCertificate(block.Bytes)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: we can use

if _, err := x509.ParseCertificate(block.Bytes); err != nil {
...
}

}

trimmed := strings.TrimSpace(v)
r := strings.NewReader(v)
Copy link
Contributor

Choose a reason for hiding this comment

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

between lines 383 and 389 it looks like we went from a string, to a reader, to a byte array. It would be easier, and error-free, to go directly from a string to a byte array like:

block, _ := pem.Decode([]byte(v))

const invalidMsg = "invalid certificate"
const privateKeyMsg = "invalid certificate (appears to be a private key)"
const badPem = "failed to parse certificate pem"
const certificate = `-----BEGIN CERTIFICATE-----
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: for better maintainability, we could consider generating a throwaway certificate here instead of hardcoding a hand-generated one. not a blocker.


func TestPrivateKey(t *testing.T) {
const invalidMsg = "invalid private key"
const privateKey = `-----BEGIN RSA PRIVATE KEY-----
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above


tectonicSystemConfigFilePath := filepath.Join(tectonicPath, tectonicSystemFileName)
return writeFile(tectonicSystemConfigFilePath, tectonicSystem)

Copy link
Contributor

Choose a reason for hiding this comment

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

we can delete this extra newline after the return

@trawler trawler force-pushed the move_tls_from_terraform_to_installer branch 3 times, most recently from 720c559 to b41bc7d Compare June 29, 2018 14:11
Copy link
Contributor

@squat squat left a comment

Choose a reason for hiding this comment

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

This PR is getting a lot more solid. I found a few bugs with the current implementation and I also have a few design suggestions. Please take a look.

if c.CA.RootCACertPath == "" {
if generateRootCA(clusterDir, key); err != nil {
return fmt.Errorf("failed to create a certificate: %v", err)

Copy link
Contributor

Choose a reason for hiding this comment

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

this newline here is a little funny


// If RootCACertPath is not provided, generate a cert
if c.CA.RootCACertPath == "" {
if generateRootCA(clusterDir, key); 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.

this is not actually checking the error returned by the generateRootCA func, but rather the error from the parent scope.

// If no file paths were provided, the certs will be auto-generated
func (c *ConfigGenerator) GenerateTLSConfig(clusterDir string) error {
var key *rsa.PrivateKey
var err error
Copy link
Contributor

Choose a reason for hiding this comment

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

there should be no need to have these block-scoped variables here. for long functions it can lead to shady scenarios where we unsuspectingly shadow the variables or forget to assign variables since the variable is already declared (as happened below where the error from generateRootCA was not actually being checked; if this variable was not declared here then the compiler would have caught that bug).

var err error

// If RootCAKeyPath is not provided, generate a key
if c.CA.RootCAKeyPath == "" {
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 it would be easier to follow the logic of this long function if both the private key generation and certificate generation were done close together and if the key file copying and the cert file copying were done together. WDYT? This if statement could be:

if c.CA.RootCAKeyPath == "" && c.CA.RootCACertPath == "" {
// generate key and certificate
} else {
// copy key and certificates
}

defer f.Close()

w := bufio.NewWriter(f)
if _, err := fmt.Fprintln(w, content); 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.

Do you know why are we writing to the writer using fmt instead of using w.WriteString [0]?

[0] https://golang.org/pkg/bufio/#Writer.WriteString

func generateTLSConfigStep(m *metadata) error {
newTLSPath := filepath.Join(m.clusterDir, newTLSPath)
if err := os.MkdirAll(newTLSPath, os.ModeDir|0755); err != nil {
return fmt.Errorf("failed to create TLS directory at %s", newTLSPath)
Copy link
Contributor

Choose a reason for hiding this comment

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

should we wrap the returned error here? or is it not necessary?

}
}

// InstallTLSNewWorkflow Generates the TLS certificates Using go, instead of TF
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this sentence has some funny casing.

}

func generateTLSConfigStep(m *metadata) error {
newTLSPath := filepath.Join(m.clusterDir, newTLSPath)
Copy link
Contributor

Choose a reason for hiding this comment

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

you are shadowing a constant here, which is a little funny. Also, as far as this function is concerned, the newTLSPath is just the path, right? This func doesn't know about anything but the new TLS stuff anyways so there is no need to further qualify the variable name in this lexical context.


// Don't let users hang themselves
if isMatch(`-BEGIN [\w-]+ PRIVATE KEY-`, trimmed) {
if err := PrivateKey(trimmed); isMatch("PRIVATE", trimmed) && 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 don't think we need to use the isMatch test anymore. The PrivateKey test you rewrote is quite thorough enough. I do not think that the matching provides any extra benefit and it is extra noise.

}
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.

@trawler trawler force-pushed the move_tls_from_terraform_to_installer branch 3 times, most recently from b9e13d7 to f584107 Compare July 2, 2018 13:42
Copy link
Contributor

@squat squat left a comment

Choose a reason for hiding this comment

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

Looks great. Just a handful of small comments

return errs
}

// validateCAKey validates and returns the content of the private key file
Copy link
Contributor

Choose a reason for hiding this comment

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

this validation func does not return any content of any file, only an error if any was encountered.

return nil
}

// validateCACert validates and returns the content of the certificate file
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

const badPem = "failed to parse certificate"

// throwaway rsa key
rsaKey, _ := tls.GeneratePrivateKey()
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 this got lost in the last round of reviews. We should be checking the errors returned by any func even in tests rather than ignoring them. If we do get an unexpected error in the setup, then we can call: t.Fatalf("failed to generate private key: %v", err) and the test suite will fail gracefully.

const invalidMsg = "failed to parse private key"

// throw-away rsa key
rsaKey, _ := tls.GeneratePrivateKey()
Copy link
Contributor

Choose a reason for hiding this comment

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

same here

@trawler trawler force-pushed the move_tls_from_terraform_to_installer branch 2 times, most recently from 6231500 to 2a09acc Compare July 3, 2018 08:38
spangenberg
spangenberg previously approved these changes Jul 3, 2018
"tectonic_aws_worker_root_volume_type": "gp2",
"tectonic_libvirt_network_if": "osbr0",
"tectonic_libvirt_resolver": "8.8.8.8",
"tectonic_base_domain": "tectonic-ci.de",
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this just changing order? it'd be useful to proactively add context for that when that's the case :)

enxebre
enxebre previously approved these changes Jul 3, 2018
@trawler trawler dismissed stale reviews from enxebre and spangenberg via 1d54899 July 3, 2018 09:55
@trawler trawler force-pushed the move_tls_from_terraform_to_installer branch from 2a09acc to 1d54899 Compare July 3, 2018 09:55
@trawler trawler merged commit 07f7b42 into coreos:master Jul 3, 2018
wking added a commit to wking/openshift-installer that referenced this pull request Jul 16, 2018
This code is descended from terraformPrepareStep, which landed in
1b4bb62 (Cli tool, 2018-02-05, coreos/tectonic-installer#2806).  The
workspace version was factored out into copyFile in 2b82fbe
(installer: Integrate multistep cli with configuration, 2018-02-16,
coreos/tectonic-installer#2960).  The config-generator version was
copy/pasted (or independently developed?) in 1d54899
(installer/pkg/config-generator/tls: generate root CA's with go,
2018-06-28, coreos/tectonic-installer#3316).  This commit DRYs that up
by pulling the duplicate code out into a shared package.

I've also changed O_RDWR to O_WRONLY.  The O_RDWR is originally from
1b4bb62, but we do not require the ability to read from the target
file.

Also add a unit test to exercise this code.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants