Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tls: generate server TLS cert from CA #2401

Merged
merged 4 commits into from
Jul 5, 2022
Merged

Conversation

dnephin
Copy link
Contributor

@dnephin dnephin commented Jun 24, 2022

Summary

The first commit moves some functions to tls.go, so best viewed by individual commit.

With the changes in this PR, a user has 3 options for configuring TLS:

  1. set tls.ACME=true, to use ACME to create a publicly trusted certificate
  2. set tls.ca and tls.caPrivateKey, which will be used to generate a TLS ceritifcate for any hostname the client requests.
  3. set tls.certificate and tls.privateKey to use that certificate for TLS

We plan on setting a CA as part of the helm deploy, so the default for any user that uses our helm charts will be to specify nothing and use option 2.

ACME is no longer the default option. It has to be explicitly enabled, which is probably better because currently every single TLS request could make an ACME API request, even when it should never be used.

Resolves #2067
And addresses parts 1 and 2 of #2362

TODO:

  • fix some CLI tests that are failing due to missing CA option
  • add test case for generating certs from a CA
  • update helm to generate the CA as part of deploy

@@ -63,64 +57,6 @@ func GenerateCertificate(hosts []string, caCert *x509.Certificate, caKey crypto.
return PEMEncodeCertificate(certBytes), keyBytes, nil
}

func SelfSignedOrLetsEncryptCert(manager *autocert.Manager) func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved this into internal/server/tls.go and renamed it to getCertificate. That's the only place it is used.

@@ -146,39 +77,3 @@ func PEMEncodeCertificate(raw []byte) []byte {
func pemEncodePrivateKey(raw []byte) []byte {
return pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: raw})
}

func newCA() (*x509.Certificate, *rsa.PrivateKey, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No longer used, we'll rely on helm or the user to provide the CA.

Copy link
Collaborator

Choose a reason for hiding this comment

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

What implications does this have on the initial deploy (ex: quickstart)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, good question. I added another TODO to the list in the description. We need to generate the CA in helm as part of the deploy. That should make it invisible to the user. They won't have to do anything new, but helm will take care of creating the secret which contains the CA and CA private key.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've pushed a commit which does this (generates the CA in helm and stores it in a secret)

Copy link
Collaborator

Choose a reason for hiding this comment

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

The other implication is that the server can no longer be served outside of a helm install, e.g. docker, compose, or local build, unless the CA is manually created and configured. It'll be nice to keep this to support those use cases but not critical

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ya, that's true. We could keep the ability to generate a CA if none is provided for development and demo purposes. I think in practice no real production use case should have the server generate a CA on first install. The operator really needs to save that CA and private key somewhere, and they aren't going to know it exists or where to find it if it was generated on first start.

I'll see about re-adding this for development.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One of the disadvantages to generating the CA by default is that if the user accidentally mis-configured either ca or caPrivateKey. For example, maybe they set only one of the two, or they set primaryKey instead of caPrimaryKey. With the changes in this PR they will get a nice error and know how to fix it.

If we always generate things for them by default then it won't fail, and they'll have a harder time debugging.

I think it might be better to require these are set ahead of starting the server. For dev maybe we could add a --dev flag and do some setup in the CLI before starting the server.

Copy link
Collaborator

@mxyng mxyng Jul 5, 2022

Choose a reason for hiding this comment

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

Either Helm or infra can exit error if only one of ca or caPrivateKey is supplied since both are required. It'll reduce the impact of the scenario you're suggesting. Either way, it can be addressed in a future change

@@ -299,63 +299,6 @@ type routine struct {
stop func()
}

func tlsConfigFromOptions(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to internal/server/tls.go

}

func getCertificate(cache autocert.Cache, opts TLSOptions) func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
var lock sync.RWMutex
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This lock should fix #2067

@dnephin dnephin force-pushed the dnephin/server-tls-config-2 branch from 83bd672 to 50d1125 Compare June 24, 2022 18:58
@dnephin dnephin force-pushed the dnephin/server-tls-config-2 branch 3 times, most recently from 871965b to cb26d63 Compare June 27, 2022 21:39
if err := manager.Cache.Put(ctx, serverName+".crt", certBytes); err != nil {
return nil, err
}
hosts := []string{"127.0.0.1", "::1", serverName}
Copy link
Contributor Author

@dnephin dnephin Jun 28, 2022

Choose a reason for hiding this comment

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

I changed this to always include localhost in the list of valid IP SANs since it's common for a cert to be used on localhost as well.

Base automatically changed from dnephin/server-tls-config to main June 28, 2022 18:01
@dnephin dnephin force-pushed the dnephin/server-tls-config-2 branch from cb26d63 to b7b5ecd Compare June 29, 2022 21:54
@dnephin dnephin marked this pull request as ready for review June 29, 2022 21:54
@dnephin dnephin force-pushed the dnephin/server-tls-config-2 branch from b7b5ecd to e10b58f Compare June 29, 2022 22:14
Copy link
Contributor Author

@dnephin dnephin 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 should be ready for review now

@@ -26,21 +23,47 @@ func tlsConfigFromOptions(
tlsCacheDir string,
opts TLSOptions,
) (*tls.Config, error) {
// TODO: print CA fingerprint when the client can trust that fingerprint
// TODO: how can we test this?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not really sure how to test the ACME integration, but it wasn't tested before and it's still not documented as a supported feature, so probably not a requirement for this PR.

{{- get $secret.data "ca.key" | b64dec | nindent 4 }}

{{- else }}
{{- $ca := genCA "Infra Server" 3650 }}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the name people will see in the Subject line of the certificate. Should we use "Infra" , or "Infra Server" ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

"Infra Server" seems fine to me 👍

These function will be modified in the next commit. Moving them now so that changes
are more obvious.
@dnephin dnephin force-pushed the dnephin/server-tls-config-2 branch 2 times, most recently from 2a51a80 to 8d63f54 Compare July 4, 2022 15:58
@@ -36,6 +36,12 @@ data:
{{- end }}
{{- end }}

{{- if (not .Values.server.config.tls) }}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't need parenthesis around the not

helm/charts/infra/values.yaml Show resolved Hide resolved
@@ -146,39 +77,3 @@ func PEMEncodeCertificate(raw []byte) []byte {
func pemEncodePrivateKey(raw []byte) []byte {
return pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: raw})
}

func newCA() (*x509.Certificate, *rsa.PrivateKey, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The other implication is that the server can no longer be served outside of a helm install, e.g. docker, compose, or local build, unless the CA is manually created and configured. It'll be nice to keep this to support those use cases but not critical

Copy link
Contributor

@ssoroka ssoroka left a comment

Choose a reason for hiding this comment

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

looks reasonable. no concerns at this point

dnephin added 3 commits July 4, 2022 17:32
With this commit, a user has 3 options for configuring TLS:

1. set tls.ACME=true, to use ACME to create a publicly trusted certificate
2. set tls.ca and tls.caPrivateKey, which will be used to generate a TLS
   ceritifcate for any hostname the client requests
3. set tls.certificate and tls.privateKey to use that certificate for TLS
The certificate is specified from opts instead of populating the cache.
@dnephin dnephin force-pushed the dnephin/server-tls-config-2 branch from 8d63f54 to 4dc9598 Compare July 4, 2022 21:33
@dnephin dnephin merged commit 4c888ca into main Jul 5, 2022
@dnephin dnephin deleted the dnephin/server-tls-config-2 branch July 5, 2022 21:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SelfSignedOrLetsEncryptCert can race with itself, creating multiple primary keys and certs
4 participants