From b82c75bfb94e2e3dbd6d34dd7d37188b60fa2e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20N=C3=A9au?= Date: Wed, 26 Jun 2024 11:24:35 +0200 Subject: [PATCH 1/3] Add strict TLS mode support (#2507) * Add agentTLSMode option Fleet now supports two distinct TLS mode for its agent when registering against an upstream cluster: * `system-store`, the default, does not change its current behaviour: the Fleet agent trusts any certificate signed by a CA found in its system store. In this mode, Fleet will also ignore a configured CA, if the system trust store is sufficient. * `strict`, to bypass the system store when validating a certificate. * Redeploy Fleet agent when TLS mode setting changes This commit takes care of watching the agent TLS mode setting in the `fleet-controller` config map, and of redeploying the Fleet agent to upstream and downstream clusters when that setting changes. Note that this only works for downstream clusters registered through a manager-initiated process [1]. Testing this is done by reusing existing agent TLS mode test cases, and triggering new deployments of the Fleet agent by patching the `fleet-controller` config map. Requirements for this include a cluster registered in manager-initiated mode, while existing multi-cluster end-to-end tests need a downstream cluster registered in agent-initiated mode. Therefore, this commit also adds a new downstream cluster to the multi-cluster CI workflow, which is so far only used for agent TLS mode tests. [1]: https://fleet.rancher.io/cluster-registration#manager-initiated --- .github/scripts/deploy-fleet.sh | 8 +- .github/workflows/e2e-ci.yml | 4 +- .github/workflows/e2e-multicluster-ci.yml | 87 ++++++++++++++- charts/fleet-agent/templates/configmap.yaml | 3 +- charts/fleet-agent/values.yaml | 4 + charts/fleet-crd/templates/crds.yaml | 3 + charts/fleet/templates/configmap.yaml | 1 + charts/fleet/values.yaml | 4 + e2e/multi-cluster/installation/agent_test.go | 100 ++++++++++++++++++ e2e/multi-cluster/installation/suite_test.go | 61 +++++++++++ e2e/testenv/kubectl/kubectl.go | 4 + internal/cmd/agent/register/register.go | 35 ++++-- internal/cmd/controller/agent/config.go | 14 +-- .../controller/controllers/cluster/import.go | 38 +++++-- internal/config/config.go | 7 ++ pkg/apis/fleet.cattle.io/v1alpha1/target.go | 7 ++ 16 files changed, 348 insertions(+), 32 deletions(-) create mode 100644 e2e/multi-cluster/installation/agent_test.go create mode 100644 e2e/multi-cluster/installation/suite_test.go diff --git a/.github/scripts/deploy-fleet.sh b/.github/scripts/deploy-fleet.sh index 3feecac483..6e7bfd8a84 100755 --- a/.github/scripts/deploy-fleet.sh +++ b/.github/scripts/deploy-fleet.sh @@ -15,6 +15,10 @@ else agentTag="dev" fi +host=$(kubectl get node k3d-upstream-server-0 -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}') +ca=$( kubectl config view --flatten -o jsonpath='{.clusters[?(@.name == "k3d-upstream")].cluster.certificate-authority-data}' | base64 -d ) +server="https://$host:6443" + helm -n cattle-fleet-system upgrade --install --create-namespace --wait fleet-crd charts/fleet-crd helm upgrade --install fleet charts/fleet \ -n cattle-fleet-system --create-namespace --wait \ @@ -22,7 +26,9 @@ helm upgrade --install fleet charts/fleet \ --set image.tag="$fleetTag" \ --set agentImage.repository="$agentRepo" \ --set agentImage.tag="$agentTag" \ - --set agentImage.imagePullPolicy=IfNotPresent + --set agentImage.imagePullPolicy=IfNotPresent \ + --set apiServerCA="$ca" \ + --set apiServerURL="$server" \ # wait for controller and agent rollout kubectl -n cattle-fleet-system rollout status deploy/fleet-controller diff --git a/.github/workflows/e2e-ci.yml b/.github/workflows/e2e-ci.yml index 845b3f52e8..b315c03451 100644 --- a/.github/workflows/e2e-ci.yml +++ b/.github/workflows/e2e-ci.yml @@ -64,7 +64,7 @@ jobs: # k3d will automatically create a network named k3d-test-cluster-1 with the range 172.18.0.0/16 with: k3d-version: ${{ env.SETUP_K3D_VERSION }} - cluster-name: "k3s-default" + cluster-name: "upstream" args: >- --agents 1 --network "nw01" @@ -72,7 +72,7 @@ jobs: - name: Import Images Into k3d run: | - ./.github/scripts/k3d-import-retry.sh rancher/fleet:dev rancher/fleet-agent:dev + ./.github/scripts/k3d-import-retry.sh rancher/fleet:dev rancher/fleet-agent:dev -c upstream #k3d image import nginx-git:test nginx-git:test - name: Set Up Tmate Debug Session diff --git a/.github/workflows/e2e-multicluster-ci.yml b/.github/workflows/e2e-multicluster-ci.yml index 85bad597a0..0d91dba916 100644 --- a/.github/workflows/e2e-multicluster-ci.yml +++ b/.github/workflows/e2e-multicluster-ci.yml @@ -62,7 +62,7 @@ jobs: --agents 1 --network "nw01" - - name: Provision k3d Downstream Cluster + name: Provision k3d Downstream Cluster for agent-initiated registration uses: AbsaOSS/k3d-action@v2 with: k3d-version: ${{ env.SETUP_K3D_VERSION }} @@ -73,11 +73,24 @@ jobs: --api-port 6644 --agents 1 --network "nw01" + - + name: Provision k3d Downstream Cluster for manager-initiated registration + uses: AbsaOSS/k3d-action@v2 + with: + k3d-version: ${{ env.SETUP_K3D_VERSION }} + cluster-name: "managed-downstream" + args: >- + -p "82:80@agent:0:direct" + -p "445:443@agent:0:direct" + --api-port 6645 + --agents 1 + --network "nw01" - name: Import Images Into k3d run: | - k3d image import rancher/fleet:dev rancher/fleet-agent:dev -c upstream - k3d image import rancher/fleet-agent:dev -c downstream + ./.github/scripts/k3d-import-retry.sh rancher/fleet:dev rancher/fleet-agent:dev -c upstream + ./.github/scripts/k3d-import-retry.sh rancher/fleet-agent:dev -c downstream + ./.github/scripts/k3d-import-retry.sh rancher/fleet-agent:dev -c managed-downstream - name: Set Up Tmate Debug Session if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.enable_tmate == 'true' }} @@ -107,14 +120,71 @@ jobs: token=$(kubectl get secret -n fleet-default second-token -o go-template='{{index .data "values" | base64decode}}' | yq eval .token -) ca=$(kubectl get secret -n cattle-fleet-system fleet-controller-bootstrap-token -o go-template='{{index .data "ca.crt" | base64decode}}') + apiServerIP=$(kubectl get node k3d-upstream-server-0 -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}') + # agent initiated cluster registration kubectl config use-context k3d-downstream helm -n cattle-fleet-system upgrade --install --create-namespace --wait fleet-agent charts/fleet-agent \ --set-string labels.env=test \ --set apiServerCA="$ca" \ - --set apiServerURL="https://172.18.0.1.sslip.io:6443" \ + --set apiServerURL="https://$apiServerIP:6443" \ --set clusterNamespace="fleet-default" \ --set token="$token" + + - + name: Deploy and Register Managed Downstream Fleet + run: | + kubectl config use-context k3d-managed-downstream + host=$(kubectl get node k3d-managed-downstream-server-0 -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}') + ca=$( kubectl config view --flatten -o jsonpath='{.clusters[?(@.name == "k3d-managed-downstream")].cluster.certificate-authority-data}' ) + client_cert=$( kubectl config view --flatten -o jsonpath='{.users[?(@.name == "admin@k3d-managed-downstream")].user.client-certificate-data}' ) + token=$( kubectl config view --flatten -o jsonpath='{.users[?(@.name == "admin@k3d-managed-downstream")].user.client-key-data}' ) + server="https://$host:6443" + + kubectl config use-context k3d-upstream + + value=$(cat < diff --git a/charts/fleet-agent/templates/configmap.yaml b/charts/fleet-agent/templates/configmap.yaml index ce61a87568..f3e83a89cc 100644 --- a/charts/fleet-agent/templates/configmap.yaml +++ b/charts/fleet-agent/templates/configmap.yaml @@ -8,5 +8,6 @@ data: {{ if .Values.labels }} "labels":{{toJson .Values.labels}}, {{ end }} - "clientID":"{{.Values.clientID}}" + "clientID":"{{.Values.clientID}}", + "agentTLSMode": "{{.Values.agentTLSMode}}" } diff --git a/charts/fleet-agent/values.yaml b/charts/fleet-agent/values.yaml index c653e2b000..b3e6035fc6 100644 --- a/charts/fleet-agent/values.yaml +++ b/charts/fleet-agent/values.yaml @@ -11,6 +11,10 @@ apiServerURL: "" # If left empty it is assumed this Kubernetes API TLS is signed by a well known CA. apiServerCA: "" +# Determines whether the agent should trust CA bundles from the operating system's trust store when connecting to a +# management cluster. True in `system-store` mode, false in `strict` mode. +agentTLSMode: "system-store" + # The cluster registration value token: "" diff --git a/charts/fleet-crd/templates/crds.yaml b/charts/fleet-crd/templates/crds.yaml index 9bda897477..5a8046b19b 100644 --- a/charts/fleet-crd/templates/crds.yaml +++ b/charts/fleet-crd/templates/crds.yaml @@ -2545,6 +2545,9 @@ spec: agentResourcesHash: nullable: true type: string + agentTLSMode: + nullable: true + type: string agentTolerationsHash: nullable: true type: string diff --git a/charts/fleet/templates/configmap.yaml b/charts/fleet/templates/configmap.yaml index 07f1b5924d..3fd0b15cf8 100644 --- a/charts/fleet/templates/configmap.yaml +++ b/charts/fleet/templates/configmap.yaml @@ -11,6 +11,7 @@ data: "apiServerURL": "{{.Values.apiServerURL}}", "apiServerCA": "{{b64enc .Values.apiServerCA}}", "agentCheckinInterval": "{{.Values.agentCheckinInterval}}", + "agentTLSMode": "{{.Values.agentTLSMode}}", "ignoreClusterRegistrationLabels": {{.Values.ignoreClusterRegistrationLabels}}, "bootstrap": { "paths": "{{.Values.bootstrap.paths}}", diff --git a/charts/fleet/values.yaml b/charts/fleet/values.yaml index 8c3a9c596c..7ee9f14c51 100644 --- a/charts/fleet/values.yaml +++ b/charts/fleet/values.yaml @@ -16,6 +16,10 @@ apiServerURL: "" # If left empty it is assumed this Kubernetes API TLS is signed by a well known CA. apiServerCA: "" +# Determines whether the agent should trust CA bundles from the operating system's trust store when connecting to a +# management cluster. True in `system-store` mode, false in `strict` mode. +agentTLSMode: "system-store" + # A duration string for how often agents should report a heartbeat agentCheckinInterval: "15m" diff --git a/e2e/multi-cluster/installation/agent_test.go b/e2e/multi-cluster/installation/agent_test.go new file mode 100644 index 0000000000..c030d8cba5 --- /dev/null +++ b/e2e/multi-cluster/installation/agent_test.go @@ -0,0 +1,100 @@ +package installation_test + +import ( + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/matchers" + "github.com/rancher/fleet/e2e/testenv/kubectl" +) + +var ( + agentMode string + kd kubectl.Command +) + +var _ = Describe("Fleet installation with TLS agent modes", func() { + BeforeEach(func() { + kd = env.Kubectl.Context(env.Downstream) + }) + + JustBeforeEach(func() { + out, err := ku.Patch( + "configmap", + "fleet-controller", + "-n", + "cattle-fleet-system", + "--type=merge", + "-p", + fmt.Sprintf( + `{"data":{"config":"{\"apiServerURL\": \"https://google.com\", \"apiServerCA\": \"\", \"agentTLSMode\": \"%s\"}"}}`, + agentMode, + ), + ) + Expect(err).ToNot(HaveOccurred(), string(out)) + + }) + + Context("with non-strict agent TLS mode", func() { + When("fetching fleet-agent-register logs", func() { + BeforeEach(func() { + agentMode = "system-store" + }) + + It("reaches the server without cert issues", func() { + Eventually(func() bool { + logs, err := kd.Namespace("cattle-fleet-system").Logs( + "-l", + "app=fleet-agent", + "--tail=-1", + ) + if err != nil { + return false + } + + regexMatcher := matchers.MatchRegexpMatcher{ + Regexp: "Failed to register agent.*could not find the requested resource", + } + reachesServerWithoutCertIssue, err := regexMatcher.Match(logs) + if err != nil { + return false + } + + return reachesServerWithoutCertIssue + }).Should(BeTrue()) + }) + }) + }) + + Context("with strict agent TLS mode", func() { + When("fetching fleet-agent-register logs", func() { + BeforeEach(func() { + agentMode = "strict" + }) + + It("cannot reach the server because the cert is signed by an unknown authority", func() { + Eventually(func() bool { + logs, err := kd.Namespace("cattle-fleet-system").Logs( + "-l", + "app=fleet-agent", + "--tail=-1", + ) + if err != nil { + return false + } + + regexMatcher := matchers.MatchRegexpMatcher{ + Regexp: "Failed to register agent.*signed by unknown authority", + } + reachesServerWithoutCertIssue, err := regexMatcher.Match(logs) + if err != nil { + return false + } + + return reachesServerWithoutCertIssue + }).Should(BeTrue()) + }) + }) + }) +}) diff --git a/e2e/multi-cluster/installation/suite_test.go b/e2e/multi-cluster/installation/suite_test.go new file mode 100644 index 0000000000..003a08fec5 --- /dev/null +++ b/e2e/multi-cluster/installation/suite_test.go @@ -0,0 +1,61 @@ +// Package installation contains e2e tests deploying Fleet to multiple clusters. The tests use kubectl to apply +// manifests. Expectations are verified by checking cluster resources. +package installation_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/rancher/fleet/e2e/testenv" + "github.com/rancher/fleet/e2e/testenv/kubectl" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestE2E(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "E2E Installation Suite for Multi-Cluster") +} + +var ( + env *testenv.Env + ku kubectl.Command + config string +) + +var _ = BeforeSuite(func() { + SetDefaultEventuallyTimeout(testenv.Timeout) + testenv.SetRoot("../..") + + env = testenv.New() + ku = env.Kubectl.Context(env.Upstream) + + // Save initial state of `fleet-controller` config map + cfg, err := ku.Get( + "configmap", + "fleet-controller", + "-n", + "cattle-fleet-system", + "-o", + "jsonpath={.data.config}") + Expect(err).ToNot(HaveOccurred(), cfg) + + cfg = strings.ReplaceAll(cfg, `"`, `\"`) + config = strings.ReplaceAll(cfg, "\n", "") +}) + +var _ = AfterSuite(func() { + // Restore initial state of config map + out, err := ku.Patch( + "configmap", + "fleet-controller", + "-n", + "cattle-fleet-system", + "--type=merge", + "-p", + fmt.Sprintf(`{"data":{"config":"%s"}}`, config), + ) + Expect(err).ToNot(HaveOccurred(), string(out)) +}) diff --git a/e2e/testenv/kubectl/kubectl.go b/e2e/testenv/kubectl/kubectl.go index 0c533fbd23..240af7c0f4 100644 --- a/e2e/testenv/kubectl/kubectl.go +++ b/e2e/testenv/kubectl/kubectl.go @@ -59,6 +59,10 @@ func (c Command) Create(args ...string) (string, error) { return c.Run(append([]string{"create"}, args...)...) } +func (c Command) Logs(args ...string) (string, error) { + return c.Run(append([]string{"logs"}, args...)...) +} + func (c Command) Patch(args ...string) (string, error) { return c.Run(append([]string{"patch"}, args...)...) } diff --git a/internal/cmd/agent/register/register.go b/internal/cmd/agent/register/register.go index 80b7adc58c..0e01ad4992 100644 --- a/internal/cmd/agent/register/register.go +++ b/internal/cmd/agent/register/register.go @@ -127,7 +127,12 @@ func runRegistration(ctx context.Context, k8s corecontrollers.Interface, namespa // update the "fleet-agent" secret with a new kubeconfig from the registration // secret. The new kubeconfig can then be used to query bundledeployments. func createAgentSecret(ctx context.Context, clusterID string, k8s corecontrollers.Interface, secret *corev1.Secret) (*corev1.Secret, error) { - clientConfig := createClientConfigFromSecret(secret) + cfg, err := config.Lookup(ctx, secret.Namespace, config.AgentConfigName, k8s.ConfigMap()) + if err != nil { + return nil, fmt.Errorf("failed to look up client config %s/%s: %w", secret.Namespace, config.AgentConfigName, err) + } + + clientConfig := createClientConfigFromSecret(secret, cfg.AgentTLSMode == config.AgentTLSModeSystemStore) ns, _, err := clientConfig.Namespace() if err != nil { @@ -139,11 +144,6 @@ func createAgentSecret(ctx context.Context, clusterID string, k8s corecontroller return nil, err } - cfg, err := config.Lookup(ctx, secret.Namespace, config.AgentConfigName, k8s.ConfigMap()) - if err != nil { - return nil, fmt.Errorf("failed to look up client config %s/%s: %w", secret.Namespace, config.AgentConfigName, err) - } - fleetK8s, err := kubernetes.NewForConfig(kc) if err != nil { return nil, err @@ -273,15 +273,32 @@ func values(data map[string][]byte) map[string][]byte { return data } -func createClientConfigFromSecret(secret *corev1.Secret) clientcmd.ClientConfig { +// createClientConfigFromSecret reads the fleet-agent-bootstrap secret and +// creates a clientConfig to access the upstream cluster +func createClientConfigFromSecret(secret *corev1.Secret, trustSystemStoreCAs bool) clientcmd.ClientConfig { data := values(secret.Data) apiServerURL := string(data[APIServerURL]) apiServerCA := data[APIServerCA] namespace := string(data[ClusterNamespace]) token := string(data[Token]) - if _, err := http.Get(apiServerURL); err == nil { - apiServerCA = nil + if trustSystemStoreCAs { // Save a request to the API server URL if system CAs are not to be trusted. + if _, err := http.Get(apiServerURL); err == nil { + apiServerCA = nil + } + } else { + // Bypass the OS trust store through env vars, see https://pkg.go.dev/crypto/x509#SystemCertPool + // We set values to paths belonging to the root filesystem, which is read-only, to prevent tampering. + // Note: this will not work on Windows nor Mac OS. Agent are expected to run on Linux nodes. + err := os.Setenv("SSL_CERT_FILE", "/dev/null") + if err != nil { + logrus.Errorf("failed to set env var SSL_CERT_FILE: %s", err.Error()) + } + + err = os.Setenv("SSL_CERT_DIR", "/dev/null") + if err != nil { + logrus.Errorf("failed to set env var SSL_CERT_DIR: %s", err.Error()) + } } cfg := clientcmdapi.Config{ diff --git a/internal/cmd/controller/agent/config.go b/internal/cmd/controller/agent/config.go index b8ad7b26cc..c88983a8f5 100644 --- a/internal/cmd/controller/agent/config.go +++ b/internal/cmd/controller/agent/config.go @@ -12,8 +12,9 @@ import ( ) type ConfigOptions struct { - Labels map[string]string - ClientID string + Labels map[string]string + ClientID string + AgentTLSMode string } func agentConfig(ctx context.Context, agentNamespace, controllerNamespace string, cg *client.Getter, opts *ConfigOptions) ([]runtime.Object, error) { @@ -32,13 +33,14 @@ func agentConfig(ctx context.Context, agentNamespace, controllerNamespace string return nil, err } - return configObjects(agentNamespace, opts.Labels, opts.ClientID) + return configObjects(agentNamespace, opts) } -func configObjects(controllerNamespace string, clusterLabels map[string]string, clientID string) ([]runtime.Object, error) { +func configObjects(controllerNamespace string, co *ConfigOptions) ([]runtime.Object, error) { cm, err := config.ToConfigMap(controllerNamespace, config.AgentConfigName, &config.Config{ - Labels: clusterLabels, - ClientID: clientID, + Labels: co.Labels, + ClientID: co.ClientID, + AgentTLSMode: co.AgentTLSMode, }) if err != nil { return nil, err diff --git a/internal/cmd/controller/controllers/cluster/import.go b/internal/cmd/controller/controllers/cluster/import.go index aaf69ff05b..68ddc3c40a 100644 --- a/internal/cmd/controller/controllers/cluster/import.go +++ b/internal/cmd/controller/controllers/cluster/import.go @@ -91,7 +91,12 @@ func (i *importHandler) onConfig(config *config.Config) error { if cluster.Spec.KubeConfigSecret == "" { continue } - if config.APIServerURL != cluster.Status.APIServerURL || hashStatusField(config.APIServerCA) != cluster.Status.APIServerCAHash { + + hasConfigChanged := config.APIServerURL != cluster.Status.APIServerURL || + hashStatusField(config.APIServerCA) != cluster.Status.APIServerCAHash || + config.AgentTLSMode != cluster.Status.AgentTLSMode + + if hasConfigChanged { logrus.Infof("API server config changed, trigger cluster import for cluster %s/%s", cluster.Namespace, cluster.Name) c := cluster.DeepCopy() c.Status.AgentConfigChanged = true @@ -253,7 +258,19 @@ func (i *importHandler) importCluster(cluster *fleet.Cluster, status fleet.Clust apiServerCA = cfg.APIServerCA } - restConfig, err := i.restConfigFromKubeConfig(secret.Data["value"]) + if cfg.AgentTLSMode != config.AgentTLSModeStrict && cfg.AgentTLSMode != config.AgentTLSModeSystemStore { + return status, + fmt.Errorf( + "provided config value for agentTLSMode is none of [%q,%q]", + config.AgentTLSModeStrict, + config.AgentTLSModeSystemStore, + ) + } + + restConfig, err := i.restConfigFromKubeConfig( + secret.Data["value"], + cfg.AgentTLSMode == config.AgentTLSModeSystemStore, + ) if err != nil { return status, err } @@ -320,8 +337,9 @@ func (i *importHandler) importCluster(cluster *fleet.Cluster, status fleet.Clust CA: apiServerCA, Host: apiServerURL, ConfigOptions: agent.ConfigOptions{ - ClientID: cluster.Spec.ClientID, - Labels: clusterLabels, + ClientID: cluster.Spec.ClientID, + Labels: clusterLabels, + AgentTLSMode: cfg.AgentTLSMode, }, ManifestOptions: agent.ManifestOptions{ AgentEnvVars: cluster.Spec.AgentEnvVars, @@ -388,6 +406,8 @@ func (i *importHandler) importCluster(cluster *fleet.Cluster, status fleet.Clust status.AgentConfigChanged = false status.APIServerURL = apiServerURL status.APIServerCAHash = hashStatusField(apiServerCA) + status.AgentTLSMode = cfg.AgentTLSMode + return status, nil } @@ -402,8 +422,9 @@ func isLegacyAgentNamespaceSelectedByUser() bool { cfg.Bootstrap.AgentNamespace == config.LegacyDefaultNamespace } -// restConfigFromKubeConfig checks kubeconfig data and tries to connect to server. If server is behind public CA, remove CertificateAuthorityData in kubeconfig file. -func (i *importHandler) restConfigFromKubeConfig(data []byte) (*rest.Config, error) { +// restConfigFromKubeConfig checks kubeconfig data and tries to connect to server. If server is behind public CA, remove +// CertificateAuthorityData in kubeconfig file unless strict TLS mode is enabled. +func (i *importHandler) restConfigFromKubeConfig(data []byte, trustSystemStoreCAs bool) (*rest.Config, error) { clientConfig, err := clientcmd.NewClientConfigFromBytes(data) if err != nil { return nil, err @@ -414,11 +435,10 @@ func (i *importHandler) restConfigFromKubeConfig(data []byte) (*rest.Config, err return nil, err } - if raw.Contexts[raw.CurrentContext] != nil { + if trustSystemStoreCAs && raw.Contexts[raw.CurrentContext] != nil { cluster := raw.Contexts[raw.CurrentContext].Cluster if raw.Clusters[cluster] != nil { - _, err := http.Get(raw.Clusters[cluster].Server) - if err == nil { + if _, err := http.Get(raw.Clusters[cluster].Server); err == nil { raw.Clusters[cluster].CertificateAuthorityData = nil } } diff --git a/internal/config/config.go b/internal/config/config.go index 42d08e7db9..0056dfdc7f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,6 +20,8 @@ const ( ManagerConfigName = "fleet-controller" AgentConfigName = "fleet-agent" AgentBootstrapConfigName = "fleet-agent-bootstrap" + AgentTLSModeStrict = "strict" + AgentTLSModeSystemStore = "system-store" Key = "config" // DefaultNamespace is the default for the system namespace, which // contains the manager and agent @@ -59,6 +61,11 @@ type Config struct { APIServerCA []byte `json:"apiServerCA,omitempty"` Bootstrap Bootstrap `json:"bootstrap,omitempty"` IgnoreClusterRegistrationLabels bool `json:"ignoreClusterRegistrationLabels,omitempty"` + + // AgentTLSMode supports two values: `system-store` and `strict`. If set to `system-store`, instructs the agent + // to trust CA bundles from the operating system's store. If set to `strict`, then the agent shall only connect + // to a server which uses the exact CA configured when creating/updating the agent. + AgentTLSMode string `json:"agentTLSMode,omitempty"` } type Bootstrap struct { diff --git a/pkg/apis/fleet.cattle.io/v1alpha1/target.go b/pkg/apis/fleet.cattle.io/v1alpha1/target.go index b8e924ed52..153a8ceaa8 100644 --- a/pkg/apis/fleet.cattle.io/v1alpha1/target.go +++ b/pkg/apis/fleet.cattle.io/v1alpha1/target.go @@ -136,6 +136,13 @@ type ClusterStatus struct { APIServerURL string `json:"apiServerURL,omitempty"` APIServerCAHash string `json:"apiServerCAHash,omitempty"` + // AgentTLSMode supports two values: `system-store` and `strict`. If set to + // `system-store`, instructs the agent to trust CA bundles from the operating + // system's store. If set to `strict`, then the agent shall only connect to a + // server which uses the exact CA configured when creating/updating the agent. + // +nullable + AgentTLSMode string `json:"agentTLSMode,omitempty"` + Display ClusterDisplay `json:"display,omitempty"` Agent AgentStatus `json:"agent,omitempty"` } From c79998ae410311aee394f7c63ec0d67e5d69cdc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20N=C3=A9au?= Date: Thu, 4 Jul 2024 14:57:32 +0200 Subject: [PATCH 2/3] Update golangci-lint action This bumps both the action and the `golangci-lint` version used by it, which should fix errors not seen locally. --- .github/workflows/golangci-lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index b5446025c7..5ae0c27ce6 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -26,10 +26,10 @@ jobs: export PATH=$PATH:/home/runner/go/bin/ - name: golangci-lint - uses: golangci/golangci-lint-action@v3.6.0 + uses: golangci/golangci-lint-action@v6.0.1 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.53 + version: v1.58 args: --timeout=10m --config=.golangci.json From 8ca89c414e0be68a730ee058e356b2c2a6d1dcd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20N=C3=A9au?= Date: Wed, 3 Jul 2024 10:23:11 +0200 Subject: [PATCH 3/3] Add scripts needed for test release This commit is only relevant to release test charts for this branch and test it within Rancher 2.8. It can be, and should be, safely reverted afterwards. --- .github/scripts/build-fleet-binaries.sh | 15 ++++ .../scripts/release-against-test-charts.sh | 75 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100755 .github/scripts/build-fleet-binaries.sh create mode 100755 .github/scripts/release-against-test-charts.sh diff --git a/.github/scripts/build-fleet-binaries.sh b/.github/scripts/build-fleet-binaries.sh new file mode 100755 index 0000000000..4b914f233e --- /dev/null +++ b/.github/scripts/build-fleet-binaries.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Description: build fleet binary and image with debug flags + +set -euxo pipefail + +export GOARCH="${GOARCH:-amd64}" +export CGO_ENABLED=0 +export GOOS=linux + +# fleet +go build -gcflags='all=-N -l' -o bin/fleetcontroller-linux-"$GOARCH" ./cmd/fleetcontroller + +# fleet agent +go build -gcflags='all=-N -l' -o "bin/fleet-linux-$GOARCH" ./cmd/fleetcli +go build -gcflags='all=-N -l' -o "bin/fleetagent-linux-$GOARCH" ./cmd/fleetagent diff --git a/.github/scripts/release-against-test-charts.sh b/.github/scripts/release-against-test-charts.sh new file mode 100755 index 0000000000..7feb1b8629 --- /dev/null +++ b/.github/scripts/release-against-test-charts.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# +# Submit new Fleet version against a test fork of rancher/charts + +set -ue + +PREV_FLEET_VERSION="$1" # e.g. 0.5.2-rc.3 +NEW_FLEET_VERSION="$2" +PREV_CHART_VERSION="$3" # e.g. 101.2.0 +NEW_CHART_VERSION="$4" +UUID="$5" # for ttl.sh image resolution + +if [ -z "${GITHUB_WORKSPACE:-}" ]; then + CHARTS_DIR="$(dirname -- "$0")/../../../charts" +else + CHARTS_DIR="${GITHUB_WORKSPACE}/charts" +fi + +pushd "${CHARTS_DIR}" > /dev/null + +if [ ! -e ~/.gitconfig ]; then + git config --global user.name "fleet-bot" + git config --global user.email fleet@suse.de +fi + +if [ ! -f bin/charts-build-scripts ]; then + make pull-scripts +fi + +if grep -q "version: ${PREV_CHART_VERSION}" ./packages/fleet/fleet/package.yaml && grep -q "${PREV_FLEET_VERSION}" ./packages/fleet/fleet/package.yaml; then + + find ./packages/fleet/ -type f -exec sed -i -e "s/${PREV_FLEET_VERSION}/${NEW_FLEET_VERSION}/g" {} \; + find ./packages/fleet/ -type f -exec sed -i -e "s/version: ${PREV_CHART_VERSION}/version: ${NEW_CHART_VERSION}/g" {} \; +else + echo "Previous Fleet version references do not exist in ./packages/fleet/ so replacing it with the new version is not possible. Exiting..." + exit 1 +fi + +for i in fleet fleet-crd fleet-agent; do + yq --inplace "del( .${i}.[] | select(. == \"${PREV_CHART_VERSION}+up${PREV_FLEET_VERSION}\") )" release.yaml + yq --inplace ".${i} += [\"${NEW_CHART_VERSION}+up${NEW_FLEET_VERSION}\"]" release.yaml +done + +git add packages/fleet release.yaml +git commit -m "Updating to Fleet v${NEW_FLEET_VERSION}" + +NEW_FULL_VERSION=${NEW_CHART_VERSION}+up${NEW_FLEET_VERSION} + +for suffix in '' '-agent' '-crd'; do + chart_dir=charts/fleet$suffix/${NEW_FULL_VERSION} + mkdir -p $chart_dir + cp -r ../fleet/charts/fleet$suffix/* $chart_dir + cd $chart_dir + + # Replace rancher/fleet and rancher/fleet-agent image names, but not eg. rancher/kubectl + sed -i \ + -e "s@repository: rancher/\(fleet.*\).*@repository: ttl.sh/rancher-\\1-$UUID@" \ + -e "s/tag: dev/tag: 1h/" \ + values.yaml + + cd - + + helm package \ + --version="$NEW_FULL_VERSION" \ + --app-version="$NEW_FLEET_VERSION" \ + -d ./assets/fleet$suffix \ + $chart_dir +done + +make index # Merge new chart entries into `index.yaml` + +git add assets/fleet* charts/fleet* index.yaml +git commit -m "Autogenerated changes for Fleet v${NEW_FLEET_VERSION}" + +popd > /dev/null