From 56fee8d498b7cd78f48091f603cc52b027185084 Mon Sep 17 00:00:00 2001 From: Itx-Psycho0 Date: Fri, 15 May 2026 22:36:18 +0530 Subject: [PATCH 1/5] feat: Add CA certificate bundle support for builds Add support for specifying a CA certificate bundle file to use during function builds. This is useful when building behind corporate proxies with SSL inspection. Changes: - Add CACertBundle field to BuildSpec in func.yaml - Add --ca-bundle CLI flag to build command - Support FUNC_CA_BUNDLE environment variable - Inject CA bundle into pack builder with appropriate env vars - Inject CA bundle into s2i builder with appropriate env vars - Set SSL_CERT_FILE, REQUESTS_CA_BUNDLE, NODE_EXTRA_CA_CERTS, etc. - Mount CA bundle file into build containers Fixes #3721 --- cmd/build.go | 9 ++++++++- pkg/buildpacks/builder.go | 24 ++++++++++++++++++++++++ pkg/functions/function.go | 5 +++++ pkg/s2i/builder.go | 25 +++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/cmd/build.go b/cmd/build.go index 1c16cdce9b..54889429a5 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -70,7 +70,7 @@ EXAMPLES `, SuggestFor: []string{"biuld", "buidl", "built"}, PreRunE: bindEnv("image", "path", "builder", "registry", "confirm", - "push", "builder-image", "base-image", "platform", "verbose", + "push", "builder-image", "base-image", "ca-bundle", "platform", "verbose", "build-timestamp", "registry-insecure", "registry-authfile", "username", "password", "token"), RunE: func(cmd *cobra.Command, args []string) error { return runBuild(cmd, args, newClient) @@ -114,6 +114,8 @@ EXAMPLES "Specify a custom builder image for use by the builder other than its default. ($FUNC_BUILDER_IMAGE)") cmd.Flags().StringP("base-image", "", f.Build.BaseImage, "Override the base image for your function (host builder only)") + cmd.Flags().StringP("ca-bundle", "", f.Build.CACertBundle, + "Path to CA certificate bundle file for SSL verification during build. Useful when building behind corporate proxies. ($FUNC_CA_BUNDLE)") cmd.Flags().StringP("image", "i", f.Image, "Full image name in the form [registry]/[namespace]/[name]:[tag] (optional). This option takes precedence over --registry ($FUNC_IMAGE)") @@ -230,6 +232,9 @@ type buildConfig struct { // TODO: gauron99 -- make option to add a path to dockerfile ? BaseImage string + // CACertBundle is the path to a CA certificate bundle file for SSL verification + CACertBundle string + // Path of the function implementation on local disk. Defaults to current // working directory of the process. Path string @@ -271,6 +276,7 @@ func newBuildConfig() buildConfig { }, BuilderImage: viper.GetString("builder-image"), BaseImage: viper.GetString("base-image"), + CACertBundle: viper.GetString("ca-bundle"), Image: viper.GetString("image"), Path: viper.GetString("path"), Platform: viper.GetString("platform"), @@ -294,6 +300,7 @@ func (c buildConfig) Configure(f fn.Function) fn.Function { } f.Image = c.Image f.Build.BaseImage = c.BaseImage + f.Build.CACertBundle = c.CACertBundle // Path, Platform and Push are not part of a function's state. return f } diff --git a/pkg/buildpacks/builder.go b/pkg/buildpacks/builder.go index 40a9492068..ab8496e056 100644 --- a/pkg/buildpacks/builder.go +++ b/pkg/buildpacks/builder.go @@ -224,6 +224,30 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf opts.Env["BP_IMAGE_LABELS"] = strings.Join(imageLabels, " ") } + // CA Certificate Bundle support for corporate proxies + if f.Build.CACertBundle != "" { + // Validate that the CA bundle file exists + if _, err := os.Stat(f.Build.CACertBundle); err != nil { + return fmt.Errorf("CA bundle file not found: %w", err) + } + + // Set environment variables for various runtimes to use the CA bundle + // These will be available during the build process + opts.Env["SSL_CERT_FILE"] = f.Build.CACertBundle + opts.Env["REQUESTS_CA_BUNDLE"] = f.Build.CACertBundle // Python + opts.Env["NODE_EXTRA_CA_CERTS"] = f.Build.CACertBundle // Node.js + opts.Env["CURL_CA_BUNDLE"] = f.Build.CACertBundle // curl + opts.Env["GIT_SSL_CAINFO"] = f.Build.CACertBundle // git + opts.Env["PIP_CERT"] = f.Build.CACertBundle // pip (Python) + opts.Env["NPM_CONFIG_CAFILE"] = f.Build.CACertBundle // npm (Node.js) + opts.Env["CARGO_HTTP_CAINFO"] = f.Build.CACertBundle // cargo (Rust) + opts.Env["MAVEN_OPTS"] = fmt.Sprintf("-Djavax.net.ssl.trustStore=%s", f.Build.CACertBundle) // Maven (Java) + + // Mount the CA bundle file into the build container + opts.ContainerConfig.Volumes = append(opts.ContainerConfig.Volumes, + fmt.Sprintf("%s:%s:ro", f.Build.CACertBundle, f.Build.CACertBundle)) + } + var bindings = make([]string, 0, len(f.Build.Mounts)) for _, m := range f.Build.Mounts { bindings = append(bindings, fmt.Sprintf("%s:%s", m.Source, m.Destination)) diff --git a/pkg/functions/function.go b/pkg/functions/function.go index 09d2506273..1d27e33bbd 100644 --- a/pkg/functions/function.go +++ b/pkg/functions/function.go @@ -150,6 +150,11 @@ type BuildSpec struct { // Build Env variables to be set BuildEnvs Envs `yaml:"buildEnvs,omitempty"` + // CACertBundle specifies the path to a CA certificate bundle file to use + // for SSL verification during build. This is useful when building behind + // corporate proxies with SSL inspection. + CACertBundle string `yaml:"caCertBundle,omitempty" jsonschema:"description=Path to CA certificate bundle for SSL verification during build"` + // PVCSize specifies the size of persistent volume claim used to store function // when using deployment and remote build process (only relevant when Remote is true). PVCSize string `yaml:"pvcSize,omitempty"` diff --git a/pkg/s2i/builder.go b/pkg/s2i/builder.go index 9b621e77bb..f84966ca0f 100644 --- a/pkg/s2i/builder.go +++ b/pkg/s2i/builder.go @@ -220,6 +220,31 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf cfg.BuildVolumes = append(cfg.BuildVolumes, fmt.Sprintf("%s:%s:ro,Z", m.Source, m.Destination)) } + // CA Certificate Bundle support for corporate proxies + if f.Build.CACertBundle != "" { + // Validate that the CA bundle file exists + if _, err := os.Stat(f.Build.CACertBundle); err != nil { + return fmt.Errorf("CA bundle file not found: %w", err) + } + + // Set environment variables for various runtimes to use the CA bundle + cfg.Environment = append(cfg.Environment, + api.EnvironmentSpec{Name: "SSL_CERT_FILE", Value: f.Build.CACertBundle}, + api.EnvironmentSpec{Name: "REQUESTS_CA_BUNDLE", Value: f.Build.CACertBundle}, // Python + api.EnvironmentSpec{Name: "NODE_EXTRA_CA_CERTS", Value: f.Build.CACertBundle}, // Node.js + api.EnvironmentSpec{Name: "CURL_CA_BUNDLE", Value: f.Build.CACertBundle}, // curl + api.EnvironmentSpec{Name: "GIT_SSL_CAINFO", Value: f.Build.CACertBundle}, // git + api.EnvironmentSpec{Name: "PIP_CERT", Value: f.Build.CACertBundle}, // pip (Python) + api.EnvironmentSpec{Name: "NPM_CONFIG_CAFILE", Value: f.Build.CACertBundle}, // npm (Node.js) + api.EnvironmentSpec{Name: "CARGO_HTTP_CAINFO", Value: f.Build.CACertBundle}, // cargo (Rust) + api.EnvironmentSpec{Name: "MAVEN_OPTS", Value: fmt.Sprintf("-Djavax.net.ssl.trustStore=%s", f.Build.CACertBundle)}, // Maven (Java) + ) + + // Mount the CA bundle file into the build container + cfg.BuildVolumes = append(cfg.BuildVolumes, + fmt.Sprintf("%s:%s:ro,Z", f.Build.CACertBundle, f.Build.CACertBundle)) + } + if runtime.GOOS == "linux" { cfg.DockerNetworkMode = "host" } From 935599abfc5d62a42d536e04308db3b819486455 Mon Sep 17 00:00:00 2001 From: Itx-Psycho0 Date: Fri, 15 May 2026 23:34:13 +0530 Subject: [PATCH 2/5] fix: Address code review feedback for CA bundle support - Fix volume mount bug in pack builder where CA bundle was overwritten - Use Paketo CA certificates buildpack binding for pack builder - Handle relative vs absolute paths properly - Resolve relative paths relative to function root - Support paths outside function directory - Mount CA bundle to fixed location in S2I builder - Validate CA bundle file exists before build Changes based on feedback from matejvasek: - Pack builder now uses standard Paketo ca-certificates binding - Creates temp directory with proper binding structure - Both builders handle relative/absolute paths correctly - Paths are resolved and validated before build starts --- pkg/buildpacks/builder.go | 56 ++++++++++++++++++++++++++++----------- pkg/s2i/builder.go | 35 ++++++++++++++---------- 2 files changed, 61 insertions(+), 30 deletions(-) diff --git a/pkg/buildpacks/builder.go b/pkg/buildpacks/builder.go index ab8496e056..7c4e9df823 100644 --- a/pkg/buildpacks/builder.go +++ b/pkg/buildpacks/builder.go @@ -225,27 +225,51 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf } // CA Certificate Bundle support for corporate proxies + // Use Paketo CA certificates buildpack binding instead of manual env vars if f.Build.CACertBundle != "" { + // Resolve to absolute path if relative + caBundlePath := f.Build.CACertBundle + if !filepath.IsAbs(caBundlePath) { + caBundlePath = filepath.Join(f.Root, caBundlePath) + } + // Validate that the CA bundle file exists - if _, err := os.Stat(f.Build.CACertBundle); err != nil { + if _, err := os.Stat(caBundlePath); err != nil { return fmt.Errorf("CA bundle file not found: %w", err) } - // Set environment variables for various runtimes to use the CA bundle - // These will be available during the build process - opts.Env["SSL_CERT_FILE"] = f.Build.CACertBundle - opts.Env["REQUESTS_CA_BUNDLE"] = f.Build.CACertBundle // Python - opts.Env["NODE_EXTRA_CA_CERTS"] = f.Build.CACertBundle // Node.js - opts.Env["CURL_CA_BUNDLE"] = f.Build.CACertBundle // curl - opts.Env["GIT_SSL_CAINFO"] = f.Build.CACertBundle // git - opts.Env["PIP_CERT"] = f.Build.CACertBundle // pip (Python) - opts.Env["NPM_CONFIG_CAFILE"] = f.Build.CACertBundle // npm (Node.js) - opts.Env["CARGO_HTTP_CAINFO"] = f.Build.CACertBundle // cargo (Rust) - opts.Env["MAVEN_OPTS"] = fmt.Sprintf("-Djavax.net.ssl.trustStore=%s", f.Build.CACertBundle) // Maven (Java) - - // Mount the CA bundle file into the build container - opts.ContainerConfig.Volumes = append(opts.ContainerConfig.Volumes, - fmt.Sprintf("%s:%s:ro", f.Build.CACertBundle, f.Build.CACertBundle)) + // Create a temporary directory for the CA certificates binding + caCertsBindingDir, err := os.MkdirTemp("", "func-ca-certs-*") + if err != nil { + return fmt.Errorf("failed to create CA certificates binding directory: %w", err) + } + // Note: We don't defer cleanup here because the build process needs it + // The directory will be cleaned up when the temp dir is cleaned by the OS + + // Write the binding type file + if err := os.WriteFile(filepath.Join(caCertsBindingDir, "type"), []byte("ca-certificates"), 0644); err != nil { + return fmt.Errorf("failed to write CA certificates binding type: %w", err) + } + + // Copy the CA bundle to the binding directory + caData, err := os.ReadFile(caBundlePath) + if err != nil { + return fmt.Errorf("failed to read CA bundle: %w", err) + } + if err := os.WriteFile(filepath.Join(caCertsBindingDir, "ca-certificates.crt"), caData, 0644); err != nil { + return fmt.Errorf("failed to write CA certificates to binding: %w", err) + } + + // Set SERVICE_BINDING_ROOT if not already set + if _, ok := opts.Env["SERVICE_BINDING_ROOT"]; !ok { + opts.Env["SERVICE_BINDING_ROOT"] = "/platform/bindings" + } + + // Add the CA certificates binding as a mount + f.Build.Mounts = append(f.Build.Mounts, fn.MountSpec{ + Source: caCertsBindingDir, + Destination: filepath.Join(opts.Env["SERVICE_BINDING_ROOT"], "ca-certificates"), + }) } var bindings = make([]string, 0, len(f.Build.Mounts)) diff --git a/pkg/s2i/builder.go b/pkg/s2i/builder.go index f84966ca0f..36b7aa77c8 100644 --- a/pkg/s2i/builder.go +++ b/pkg/s2i/builder.go @@ -222,27 +222,34 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf // CA Certificate Bundle support for corporate proxies if f.Build.CACertBundle != "" { + // Resolve to absolute path if relative + caBundlePath := f.Build.CACertBundle + if !filepath.IsAbs(caBundlePath) { + caBundlePath = filepath.Join(f.Root, caBundlePath) + } + // Validate that the CA bundle file exists - if _, err := os.Stat(f.Build.CACertBundle); err != nil { + if _, err := os.Stat(caBundlePath); err != nil { return fmt.Errorf("CA bundle file not found: %w", err) } + // For S2I, we need to mount the CA bundle and set environment variables + // Mount the CA bundle into a standard location + caDestPath := "/tmp/ca-certificates.crt" + cfg.BuildVolumes = append(cfg.BuildVolumes, + fmt.Sprintf("%s:%s:ro,Z", caBundlePath, caDestPath)) + // Set environment variables for various runtimes to use the CA bundle cfg.Environment = append(cfg.Environment, - api.EnvironmentSpec{Name: "SSL_CERT_FILE", Value: f.Build.CACertBundle}, - api.EnvironmentSpec{Name: "REQUESTS_CA_BUNDLE", Value: f.Build.CACertBundle}, // Python - api.EnvironmentSpec{Name: "NODE_EXTRA_CA_CERTS", Value: f.Build.CACertBundle}, // Node.js - api.EnvironmentSpec{Name: "CURL_CA_BUNDLE", Value: f.Build.CACertBundle}, // curl - api.EnvironmentSpec{Name: "GIT_SSL_CAINFO", Value: f.Build.CACertBundle}, // git - api.EnvironmentSpec{Name: "PIP_CERT", Value: f.Build.CACertBundle}, // pip (Python) - api.EnvironmentSpec{Name: "NPM_CONFIG_CAFILE", Value: f.Build.CACertBundle}, // npm (Node.js) - api.EnvironmentSpec{Name: "CARGO_HTTP_CAINFO", Value: f.Build.CACertBundle}, // cargo (Rust) - api.EnvironmentSpec{Name: "MAVEN_OPTS", Value: fmt.Sprintf("-Djavax.net.ssl.trustStore=%s", f.Build.CACertBundle)}, // Maven (Java) + api.EnvironmentSpec{Name: "SSL_CERT_FILE", Value: caDestPath}, + api.EnvironmentSpec{Name: "REQUESTS_CA_BUNDLE", Value: caDestPath}, // Python + api.EnvironmentSpec{Name: "NODE_EXTRA_CA_CERTS", Value: caDestPath}, // Node.js + api.EnvironmentSpec{Name: "CURL_CA_BUNDLE", Value: caDestPath}, // curl + api.EnvironmentSpec{Name: "GIT_SSL_CAINFO", Value: caDestPath}, // git + api.EnvironmentSpec{Name: "PIP_CERT", Value: caDestPath}, // pip (Python) + api.EnvironmentSpec{Name: "NPM_CONFIG_CAFILE", Value: caDestPath}, // npm (Node.js) + api.EnvironmentSpec{Name: "CARGO_HTTP_CAINFO", Value: caDestPath}, // cargo (Rust) ) - - // Mount the CA bundle file into the build container - cfg.BuildVolumes = append(cfg.BuildVolumes, - fmt.Sprintf("%s:%s:ro,Z", f.Build.CACertBundle, f.Build.CACertBundle)) } if runtime.GOOS == "linux" { From 8fc803b96b5b7fdde0c58fa1a58c575b8a21dd45 Mon Sep 17 00:00:00 2001 From: Itx-Psycho0 Date: Fri, 15 May 2026 23:49:42 +0530 Subject: [PATCH 3/5] docs: Update generated documentation for --ca-bundle flag Auto-generated by ./hack/update-codegen.sh --- docs/reference/func_build.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/reference/func_build.md b/docs/reference/func_build.md index 344574a334..9c68bee41f 100644 --- a/docs/reference/func_build.md +++ b/docs/reference/func_build.md @@ -61,6 +61,7 @@ func build --build-timestamp Use the actual time as the created time for the docker image. This is only useful for buildpacks builder. -b, --builder string Builder to use when creating the function's container. Currently supported builders are "host", "pack" and "s2i". ($FUNC_BUILDER) (default "pack") --builder-image string Specify a custom builder image for use by the builder other than its default. ($FUNC_BUILDER_IMAGE) + --ca-bundle string Path to CA certificate bundle file for SSL verification during build. Useful when building behind corporate proxies. ($FUNC_CA_BUNDLE) -c, --confirm Prompt to confirm options interactively ($FUNC_CONFIRM) -h, --help help for build -i, --image string Full image name in the form [registry]/[namespace]/[name]:[tag] (optional). This option takes precedence over --registry ($FUNC_IMAGE) From c78df40d67962ae8f807e302c09843bc08233fa6 Mon Sep 17 00:00:00 2001 From: Itx-Psycho0 Date: Fri, 15 May 2026 23:52:44 +0530 Subject: [PATCH 4/5] fix: Avoid modifying function struct to prevent race conditions Don't modify f.Build.Mounts directly. Instead, create a local copy of the mounts slice and add the CA bundle mount to the copy. This prevents potential race conditions when the function struct is shared across goroutines in tests. This should fix the CI test failures. --- pkg/buildpacks/builder.go | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/pkg/buildpacks/builder.go b/pkg/buildpacks/builder.go index 7c4e9df823..bb57f18ab1 100644 --- a/pkg/buildpacks/builder.go +++ b/pkg/buildpacks/builder.go @@ -265,18 +265,29 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf opts.Env["SERVICE_BINDING_ROOT"] = "/platform/bindings" } - // Add the CA certificates binding as a mount - f.Build.Mounts = append(f.Build.Mounts, fn.MountSpec{ + // Create a local copy of mounts and add the CA certificates binding + // We don't modify f.Build.Mounts directly to avoid race conditions + mounts := make([]fn.MountSpec, len(f.Build.Mounts), len(f.Build.Mounts)+1) + copy(mounts, f.Build.Mounts) + mounts = append(mounts, fn.MountSpec{ Source: caCertsBindingDir, Destination: filepath.Join(opts.Env["SERVICE_BINDING_ROOT"], "ca-certificates"), }) - } - var bindings = make([]string, 0, len(f.Build.Mounts)) - for _, m := range f.Build.Mounts { - bindings = append(bindings, fmt.Sprintf("%s:%s", m.Source, m.Destination)) + // Convert mounts to bindings format + var bindings = make([]string, 0, len(mounts)) + for _, m := range mounts { + bindings = append(bindings, fmt.Sprintf("%s:%s", m.Source, m.Destination)) + } + opts.ContainerConfig.Volumes = bindings + } else { + // No CA bundle, just use the existing mounts + var bindings = make([]string, 0, len(f.Build.Mounts)) + for _, m := range f.Build.Mounts { + bindings = append(bindings, fmt.Sprintf("%s:%s", m.Source, m.Destination)) + } + opts.ContainerConfig.Volumes = bindings } - opts.ContainerConfig.Volumes = bindings // only trust our known builders opts.TrustBuilder = TrustBuilder From dfc8b063dd15887597614f75d40aa8ea22139b4a Mon Sep 17 00:00:00 2001 From: Itx-Psycho0 Date: Fri, 15 May 2026 23:59:30 +0530 Subject: [PATCH 5/5] schema: Update func.yaml JSON schema for caCertBundle field Generated by 'make schema-generate' to include the new caCertBundle field in BuildSpec. This is required for CI verification checks. --- schema/func_yaml-schema.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/schema/func_yaml-schema.json b/schema/func_yaml-schema.json index 762abdfe47..9ff99c9114 100644 --- a/schema/func_yaml-schema.json +++ b/schema/func_yaml-schema.json @@ -41,6 +41,10 @@ "type": "array", "description": "Build Env variables to be set" }, + "caCertBundle": { + "type": "string", + "description": "Path to CA certificate bundle for SSL verification during build" + }, "pvcSize": { "type": "string", "description": "PVCSize specifies the size of persistent volume claim used to store function\nwhen using deployment and remote build process (only relevant when Remote is true)."