diff options
author | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2020-01-29 15:21:33 +0300 |
---|---|---|
committer | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2020-01-29 15:21:33 +0300 |
commit | 7e8b22c4070baba5425760cf7285486a8c97b335 (patch) | |
tree | 42425335e48ae596d1d9d919f9ae8d59051b541f /internal/serving | |
parent | 2afc2463559e19ee40bcb0770ce49438adca0735 (diff) |
Provide a real function location when proxing serverless requests
Diffstat (limited to 'internal/serving')
-rw-r--r-- | internal/serving/serverless/certs.go | 26 | ||||
-rw-r--r-- | internal/serving/serverless/cluster.go | 33 | ||||
-rw-r--r-- | internal/serving/serverless/director.go | 9 | ||||
-rw-r--r-- | internal/serving/serverless/function.go | 18 | ||||
-rw-r--r-- | internal/serving/serverless/serverless.go | 4 | ||||
-rw-r--r-- | internal/serving/serverless/serverless_test.go | 45 | ||||
-rw-r--r-- | internal/serving/serverless/transport.go | 2 |
7 files changed, 91 insertions, 46 deletions
diff --git a/internal/serving/serverless/certs.go b/internal/serving/serverless/certs.go new file mode 100644 index 00000000..674e8b25 --- /dev/null +++ b/internal/serving/serverless/certs.go @@ -0,0 +1,26 @@ +package serverless + +import ( + "crypto/tls" + "crypto/x509" +) + +// Certs holds definition of certificates we use to perform mTLS +// handshake with a cluster +type Certs struct { + RootCerts *x509.CertPool + Certificate tls.Certificate +} + +// NewClusterCerts creates a new cluster configuration from cert / key pair +func NewClusterCerts(clientCert, clientKey string) (*Certs, error) { + cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey)) + if err != nil { + return nil, err + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM([]byte(clientCert)) + + return &Certs{RootCerts: caCertPool, Certificate: cert}, nil +} diff --git a/internal/serving/serverless/cluster.go b/internal/serving/serverless/cluster.go index b511f409..6bdd51da 100644 --- a/internal/serving/serverless/cluster.go +++ b/internal/serving/serverless/cluster.go @@ -2,35 +2,20 @@ package serverless import ( "crypto/tls" - "crypto/x509" + "strings" ) // Cluster represent a Knative cluster that we want to proxy requests to type Cluster struct { - Address string - Port string - Hostname string - Certs *ClusterCerts + Address string // Address is a real IP address of a cluster ingress + Port string // Port is a real port of HTTP TLS service + Name string // Name is a cluster name, used in cluster certificates + Certs *Certs } -// ClusterCerts holds definition of certificates we use to perform mTLS -// handshake -type ClusterCerts struct { - RootCerts *x509.CertPool - Certificate tls.Certificate -} - -// NewClusterCerts creates a new cluster configuration from cert / key pair -func NewClusterCerts(clientCert, clientKey string) (*ClusterCerts, error) { - cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey)) - if err != nil { - return nil, err - } - - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM([]byte(clientCert)) - - return &ClusterCerts{RootCerts: caCertPool, Certificate: cert}, nil +// Host returns a real cluster location based on IP address and port +func (c Cluster) Host() string { + return strings.Join([]string{c.Address, c.Port}, ":") } // TLSConfig builds a new tls.Config and returns a pointer to it @@ -38,6 +23,6 @@ func (c Cluster) TLSConfig() *tls.Config { return &tls.Config{ Certificates: []tls.Certificate{c.Certs.Certificate}, RootCAs: c.Certs.RootCerts, - ServerName: c.Hostname, + ServerName: c.Name, } } diff --git a/internal/serving/serverless/director.go b/internal/serving/serverless/director.go index 68887a13..4478b104 100644 --- a/internal/serving/serverless/director.go +++ b/internal/serving/serverless/director.go @@ -2,19 +2,18 @@ package serverless import ( "net/http" - "strings" "github.com/tomasen/realip" ) // NewDirectorFunc returns a director function capable of configuring a proxy // request -func NewDirectorFunc(cluster Cluster) func(*http.Request) { +func NewDirectorFunc(function Function) func(*http.Request) { return func(request *http.Request) { - location := strings.Join([]string{cluster.Address, cluster.Port}, ":") + host := function.Host() - request.Host = location - request.URL.Host = location + request.Host = host + request.URL.Host = host request.URL.Scheme = "https" request.Header.Set("User-Agent", "GitLab Pages Daemon") request.Header.Set("X-Forwarded-For", realip.FromRequest(request)) diff --git a/internal/serving/serverless/function.go b/internal/serving/serverless/function.go new file mode 100644 index 00000000..20d4ec2c --- /dev/null +++ b/internal/serving/serverless/function.go @@ -0,0 +1,18 @@ +package serverless + +import "strings" + +// Function represents a Knative service that is going to be invoked by the +// proxied request +type Function struct { + Name string // Name is a function name, it includes a "service name" component too + Namespace string // Namespace is a kubernetes namespace this function has been deployed to + BaseDomain string // BaseDomain is a cluster base domain, used to route requests to apropriate service +} + +// Host returns a function address that we are going to expose in the `Host:` +// header to make it possible to route a proxied request to appropriate service +// in a Knative cluster +func (f Function) Host() string { + return strings.Join([]string{f.Name, f.Namespace, f.BaseDomain}, ".") +} diff --git a/internal/serving/serverless/serverless.go b/internal/serving/serverless/serverless.go index 3b7b7d43..a8d090da 100644 --- a/internal/serving/serverless/serverless.go +++ b/internal/serving/serverless/serverless.go @@ -14,9 +14,9 @@ type Serverless struct { } // New returns a new serving instance -func New(cluster Cluster) serving.Serving { +func New(function Function, cluster Cluster) serving.Serving { proxy := httputil.ReverseProxy{ - Director: NewDirectorFunc(cluster), + Director: NewDirectorFunc(function), Transport: NewTransport(cluster), ErrorHandler: NewErrorHandler(), } diff --git a/internal/serving/serverless/serverless_test.go b/internal/serving/serverless/serverless_test.go index 96627d7a..2d0b7df0 100644 --- a/internal/serving/serverless/serverless_test.go +++ b/internal/serving/serverless/serverless_test.go @@ -15,7 +15,7 @@ import ( "gitlab.com/gitlab-org/gitlab-pages/internal/serving" ) -func withTestCluster(t *testing.T, cert, key string, block func(*http.ServeMux, *url.URL, *ClusterCerts)) { +func withTestCluster(t *testing.T, cert, key string, block func(*http.ServeMux, *url.URL, *Certs)) { mux := http.NewServeMux() cluster := httptest.NewUnstartedServer(mux) @@ -38,13 +38,20 @@ func withTestCluster(t *testing.T, cert, key string, block func(*http.ServeMux, func TestServeFileHTTP(t *testing.T) { t.Run("when proxying simple request to a cluster", func(t *testing.T) { - withTestCluster(t, fixture.Certificate, fixture.Key, func(mux *http.ServeMux, server *url.URL, certs *ClusterCerts) { - serverless := New(Cluster{ - Hostname: "knative.gitlab-example.com", - Address: server.Hostname(), - Port: server.Port(), - Certs: certs, - }) + withTestCluster(t, fixture.Certificate, fixture.Key, func(mux *http.ServeMux, server *url.URL, certs *Certs) { + serverless := New( + Function{ + Name: "my-func", + Namespace: "my-namespace-123", + BaseDomain: "knative.example.com", + }, + Cluster{ + Name: "knative.gitlab-example.com", + Address: server.Hostname(), + Port: server.Port(), + Certs: certs, + }, + ) writer := httptest.NewRecorder() request := httptest.NewRequest("GET", "http://example.gitlab.com/", nil) @@ -52,6 +59,7 @@ func TestServeFileHTTP(t *testing.T) { request.Header.Set("X-Real-IP", "127.0.0.105") mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "my-func.my-namespace-123.knative.example.com", r.Host) assert.Equal(t, "GitLab Pages Daemon", r.Header.Get("User-Agent")) assert.Equal(t, "https", r.Header.Get("X-Forwarded-Proto")) assert.Contains(t, r.Header.Get("X-Forwarded-For"), "127.0.0.105") @@ -66,13 +74,20 @@ func TestServeFileHTTP(t *testing.T) { }) t.Run("when proxying request with invalid hostname", func(t *testing.T) { - withTestCluster(t, fixture.Certificate, fixture.Key, func(mux *http.ServeMux, server *url.URL, certs *ClusterCerts) { - serverless := New(Cluster{ - Hostname: "knative.invalid-gitlab-example.com", - Address: server.Hostname(), - Port: server.Port(), - Certs: certs, - }) + withTestCluster(t, fixture.Certificate, fixture.Key, func(mux *http.ServeMux, server *url.URL, certs *Certs) { + serverless := New( + Function{ + Name: "my-func", + Namespace: "my-namespace-123", + BaseDomain: "knative.example.com", + }, + Cluster{ + Name: "knative.invalid-gitlab-example.com", + Address: server.Hostname(), + Port: server.Port(), + Certs: certs, + }, + ) writer := httptest.NewRecorder() request := httptest.NewRequest("GET", "http://example.gitlab.com/", nil) diff --git a/internal/serving/serverless/transport.go b/internal/serving/serverless/transport.go index d98663f5..98a5473e 100644 --- a/internal/serving/serverless/transport.go +++ b/internal/serving/serverless/transport.go @@ -22,6 +22,8 @@ func NewTransport(cluster Cluster) *Transport { } dialContext := func(ctx context.Context, network, address string) (net.Conn, error) { + address = cluster.Host() + return dialer.DialContext(ctx, network, address) } |