Skip to content

Commit 3cb6b96

Browse files
committed
Added code to get http-connect to work with the test client.
Unified the logic for client grpc and http-connect. Introduced a getDialer method. Moved all logic for grpc & http-connect to getDialer. Made both paths return a dialer and swtiched to using standard http client.
1 parent 1eb6f70 commit 3cb6b96

File tree

4 files changed

+159
-50
lines changed

4 files changed

+159
-50
lines changed

README.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,21 +60,21 @@ python -m SimpleHTTPServer
6060
```
6161

6262
- Start proxy service
63-
```
63+
```console
6464
./bin/proxy-server --serverCaCert=certs/master/issued/ca.crt --serverCert=certs/master/issued/proxy-master.crt --serverKey=certs/master/private/proxy-master.key --clusterCaCert=certs/agent/issued/ca.crt --clusterCert=certs/agent/issued/proxy-master.crt --clusterKey=certs/agent/private/proxy-master.key
6565
```
6666

6767
- Start agent service
68-
```
68+
```console
6969
./bin/proxy-agent --caCert=certs/agent/issued/ca.crt --agentCert=certs/agent/issued/proxy-agent.crt --agentKey=certs/agent/private/proxy-agent.key
7070
```
7171

7272
- Run client (mTLS enabled sample client)
73-
```
73+
```console
7474
./bin/proxy-test-client --caCert=certs/master/issued/ca.crt --clientCert=certs/master/issued/proxy-client.crt --clientKey=certs/master/private/proxy-client.key
7575
```
7676

77-
### HTTP-Connect Client using mTLS Proxy with dial back Agent
77+
### HTTP-Connect Client using mTLS Proxy with dial back Agent (Either curl OR test client)
7878

7979
```
8080
client =HTTP-CONNECT=> (:8090) proxy (:8091) <=GRPC= agent =HTTP=> SimpleHTTPServer(:8000)
@@ -89,17 +89,22 @@ python -m SimpleHTTPServer
8989
```
9090

9191
- Start proxy service
92-
```
92+
```console
9393
./bin/proxy-server --mode=http-connect --serverCaCert=certs/master/issued/ca.crt --serverCert=certs/master/issued/proxy-master.crt --serverKey=certs/master/private/proxy-master.key --clusterCaCert=certs/agent/issued/ca.crt --clusterCert=certs/agent/issued/proxy-master.crt --clusterKey=certs/agent/private/proxy-master.key
9494
```
9595

9696
- Start agent service
97-
```
97+
```console
9898
./bin/proxy-agent --caCert=certs/agent/issued/ca.crt --agentCert=certs/agent/issued/proxy-agent.crt --agentKey=certs/agent/private/proxy-agent.key
9999
```
100100

101-
- Run curl client (curl using a mTLS http-connect proxy)
101+
- Run client (mTLS & http-connect enabled sample client)
102+
```console
103+
./bin/proxy-test-client --mode=http-connect --proxyHost=127.0.0.1 --caCert=certs/master/issued/ca.crt --clientCert=certs/master/issued/proxy-client.crt --clientKey=certs/master/private/proxy-client.key
102104
```
105+
106+
- Run curl client (curl using a mTLS http-connect proxy)
107+
```console
103108
curl -v -p --proxy-key certs/master/private/proxy-client.key --proxy-cert certs/master/issued/proxy-client.crt --proxy-cacert certs/master/issued/ca.crt --proxy-cert-type PEM -x https://127.0.0.1:8090 http://localhost:8000```
104109
```
105110

cmd/client/main.go

Lines changed: 146 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,23 @@ limitations under the License.
1717
package main
1818

1919
import (
20+
"bufio"
21+
"context"
2022
"crypto/tls"
2123
"crypto/x509"
2224
"flag"
2325
"fmt"
24-
"io"
2526
"io/ioutil"
27+
"net"
28+
"net/http"
2629
"os"
2730

2831
"github.com/spf13/cobra"
2932
"github.com/spf13/pflag"
3033
"google.golang.org/grpc"
3134
"google.golang.org/grpc/credentials"
3235
"k8s.io/klog"
36+
3337
"sigs.k8s.io/apiserver-network-proxy/pkg/agent/client"
3438
"sigs.k8s.io/apiserver-network-proxy/pkg/util"
3539
)
@@ -54,23 +58,45 @@ func main() {
5458
}
5559

5660
type GrpcProxyClientOptions struct {
57-
clientCert string
58-
clientKey string
59-
caCert string
61+
clientCert string
62+
clientKey string
63+
caCert string
64+
requestProto string
65+
requestPath string
66+
requestHost string
67+
requestPort int
68+
proxyHost string
69+
proxyPort int
70+
mode string
71+
6072
}
6173

6274
func (o *GrpcProxyClientOptions) Flags() *pflag.FlagSet {
6375
flags := pflag.NewFlagSet("proxy", pflag.ContinueOnError)
6476
flags.StringVar(&o.clientCert, "clientCert", o.clientCert, "If non-empty secure communication with this cert.")
6577
flags.StringVar(&o.clientKey, "clientKey", o.clientKey, "If non-empty secure communication with this key.")
6678
flags.StringVar(&o.caCert, "caCert", o.caCert, "If non-empty the CAs we use to validate clients.")
79+
flags.StringVar(&o.requestProto, "requestProto", o.requestProto, "The protocol for the request to send through the proxy.")
80+
flags.StringVar(&o.requestPath, "requestPath", o.requestPath, "The url request to send through the proxy.")
81+
flags.StringVar(&o.requestHost, "requestHost", o.requestHost, "The host of the request server.")
82+
flags.IntVar(&o.requestPort, "requestPort", o.requestPort, "The port the request server is listening on.")
83+
flags.StringVar(&o.proxyHost, "proxyHost", o.proxyHost, "The host of the proxy server.")
84+
flags.IntVar(&o.proxyPort, "proxyPort", o.proxyPort, "The port the proxy server is listening on.")
85+
flags.StringVar(&o.mode, "mode", o.mode, "Mode can be either 'grpc' or 'http-connect'.")
6786
return flags
6887
}
6988

7089
func (o *GrpcProxyClientOptions) Print() {
71-
klog.Warningf("ClientCert set to \"%s\".\n", o.clientCert)
72-
klog.Warningf("ClientKey set to \"%s\".\n", o.clientKey)
73-
klog.Warningf("CACert set to \"%s\".\n", o.caCert)
90+
klog.Warningf("ClientCert set to %q.\n", o.clientCert)
91+
klog.Warningf("ClientKey set to %q.\n", o.clientKey)
92+
klog.Warningf("CACert set to %q.\n", o.caCert)
93+
klog.Warningf("RequestProto set to %q.\n", o.requestProto)
94+
klog.Warningf("RequestPath set to %q.\n", o.requestPath)
95+
klog.Warningf("RequestHost set to %q.\n", o.requestHost)
96+
klog.Warningf("RequestPort set to %d.\n", o.requestPort)
97+
klog.Warningf("ProxyHost set to %q.\n", o.proxyHost)
98+
klog.Warningf("ProxyPort set to %d.\n", o.proxyPort)
99+
klog.Warningf("Mode set to %q.\n", o.mode)
74100
}
75101

76102
func (o *GrpcProxyClientOptions) Validate() error {
@@ -79,30 +105,55 @@ func (o *GrpcProxyClientOptions) Validate() error {
79105
return err
80106
}
81107
if o.clientCert == "" {
82-
return fmt.Errorf("cannot have client cert empty when client key is set to \"%s\"", o.clientKey)
108+
return fmt.Errorf("cannot have client cert empty when client key is set to %q", o.clientKey)
83109
}
84110
}
85111
if o.clientCert != "" {
86112
if _, err := os.Stat(o.clientCert); os.IsNotExist(err) {
87113
return err
88114
}
89115
if o.clientKey == "" {
90-
return fmt.Errorf("cannot have client key empty when client cert is set to \"%s\"", o.clientCert)
116+
return fmt.Errorf("cannot have client key empty when client cert is set to %q", o.clientCert)
91117
}
92118
}
93119
if o.caCert != "" {
94120
if _, err := os.Stat(o.caCert); os.IsNotExist(err) {
95121
return err
96122
}
97123
}
124+
if o.requestProto != "http" && o.requestProto != "https" {
125+
return fmt.Errorf("request protocol must be set to either 'http' or 'https' not %q", o.requestProto)
126+
}
127+
if o.mode != "grpc" && o.mode != "http-connect" {
128+
return fmt.Errorf("mode must be set to either 'grpc' or 'http-connect' not %q", o.mode)
129+
}
130+
if o.requestPort > 49151 {
131+
return fmt.Errorf("please do not try to use ephemeral port %d for the request server port", o.requestPort)
132+
}
133+
if o.requestPort < 1024 {
134+
return fmt.Errorf("please do not try to use reserved port %d for the request server port", o.requestPort)
135+
}
136+
if o.proxyPort > 49151 {
137+
return fmt.Errorf("please do not try to use ephemeral port %d for the proxy server port", o.proxyPort)
138+
}
139+
if o.proxyPort < 1024 {
140+
return fmt.Errorf("please do not try to use reserved port %d for the proxy server port", o.proxyPort)
141+
}
98142
return nil
99143
}
100144

101145
func newGrpcProxyClientOptions() *GrpcProxyClientOptions {
102146
o := GrpcProxyClientOptions{
103-
clientCert: "",
104-
clientKey: "",
105-
caCert: "",
147+
clientCert: "",
148+
clientKey: "",
149+
caCert: "",
150+
requestProto: "http",
151+
requestPath: "/",
152+
requestHost: "localhost",
153+
requestPort: 8000,
154+
proxyHost: "localhost",
155+
proxyPort: 8090,
156+
mode: "grpc",
106157
}
107158
return &o
108159
}
@@ -125,24 +176,54 @@ type Client struct {
125176
func (c *Client) run(o *GrpcProxyClientOptions) error {
126177
o.Print()
127178
if err := o.Validate(); err != nil {
128-
return err
179+
return fmt.Errorf("failed to validate proxy client options, got %v", err)
129180
}
130181

131182
// Run remote simple http service on server side as
132183
// "python -m SimpleHTTPServer"
133184

185+
186+
dialer, err := c.getDialer(o)
187+
if err != nil {
188+
return fmt.Errorf("failed to get dialer for client, got %v", err)
189+
}
190+
transport := &http.Transport{
191+
DialContext: dialer,
192+
}
193+
client := &http.Client{
194+
Transport: transport,
195+
}
196+
requestURL := fmt.Sprintf("%s://%s:%d%s", o.requestProto, o.requestHost, o.requestPort, o.requestPath)
197+
request, err := http.NewRequest("GET", requestURL, nil)
198+
if err != nil {
199+
return fmt.Errorf("failed to create request %s to send, got %v", requestURL, err)
200+
}
201+
response, err := client.Do(request)
202+
if err != nil {
203+
return fmt.Errorf("failed to send request to client, got %v", err)
204+
}
205+
data, err := ioutil.ReadAll(response.Body)
206+
if err != nil {
207+
return fmt.Errorf("failed to read response from client, got %v", err)
208+
}
209+
klog.Info(string(data))
210+
211+
return nil
212+
}
213+
214+
func (c *Client) getDialer(o *GrpcProxyClientOptions) (func(ctx context.Context, network, addr string) (net.Conn, error), error) {
134215
clientCert, err := tls.LoadX509KeyPair(o.clientCert, o.clientKey)
135216
if err != nil {
136-
return err
217+
return nil, fmt.Errorf("failed to read key pair %s & %s, got %v", o.clientCert, o.clientKey, err)
137218
}
138219
certPool := x509.NewCertPool()
139220
caCert, err := ioutil.ReadFile(o.caCert)
140221
if err != nil {
141-
return err
222+
return nil, fmt.Errorf("failed to read cert file %s, got %v", o.caCert, err)
142223
}
143224
ok := certPool.AppendCertsFromPEM(caCert)
144225
if !ok {
145-
return fmt.Errorf("failed to append CA cert to the cert pool")
226+
return nil, fmt.Errorf("failed to append CA cert to the cert pool")
146227
}
147228

148229
transportCreds := credentials.NewTLS(&tls.Config{
@@ -151,33 +232,59 @@ func (c *Client) run(o *GrpcProxyClientOptions) error {
151232
RootCAs: certPool,
152233
})
153234

154-
dialOption := grpc.WithTransportCredentials(transportCreds)
155-
tunnel, err := client.CreateGrpcTunnel("localhost:8090", dialOption)
156-
if err != nil {
157-
return err
158-
}
159-
160-
conn, err := tunnel.Dial("tcp", "localhost:8000")
161-
if err != nil {
162-
return err
163-
}
164-
165-
_, err = conn.Write([]byte("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"))
166-
if err != nil {
167-
return err
168-
}
235+
var proxyConn net.Conn
236+
switch o.mode {
237+
case "grpc":
238+
dialOption := grpc.WithTransportCredentials(transportCreds)
239+
serverAddress := fmt.Sprintf("%s:%d", o.proxyHost, o.proxyPort)
240+
tunnel, err := client.CreateGrpcTunnel(serverAddress, dialOption)
241+
if err != nil {
242+
return nil, fmt.Errorf("failed to create tunnel %s, got %v", serverAddress, err)
243+
}
169244

170-
var buf [1 << 12]byte
245+
requestAddress := fmt.Sprintf("%s:%d", o.requestHost, o.requestPort)
246+
proxyConn, err = tunnel.Dial("tcp", requestAddress)
247+
if err != nil {
248+
return nil, fmt.Errorf("failed to dial request %s, got %v", requestAddress, err)
249+
}
250+
case "http-connect":
251+
proxyAddress := fmt.Sprintf("%s:%d", o.proxyHost, o.proxyPort)
252+
requestAddress := fmt.Sprintf("%s:%d", o.requestHost, o.requestPort)
171253

172-
for {
173-
n, err := conn.Read(buf[:])
174-
if err == io.EOF {
175-
break
254+
proxyConn, err = tls.Dial("tcp", proxyAddress,
255+
&tls.Config{
256+
ServerName: o.proxyHost,
257+
Certificates: []tls.Certificate{clientCert},
258+
RootCAs: certPool,
259+
},
260+
)
261+
if err != nil {
262+
return nil, fmt.Errorf("dialing proxy %q failed: %v", proxyAddress, err)
176263
}
264+
fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", requestAddress, "127.0.0.1")
265+
br := bufio.NewReader(proxyConn)
266+
res, err := http.ReadResponse(br, nil)
177267
if err != nil {
178-
return err
268+
return nil, fmt.Errorf("reading HTTP response from CONNECT to %s via proxy %s failed: %v",
269+
requestAddress, proxyAddress, err)
179270
}
180-
klog.Info(string(buf[:n]))
271+
if res.StatusCode != 200 {
272+
return nil, fmt.Errorf("proxy error from %s while dialing %s: %v", proxyAddress, requestAddress, res.Status)
273+
}
274+
275+
// It's safe to discard the bufio.Reader here and return the
276+
// original TCP conn directly because we only use this for
277+
// TLS, and in TLS the client speaks first, so we know there's
278+
// no unbuffered data. But we can double-check.
279+
if br.Buffered() > 0 {
280+
return nil, fmt.Errorf("unexpected %d bytes of buffered data from CONNECT proxy %q",
281+
br.Buffered(), proxyAddress)
282+
}
283+
default:
284+
return nil, fmt.Errorf("failed to process mode %s", o.mode)
181285
}
182-
return nil
286+
287+
return func(ctx context.Context, network, addr string) (net.Conn, error) {
288+
return proxyConn, nil
289+
}, nil
183290
}

pkg/agent/agentserver/server.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ func (c *ProxyClientConnection) send(pkt *agent.Packet) error {
4343
return nil
4444
}
4545
writer := c.Http
46-
klog.Warningf("pkt is set to %v.", pkt)
47-
klog.Warningf("GetData() returns %v.", pkt.GetData())
4846
_, err := writer.Write(pkt.GetData().Data)
4947
return err
5048
} else {
@@ -227,4 +225,4 @@ func (s *ProxyServer) serveRecvBackend(stream agent.AgentService_ConnectServer,
227225
klog.Warningf("unrecognized packet %+v", pkt)
228226
}
229227
}
230-
}
228+
}

pkg/agent/agentserver/tunnel.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ func (t *Tunnel) ServeHTTP(w http.ResponseWriter, r *http.Request) {
3434
r.Method,
3535
r.Host,
3636
r.TLS.PeerCertificates[0].Subject.CommonName) // can do authz with certs
37-
3837
if r.Method != http.MethodConnect {
3938
http.Error(w, "this proxy only supports CONNECT passthrough", http.StatusMethodNotAllowed)
4039
return

0 commit comments

Comments
 (0)