Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make HUB_CERTIFICATE_AUTHORITY optional to allow agent use OS's root CA #149

Merged
merged 5 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions api/v1alpha1/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ type ClusterNamespace string
// ServiceInUseBy describes the member clusters that have requested to import a Service from the hub cluster.
// This object is not provided directly as a part of fleet networking API, but provided as a contract for
// marshaling/unmarshaling ServiceImport annotations, specifically for
// * the InternalServiceImport controller to annotate on a ServiceImport which member clusters have requested to
// import the Service; and
// * the EndpointSliceExport controller to find out from annotations on a ServiceImport which member clusters
// have requested to import the Service.
// - the InternalServiceImport controller to annotate on a ServiceImport which member clusters have requested to
// import the Service; and
// - the EndpointSliceExport controller to find out from annotations on a ServiceImport which member clusters
// have requested to import the Service.
mingqishao marked this conversation as resolved.
Show resolved Hide resolved
type ServiceInUseBy struct {
MemberClusters map[ClusterNamespace]ClusterID
}
17 changes: 8 additions & 9 deletions pkg/common/hubconfig/hubconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,21 @@ func PrepareHubConfig(tlsClientInsecure bool) (*rest.Config, error) {
},
}
} else {
var caData []byte
hubCA, err := env.Lookup(hubCAEnvKey)
if err != nil {
klog.ErrorS(err, "Hub certificate authority cannot be empty")
return nil, err
}
decodedClusterCaCertificate, err := base64.StdEncoding.DecodeString(hubCA)
if err != nil {
klog.ErrorS(err, "Cannot decode hub cluster certificate authority data")
return nil, err
if err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is that the err != nil not checked?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the env.Lookup() is customized function in this module which wrap the os.Envlookup(). err == nil mean the environment be found.

Actually I do feel this wrap function is a kind of confusing, I use it only because of keeping consistent with other codes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if err == nil {
// use the agent OS's root CA when the hubCA env is not set
if err == nil { // check if hubCA env is not set

Please add the comment here to improve the readability as in normal case we check the err != nil and need some signal here to indicate why we check err == nil here.

caData, err = base64.StdEncoding.DecodeString(hubCA)
if err != nil {
klog.ErrorS(err, "Cannot decode hub cluster certificate authority data")
return nil, err
}
}
hubConfig = &rest.Config{
BearerTokenFile: tokenFilePath,
Host: hubURL,
TLSClientConfig: rest.TLSClientConfig{
Insecure: tlsClientInsecure,
CAData: decodedClusterCaCertificate,
CAData: caData,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you please clarify that, if the hubCAEnvKey is not set, the caData will be empty, it will use the OS's root CA?

Have you manually tested the agent using OS's root CA to talk to hub api server?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you please clarify that, if the hubCAEnvKey is not set, the caData will be empty, it will use the OS's root CA?
Yes. that is the purpose of this PR.

Have you manually tested the agent using OS's root CA to talk to hub api server?
Yes, I did. I tested it manually.

},
}
}
Expand Down
131 changes: 64 additions & 67 deletions pkg/common/hubconfig/hubconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,111 +18,108 @@ import (
func TestPrepareHubConfig(t *testing.T) {
var (
fakeHubhubServerURLEnvVal = "fake-hub-server-url"
fakeConfigtokenConfigPathEnvVal = "fake-config-path" //nolint:gosec
fakeCerhubCAEnvVal = base64.StdEncoding.EncodeToString([]byte("fake-certificate-authority"))
fakeConfigtokenConfigPathEnvVal = "testdata/fake-config-path" //nolint:gosec
fakeCerhubCA = []byte("fake-certificate-authority")
fakeCerhubCAEnvVal = base64.StdEncoding.EncodeToString(fakeCerhubCA)
)

cleanupFunc := func() {
os.Unsetenv(hubServerURLEnvKey)
os.Unsetenv(tokenConfigPathEnvKey)
os.Unsetenv(hubCAEnvKey)
os.Remove(fakeConfigtokenConfigPathEnvVal)
}

defer cleanupFunc()

testCases := []struct {
name string
environmentVariables map[string]string
tlsClientInsecure bool
wantErr bool
validate func(t *testing.T, config *rest.Config, err error)
}{
{
name: "environment variable `HUB_SERVER_URL` is not present",
name: "environment variable `HUB_SERVER_URL` is not present - error",
environmentVariables: map[string]string{tokenConfigPathEnvKey: fakeConfigtokenConfigPathEnvVal, hubCAEnvKey: fakeCerhubCAEnvVal},
tlsClientInsecure: false,
wantErr: true,
validate: func(t *testing.T, config *rest.Config, err error) {
if err == nil {
t.Errorf("expect return error if HUB_SERVER_URL not present")
}
},
},
{
name: "environment variable `CONFIG_PATH` is not present",
name: "environment variable `CONFIG_PATH` is not present - error",
environmentVariables: map[string]string{hubServerURLEnvKey: fakeHubhubServerURLEnvVal, hubCAEnvKey: fakeCerhubCAEnvVal},
tlsClientInsecure: false,
wantErr: true,
validate: func(t *testing.T, config *rest.Config, err error) {
if err == nil {
t.Errorf("expect return error if CONFIG_PATH not present")
}
},
},
{
name: "environment variable `HUB_CERTIFICATE_AUTHORITY` is not present when tlsClientInsecure is false",
name: "environment variable `HUB_CERTIFICATE_AUTHORITY` is not present when tlsClientInsecure is false - use OS's CA bundle",
environmentVariables: map[string]string{hubServerURLEnvKey: fakeHubhubServerURLEnvVal, tokenConfigPathEnvKey: fakeConfigtokenConfigPathEnvVal},
tlsClientInsecure: false,
wantErr: 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)
}
wantConfig := &rest.Config{
BearerTokenFile: fakeConfigtokenConfigPathEnvVal,
Host: fakeHubhubServerURLEnvVal,
TLSClientConfig: rest.TLSClientConfig{
Insecure: false,
},
}
if !cmp.Equal(config, wantConfig) {
t.Errorf("got hub config %+v, want %+v", config, wantConfig)
}
},
},
{
name: "environment variable `HUB_CERTIFICATE_AUTHORITY` is not present when tlsClientInsecure is true",
name: "environment variable `HUB_CERTIFICATE_AUTHORITY` is not present when tlsClientInsecure is true - use insecure TLS",
environmentVariables: map[string]string{hubServerURLEnvKey: fakeHubhubServerURLEnvVal, tokenConfigPathEnvKey: fakeConfigtokenConfigPathEnvVal},
tlsClientInsecure: true,
wantErr: false,
},
{
name: "hub configuration preparation is done when all requirements meet",
environmentVariables: map[string]string{hubServerURLEnvKey: fakeHubhubServerURLEnvVal, tokenConfigPathEnvKey: fakeConfigtokenConfigPathEnvVal, hubCAEnvKey: fakeCerhubCAEnvVal},
tlsClientInsecure: false,
wantErr: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// remove all environment variables related to this test
cleanupFunc()

for envKey, envVal := range tc.environmentVariables {
os.Setenv(envKey, envVal)
if envKey == tokenConfigPathEnvKey {
if _, err := os.Create(fakeConfigtokenConfigPathEnvVal); err != nil {
t.Errorf("failed to create file %s, err: %s", fakeConfigtokenConfigPathEnvVal, err.Error())
}
validate: func(t *testing.T, config *rest.Config, err error) {
if err != nil {
t.Errorf("not expect error but actually get error %s", err)
}
}

hubConfig, err := PrepareHubConfig(tc.tlsClientInsecure)
if (err != nil) != tc.wantErr {
t.Fatalf("PrepareHubConfig() error = %v, wantErr %t", err, tc.wantErr)
}

if tc.wantErr {
return
}

if tc.tlsClientInsecure {
expectedHubConfig := &rest.Config{
wantConfig := &rest.Config{
BearerTokenFile: fakeConfigtokenConfigPathEnvVal,
Host: fakeHubhubServerURLEnvVal,
TLSClientConfig: rest.TLSClientConfig{
Insecure: tc.tlsClientInsecure,
Insecure: true,
},
}
if !cmp.Equal(*hubConfig, *expectedHubConfig) {
t.Errorf("PrepareHubConfig() got hub config: %v, want: %v", expectedHubConfig, hubConfig)
if !cmp.Equal(config, wantConfig) {
t.Errorf("got hub config %+v, want %+v", config, wantConfig)
}
}

if !tc.tlsClientInsecure {
decodedClusterCaCertificate, err := base64.StdEncoding.DecodeString(fakeCerhubCAEnvVal)
},
},
{
name: "hub configuration preparation is done when all requirements meet - use CAData",
environmentVariables: map[string]string{hubServerURLEnvKey: fakeHubhubServerURLEnvVal, tokenConfigPathEnvKey: fakeConfigtokenConfigPathEnvVal, hubCAEnvKey: fakeCerhubCAEnvVal},
tlsClientInsecure: false,
validate: func(t *testing.T, config *rest.Config, err error) {
if err != nil {
t.Fatalf("failed to base-encode hub CA, error: %s", err.Error())
t.Errorf("not expect error but actually get error %s", err)
}
expectedHubConfig := &rest.Config{
wantConfig := &rest.Config{
BearerTokenFile: fakeConfigtokenConfigPathEnvVal,
Host: fakeHubhubServerURLEnvVal,
TLSClientConfig: rest.TLSClientConfig{
Insecure: tc.tlsClientInsecure,
CAData: decodedClusterCaCertificate,
Insecure: false,
CAData: fakeCerhubCA,
},
}

if !cmp.Equal(hubConfig, expectedHubConfig) {
t.Errorf("PrepareHubConfig() got hub config: %v, want: %v", expectedHubConfig, hubConfig)
if !cmp.Equal(config, wantConfig) {
t.Errorf("got hub config %+v, want %+v", config, wantConfig)
}
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for envKey, envVal := range tc.environmentVariables {
t.Setenv(envKey, envVal)
}

hubConfig, err := PrepareHubConfig(tc.tlsClientInsecure)
tc.validate(t, hubConfig, err)
})
}
}
Expand Down
1 change: 1 addition & 0 deletions pkg/common/hubconfig/testdata/fake-config-path
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
this is token
14 changes: 7 additions & 7 deletions pkg/common/uniquename/uniquename.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ func removeDots(s string) string {
// is [NAMESPACE]-[NAME]-[SUFFIX], e.g. an object `app` from the namespace `work` will be assigned a unique
// name like `work-app-1x2yz`. The function may truncate name components as it sees fit.
// Note: this function assumes that
// * the input object namespace is a valid RFC 1123 DNS label; and
// * the input object name follows one of the three formats used in Kubernetes (RFC 1123 DNS subdomain,
// RFC 1123 DNS label, RFC 1035 DNS label).
// - the input object namespace is a valid RFC 1123 DNS label; and
// - the input object name follows one of the three formats used in Kubernetes (RFC 1123 DNS subdomain,
// RFC 1123 DNS label, RFC 1035 DNS label).
Comment on lines +56 to +58
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are those in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those changes was made by "make reviewable"

func ClusterScopedUniqueName(format Format, namespace, name string) (string, error) {
reservedSlots := 2 + uuidLength // 2 dashes + 5 character UUID string

Expand Down Expand Up @@ -124,10 +124,10 @@ func ClusterScopedUniqueName(format Format, namespace, name string) (string, err
// `work` in cluster `bravelion` will be assigned a unique name like `bravelion-work-app-1x2yz`. The function may
// truncate name components as it sees fit.
// Note: this function assumes that
// * the input cluster ID is a valid RFC 1123 DNS subdomain; and
// * the input object namespace is a valid RFC 1123 DNS label; and
// * the input object name follows one of the three formats used in Kubernetes (RFC 1123 DNS subdomain,
// RFC 1123 DNS label, RFC 1035 DNS label).
// - the input cluster ID is a valid RFC 1123 DNS subdomain; and
// - the input object namespace is a valid RFC 1123 DNS label; and
// - the input object name follows one of the three formats used in Kubernetes (RFC 1123 DNS subdomain,
// RFC 1123 DNS label, RFC 1035 DNS label).
func FleetScopedUniqueName(format Format, clusterID, namespace, name string) (string, error) {
reservedSlots := 3 + uuidLength // 3 dashes + 5 character UUID string

Expand Down
6 changes: 3 additions & 3 deletions pkg/controllers/member/internalmembercluster/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ func findAgentStatus(status []fleetv1alpha1.AgentStatus, agentType fleetv1alpha1

// setAgentStatus sets the corresponding condition in conditions based on the new agent status.
// status must be non-nil.
// 1. if the condition of the specified type already exists (all fields of the existing condition are updated to
// newCondition, LastTransitionTime is set to now if the new status differs from the old status)
// 2. if a condition of the specified type does not exist (LastTransitionTime is set to now() if unset, and newCondition is appended)
// 1. if the condition of the specified type already exists (all fields of the existing condition are updated to
// newCondition, LastTransitionTime is set to now if the new status differs from the old status)
// 2. if a condition of the specified type does not exist (LastTransitionTime is set to now() if unset, and newCondition is appended)
func setAgentStatus(status *[]fleetv1alpha1.AgentStatus, newStatus fleetv1alpha1.AgentStatus) {
existingStatus := findAgentStatus(*status, newStatus.Type)
if existingStatus == nil {
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/export_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ package e2e
import (
"context"
"fmt"
"io/ioutil"
"io"
"net/http"
"strings"
"time"
Expand Down Expand Up @@ -991,7 +991,7 @@ func fetchHTTPRequestBody(requestURL string) (string, error) {
return "", err
}
defer res.Body.Close()
respBody, err := ioutil.ReadAll(res.Body)
respBody, err := io.ReadAll(res.Body)
if err != nil {
return "", err
}
Expand Down