Skip to content

Commit

Permalink
Fixes #183: Adding support for proxies
Browse files Browse the repository at this point in the history
  • Loading branch information
Janos Bonic committed Apr 27, 2022
1 parent aa814b2 commit 24cd191
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 20 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.16

require (
github.com/google/uuid v1.3.0
github.com/ovirt/go-ovirt v0.0.0-20210809163552-d4276e35d3db
github.com/ovirt/go-ovirt v0.0.0-20220427092237-114c47f2835c
github.com/ovirt/go-ovirt-client-log/v2 v2.2.0
github.com/stretchr/testify v1.7.0 // indirect
)
8 changes: 2 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/ovirt/go-ovirt v0.0.0-20210809163552-d4276e35d3db h1:ahvAlEurj4TF1SExDJHNeqknQC8lAwnZEPLyZJuRyd0=
github.com/ovirt/go-ovirt v0.0.0-20210809163552-d4276e35d3db/go.mod h1:Zkdj9/rW6eyuw0uOeEns6O3pP5G2ak+bI/tgkQ/tEZI=
github.com/ovirt/go-ovirt v0.0.0-20220427092237-114c47f2835c h1:jXRFpl7+W0YZj/fghoYuE4vJWW/KeQGvdrhnRwRGtAY=
github.com/ovirt/go-ovirt v0.0.0-20220427092237-114c47f2835c/go.mod h1:Zkdj9/rW6eyuw0uOeEns6O3pP5G2ak+bI/tgkQ/tEZI=
github.com/ovirt/go-ovirt-client-log/v2 v2.2.0 h1:7iZQs+8moX7aopeAdNU1b12mF/yWWBVA/Pey55F9PTQ=
github.com/ovirt/go-ovirt-client-log/v2 v2.2.0/go.mod h1:mDoU3KIwftpsgZGzXGk5d2UEJYTY0bYMfg/GwPapXL0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/zchee/go-qcow2 v0.0.0-20170102190316-9a991fd172f0 h1:dyzM+LWIb64QGUsDUZ2y6e2NkkqejOv8PEv05ajx1co=
github.com/zchee/go-qcow2 v0.0.0-20170102190316-9a991fd172f0/go.mod h1:XO8ymgJzGFNntEd5L8yc0l1wyz3T5DbQD4lguyldfMA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
112 changes: 99 additions & 13 deletions new.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ovirtclient
import (
"math/rand"
"net/http"
"net/url"
"strings"
"time"

Expand All @@ -18,6 +19,59 @@ type ExtraSettings interface {
ExtraHeaders() map[string]string
// Compression enables GZIP or DEFLATE compression on HTTP queries
Compression() bool
// Proxy returns the proxy server to use for connecting the oVirt engine. If none is set, the system settings are
// used. If an empty string is passed, no proxy is used.
Proxy() *string
}

// ExtraSettingsBuilder is a buildable version of ExtraSettings.
type ExtraSettingsBuilder interface {
ExtraSettings

// WithExtraHeaders adds extra headers to send along with each request.
WithExtraHeaders(map[string]string) ExtraSettingsBuilder
// WithCompression enables compression on HTTP requests.
WithCompression() ExtraSettingsBuilder
// WithProxy explicitly sets a proxy server to use for requests.
WithProxy(string) ExtraSettingsBuilder
}

// NewExtraSettings creates a builder for ExtraSettings.
func NewExtraSettings() ExtraSettingsBuilder {
return &extraSettings{}
}

type extraSettings struct {
headers map[string]string
compression bool
proxy *string
}

func (e *extraSettings) ExtraHeaders() map[string]string {
return e.headers
}

func (e *extraSettings) Compression() bool {
return e.compression
}

func (e *extraSettings) Proxy() *string {
return e.proxy
}

func (e *extraSettings) WithExtraHeaders(m map[string]string) ExtraSettingsBuilder {
e.headers = m
return e
}

func (e *extraSettings) WithCompression() ExtraSettingsBuilder {
e.compression = true
return e
}

func (e *extraSettings) WithProxy(addr string) ExtraSettingsBuilder {
e.proxy = &addr
return e
}

// New creates a new copy of the enhanced oVirt client. It accepts the following options:
Expand Down Expand Up @@ -97,37 +151,33 @@ func New(
// NewWithVerify is equivalent to New, but allows customizing the verification function for the connection.
// Alternatively, a nil can be passed to disable connection verification.
func NewWithVerify(
url string,
u string,
username string,
password string,
tls TLSProvider,
logger Logger,
extraSettings ExtraSettings,
verify func(connection Client) error,
) (ClientWithLegacySupport, error) {
if err := validateURL(url); err != nil {
return nil, wrap(err, EBadArgument, "invalid URL: %s", url)
if err := validateURL(u); err != nil {
return nil, wrap(err, EBadArgument, "invalid URL: %s", u)
}
if err := validateUsername(username); err != nil {
return nil, wrap(err, "invalid username: %s", username)
return nil, wrap(err, EBadArgument, "invalid username: %s", username)
}
tlsConfig, err := tls.CreateTLSConfig()
if err != nil {
return nil, wrap(err, ETLSError, "failed to create TLS configuration")
}

connBuilder := ovirtsdk4.NewConnectionBuilder().
URL(url).
URL(u).
Username(username).
Password(password).
TLSConfig(tlsConfig)
if extraSettings != nil {
if len(extraSettings.ExtraHeaders()) > 0 {
connBuilder.Headers(extraSettings.ExtraHeaders())
}
if extraSettings.Compression() {
connBuilder.Compress(true)
}
proxyFunc, err := processExtraSettings(extraSettings, connBuilder)
if err != nil {
return nil, err
}

conn, err := connBuilder.Build()
Expand All @@ -138,14 +188,15 @@ func NewWithVerify(
httpClient := http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
Proxy: proxyFunc,
},
}

client := &oVirtClient{
conn: conn,
httpClient: httpClient,
logger: logger,
url: url,
url: u,
nonSecureRandom: rand.New(rand.NewSource(time.Now().UnixNano())), //nolint:gosec
}

Expand All @@ -158,6 +209,41 @@ func NewWithVerify(
return client, nil
}

func processExtraSettings(
extraSettings ExtraSettings,
connBuilder *ovirtsdk4.ConnectionBuilder,
) (func(req *http.Request) (*url.URL, error), error) {
proxyFunc := http.ProxyFromEnvironment
if extraSettings == nil {
connBuilder.ProxyFromEnvironment()
return proxyFunc, nil
}
if len(extraSettings.ExtraHeaders()) > 0 {
connBuilder.Headers(extraSettings.ExtraHeaders())
}
if extraSettings.Compression() {
connBuilder.Compress(true)
}
proxy := extraSettings.Proxy()
if proxy == nil {
connBuilder.ProxyFromEnvironment()
return proxyFunc, nil
}
if *proxy != "" {
u, err := url.Parse(*proxy)
if err != nil {
return nil, wrap(err, EBadArgument, "failed to parse proxy URL: %s", *proxy)
}
connBuilder.Proxy(u)
proxyFunc = func(req *http.Request) (*url.URL, error) {
return u, nil
}
} else {
proxyFunc = nil
}
return proxyFunc, nil
}

func testConnection(conn Client) error {
return conn.Test()
}
Expand Down
142 changes: 142 additions & 0 deletions new_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package ovirtclient_test

import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"net"
"net/http"
"strings"
"sync"
"testing"

ovirtclient "github.com/ovirt/go-ovirt-client"
Expand Down Expand Up @@ -153,3 +158,140 @@ func TestBadTLS(t *testing.T) {
t.Fatalf("the returned error was not an EngineError (%v)", err)
}
}

func TestProxy(t *testing.T) {
counter := 0
proxy := startProxyServer(t, &counter)
t.Parallel()
// Real CA is the CA we will use in the server
realCAPrivKey, realCACert, realCABytes, err := createCA()
if err != nil {
t.Fatalf("failed to create real CA (%v)", err)
}

serverPrivKey, serverCert, err := createSignedCert(
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
realCAPrivKey,
realCACert,
)
if err != nil {
t.Fatalf("failed to create server certificate (%v)", err)
}

port := getNextFreePort()

srv, err := newTestServer(port, serverCert, serverPrivKey, &unauthorizedHandler{})
if err != nil {
t.Fatal(err)
}

if err := srv.Start(); err != nil {
t.Fatal(err)
}
t.Cleanup(srv.Stop)

logger := ovirtclientlog.NewTestLogger(t)
_, err = ovirtclient.New(
fmt.Sprintf("https://127.0.0.1:%d", port),
"admin@internal",
"asdf",
ovirtclient.TLS().CACertsFromMemory(realCABytes),
logger,
ovirtclient.NewExtraSettings().WithProxy(fmt.Sprintf("http://%s", proxy)),
)
if err == nil {
t.Fatalf("No error on establishing a connection (%v)", err)
}
if !ovirtclient.HasErrorCode(err, ovirtclient.EAccessDenied) {
t.Fatalf("incorrect error code returned: %v", err)
}
if counter == 0 {
t.Fatalf("Connection did not go through the proxy.")
}
}

func startProxyServer(t *testing.T, counter *int) string {
wg := sync.WaitGroup{}
wg.Add(1)
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("failed to open listen socket for the proxy (%v)", err)
}
var serverError error
srv := http.Server{
Addr: ln.Addr().String(),
// Disable HTTP/2.
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
}
srv.Handler = &proxyHandler{counter: counter}
go func() {
defer wg.Done()
if err := srv.Serve(ln); err != nil {
if !errors.Is(err, http.ErrServerClosed) {
serverError = err
}
}
}()
t.Cleanup(
func() {
if err := srv.Close(); err != nil {
t.Fatalf("failed to close listen socket (%v)", err)
}
wg.Wait()
if serverError != nil {
t.Fatalf("proxy server stopped unexpectedly (%v)", err)
}
})
return ln.Addr().String()
}

type proxyHandler struct {
counter *int
}

func (p *proxyHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
*p.counter++
if request.Method != "CONNECT" {
writer.WriteHeader(400)
return
}
target := request.URL.Host

hijacker, ok := writer.(http.Hijacker)
if !ok {
writer.WriteHeader(500)
return
}

backendConn, err := net.Dial("tcp", target)
if err != nil {
writer.WriteHeader(500)
return
}

writer.WriteHeader(200)
conn, clientBuf, err := hijacker.Hijack()
if err != nil {
return
}
if clientBuf != nil {
buf := clientBuf.AvailableBuffer()
if len(buf) > 0 {
_, _ = backendConn.Write(buf)
}
}

wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
_, _ = io.Copy(backendConn, conn)
_ = backendConn.Close()
}()
go func() {
defer wg.Done()
_, _ = io.Copy(conn, backendConn)
_ = conn.Close()
}()
wg.Wait()
}

0 comments on commit 24cd191

Please sign in to comment.