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

Add SNI support to the apiserver #35109

Merged
merged 3 commits into from
Nov 1, 2016
Merged

Conversation

sttts
Copy link
Contributor

@sttts sttts commented Oct 19, 2016

This PR adds the --tls-sni-key-cert flag to the apiserver. It can be passed multiple times in the following ways:

$ apiserver \
    --tls-sni-cert-key '*.example.com,example.com: example.key,example.crt' \
    --tls-sni-cert-key 'foo.key,foo.crt'

The first variant explicitly sets the accepted domain names, the second variant reads the common names and DNS names from the certificate itself.

If no domain name matches, the existing certificate (--tls-cert-file) is used.

    fs.Var(config.NewNamedCertKeyArray(&s.SNICertKeys), "tls-sni-cert-key", ""+
        "A pair of x509 certificate and private key file paths, optionally prefixed with a list of "+
        "domain patterns which are fully qualified domain names, possibly with prefixed wildcard "+
        "segments. If no domain patterns are provided, the names of the certificate are "+
        "extracted. Non-wildcard matches trump over wildcard matches, explicit domain patterns "+
        "trump over extracted names. For multiple key/certificate pairs, use the "+
        "--tls-sni-key-cert multiple times. "+
        "Examples: \"example.key,example.crt\" or \"*.foo.com,foo.com:foo.key,foo.crt\".")
Add SNI support to the apiserver

Pass multiple certificates and domain name patterns with `--tls-sni-cert-key` and the right certificate will be chosen depending on the url the client is using.

This change is Reviewable

@sttts sttts added this to the v1.5 milestone Oct 19, 2016
@k8s-github-robot k8s-github-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. release-note-label-needed labels Oct 19, 2016
@k8s-github-robot k8s-github-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Oct 19, 2016
@k8s-github-robot k8s-github-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Oct 19, 2016
@sttts sttts removed their assignment Oct 19, 2016
@sttts
Copy link
Contributor Author

sttts commented Oct 19, 2016

@k8s-bot gci gke e2e test this

@sttts sttts added release-note Denotes a PR that will be considered when it comes time to generate release notes. and removed release-note-label-needed labels Oct 19, 2016
@liggitt
Copy link
Member

liggitt commented Oct 19, 2016

needs tests that actually start a TLS server and check that a default-cert+sni-certs config actually serves the sni cert to one of the sni hostnames, and falls back to the default cert for all other hostnames

return nil, err
}
if len(nkc.Names) > 0 {
for _, name := range nkc.Names {
Copy link
Member

Choose a reason for hiding this comment

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

explicitly specified names should trump auto-populated names

for example, if I have the following certs:
cert1: CN=bar.com
cert2: CN=foo.com, dnsSAN=bar.com

and I specify these flags:
--tls-sni-cert-key=cert1.pem,key1.pem,bar.com --tls-sni-cert-key=cert2.pem,key2.pem

I should end up with
"bar.com": cert1
"foo.com": cert2

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 think we should give auto-generation precedence, but enforce that the first cert given trumps any later one. That matches your example. WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

explicitly given names should always outweigh auto-detected ones

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

"domain patterns which are fully qualified domain names, possibly with prefixed wildcard "+
"segments. If no domain patterns are provided, the common names of the certificate are "+
"used instead. For multiple key/certificate pairs, use the --tls-sni-key-cert multiple times. "+
"Examples: \"example.key,example.crt\" or \"*.foo.com,foo.com:foo.key,foo.crt\".")
Copy link
Member

Choose a reason for hiding this comment

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

maybe put the variable args at the end, and the cert first (all the go stdlib functions do cert,key)? (e.g. cert,key[,name,...])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

maybe with a colon?

Copy link
Member

Choose a reason for hiding this comment

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

sure, cert:key[:name,...]?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

key,cert:name,name,name

Copy link
Member

Choose a reason for hiding this comment

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

put cert first for consistency, unsure about the delimiters

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 am fine with either order. Where do we have cert,key? I my head the key exists first, then the cert ;)

Copy link
Member

Choose a reason for hiding this comment

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

server.ListenAndServeTLS(cert,key)
tls.X509KeyPair(certbytes,keybytes)
tls.LoadX509KeyPair(cert,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.

That's not user facing. But cert/key is fine as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed it to: cert,key:name,name,name

return "namedKeyCert"
}

func (a *NamedKeyCertArray) String() string {
Copy link
Member

Choose a reason for hiding this comment

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

this is for display only, not for re-parsing, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's only used for the default output right now: [].

@@ -200,7 +206,8 @@ func (s *GenericAPIServer) Run() {
// Can't use SSLv3 because of POODLE and BEAST
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
// Can't use TLSv1.1 because of RC4 cipher usage
MinVersion: tls.VersionTLS12,
MinVersion: tls.VersionTLS12,
NameToCertificate: namedCerts,
Copy link
Member

Choose a reason for hiding this comment

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

does this need to include the names from the default cert as well?

Copy link
Member

Choose a reason for hiding this comment

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

when I did this in OpenShift, I had to use a custom GetCertificate function instead of NameToCertificate:

// Set SNI certificate func
// Do not use NameToCertificate, since that requires certificates be included in the server's tlsConfig.Certificates list,
// which we do not control when running with http.Server#ListenAndServeTLS
GetCertificate: cmdutil.GetCertificateFunc(extraCerts),

can you verify this is no longer the case?

edit: looks like this may have been fixed in go1.5

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is a default GetCertificate now.

Copy link
Member

Choose a reason for hiding this comment

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

there was a default GetCertificate in go1.4 as well, which behaved differently, and required jumping through hoops to get the right cert selected. The current implementation looks like it does what we want, but I do want tests to make sure the combination of ListenAndServeTLS(defaultCert,defaultKey) and the NameToCertificate map we're passing in selects the right cert.

@sttts
Copy link
Contributor Author

sttts commented Oct 19, 2016

@liggitt do we have any test for GenericApiServer.run() right now? I guess we don't, but we should. I vote for adding a close channel to run() and allow clean shutdown. /cc @deads2k

@liggitt
Copy link
Member

liggitt commented Oct 19, 2016

I don't see a clean way to shut down a server started with ListenAndServeTLS

@k8s-github-robot k8s-github-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Oct 20, 2016
@deads2k
Copy link
Contributor

deads2k commented Oct 20, 2016

@liggitt do we have any test for GenericApiServer.run() right now? I guess we don't, but we should. I vote for adding a close channel to run() and allow clean shutdown. /cc @deads2k

I'm ok adding a channel, but I doubt it will bring everything down cleanly. We had a lot of trouble with that in the distant past.

@sttts
Copy link
Contributor Author

sttts commented Oct 20, 2016

@deads2k former go versions (<= 1.3) had races when closing the http.Listener asynchronously to bring down a server. I have read this has improved and maybe this was a reason for the trouble your are mentioning.

@sttts
Copy link
Contributor Author

sttts commented Oct 21, 2016

@deads2k @liggitt added integration tests

@k8s-github-robot k8s-github-robot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Oct 21, 2016
@k8s-ci-robot
Copy link
Contributor

Jenkins Kubemark GCE e2e failed for commit ce9830c9e1292ba79eb4a01ab0bd1fb46d2665cc. Full PR test history.

The magic incantation to run this job again is @k8s-bot kubemark e2e test this. Please help us cut down flakes by linking to an open flake issue when you hit one in your PR.

@k8s-ci-robot
Copy link
Contributor

Jenkins GCI GCE e2e failed for commit ce9830c9e1292ba79eb4a01ab0bd1fb46d2665cc. Full PR test history.

The magic incantation to run this job again is @k8s-bot gci gce e2e test this. Please help us cut down flakes by linking to an open flake issue when you hit one in your PR.

@k8s-github-robot k8s-github-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Oct 31, 2016
@k8s-github-robot k8s-github-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Oct 31, 2016
@liggitt
Copy link
Member

liggitt commented Oct 31, 2016

LGTM, squash in "address comments" commit

@@ -105,6 +106,6 @@ func Run(serverOptions *genericoptions.ServerRunOptions) error {
if err := s.InstallAPIGroup(&apiGroupInfo); err != nil {
return fmt.Errorf("Error in installing API: %v", err)
}
s.PrepareRun().Run()
s.PrepareRun().Run(wait.NeverStop)
Copy link
Contributor

Choose a reason for hiding this comment

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

This one we want to stop.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added a close channel everywhere where the example apiserver is used.

InsecureServingInfo *ServingInfo

// numerical ports, set after listening
effectiveSecurePort, effectiveInsecurePort int
Copy link
Contributor

Choose a reason for hiding this comment

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

Just reading through, this looks weird. I'm assuming I'll see why later, but a better comment may help.

go func() {
defer utilruntime.HandleCrash()

for {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we still trying multiple times? Didn't we decide we preferred crashlooping? I'm ok as a followup.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only after the first synchronous try which worked. That was my understanding from our small irc discussion. A follow-up is fine. I prefer a hard crash in such a case such that the outer orchestrator (e.g. systemd or kubelet) can do its job.

Copy link
Member

Choose a reason for hiding this comment

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

I can't think of a good reason to retry such a fundamental failure internally

// Many clients fail TLS connections if they see two different certs with the same serial number
// from the same signer (and they have a local memory of previously seen certs from a given signer).
// Hence, we put a timestamp here.
SerialNumber: new(big.Int).SetInt64(time.Now().Unix()),
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd rather see UID. @liggitt did you prefer timestamp?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, and if it switches to uid, it needs to be positive.

Copy link
Member

Choose a reason for hiding this comment

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

yeah, random would probably be better

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In fact this was an accident that this function was changed at all. It's unrelated to SNI. Will undo the change.

@deads2k
Copy link
Contributor

deads2k commented Oct 31, 2016

minor comments, lgtm otherwise.

@sttts sttts added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Nov 1, 2016
@k8s-ci-robot
Copy link
Contributor

Jenkins unit/integration failed for commit 2ceca5d7d42bace0766e181b72d8984d603bd54b. Full PR test history.

The magic incantation to run this job again is @k8s-bot unit test this. Please help us cut down flakes by linking to an open flake issue when you hit one in your PR.

@k8s-github-robot k8s-github-robot removed the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Nov 1, 2016
@sttts sttts added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Nov 1, 2016
@sttts
Copy link
Contributor Author

sttts commented Nov 1, 2016

Squashed.

@k8s-github-robot
Copy link

@k8s-bot test this [submit-queue is verifying that this PR is safe to merge]

@k8s-github-robot
Copy link

Automatic merge from submit-queue

@k8s-github-robot k8s-github-robot merged commit 6babfb6 into kubernetes:master Nov 1, 2016
k8s-github-robot pushed a commit that referenced this pull request Nov 7, 2016
Automatic merge from submit-queue

Restore old apiserver cert CN

This patch got lost during rebase of #35109:

- set `host@<unix-timestamp>` as CN in self-signed apiserver certs
- skip non-domain CN in getNamedCertificateMap
@sttts sttts deleted the sttts-sni branch March 1, 2017 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/apiserver lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants