installer/pkg/config-generator/tls: generate root CA's with go#3316
Conversation
|
Can one of the admins verify this patch? |
| if c.CA.RootCAKeyPath == "" { | ||
| key, err = generatePrivateKey(clusterDir) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to generate private key: %s", err) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
for consistency please use %v to format errors.
installer/pkg/validate/validate.go
Outdated
| if block == nil { | ||
| return errors.New("failed to parse certificate pem") | ||
| } | ||
| _, err := x509.ParseCertificate(block.Bytes) |
There was a problem hiding this comment.
nit: we can use
if _, err := x509.ParseCertificate(block.Bytes); err != nil {
...
}
installer/pkg/validate/validate.go
Outdated
| } | ||
|
|
||
| trimmed := strings.TrimSpace(v) | ||
| r := strings.NewReader(v) |
There was a problem hiding this comment.
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----- |
There was a problem hiding this comment.
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----- |
installer/pkg/workflow/utils.go
Outdated
|
|
||
| tectonicSystemConfigFilePath := filepath.Join(tectonicPath, tectonicSystemFileName) | ||
| return writeFile(tectonicSystemConfigFilePath, tectonicSystem) | ||
|
|
There was a problem hiding this comment.
we can delete this extra newline after the return
720c559 to
b41bc7d
Compare
squat
left a comment
There was a problem hiding this comment.
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) | ||
|
|
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 == "" { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
Do you know why are we writing to the writer using fmt instead of using w.WriteString [0]?
| 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) |
There was a problem hiding this comment.
should we wrap the returned error here? or is it not necessary?
installer/pkg/workflow/install.go
Outdated
| } | ||
| } | ||
|
|
||
| // InstallTLSNewWorkflow Generates the TLS certificates Using go, instead of TF |
There was a problem hiding this comment.
nit: this sentence has some funny casing.
installer/pkg/workflow/install.go
Outdated
| } | ||
|
|
||
| func generateTLSConfigStep(m *metadata) error { | ||
| newTLSPath := filepath.Join(m.clusterDir, newTLSPath) |
There was a problem hiding this comment.
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.
installer/pkg/validate/validate.go
Outdated
|
|
||
| // Don't let users hang themselves | ||
| if isMatch(`-BEGIN [\w-]+ PRIVATE KEY-`, trimmed) { | ||
| if err := PrivateKey(trimmed); isMatch("PRIVATE", trimmed) && err == nil { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Yes, should be pretty much analogous to ignition validation https://github.com/coreos/tectonic-installer/blob/master/installer/pkg/config/validate.go#L296
b9e13d7 to
f584107
Compare
squat
left a comment
There was a problem hiding this comment.
Looks great. Just a handful of small comments
installer/pkg/config/validate.go
Outdated
| return errs | ||
| } | ||
|
|
||
| // validateCAKey validates and returns the content of the private key file |
There was a problem hiding this comment.
this validation func does not return any content of any file, only an error if any was encountered.
installer/pkg/config/validate.go
Outdated
| return nil | ||
| } | ||
|
|
||
| // validateCACert validates and returns the content of the certificate file |
| const badPem = "failed to parse certificate" | ||
|
|
||
| // throwaway rsa key | ||
| rsaKey, _ := tls.GeneratePrivateKey() |
There was a problem hiding this comment.
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() |
6231500 to
2a09acc
Compare
| "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", |
There was a problem hiding this comment.
Is this just changing order? it'd be useful to proactively add context for that when that's the case :)
2a09acc to
1d54899
Compare
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.
Fixes #INST-1106