diff --git a/CHANGELOG.md b/CHANGELOG.md index bf84ff2..04ea876 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Added - Support for Kube 1.32. - Container resizes now performed through `resize` subresource. +- Ability to register an additional CA certificate (or chain) when building the kind node image for integration tests + and sandbox scripts. ### Changed - Upgrades Go to 1.23.3. diff --git a/README.md b/README.md index 8cdbcce..3026970 100644 --- a/README.md +++ b/README.md @@ -538,12 +538,13 @@ Integration tests are implemented as Go tests and located in `test/integration`. The integration tests use [echo-server](https://github.com/Ealenn/Echo-Server) for containers. Note: the very first execution might take some time to complete. -A number of environment variable-based configuration options are available: +A number of environment variable-based configuration items are available: | Name | Default | Description | |--------------------------|---------|--------------------------------------------------------------------------------------------------------------------------------------| | `KUBE_VERSION` | - | The _major.minor_ version of Kube to run tests against e.g. `1.31`. | | `MAX_PARALLELISM` | `4` | The maximum number of tests that can run in parallel. | +| `EXTRA_CA_CERT_PATH` | - | See below. | | `REUSE_CLUSTER` | `false` | Whether to reuse an existing CSA kind cluster (if it already exists). `KUBE_VERSION` has no effect if an existing cluster is reused. | | `INSTALL_METRICS_SERVER` | `false` | Whether to install metrics-server. | | `KEEP_CSA` | `false` | Whether to keep the CSA installation after tests finish. | @@ -555,6 +556,10 @@ namespace (but using the same single CSA installation). If local resources are l accordingly and ensure `DELETE_NS_AFTER_TEST` is `true`. Each test typically spins up 2 pods, each with 2 containers; see source for resource allocations. +`EXTRA_CA_CERT_PATH` is an optional configuration item that allows registration of an additional CA certificate +(or chain) when building the kind node image. This will be required if a technology that intercepts encrypted network +traffic via insertion of its own CA is being used. The path must be absolute and reference a PEM-formatted file. + ## Running Locally A number of Bash scripts are supplied in the `scripts/sandbox` directory that allow you to try out CSA using [echo-server](https://github.com/Ealenn/Echo-Server). The scripts are similar in nature to the setup/teardown work @@ -575,7 +580,10 @@ Executing `csa-install.sh`: - [Leader election](#controller) is enabled; 2 pods are created. - [Log verbosity level](#log) is `2` (trace). -Note: the very first execution might take some time to complete. +Note: +- To register an additional CA certificate (or chain) when building the kind node image as described + [above](#integration), pass `--extra-ca-cert-path=/path/to/ca.pem` when executing the script. +- The very first execution might take some time to complete. ### Tailing CSA Logs Executing `csa-tail-logs.sh` tails logs from the current CSA leader pod. diff --git a/internal/pod/error.go b/internal/pod/error.go index c4918fe..a497660 100644 --- a/internal/pod/error.go +++ b/internal/pod/error.go @@ -39,7 +39,7 @@ func NewValidationError(message string, toWrap error) error { func (e ValidationError) Error() string { if e.wrapped == nil { - return fmt.Sprintf("validation error: %s", e.message) + return "validation error: " + e.message } return fmt.Errorf("validation error: %s: %w", e.message, e.wrapped).Error() diff --git a/internal/pod/kubehelper_test.go b/internal/pod/kubehelper_test.go index 6761f24..0ddaeef 100644 --- a/internal/pod/kubehelper_test.go +++ b/internal/pod/kubehelper_test.go @@ -526,7 +526,7 @@ func TestKubeHelperExpectedLabelValueAs(t *testing.T) { name: "test", as: "test", }, - wantPanicErrMsg: fmt.Sprintf("as 'test' not supported"), + wantPanicErrMsg: "as 'test' not supported", }, { name: "Ok", diff --git a/scripts/sandbox/csa-install.sh b/scripts/sandbox/csa-install.sh index 7405fb7..16ead3f 100644 --- a/scripts/sandbox/csa-install.sh +++ b/scripts/sandbox/csa-install.sh @@ -14,13 +14,61 @@ # See the License for the specific language governing permissions and # limitations under the License. +extra_ca_cert_path="" + +while [ $# -gt 0 ]; do + case "$1" in + --extra-ca-cert-path=*) + extra_ca_cert_path="${1#*=}" + ;; + *) + echo "Unrecognized argument: $1. Supported: --extra-ca-cert-path (optional)." + exit 1 + esac + shift +done + source config/vars.sh source csa-uninstall.sh # shellcheck disable=SC2154 if [ -z "$(docker images --filter "reference=$kind_node_docker_tag" --format '{{.Repository}}:{{.Tag}}')" ]; then - # shellcheck disable=SC2154 - kind build node-image --type release "$kind_kube_version" --image "$kind_node_docker_tag" + if [ -n "$extra_ca_cert_path" ]; then + if [ ! -e "$extra_ca_cert_path" ]; then + echo "File supplied via --extra-ca-cert-path doesn't exist." + exit 1 + fi + + default_kind_base_image=$(kind build node-image --help | sed -n 's/.*--base-image.*default ".*\(kindest[^"]*\)".*/\1/p') + if [ -z "$default_kind_base_image" ]; then + echo "Unable to locate default base image." + exit 1 + fi + + # Gets overwritten if original base image tag is used, so alter. + built_kind_base_image_tag="$default_kind_base_image-extracacert" + + temp_dir=$(mktemp -d) + copied_extra_ca_cert_filename="extra-ca-cert.crt" + + cp "$extra_ca_cert_path" "$temp_dir/$copied_extra_ca_cert_filename" + docker build \ + -f extracacert/Dockerfile \ + -t "$built_kind_base_image_tag" \ + --build-arg "BASE_IMAGE=$default_kind_base_image" \ + --build-arg "EXTRA_CA_CERT_FILENAME=$copied_extra_ca_cert_filename" \ + "$temp_dir" + rm -rf "$temp_dir" + + kind build node-image \ + --type release "$kind_kube_version" \ + --base-image "$built_kind_base_image_tag" \ + --image "$kind_node_docker_tag" + else + kind build node-image \ + --type release "$kind_kube_version" \ + --image "$kind_node_docker_tag" + fi fi # shellcheck disable=SC2154 @@ -39,7 +87,6 @@ kubectl apply -k config/metricsserver --kubeconfig "$kind_kubeconfig" # shellcheck disable=SC2154 docker pull "$echo_server_docker_image_tag" -# shellcheck disable=SC2154 kind load docker-image "$echo_server_docker_image_tag" --name "$kind_cluster_name" # shellcheck disable=SC2154 diff --git a/scripts/sandbox/extracacert/Dockerfile b/scripts/sandbox/extracacert/Dockerfile new file mode 100644 index 0000000..5772d34 --- /dev/null +++ b/scripts/sandbox/extracacert/Dockerfile @@ -0,0 +1,6 @@ +ARG BASE_IMAGE=kindest/base:v20241108-5c6d2daf + +FROM ${BASE_IMAGE} +ARG EXTRA_CA_CERT_FILENAME +COPY ./${EXTRA_CA_CERT_FILENAME} /usr/local/share/ca-certificates/extra-ca-cert.crt +RUN update-ca-certificates diff --git a/test/integration/extracacert/Dockerfile b/test/integration/extracacert/Dockerfile new file mode 100644 index 0000000..5772d34 --- /dev/null +++ b/test/integration/extracacert/Dockerfile @@ -0,0 +1,6 @@ +ARG BASE_IMAGE=kindest/base:v20241108-5c6d2daf + +FROM ${BASE_IMAGE} +ARG EXTRA_CA_CERT_FILENAME +COPY ./${EXTRA_CA_CERT_FILENAME} /usr/local/share/ca-certificates/extra-ca-cert.crt +RUN update-ca-certificates diff --git a/test/integration/kind.go b/test/integration/kind.go index 84481e0..c3cc1e9 100644 --- a/test/integration/kind.go +++ b/test/integration/kind.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "os/exec" + "regexp" "strings" "testing" @@ -66,12 +67,12 @@ func kindSetupCluster(t *testing.T) { os.Exit(1) } - dockerTag := fmt.Sprintf("kindest/node:%s", kubeFullVersion) + dockerTag := "kindest/node:" + kubeFullVersion output, _ = cmdRun( t, exec.Command("docker", "images", - "--filter", fmt.Sprintf("reference=%s", dockerTag), + "--filter", "reference="+dockerTag, "--format", "{{.Repository}}:{{.Tag}}", ), "getting existing docker images...", @@ -80,16 +81,82 @@ func kindSetupCluster(t *testing.T) { ) if output == "" { - _, _ = cmdRun( - t, - exec.Command("kind", "build", "node-image", - "--type", "release", kubeFullVersion, - "--image", dockerTag, - ), - "building kind node image...", - "unable to build kind node image", - true, - ) + if suppliedConfig.extraCaCertPath != "" { + output, _ = cmdRun( + t, + exec.Command("kind", "build", "node-image", "--help"), + "getting kind default node image...", + "unable to get kind default node image", + true, + ) + + defaultKindBaseImage := regexp.MustCompile(`kindest/base:v[0-9]+-[a-f0-9]+`).FindString(output) + if defaultKindBaseImage == "" { + logMessage(t, "unable to locate default base image") + os.Exit(1) + } + + builtKindBaseImageTag := defaultKindBaseImage + "-extracacert" + + tempDir, err := os.MkdirTemp("", "*") + if err != nil { + logMessage(t, common.WrapErrorf(err, "unable to create temporary directory")) + os.Exit(1) + } + defer func(path string) { + _ = os.RemoveAll(path) + }(tempDir) + + copiedExtraCaCertFilename := "extra-ca-cert.crt" + + cert, err := os.ReadFile(suppliedConfig.extraCaCertPath) + if err != nil { + logMessage(t, common.WrapErrorf(err, "unable to read extra CA certificate file")) + os.Exit(1) + } + + if err := os.WriteFile(tempDir+pathSeparator+copiedExtraCaCertFilename, cert, 0644); err != nil { + logMessage(t, common.WrapErrorf(err, "unable to write CA certificate file in temporary directory")) + os.Exit(1) + } + + _, _ = cmdRun( + t, + exec.Command("docker", "build", + "-f", "extracacert/Dockerfile", + "-t", builtKindBaseImageTag, + "--build-arg", "BASE_IMAGE="+defaultKindBaseImage, + "--build-arg", "EXTRA_CA_CERT_FILENAME="+copiedExtraCaCertFilename, + tempDir, + ), + "building kind base image...", + "unable to build kind base image", + true, + ) + + _, _ = cmdRun( + t, + exec.Command("kind", "build", "node-image", + "--type", "release", kubeFullVersion, + "--base-image", builtKindBaseImageTag, + "--image", dockerTag, + ), + "building kind node image...", + "unable to build kind node image", + true, + ) + } else { + _, _ = cmdRun( + t, + exec.Command("kind", "build", "node-image", + "--type", "release", kubeFullVersion, + "--image", dockerTag, + ), + "building kind node image...", + "unable to build kind node image", + true, + ) + } } _, _ = cmdRun( diff --git a/test/integration/suppliedconfig.go b/test/integration/suppliedconfig.go index 4ce3985..18b39fa 100644 --- a/test/integration/suppliedconfig.go +++ b/test/integration/suppliedconfig.go @@ -25,6 +25,7 @@ import ( type suppliedConfigStruct struct { kubeVersion string maxParallelism string + extraCaCertPath string reuseCluster bool installMetricsServer bool keepCsa bool @@ -35,6 +36,7 @@ type suppliedConfigStruct struct { var suppliedConfig = suppliedConfigStruct{ kubeVersion: "", maxParallelism: "4", + extraCaCertPath: "", reuseCluster: false, installMetricsServer: false, keepCsa: false, @@ -43,16 +45,18 @@ var suppliedConfig = suppliedConfigStruct{ } func suppliedConfigInit() { - suppliedConfigSetString("KUBE_VERSION", &suppliedConfig.kubeVersion) - suppliedConfigSetString("MAX_PARALLELISM", &suppliedConfig.maxParallelism) - suppliedConfigSetBool("REUSE_CLUSTER", &suppliedConfig.reuseCluster) - suppliedConfigSetBool("INSTALL_METRICS_SERVER", &suppliedConfig.installMetricsServer) - suppliedConfigSetBool("KEEP_CSA", &suppliedConfig.keepCsa) - suppliedConfigSetBool("KEEP_CLUSTER", &suppliedConfig.keepCluster) - suppliedConfigSetBool("DELETE_NS_AFTER_TEST", &suppliedConfig.deleteNsPostTest) + suppliedConfigSetString("KUBE_VERSION", &suppliedConfig.kubeVersion, true) + suppliedConfigSetString("MAX_PARALLELISM", &suppliedConfig.maxParallelism, true) + suppliedConfigSetString("EXTRA_CA_CERT_PATH", &suppliedConfig.extraCaCertPath, false) + suppliedConfigSetBool("REUSE_CLUSTER", &suppliedConfig.reuseCluster, true) + suppliedConfigSetBool("INSTALL_METRICS_SERVER", &suppliedConfig.installMetricsServer, true) + suppliedConfigSetBool("KEEP_CSA", &suppliedConfig.keepCsa, true) + suppliedConfigSetBool("KEEP_CLUSTER", &suppliedConfig.keepCluster, true) + suppliedConfigSetBool("DELETE_NS_AFTER_TEST", &suppliedConfig.deleteNsPostTest, true) - logMessage(nil, fmt.Sprintf("(config) KUBE_VERSION: %s", suppliedConfig.kubeVersion)) - logMessage(nil, fmt.Sprintf("(config) MAX_PARALLELISM: %s", suppliedConfig.maxParallelism)) + logMessage(nil, fmt.Sprintf("(config) KUBE_VERSION: "+suppliedConfig.kubeVersion)) + logMessage(nil, fmt.Sprintf("(config) MAX_PARALLELISM: "+suppliedConfig.maxParallelism)) + logMessage(nil, fmt.Sprintf("(config) EXTRA_CA_CERT_PATH: "+suppliedConfig.extraCaCertPath)) logMessage(nil, fmt.Sprintf("(config) REUSE_CLUSTER: %t", suppliedConfig.reuseCluster)) logMessage(nil, fmt.Sprintf("(config) INSTALL_METRICS_SERVER: %t", suppliedConfig.installMetricsServer)) logMessage(nil, fmt.Sprintf("(config) KEEP_CSA: %t", suppliedConfig.keepCsa)) @@ -60,11 +64,11 @@ func suppliedConfigInit() { logMessage(nil, fmt.Sprintf("(config) DELETE_NS_AFTER_TEST: %t", suppliedConfig.deleteNsPostTest)) } -func suppliedConfigSetString(env string, config *string) { +func suppliedConfigSetString(env string, config *string, required bool) { envVal := os.Getenv(env) + haveEnvOrDefault := envVal != "" || (config != nil && *config != "") - if envVal == "" && (config == nil || *config == "") { - // Require env unless defaulted via supplied. + if !haveEnvOrDefault && required { logMessage(nil, fmt.Sprintf("(config) '%s' value is required", env)) os.Exit(1) } @@ -74,11 +78,11 @@ func suppliedConfigSetString(env string, config *string) { } } -func suppliedConfigSetBool(env string, config *bool) { +func suppliedConfigSetBool(env string, config *bool, required bool) { envVal := os.Getenv(env) + haveEnvOrDefault := envVal != "" || config != nil - if envVal == "" && config == nil { - // Require env unless defaulted via supplied. + if !haveEnvOrDefault && required { logMessage(nil, fmt.Sprintf("(config) '%s' value is required", env)) os.Exit(1) }