Skip to content

Commit

Permalink
feat: add HUB_KUBE_HEADER system environment variable (#150)
Browse files Browse the repository at this point in the history
* add HUB_KUBE_HEADER system environment variable

* add unit test to increase coverage
  • Loading branch information
mingqishao committed Aug 2, 2023
1 parent 5c58841 commit ea922fb
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 1 deletion.
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ require (
sigs.k8s.io/controller-runtime v0.12.2
)

require go.goms.io/fleet v0.6.1
require (
github.com/stretchr/testify v1.7.0
go.goms.io/fleet v0.6.1
)

replace (
// https://nvd.nist.gov/vuln/detail/CVE-2022-1996
Expand Down Expand Up @@ -69,6 +72,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
Expand Down
32 changes: 32 additions & 0 deletions pkg/common/httpclient/round_tripper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package httpclient

import (
"fmt"
"net/http"
)

type customHeadersRoundTripper struct {
header http.Header
delegatedRoundTripper http.RoundTripper
}

func NewCustomHeadersRoundTripper(header http.Header, rt http.RoundTripper) http.RoundTripper {
return &customHeadersRoundTripper{
header: header,
delegatedRoundTripper: rt,
}
}

var _ http.RoundTripper = &customHeadersRoundTripper{}

func (c *customHeadersRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
for key, values := range c.header {
if req.Header.Get(key) != "" {
return nil, fmt.Errorf("custom header %q can't override other header", key)
}
for _, v := range values {
req.Header.Add(key, v)
}
}
return c.delegatedRoundTripper.RoundTrip(req)
}
45 changes: 45 additions & 0 deletions pkg/common/httpclient/round_tripper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package httpclient

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

func Test_CustomHeadersRoundTripper(t *testing.T) {
t.Parallel()
t.Run("add new header", func(t *testing.T) {
t.Parallel()
f := &fakeRoundTripper{}
header := make(http.Header)
header.Set("new-header", "new-header-value")
rt := NewCustomHeadersRoundTripper(header, f)
req := httptest.NewRequest(http.MethodGet, "/host", nil)
_, err := rt.RoundTrip(req)
assert.Nil(t, err)
assert.Equal(t, f.req.Header.Get("new-header"), "new-header-value")
})

t.Run("don't override exist header", func(t *testing.T) {
t.Parallel()
f := &fakeRoundTripper{}
header := make(http.Header)
header.Set("exist-header", "new-value")
rt := NewCustomHeadersRoundTripper(header, f)
req := httptest.NewRequest(http.MethodGet, "/host", nil)
req.Header.Set("exist-header", "old-value")
_, err := rt.RoundTrip(req)
assert.NotNil(t, err)
})
}

type fakeRoundTripper struct {
req *http.Request
}

func (f *fakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
f.req = req
return nil, nil
}
21 changes: 21 additions & 0 deletions pkg/common/hubconfig/hubconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,30 @@ Licensed under the MIT license.
package hubconfig

import (
"bufio"
"encoding/base64"
"errors"
"fmt"
"io"
"net/http"
"net/textproto"
"os"
"strings"

"k8s.io/client-go/rest"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"

"go.goms.io/fleet-networking/pkg/common/env"
"go.goms.io/fleet-networking/pkg/common/httpclient"
)

const (
// Environment variable keys for hub config
hubServerURLEnvKey = "HUB_SERVER_URL"
tokenConfigPathEnvKey = "CONFIG_PATH" //nolint:gosec
hubCAEnvKey = "HUB_CERTIFICATE_AUTHORITY"
hubKubeHeaderEnvKey = "HUB_KUBE_HEADER"

// Naming pattern of member cluster namespace in hub cluster, should be the same as envValue as defined in
// https://github.com/Azure/fleet/blob/main/pkg/utils/common.go
Expand Down Expand Up @@ -84,6 +92,19 @@ func PrepareHubConfig(tlsClientInsecure bool) (*rest.Config, error) {
}
}

// Sometime the hub cluster need additional http header for authentication or authorization.
// the "HUB_KUBE_HEADER" to allow sending custom header to hub's API Server for authentication and authorization.
if header, err := env.Lookup(hubKubeHeaderEnvKey); err == nil {
r := textproto.NewReader(bufio.NewReader(strings.NewReader(header)))
h, err := r.ReadMIMEHeader()
if err != nil && !errors.Is(err, io.EOF) {
klog.ErrorS(err, "failed to parse HUB_KUBE_HEADER %q", header)
return nil, err
}
hubConfig.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
return httpclient.NewCustomHeadersRoundTripper(http.Header(h), rt)
}
}
return hubConfig, nil
}

Expand Down
24 changes: 24 additions & 0 deletions pkg/common/hubconfig/hubconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,30 @@ func TestPrepareHubConfig(t *testing.T) {
}
},
},
{
name: "environment variable `HUB_KUBE_HEADER` exists - config have WrapTransport",
environmentVariables: map[string]string{hubServerURLEnvKey: fakeHubhubServerURLEnvVal, hubKubeHeaderEnvKey: "custom-header: value", tokenConfigPathEnvKey: fakeConfigtokenConfigPathEnvVal},
tlsClientInsecure: true,
validate: func(t *testing.T, config *rest.Config, err error) {
if err != nil {
t.Errorf("not expect error but actually get error %s", err)
}

if config.WrapTransport == nil {
t.Error("config.WrapTransport should not be nil if having HUB_KUBE_HEADER system variable")
}
},
},
{
name: "environment variable `HUB_KUBE_HEADER` has wrong format - error",
environmentVariables: map[string]string{hubServerURLEnvKey: fakeHubhubServerURLEnvVal, hubKubeHeaderEnvKey: "wrong header format", tokenConfigPathEnvKey: fakeConfigtokenConfigPathEnvVal},
tlsClientInsecure: true,
validate: func(t *testing.T, config *rest.Config, err error) {
if err == nil {
t.Errorf("expect error about wrong header format but not")
}
},
},
}

for _, tc := range testCases {
Expand Down

0 comments on commit ea922fb

Please sign in to comment.