From 9152c0f5b3c266bde6e461feb2d0dbd7acb829da Mon Sep 17 00:00:00 2001 From: Alex Vulaj Date: Mon, 5 Aug 2024 15:00:13 -0400 Subject: [PATCH] Provide CA Certificates via cloud-init's ca_certs module Co-authored-by: Anthony Byrne --- pkg/probes/curl/curl_json.go | 43 +++++++++++++++++++------- pkg/probes/curl/curl_json_test.go | 2 +- pkg/probes/curl/userdata-template.yaml | 2 +- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/pkg/probes/curl/curl_json.go b/pkg/probes/curl/curl_json.go index 12555b98..f84776e6 100644 --- a/pkg/probes/curl/curl_json.go +++ b/pkg/probes/curl/curl_json.go @@ -2,7 +2,9 @@ package curl import ( _ "embed" + "encoding/base64" "fmt" + "gopkg.in/yaml.v3" "os" "strconv" "strings" @@ -104,17 +106,36 @@ func (clp Probe) GetExpandedUserData(userDataVariables map[string]string) (strin return "", fmt.Errorf("invalid userdata variable DELAY: %w", err) } - // For compatibility reasons, we expect CACERT to be either empty or a base64-encoded - // PEM-formatted CA certicate. When one is provided, we "render" it with a cloud-init - // preamble that writes the file to disk, and we tell curl about the cert file via flag - if userDataVariables["CACERT"] != "" { - cloudInitPreamble := `write_files: -- path: /proxy.pem - permissions: '0755' - encoding: b64 - content: ` - userDataVariables["CACERT_RENDERED"] = cloudInitPreamble + userDataVariables["CACERT"] - userDataVariables["CURLOPT"] += " --cacert /proxy.pem " + // We expect CACERT to be either empty or a base64 encoded PEM-formatted CA certificate string. + // When a CA certificate is provided, we add it to the system's CA store via cloud-init. + // Docs: https://cloudinit.readthedocs.io/en/latest/reference/modules.html#ca-certificates + if cacert := userDataVariables["CACERT"]; cacert != "" { + type CaCert struct { + Trusted []string `yaml:"trusted"` + } + type CloudConfig struct { + CaCerts CaCert `yaml:"ca_certs"` + } + + decodedCert, err := base64.StdEncoding.DecodeString(cacert) + if err != nil { + return "", fmt.Errorf("failed to base64 decode provided CA certificate: %w", err) + } + + cloudInit := CloudConfig{ + CaCerts: CaCert{ + Trusted: []string{ + strings.TrimSpace(string(decodedCert)), + }, + }, + } + + cloudInitYamlBytes, cloudInitMarshalErr := yaml.Marshal(&cloudInit) + if cloudInitMarshalErr != nil { + return "", fmt.Errorf("unable to create cloud init config: %w", cloudInitMarshalErr) + } + + userDataVariables["CACERT_RENDERED"] = strings.TrimSpace(string(cloudInitYamlBytes)) } // Also for compatibility reasons, we map the NOTLS variable to curl's "insecure" flag diff --git a/pkg/probes/curl/curl_json_test.go b/pkg/probes/curl/curl_json_test.go index 8d0ad2bd..7a333a5f 100644 --- a/pkg/probes/curl/curl_json_test.go +++ b/pkg/probes/curl/curl_json_test.go @@ -57,7 +57,7 @@ func TestCurlJSONProbe_GetExpandedUserData(t *testing.T) { "URLS": "http://example.com:80 https://example.org:443", "CACERT": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNiakNDQWZPZ0F3SUJBZ0lRWXZZeWJPWEU0MmhjRzJMZG5DNmRsVEFLQmdncWhrak9QUVFEQXpCNE1Rc3cKQ1FZRFZRUUdFd0pGVXpFUk1BOEdBMVVFQ2d3SVJrNU5WQzFTUTAweERqQU1CZ05WQkFzTUJVTmxjbVZ6TVJndwpGZ1lEVlFSaERBOVdRVlJGVXkxUk1qZ3lOakF3TkVveExEQXFCZ05WQkFNTUkwRkRJRkpCU1ZvZ1JrNU5WQzFTClEwMGdVMFZTVmtsRVQxSkZVeUJUUlVkVlVrOVRNQjRYRFRFNE1USXlNREE1TXpjek0xb1hEVFF6TVRJeU1EQTUKTXpjek0xb3dlREVMTUFrR0ExVUVCaE1DUlZNeEVUQVBCZ05WQkFvTUNFWk9UVlF0VWtOTk1RNHdEQVlEVlFRTApEQVZEWlhKbGN6RVlNQllHQTFVRVlRd1BWa0ZVUlZNdFVUSTRNall3TURSS01Td3dLZ1lEVlFRRERDTkJReUJTClFVbGFJRVpPVFZRdFVrTk5JRk5GVWxaSlJFOVNSVk1nVTBWSFZWSlBVekIyTUJBR0J5cUdTTTQ5QWdFR0JTdUIKQkFBaUEySUFCUGE2VjFQSXlxdmZOa3BTSWVTWDBvTm5udkJsVWRCZWg4ZEhzVm55VjBlYkFBS1RSQmRwMjBMSApzYkk2R0E2MFhZeXpabDJoTlBrMkxFbmI4MGI4czBScFJCTm0vZGZGL2E4MlRjNERUUWR4ejY5cUJkS2lRMW9LClVtOEJBMDZPaTZOQ01FQXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0hRWUQKVlIwT0JCWUVGQUc1TCsrL0VZWmc4ay9RUVc2cmN4L24wbTVKTUFvR0NDcUdTTTQ5QkFNREEya0FNR1lDTVFDdQpTdU1yUU1OMEVmS1ZyUllqM2s0TUd1WmRwU1JlYTBSNy9EamlUOHVjUlJjUlRCUW5KbFU1ZFVvRHpCT1FuNUlDCk1RRDZTbXhnaUhQejdyaVlZcW5PSzhMWmlxWndNUjJ2c0pSTTYwL0c0OUh6WXFjOC81TXVCMXhKQVdkcEVnSnkKditjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==", }, - wantRegex: `#cloud-config[\s\S]*proxy.pem[\s\S]*encoding: b64[\s\S]*LS0tLS1CRUd\w*Cg==\n[\s\S]*https://example.org:443`, + wantRegex: `#cloud-config[\s\S]*ca_certs[\s\S]*trusted:[\s\S]*BEGIN CERTIFICATE[\s\S]*END CERTIFICATE`, }, { name: "set NOTLS", diff --git a/pkg/probes/curl/userdata-template.yaml b/pkg/probes/curl/userdata-template.yaml index 54167d77..72daab68 100644 --- a/pkg/probes/curl/userdata-template.yaml +++ b/pkg/probes/curl/userdata-template.yaml @@ -6,7 +6,7 @@ runcmd: - dmesg -D - echo "${USERDATA_BEGIN}" >/dev/ttyS0 - export http_proxy=${HTTP_PROXY} https_proxy=${HTTPS_PROXY} - - curl --retry 3 --retry-connrefused -t B -Z -s -I -m ${TIMEOUT} -w "%{stderr}${LINE_PREFIX}%{json}\n" ${CURLOPT} ${URLS} --proto =http,https,telnet ${TLSDISABLED_URLS_RENDERED} 2>/dev/ttyS0 + - curl --capath /etc/pki/tls/certs/ --proxy-capath /etc/pki/tls/certs/ --retry 3 --retry-connrefused -t B -Z -s -I -m ${TIMEOUT} -w "%{stderr}${LINE_PREFIX}%{json}\n" ${CURLOPT} ${URLS} --proto =http,https,telnet ${TLSDISABLED_URLS_RENDERED} 2>/dev/ttyS0 - echo "${USERDATA_END}" >/dev/ttyS0 power_state: delay: ${DELAY}