Skip to content

Commit

Permalink
Implement h2c with backend
Browse files Browse the repository at this point in the history
  • Loading branch information
juliens authored and traefiker committed May 24, 2018
1 parent 83e09ac commit 9420308
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 52 deletions.
14 changes: 10 additions & 4 deletions docs/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,16 +345,22 @@ Here is an example of backends and servers definition:
[backends.backend2]
# ...
[backends.backend2.servers.server1]
url = "http://172.17.0.4:80"
url = "https://172.17.0.4:443"
weight = 1
[backends.backend2.servers.server2]
url = "http://172.17.0.5:80"
url = "https://172.17.0.5:443"
weight = 2
[backends.backend3]
# ...
[backends.backend3.servers.server1]
url = "h2c://172.17.0.6:80"
weight = 1
```

- Two backends are defined: `backend1` and `backend2`
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1`.
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2`.
- `backend1` will forward the traffic to two servers: `172.17.0.2:80` with weight `10` and `172.17.0.3:80` with weight `1`.
- `backend2` will forward the traffic to two servers: `172.17.0.4:443` with weight `1` and `172.17.0.5:443` with weight `2` both using TLS.
- `backend3` will forward the traffic to: `172.17.0.6:80` with weight `1` using HTTP2 without TLS.

#### Load-balancing

Expand Down
5 changes: 1 addition & 4 deletions docs/img/grpc.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 15 additions & 43 deletions docs/user-guide/grpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,13 @@
This section explains how to use Traefik as reverse proxy for gRPC application with self-signed certificates.

!!! warning
As gRPC needs HTTP2, we need HTTPS certificates on both gRPC Server and Træfik.
As gRPC needs HTTP2, we need HTTPS certificates on Træfik.
For exchanges with the backend, we will use h2c (HTTP2 on HTTP without TLS)

<p align="center">
<img src="/img/grpc.svg" alt="gRPC architecture" title="gRPC architecture" />
</p>

## gRPC Server certificate

In order to secure the gRPC server, we generate a self-signed certificate for backend url:

```bash
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./backend.key -out ./backend.cert
```

That will prompt for information, the important answer is:

```
Common Name (e.g. server FQDN or YOUR name) []: backend.local
```

## gRPC Client certificate

Generate your self-signed certificate for frontend url:
Expand All @@ -44,9 +31,6 @@ At last, we configure our Træfik instance to use both self-signed certificates.
```toml
defaultEntryPoints = ["https"]

# For secure connection on backend.local
rootCAs = [ "./backend.cert" ]

[entryPoints]
[entryPoints.https]
address = ":4443"
Expand All @@ -64,8 +48,8 @@ rootCAs = [ "./backend.cert" ]
[backends]
[backends.backend1]
[backends.backend1.servers.server1]
# Access on backend with HTTPS
url = "https://backend.local:8080"
# Access on backend with h2c
url = "h2c://backend.local:8080"


[frontends]
Expand All @@ -76,40 +60,25 @@ rootCAs = [ "./backend.cert" ]
```

!!! warning
With some backends, the server URLs use the IP, so you may need to configure `insecureSkipVerify` instead of the `rootCAS` to activate HTTPS without hostname verification.
For provider with label, you will have to specify the `traefik.protocol=h2c`

## Conclusion

We don't need specific configuration to use gRPC in Træfik, we just need to be careful that all the exchanges (between client and Træfik, and between Træfik and backend) are HTTPS communications because gRPC uses HTTP2.
We don't need specific configuration to use gRPC in Træfik, we just need to be careful that exchanges between client and Træfik are HTTPS communications.
For exchanges between Træfik and backend, you need to use `h2c` protocol, or use HTTPS communications to have HTTP2.

## A gRPC example in go

We will use the gRPC greeter example in [grpc-go](https://github.com/grpc/grpc-go/tree/master/examples/helloworld)

!!! warning
In order to use this gRPC example, we need to modify it to use HTTPS

So we modify the "gRPC server example" to use our own self-signed certificate:

We can keep the Server example as is with the h2c protocol
```go
// ...

// Read cert and key file
BackendCert, _ := ioutil.ReadFile("./backend.cert")
BackendKey, _ := ioutil.ReadFile("./backend.key")

// Generate Certificate struct
cert, err := tls.X509KeyPair(BackendCert, BackendKey)
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to parse certificate: %v", err)
log.Fatalf("failed to listen: %v", err)
}

// Create credentials
creds := credentials.NewServerTLSFromCert(&cert)

// Use Credentials in gRPC server options
serverOption := grpc.Creds(creds)
var s *grpc.Server = grpc.NewServer(serverOption)
var s *grpc.Server = grpc.NewServer()
defer s.Stop()

pb.RegisterGreeterServer(s, &server{})
Expand All @@ -118,6 +87,10 @@ err := s.Serve(lis)
// ...
```

!!! warning
In order to use this gRPC example, we need to modify it to use HTTPS


Next we will modify gRPC Client to use our Træfik self-signed certificate:

```go
Expand Down Expand Up @@ -147,4 +120,3 @@ r, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: name})

// ...
```

29 changes: 29 additions & 0 deletions integration/fixtures/grpc/config_h2c.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
defaultEntryPoints = ["https"]

rootCAs = [ """{{ .CertContent }}""" ]

[entryPoints]
[entryPoints.https]
address = ":4443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
certFile = """{{ .CertContent }}"""
keyFile = """{{ .KeyContent }}"""


[api]

[file]

[backends]
[backends.backend1]
[backends.backend1.servers.server1]
url = "h2c://127.0.0.1:{{ .GRPCServerPort }}"
weight = 1


[frontends]
[frontends.frontend1]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Host:127.0.0.1"
53 changes: 52 additions & 1 deletion integration/grpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ func startGRPCServer(lis net.Listener, server *myserver) error {
helloworld.RegisterGreeterServer(s, server)
return s.Serve(lis)
}

func starth2cGRPCServer(lis net.Listener, server *myserver) error {
s := grpc.NewServer()
defer s.Stop()

helloworld.RegisterGreeterServer(s, server)
return s.Serve(lis)
}

func getHelloClientGRPC() (helloworld.GreeterClient, func() error, error) {
roots := x509.NewCertPool()
roots.AppendCertsFromPEM(LocalhostCert)
Expand Down Expand Up @@ -137,12 +146,54 @@ func (s *GRPCSuite) TestGRPC(c *check.C) {
// wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("Host:127.0.0.1"))
c.Assert(err, check.IsNil)

var response string
err = try.Do(1*time.Second, func() error {
response, err = callHelloClientGRPC("World")
return err
})
c.Assert(err, check.IsNil)
c.Assert(response, check.Equals, "Hello World")
}

func (s *GRPCSuite) TestGRPCh2c(c *check.C) {
lis, err := net.Listen("tcp", ":0")
_, port, err := net.SplitHostPort(lis.Addr().String())
c.Assert(err, check.IsNil)

go func() {
err := starth2cGRPCServer(lis, &myserver{})
c.Log(err)
c.Assert(err, check.IsNil)
}()

file := s.adaptFile(c, "fixtures/grpc/config_h2c.toml", struct {
CertContent string
KeyContent string
GRPCServerPort string
}{
CertContent: string(LocalhostCert),
KeyContent: string(LocalhostKey),
GRPCServerPort: port,
})

defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)

err = cmd.Start()
c.Assert(err, check.IsNil)
defer cmd.Process.Kill()

// wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("Host:127.0.0.1"))
c.Assert(err, check.IsNil)

var response string
err = try.Do(1*time.Second, func() error {
response, err = callHelloClientGRPC("World")
return err
})
c.Assert(err, check.IsNil)
c.Assert(response, check.Equals, "Hello World")
}
Expand Down Expand Up @@ -179,12 +230,12 @@ func (s *GRPCSuite) TestGRPCInsecure(c *check.C) {
// wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("Host:127.0.0.1"))
c.Assert(err, check.IsNil)

var response string
err = try.Do(1*time.Second, func() error {
response, err = callHelloClientGRPC("World")
return err
})

c.Assert(err, check.IsNil)
c.Assert(response, check.Equals, "Hello World")
}
Expand Down
19 changes: 19 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ func NewServer(globalConfiguration configuration.GlobalConfiguration, provider p
return server
}

type h2cTransportWrapper struct {
*http2.Transport
}

func (t *h2cTransportWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
req.URL.Scheme = "http"
return t.Transport.RoundTrip(req)
}

// createHTTPTransport creates an http.Transport configured with the GlobalConfiguration settings.
// For the settings that can't be configured in Traefik it uses the default http.Transport settings.
// An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHost
Expand All @@ -168,6 +177,16 @@ func createHTTPTransport(globalConfiguration configuration.GlobalConfiguration)
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}

transport.RegisterProtocol("h2c", &h2cTransportWrapper{
Transport: &http2.Transport{
DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) {
return net.Dial(netw, addr)
},
AllowHTTP: true,
},
})

if globalConfiguration.ForwardingTimeouts != nil {
transport.ResponseHeaderTimeout = time.Duration(globalConfiguration.ForwardingTimeouts.ResponseHeaderTimeout)
}
Expand Down

0 comments on commit 9420308

Please sign in to comment.