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

Support using letsencrypt with tls-alpn-01 #2729

Open
kristoiv opened this issue Mar 29, 2019 · 9 comments · Fixed by #2744
Open

Support using letsencrypt with tls-alpn-01 #2729

kristoiv opened this issue Mar 29, 2019 · 9 comments · Fixed by #2744
Assignees
Labels

Comments

@kristoiv
Copy link

@kristoiv kristoiv commented Mar 29, 2019

I cannot seem to generate grpc-server certificates using "golang.org/x/crypto/acme/autocert" with the "tls-alpn-01" challenge type. The reason is most likely just grpc-go overwriting my supplied NextProtocols-list here: https://github.com/grpc/grpc-go/blob/master/credentials/credentials.go#L214.

Can we do something about this?

What version of gRPC are you using?

master

What version of Go are you using (go version)?

1.12 darwin/amd64

What operating system (Linux, Windows, …) and version?

MacOSX

What did you do?

manager = &autocert.Manager{
	Prompt:     autocert.AcceptTOS,
	Cache:      autocert.DirCache(cacheDir),
	HostPolicy: autocert.HostWhitelist(domains...),
	Email:      email,
}
opts = append(opts, grpc.Creds(credentials.NewTLS(manager.TLSConfig())))

What did you expect to see?

Certificates being created as expected.

What did you see instead?

Nothing. No new certificate. Challenge type tls-alpn-01 cannot work if the "acme-tls/1" protocol isn't available. Overridden here: https://github.com/grpc/grpc-go/blob/master/credentials/credentials.go#L214

@menghanl

This comment has been minimized.

Copy link
Contributor

@menghanl menghanl commented Apr 4, 2019

I have a tentative fix in #2744. Please take a look and give it a try. Thanks!

@kristoiv

This comment has been minimized.

Copy link
Author

@kristoiv kristoiv commented Apr 8, 2019

I answered in the pull request:

I can confirm that this solves my problem in issue #2729 . It neatly block requests from a grpc client while it creates the certificate in the background and as soon as it is ready it completes the request. This works flawlessly with only port 443 opened.

@dfawley

This comment has been minimized.

Copy link
Contributor

@dfawley dfawley commented Apr 8, 2019

Based on a discussion this morning, I'm a little concerned we may not have solved this correctly, and that the right solution is to have a custom handshaker if Let's Encrypt is needed. @menghanl is going to look into this more.

@dfawley dfawley reopened this Apr 8, 2019
@nleiva

This comment has been minimized.

Copy link

@nleiva nleiva commented Jul 3, 2019

@kristoiv could you elaborate on how you made this work?. I Have only managed to make this work partially and wondering if it's me or this is not really working for gRPC.

My gRPC service is on port 50051 while at the same time I listen on 443 for HTTPs request for the challenge. The code looks roughly like this.

manager := autocert.Manager{
	Prompt:     autocert.AcceptTOS,
	Cache:      autocert.DirCache("golang-autocert"),
	HostPolicy: autocert.HostWhitelist("test.nleiva.com"),
}

opts = grpc.Creds(credentials.NewTLS(manager.TLSConfig()))
s := grpc.NewServer(opts...)
// ... register gRPC services ...

go http.Serve(autocert.NewListener("test.nleiva.com"), newMux())

lis, _ := net.Listen("tcp", ":50051")
if err := s.Serve(lis); err != nil {
	log.Fatalf("failed to serve: %v", err)
}

I see I got the certs, in fact I can serve HTTPs in port 443 with them (https://test.nleiva.com works).

~/.cache$ sudo ls -la golang-autocert/
total 16
drwx------ 2 root   root   4096 Jul  3 14:43 .
drwx------ 4 ubuntu ubuntu 4096 Jul  3 14:41 ..
-rw------- 1 root   root    227 Jul  3 14:43 acme_account+key
-rw------- 1 root   root   3509 Jul  3 14:43 test.nleiva.com

However, when I try to reach the gRPC services at 50051 it fails:

2019/07/03 14:52:12 http: TLS handshake error from [2600:3000:1511:200::1d]:52584: acme/autocert: no token cert for "test.nleiva.com"
2019/07/03 14:52:12 http: TLS handshake error from 64.78.149.164:41708: acme/autocert: no token cert for "test.nleiva.com"

EDIT: Interestingly enough, this works just fine if both the HTTPs and gRPC servers listen on the same port 443.

@kristoiv

This comment has been minimized.

Copy link
Author

@kristoiv kristoiv commented Jul 5, 2019

@nleiva I only tested with everything on 443, the thinking being that I would want everything on a single port.

@jsha

This comment has been minimized.

Copy link
Contributor

@jsha jsha commented Jul 21, 2019

I wanted to chime in, as a Let's Encrypt engineer, to mention that for gRPC applications, Let's Encrypt (or any other publicly trusted CA) is usually not the right choice. It's generally better to use an internal CA. That doesn't have to be complicated - you can make one in ten minutes with minica.

Using a public CA makes sense when you need a lot of unrelated clients to trust your service without any prior manual configuration - for instance, browsers visiting a web site. For most RPC clients, there's a fair amount of configuration going into their TLS setup, so it makes sense to configure your clients to use an internal CA, and get the benefits (simplicity, tighter security scope).

I don't see any problems with this particular approach, or with making gRPC compatible with tls-alpn-01, but wanted to leave this comment here for future visitors looking for information on how to configure TLS for their gRPC.

@stale

This comment has been minimized.

Copy link

@stale stale bot commented Sep 6, 2019

This issue is labeled as requiring an update from the reporter, and no update has been received after 7 days. If no update is provided in the next 7 days, this issue will be automatically closed.

@stale stale bot added the stale label Sep 6, 2019
@dfawley dfawley removed the stale label Sep 6, 2019
@kristoiv

This comment has been minimized.

Copy link
Author

@kristoiv kristoiv commented Sep 10, 2019

@kristoiv could you elaborate on how you made this work?. I Have only managed to make this work partially and wondering if it's me or this is not really working for gRPC.

@nleiva It may be required to be on the same port, not sure. But I notice that in your code you do not actually serve port 443 with your manager but instead a generic one returned by NewListener(). That will create a generic manager for itself with a different cache directory etc. So this would probably never work?

@nleiva

This comment has been minimized.

Copy link

@nleiva nleiva commented Sep 10, 2019

You are correct @kristoiv. I documented the final version of my test code here, using manager.Listener() instead.

func grpcHandlerFunc(g *grpc.Server, h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ct := r.Header.Get("Content-Type")
		if r.ProtoMajor == 2 && strings.Contains(ct, "application/grpc") {
			g.ServeHTTP(w, r)
		} else {
			h.ServeHTTP(w, r)
		}
	})
}

...

// Listener
lis = manager.Listener()

if err = http.Serve(lis, grpcHandlerFunc(s, httpsHandler())); err != nil {
	log.Fatalf("failed to serve: %v", err))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants
You can’t perform that action at this time.