From ccbd5aed39b360664d1f80c8b146050e9df9ff7b Mon Sep 17 00:00:00 2001 From: Philipp Kleber Date: Mon, 7 Oct 2024 21:19:46 +0200 Subject: [PATCH] feat: optionally decode hcloud userdata as base64 When fetching the machine configuration in the hcloud platform implementation, try to decode the data returned from the 'userdata' endpoint as a base64 string. If the data is not in base64 format, decoding does not succeed and the unmodified data is used. Signed-off-by: Philipp Kleber Signed-off-by: Andrey Smirnov --- .../v1alpha1/platform/hcloud/export_test.go | 10 ++ .../v1alpha1/platform/hcloud/hcloud.go | 21 ++- .../v1alpha1/platform/hcloud/hcloud_test.go | 14 ++ .../hcloud/testdata/userdata-base64.txt | 1 + .../hcloud/testdata/userdata-plain.yaml | 124 ++++++++++++++++++ 5 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/export_test.go create mode 100644 internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/testdata/userdata-base64.txt create mode 100644 internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/testdata/userdata-plain.yaml diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/export_test.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/export_test.go new file mode 100644 index 0000000000..9939580ab4 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/export_test.go @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package hcloud + +// MaybeBase64Decode is exported for testing. +func MaybeBase64Decode(data []byte) []byte { + return maybeBase64Decode(data) +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/hcloud.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/hcloud.go index 041b6a64b4..9342c96ed9 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/hcloud.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/hcloud.go @@ -7,6 +7,7 @@ package hcloud import ( "context" + "encoding/base64" "fmt" "log" "net/netip" @@ -156,9 +157,27 @@ func (h *Hcloud) Configuration(ctx context.Context, r state.State) ([]byte, erro log.Printf("fetching machine config from: %q", HCloudUserDataEndpoint) - return download.Download(ctx, HCloudUserDataEndpoint, + configBytes, err := download.Download(ctx, HCloudUserDataEndpoint, download.WithErrorOnNotFound(errors.ErrNoConfigSource), download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource)) + if err != nil { + return nil, err + } + + // Try to parse the downloaded config bytes as base64 string, so that users can provide the config in base64 format. + // This also allows users to gzip this data, since the calling code will try to un-gzip the data if it detects it. + return maybeBase64Decode(configBytes), nil +} + +// maybeBase64Decode tries to interpret the provided bytes as base64 string and decode them. +// If the provided bytes are not a valid base64 string, the original bytes are returned. +func maybeBase64Decode(data []byte) []byte { + out, err := base64.StdEncoding.AppendDecode(nil, data) + if err != nil { + return data + } + + return out } // Mode implements the runtime.Platform interface. diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/hcloud_test.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/hcloud_test.go index bed05b1920..f2651d83a0 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/hcloud_test.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/hcloud_test.go @@ -44,3 +44,17 @@ func TestParseMetadata(t *testing.T) { assert.Equal(t, expectedNetworkConfig, string(marshaled)) } + +//go:embed testdata/userdata-plain.yaml +var userdataPlain []byte + +//go:embed testdata/userdata-base64.txt +var userdataBase64 []byte + +func TestParseUserdata(t *testing.T) { + decodedUserdataPlain := hcloud.MaybeBase64Decode(userdataPlain) + decodedUserdataBase64 := hcloud.MaybeBase64Decode(userdataBase64) + + assert.Equal(t, decodedUserdataPlain, decodedUserdataBase64) + assert.Equal(t, userdataPlain, decodedUserdataBase64) +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/testdata/userdata-base64.txt b/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/testdata/userdata-base64.txt new file mode 100644 index 0000000000..1711584665 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/testdata/userdata-base64.txt @@ -0,0 +1 @@ +dmVyc2lvbjogdjFhbHBoYTEKZGVidWc6IGZhbHNlCnBlcnNpc3Q6IHRydWUKbWFjaGluZToKICB0eXBlOiBjb250cm9scGxhbmUKICBjZXJ0U0FOczoKICAgIC0gMTAuMC4xLjEwMQogICAgLSAxMC4wLjEuMTAwCiAga3ViZWxldDoKICAgIGltYWdlOiBnaGNyLmlvL3NpZGVyb2xhYnMva3ViZWxldDp2MS4zMS4xCiAgICBleHRyYUFyZ3M6CiAgICAgIGNsb3VkLXByb3ZpZGVyOiBleHRlcm5hbAogICAgICByb3RhdGUtc2VydmVyLWNlcnRpZmljYXRlczogInRydWUiCiAgICBkZWZhdWx0UnVudGltZVNlY2NvbXBQcm9maWxlRW5hYmxlZDogdHJ1ZQogICAgbm9kZUlQOgogICAgICB2YWxpZFN1Ym5ldHM6CiAgICAgICAgLSAxMC4wLjEuMC8yNAogICAgZGlzYWJsZU1hbmlmZXN0c0RpcmVjdG9yeTogdHJ1ZQogIG5ldHdvcms6CiAgICBob3N0bmFtZTogY29udHJvbHBsYW5lLTAwMQogICAgaW50ZXJmYWNlczoKICAgICAgLSBpbnRlcmZhY2U6IGV0aDAKICAgICAgICBkaGNwOiB0cnVlCiAgICBrdWJlc3BhbjoKICAgICAgZW5hYmxlZDogZmFsc2UKICBpbnN0YWxsOgogICAgZGlzazogL2Rldi9zZGEKICAgIGV4dHJhS2VybmVsQXJnczoKICAgICAgLSBpcHY2LmRpc2FibGU9MQogICAgaW1hZ2U6IGdoY3IuaW8vc2lkZXJvbGFicy9pbnN0YWxsZXI6djEuOC4wCiAgICB3aXBlOiBmYWxzZQogIHN5c2N0bHM6CiAgICBuZXQuY29yZS5uZXRkZXZfbWF4X2JhY2tsb2c6ICI0MDk2IgogICAgbmV0LmNvcmUuc29tYXhjb25uOiAiNjU1MzUiCiAgZmVhdHVyZXM6CiAgICByYmFjOiB0cnVlCiAgICBzdGFibGVIb3N0bmFtZTogdHJ1ZQogICAga3ViZXJuZXRlc1RhbG9zQVBJQWNjZXNzOgogICAgICBlbmFibGVkOiB0cnVlCiAgICAgIGFsbG93ZWRSb2xlczoKICAgICAgICAtIG9zOnJlYWRlcgogICAgICBhbGxvd2VkS3ViZXJuZXRlc05hbWVzcGFjZXM6CiAgICAgICAgLSBrdWJlLXN5c3RlbQogICAgYXBpZENoZWNrRXh0S2V5VXNhZ2U6IHRydWUKICAgIGRpc2tRdW90YVN1cHBvcnQ6IHRydWUKICAgIGt1YmVQcmlzbToKICAgICAgZW5hYmxlZDogdHJ1ZQogICAgICBwb3J0OiA3NDQ1CiAgICBob3N0RE5TOgogICAgICBlbmFibGVkOiB0cnVlCiAgICAgIGZvcndhcmRLdWJlRE5TVG9Ib3N0OiB0cnVlCiAgICAgIHJlc29sdmVNZW1iZXJOYW1lczogdHJ1ZQogIGtlcm5lbDoge30KICBub2RlTGFiZWxzOgogICAgbm9kZS5rdWJlcm5ldGVzLmlvL2V4Y2x1ZGUtZnJvbS1leHRlcm5hbC1sb2FkLWJhbGFuY2VyczogIiIKY2x1c3RlcjoKICBjb250cm9sUGxhbmU6CiAgICBlbmRwb2ludDogaHR0cHM6Ly8xMC4wLjEuMTAwOjY0NDMKICBjbHVzdGVyTmFtZTogdGVzdC1jbHVzdGVyCiAgbmV0d29yazoKICAgIGNuaToKICAgICAgbmFtZTogbm9uZQogICAgZG5zRG9tYWluOiBjbHVzdGVyLmxvY2FsCiAgICBwb2RTdWJuZXRzOgogICAgICAtIDEwLjAuMTYuMC8yMAogICAgc2VydmljZVN1Ym5ldHM6CiAgICAgIC0gMTAuMC44LjAvMjEKICBhcGlTZXJ2ZXI6CiAgICBpbWFnZTogcmVnaXN0cnkuazhzLmlvL2t1YmUtYXBpc2VydmVyOnYxLjMxLjEKICAgIGNlcnRTQU5zOgogICAgICAtIDEwLjAuMS4xMDAKICAgICAgLSAxMC4wLjEuMTAxCiAgICAgIC0gMTAuMC4xLjEwMAogICAgZGlzYWJsZVBvZFNlY3VyaXR5UG9saWN5OiB0cnVlCiAgICBhZG1pc3Npb25Db250cm9sOgogICAgICAtIG5hbWU6IFBvZFNlY3VyaXR5CiAgICAgICAgY29uZmlndXJhdGlvbjoKICAgICAgICAgIGFwaVZlcnNpb246IHBvZC1zZWN1cml0eS5hZG1pc3Npb24uY29uZmlnLms4cy5pby92MWFscGhhMQogICAgICAgICAgZGVmYXVsdHM6CiAgICAgICAgICAgIGF1ZGl0OiByZXN0cmljdGVkCiAgICAgICAgICAgIGF1ZGl0LXZlcnNpb246IGxhdGVzdAogICAgICAgICAgICBlbmZvcmNlOiBiYXNlbGluZQogICAgICAgICAgICBlbmZvcmNlLXZlcnNpb246IGxhdGVzdAogICAgICAgICAgICB3YXJuOiByZXN0cmljdGVkCiAgICAgICAgICAgIHdhcm4tdmVyc2lvbjogbGF0ZXN0CiAgICAgICAgICBleGVtcHRpb25zOgogICAgICAgICAgICBuYW1lc3BhY2VzOgogICAgICAgICAgICAgIC0ga3ViZS1zeXN0ZW0KICAgICAgICAgICAgcnVudGltZUNsYXNzZXM6IFtdCiAgICAgICAgICAgIHVzZXJuYW1lczogW10KICAgICAgICAgIGtpbmQ6IFBvZFNlY3VyaXR5Q29uZmlndXJhdGlvbgogICAgYXVkaXRQb2xpY3k6CiAgICAgIGFwaVZlcnNpb246IGF1ZGl0Lms4cy5pby92MQogICAgICBraW5kOiBQb2xpY3kKICAgICAgcnVsZXM6CiAgICAgICAgLSBsZXZlbDogTWV0YWRhdGEKICBjb250cm9sbGVyTWFuYWdlcjoKICAgIGltYWdlOiByZWdpc3RyeS5rOHMuaW8va3ViZS1jb250cm9sbGVyLW1hbmFnZXI6djEuMzEuMQogICAgZXh0cmFBcmdzOgogICAgICBiaW5kLWFkZHJlc3M6IDAuMC4wLjAKICAgICAgY2xvdWQtcHJvdmlkZXI6IGV4dGVybmFsCiAgICAgIG5vZGUtY2lkci1tYXNrLXNpemUtaXB2NDogIjI0IgogIHByb3h5OgogICAgZGlzYWJsZWQ6IHRydWUKICAgIGltYWdlOiByZWdpc3RyeS5rOHMuaW8va3ViZS1wcm94eTp2MS4zMS4xCiAgc2NoZWR1bGVyOgogICAgaW1hZ2U6IHJlZ2lzdHJ5Lms4cy5pby9rdWJlLXNjaGVkdWxlcjp2MS4zMS4xCiAgICBleHRyYUFyZ3M6CiAgICAgIGJpbmQtYWRkcmVzczogMC4wLjAuMAogIGRpc2NvdmVyeToKICAgIGVuYWJsZWQ6IHRydWUKICAgIHJlZ2lzdHJpZXM6CiAgICAgIGt1YmVybmV0ZXM6CiAgICAgICAgZGlzYWJsZWQ6IHRydWUKICAgICAgc2VydmljZToge30KICBldGNkOgogICAgZXh0cmFBcmdzOgogICAgICBsaXN0ZW4tbWV0cmljcy11cmxzOiBodHRwOi8vMC4wLjAuMDoyMzgxCiAgICBhZHZlcnRpc2VkU3VibmV0czoKICAgICAgLSAxMC4wLjEuMC8yNAogIGNvcmVETlM6CiAgICBkaXNhYmxlZDogZmFsc2UKICBleHRlcm5hbENsb3VkUHJvdmlkZXI6CiAgICBlbmFibGVkOiB0cnVlCg== \ No newline at end of file diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/testdata/userdata-plain.yaml b/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/testdata/userdata-plain.yaml new file mode 100644 index 0000000000..507885cb1c --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/testdata/userdata-plain.yaml @@ -0,0 +1,124 @@ +version: v1alpha1 +debug: false +persist: true +machine: + type: controlplane + certSANs: + - 10.0.1.101 + - 10.0.1.100 + kubelet: + image: ghcr.io/siderolabs/kubelet:v1.31.1 + extraArgs: + cloud-provider: external + rotate-server-certificates: "true" + defaultRuntimeSeccompProfileEnabled: true + nodeIP: + validSubnets: + - 10.0.1.0/24 + disableManifestsDirectory: true + network: + hostname: controlplane-001 + interfaces: + - interface: eth0 + dhcp: true + kubespan: + enabled: false + install: + disk: /dev/sda + extraKernelArgs: + - ipv6.disable=1 + image: ghcr.io/siderolabs/installer:v1.8.0 + wipe: false + sysctls: + net.core.netdev_max_backlog: "4096" + net.core.somaxconn: "65535" + features: + rbac: true + stableHostname: true + kubernetesTalosAPIAccess: + enabled: true + allowedRoles: + - os:reader + allowedKubernetesNamespaces: + - kube-system + apidCheckExtKeyUsage: true + diskQuotaSupport: true + kubePrism: + enabled: true + port: 7445 + hostDNS: + enabled: true + forwardKubeDNSToHost: true + resolveMemberNames: true + kernel: {} + nodeLabels: + node.kubernetes.io/exclude-from-external-load-balancers: "" +cluster: + controlPlane: + endpoint: https://10.0.1.100:6443 + clusterName: test-cluster + network: + cni: + name: none + dnsDomain: cluster.local + podSubnets: + - 10.0.16.0/20 + serviceSubnets: + - 10.0.8.0/21 + apiServer: + image: registry.k8s.io/kube-apiserver:v1.31.1 + certSANs: + - 10.0.1.100 + - 10.0.1.101 + - 10.0.1.100 + disablePodSecurityPolicy: true + admissionControl: + - name: PodSecurity + configuration: + apiVersion: pod-security.admission.config.k8s.io/v1alpha1 + defaults: + audit: restricted + audit-version: latest + enforce: baseline + enforce-version: latest + warn: restricted + warn-version: latest + exemptions: + namespaces: + - kube-system + runtimeClasses: [] + usernames: [] + kind: PodSecurityConfiguration + auditPolicy: + apiVersion: audit.k8s.io/v1 + kind: Policy + rules: + - level: Metadata + controllerManager: + image: registry.k8s.io/kube-controller-manager:v1.31.1 + extraArgs: + bind-address: 0.0.0.0 + cloud-provider: external + node-cidr-mask-size-ipv4: "24" + proxy: + disabled: true + image: registry.k8s.io/kube-proxy:v1.31.1 + scheduler: + image: registry.k8s.io/kube-scheduler:v1.31.1 + extraArgs: + bind-address: 0.0.0.0 + discovery: + enabled: true + registries: + kubernetes: + disabled: true + service: {} + etcd: + extraArgs: + listen-metrics-urls: http://0.0.0.0:2381 + advertisedSubnets: + - 10.0.1.0/24 + coreDNS: + disabled: false + externalCloudProvider: + enabled: true