---
# System prepended metadata

title: 'Review of "Design: More Modules"'

---

# Review of "Design: More Modules"

> This doc was written by Mael on 16 May 2023.

After reading [20230302.gomod.md][], I realized that I didn't understand the problem we are solving with the many new go.mod files. In this document, I aim at sharing what I do understand as well as my opinions.

In this document, you will learn more about the problem with Helm's support window that doesn't match cert-manager's support window, as well as the larger problem of unsupported dependencies and how it affects our patch response time.

> **Mael's general opinion:** I don't see any major problem with the curent implementation, although I have opinions about it (see below "My opinion" paragraphs). We can go ahead a release 1.12 with the go.mod changes.
> 
> The problem of unsupported deps will still be painful, but we will have more options to fix things when a compiled dep becomes vulnerable. The risk of Go submodules still exists and we will see people getting broken somehow by our new setup.

[20230302.gomod.md]: https://github.com/cert-manager/cert-manager/blob/master/design/20230302.gomod.md "Design: More Modules"

## Dependency Attack Surface (compiled deps) vs. Patch Response Time (go.mod deps)

In this section, we detail how compiled and go.mod deps relate to the attack surface and the patch response time.

Let us start by clarifying the difference between the compiled dependencies (`go version -m`) versus the go.mod dependencies (`go list -m`).

**Compiled deps.** The compiled dependencies are the dependencies that get used (e.g., calling a function). The compiled deps depend on the Go binary that we ship in our containers (cmctl, controller, webhook, and cainjector).

**Go.mod deps.** The go.mod dependencies is a list of all dependencies stemming from the go.mod `require` statements. Many of these dependencies are not actually used. For example, the controller binary in cert-manager 1.11.0 has 631 "go mod" dependencies and only 152 compiled dependencies.

Now, let us define the "dependency surface attack" and the "patch response time", and why solving the patch response time is what we want to be solving.

**Dependency Surface Attack.** The dependency surface attack is the number of compiled deps that may be reported in CVEs for existing versions of cert-manager. The official tool [govulncheck](https://go.dev/blog/vuln) is our reference tool to determine vulnarabilities in Go projects. It only reports vulnerabilities for functions that are actually used (the compiled dependencies), not the whole go.mod dependencies. The larger the list of compiled deps is, the larger the dependency attack surface is. The dependency surface attack is different for each of the four binaries we ship.

Since the compiled dependencies don't change with the new Go submodules, we don't decrease the dependency attack surface for any of the binaries.

**Patch Response Time:** this is the time needed to upgrade a given dependency that was reported. The larger the go.mod dependencies list is, the harder it becomes to react in case of a CVE report.

To conclude, the patch response time is the problem we are trying to solve. Decreasing the dependency attack surface is not a problem we are trying to solve.

## The Problem of Slow Patch Response Time and Unsupported Dependencies

Our patch response time increases due to one problem: unsupported dependencies in our compiled binaries. To explain what an unsupported dependency is, I will summarize the example given in [20230302.gomod.md][].

The example given in [20230302.gomod.md][] focuses on [CVE-2023-25165][]. This CVE was reported for Helm 3.10.3. Helm 3.10.3 is a compiled dependency of cmctl 1.10.0 (and earlier versions). Since Helm 3.10 was only supported for 4 months\* and that the CVE was raised a month after the end of support of 3.10, the 3.10 branch wasn't patched.

[CVE-2023-25165]: https://github.com/advisories/GHSA-pwcw-6f5g-gxf8

> ***Note:** The Helm team only releases security patches for the latest minor version. When 3.11 was released in  and they release a minor version every 4 months.
> 
> | Version | Release date | End of support |
> |---------|--------------|----------------|
> | 3.12    | 10 May 2023  | ongoing        |
> | 3.11    | 11 Jan 2023  | 10 May 2023    |
> | 3.10    | 21 Sep 2022  | 11 Jan 2023    |
> 
> For context, here are cert-manager's supported releases around that time:
> 
> | Version | Release Date | End of support |
> |---------|--------------|----------------|
> | 1.11    | Jan 11, 2023 | ongoing        |
> | 1.10    | Oct 17, 2022 | 20 May 2023    |

That CVE hit us in the middle of the support window of cert-manager 1.10 which meant we had to respond with a patch version.

But upgrading from Helm 3.10 to 3.11 isn't acceptable for a patch release (even more so because it was bumping k8s.io/api too), at least not for the three main binaries (controller, webhook, and cainjector).

## Are Go submodules a Good Solution?

Our problem is that Helm (and possibly other dependencies) are not matching the support window of cert-manager, increasing indefinitely our patch response time.

It would have been possible to make an exception to the rule "no minor version bumps in patches" for cmctl if only it was possible to upgrade the cmctl dependencies independently from the other three binaries.

The design [20230302.gomod.md][] offers a solution to the problem of Helm not matching the support window of cert-manager. More specifically, the design aims at splitting cmctl from the rest of cert-manager.

> **My opinion:** I would much prefer split the check-api part away from cmctl. check-api would be shipped as a container and used during Helm installations, and cmctl would be retrograded to a "dev" tool: we would decide that cmctl is less critical with a simpler support window ("latest release only") to make sure we are in sync with Helm patches. This solution solves the problem.
> 
> Splitting cmctl with a new go.mod also solves the problem with Helm but with the additional risk and cost of the extra go.mod.
> 
> An altern
>
> Beyond that specific problem with Helm, the general problem of unsupported depedencies stems from mismatching support windows and I think we should also examine which other dependencies may have the same issue.
>
> I also wish we had tried reaching out to the Helm team and find out whether releasing Helm 3.10.4 was possible.

I suggest the following long-term remediations to the general problem of unsupported dependencies in our compiled deps:

- Examine (and re-examine over time) the support windows for each of our compiled dependencies and determine which dependencies pose a risk.
- Decrease the amount of compiled dependencies in each binary.

## Issues with the Scope of the Design

The design document goes beyond fixing the above problem of Helm not matching the support window of cert-manager. It aims at fixing the general problem of slow or indefinite patch response time. In this section, I'll examine the solutions that don't solve an immediate problem.

**Splitting webhook, controller, cainjector, acmesolver (for 1.12).** The solution also aims at splitting all the binaries, not just cmctl. I understand this goal: imagining that a dependency such as google-cloud-sdk becomes vulnerable, it would make sense to only bump the patch version of google-cloud-sdk in the controller binary and not in the webhook and cainjector.

> **My opinion:** This solution would alliviate a part of the general problem of unsupported releases by allowing us to make exceptions to the rule "no minor bumps in patches" when that makes sense. We would still have the problem of one binary having a different set of versions than the others (especially `k8s.io/api`), but it could be acceptable.
> 
> I think that examining the support windows of each of our compiled dependencies should be our first goal before splitting the binaries. If none of the dependencies pose a threat to the patch response time, I would not split the binaries into separate go.mod files.

**Splitting testing dependencies (for 1.12).** The solution aims at splitting the end-to-end test dependencies. Since these deps never become compiled dependencies, spitting them won't decrease the attack surface. Splitting these deps does reduce the number of go.mod deps, which helps in reducing the number of conflicting dependencies, but conflicting dependencies usually only happen when bumping the minor version of a given dependency (e.g., Helm 3.10 to 3.11), which is not something we do while patching a vulnerability.

> **My opinion:** I don't think splitting the end-to-end dependencies is solving a problem that we identified.

**Splitting controller into issuers (later goal).** The solution also aims at (later on) split the controller's go.mod into multiple go.mod files for each issuer.

> **My opinion:** I do not know what problem this is solving since we will still create the same controller binary.

**Standalone module for pkg/apis (later goal).**

> **My opinion:** This extra goal would require an exploration of the problem.
