source-controller icon indicating copy to clipboard operation
source-controller copied to clipboard

GitRepository no longer works with 1.5.0

Open Szpadel opened this issue 9 months ago • 14 comments

After upgrading to 1.5.0 gitrepository resources fail with error failed to checkout and determine revision: unable to clone 'ssh://[email protected]:2221/example/example.git': exec: "git": executable file not found in $PATH'

what might be not standard here is custom port used for git access and recurseSubmodules: true

Szpadel avatar Mar 28 '25 08:03 Szpadel

The git binary is not present in 1.4.1 either, that's because we use gogit for talking the Git protocol, purely in Go.

docker run -it --rm --entrypoint /bin/sh ghcr.io/fluxcd/source-controller:v1.4.1
~ $ git
/bin/sh: git: not found
~ $ exit
docker run -it --rm --entrypoint /bin/sh ghcr.io/fluxcd/source-controller:v1.5.0
~ $ git
/bin/sh: git: not found
~ $ exit

matheuscscp avatar Mar 28 '25 13:03 matheuscscp

So it looks like there is some incorrect reference introduced in code?

Szpadel avatar Mar 28 '25 13:03 Szpadel

Can you please share your manifests?

matheuscscp avatar Mar 28 '25 13:03 matheuscscp

I think combination here is: recurseSubmodules: true + custom port in url

kind: GitRepository
metadata:
  name: example
  namespace: flux-system
spec:
  interval: 1m
  recurseSubmodules: true
  url: ssh://[email protected]:1234/repo.git
  secretRef:
    name: sshkey

Szpadel avatar Jun 16 '25 06:06 Szpadel

OK, I have easy reproduction path: Please check this repository: https://github.com/Szpadel/flux-repr/ You need to have this manifest:

apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: example
  namespace: flux-system
spec:
  interval: 1m
  recurseSubmodules: true
  ref:
    branch: main
  url: https://github.com/Szpadel/flux-repr.git

and set gitmodule with relative path:

[submodule "example"]
	path = example
	url = ../chrome-headless-render-pdf

I suspect it's caused by this code: https://github.com/go-git/go-git/blob/main/plumbing/transport/file/client.go Relevant: https://github.com/go-git/go-git/issues/339

Szpadel avatar Jun 16 '25 09:06 Szpadel

I do not believe those logs are useful, because it looks like real failure is masked by flux

{"level":"error","ts":"2025-06-16T06:53:46.777Z","msg":"failed to checkout and determine revision: unable to clone 'ssh://[email protected]:2221/example/example.git': exec: \"git\": executable file not found in $PATH","name":"cluster-config","namespace":"flux-system","reconciler kind":"GitRepository","annotations":null,"error":"GitOperationFailed","stacktrace":"github.com/fluxcd/pkg/runtime/events.(*Recorder).AnnotatedEventf\n\tgithub.com/fluxcd/pkg/[email protected]/events/recorder.go:179\ngithub.com/fluxcd/pkg/runtime/events.(*Recorder).Eventf\n\tgithub.com/fluxcd/pkg/[email protected]/events/recorder.go:143\ngithub.com/fluxcd/source-controller/internal/reconcile/summarize.recordEvent\n\tgithub.com/fluxcd/source-controller/internal/reconcile/summarize/processor.go:101\ngithub.com/fluxcd/source-controller/internal/reconcile/summarize.ErrorActionHandler\n\tgithub.com/fluxcd/source-controller/internal/reconcile/summarize/processor.go:58\ngithub.com/fluxcd/source-controller/internal/reconcile/summarize.(*Helper).SummarizeAndPatch\n\tgithub.com/fluxcd/source-controller/internal/reconcile/summarize/summary.go:193\ngithub.com/fluxcd/source-controller/internal/controller.(*GitRepositoryReconciler).Reconcile.func1\n\tgithub.com/fluxcd/source-controller/internal/controller/gitrepository_controller.go:211\ngithub.com/fluxcd/source-controller/internal/controller.(*GitRepositoryReconciler).Reconcile\n\tgithub.com/fluxcd/source-controller/internal/controller/gitrepository_controller.go:248\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Reconcile\n\tsigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:119\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler\n\tsigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:340\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem\n\tsigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:300\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.1\n\tsigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:202"}
{"level":"debug","ts":"2025-06-16T06:53:46.779Z","logger":"events","msg":"failed to checkout and determine revision: unable to clone 'ssh://[email protected]:2221/example/example.git': exec: \"git\": executable file not found in $PATH","type":"Warning","object":{"kind":"GitRepository","namespace":"flux-system","name":"cluster-config","uid":"ff6be494-5922-40a8-9d11-4e6d0b6194de","apiVersion":"source.toolkit.fluxcd.io/v1","resourceVersion":"34193087"},"reason":"GitOperationFailed"}
{"level":"error","ts":"2025-06-16T06:53:46.810Z","msg":"Reconciler error","controller":"gitrepository","controllerGroup":"source.toolkit.fluxcd.io","controllerKind":"GitRepository","GitRepository":{"name":"cluster-config","namespace":"flux-system"},"namespace":"flux-system","name":"cluster-config","reconcileID":"c6feeca9-d69c-4d22-9c50-2342487cbd5f","error":"failed to checkout and determine revision: unable to clone 'ssh://[email protected]:2221/example/example.git': exec: \"git\": executable file not found in $PATH","stacktrace":"sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler\n\tsigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:353\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem\n\tsigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:300\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.1\n\tsigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:202"}

Szpadel avatar Jun 16 '25 09:06 Szpadel

The GitRepo above reconciles successfully for me in Flux 2.4.0:

k get fluxinstance flux
NAME   AGE   READY   STATUS                          REVISION
flux   87s   True    Reconciliation finished in 1s   v2.4.0@sha256:e8b07973149a4b84d946be222086ac8289c241ff80fa2ce39a28c62c577fa6fb
k get gitrepo
NAME      URL                                        AGE   READY   STATUS
example   https://github.com/Szpadel/flux-repr.git   56s   True    stored artifact for revision 'main@sha1:791b1e0e8c5e444da9cc3c69d2e7fb08329f094b'

And fails with exec: "git": executable file not found in $PATH in Flux 2.5.1:

k get fluxinstance flux
NAME   AGE     READY   STATUS                          REVISION
flux   8m35s   True    Reconciliation finished in 1s   v2.5.1@sha256:adf61999819254cbbf77f7a7aa6427c8cc63d1a1a94f2d169a9ac11d96fcf6bb
k get gitrepo
NAME      URL                                        AGE   READY   STATUS
example   https://github.com/Szpadel/flux-repr.git   8s    False   failed to checkout and determine revision: unable to clone 'https://github.com/Szpadel/flux-repr.git': exec: "git": executable file not found in $PATH

This is indeed a regression in SC from 1.4.0 to 1.5.0.

@pjbgf Maybe you have some insights? I don't think we changed anything drastically in this code path in Flux from 2.4 to 2.5

matheuscscp avatar Jun 16 '25 14:06 matheuscscp

Additional Investigation: go-git CVE Fix Breaking Submodule Relative URLs

Hi 👋 I had some time and got curious about this issue, so I did some digging on my end. Thought I'd share what I found in case it helps:

Working vs Non-working Versions

  • Flux 2.4.0 (go-git v5.12.0): Submodules work correctly
  • Flux 2.5.1 (go-git v5.13.2): Submodules fail to clone
  • This indicates a regression between these versions

Problem Identified: Relative Submodule URLs

I forked @Szpadel's reproduction repository and confirmed the issue is specifically with relative submodule URLs:

Original repo (fails): https://github.com/Szpadel/flux-repr
Fixed repo (works): https://github.com/cappyzawa/flux-repr

Change required:

[submodule "subdir"]
    path = subdir
-   url = ../flux-repr-sub
+   url = https://github.com/Szpadel/flux-repr-sub

Results:

  • ❌ Relative URLs: GitRepository fails to become Ready
  • ✅ Absolute URLs: GitRepository becomes Ready

Test Environment:

  • Flux v2.5.1
  • source-controller image: fluxcd/source-controller:v1.5.0
  • go-git version: v5.13.2

Root Cause: CVE-2025-21613 Security Fix

The regression is caused by PR #1141 in go-git, which was a security fix for CVE-2025-21613.

Commit: 9550dfab (July 13, 2024)
Title: "plumbing: transport/file, Change paths to absolute"

The Breaking Change

In plumbing/transport/common.go, the parseFile function was modified:

// Before (v5.12.0)
func parseFile(endpoint string) (*Endpoint, bool) {
    if giturl.MatchesScheme(endpoint) {
        return nil, false
    }

    path := endpoint  // ← Used as-is
    return &Endpoint{
        Protocol: "file",
        Path:     path,
    }, true
}

// After (v5.13.0+)
func parseFile(endpoint string) (*Endpoint, bool) {
    if giturl.MatchesScheme(endpoint) {
        return nil, false
    }

    path, err := filepath.Abs(endpoint)  // ← Forced to absolute path
    if err != nil {
        return nil, false
    }

    return &Endpoint{
        Protocol: "file",
        Path:     path,
    }, true
}

Impact on Submodule Relative URLs

Before the fix:

  • Relative URL ../other-repofile://../other-repo
  • Processed as relative path

After the fix:

  • Relative URL ../other-repofile:///absolute/path/other-repo
  • Forced to absolute path, treated as local file
  • Triggers file transport which requires git binary
  • Results in: exec: "git": executable file not found in $PATH

Test Evidence

The commit also updated test expectations, proving the behavioral change:

// Before
c.Assert(e.Path, Equals, "foo.git")
c.Assert(e.String(), Equals, "file://foo.git")

// After
abs, err := filepath.Abs("foo.git")
c.Assert(e.Path, Equals, abs)
c.Assert(e.String(), Equals, "file://"+abs)

Impact Assessment

This is an unintended breaking change introduced by a security fix. While the security fix was necessary, it broke the relative URL resolution for submodules that don't use explicit protocols.

Temporary Workaround

Until this regression is fixed:

  1. Convert relative submodule URLs to absolute URLs in .gitmodules
  2. Or downgrade to Flux 2.4.0

Hope this helps! Let me know if you need any clarification or want me to test anything else 👍

cappyzawa avatar Jun 17 '25 03:06 cappyzawa

@cappyzawa thanks for weighing in, that's spot on.

There is already an issue for this in https://github.com/go-git/go-git/issues/1380, which is likely only going to be resolved on the v6 release.

pjbgf avatar Jun 17 '25 08:06 pjbgf

Instead of downgrading to Flux 2.4.0 for which support will end in Q3 2025, I suggest updating the repo with absolute URLs for the Git submodule.

stefanprodan avatar Jun 17 '25 08:06 stefanprodan

@stefanprodan Just wanted to point that it's not universal solution. For some users this might be just using http vs ssh for cloning, but in our case we have to use different url when cloning from local machine vs from the cluster and using relative path in submodule is the only way this works in all cases.

If we could have some workaround on flux side that would be very appreciated.

Szpadel avatar Jun 17 '25 09:06 Szpadel

If we could have some workaround on flux side that would be very appreciated.

There is no workaround, Flux uses go-git and we can't rollback to a version with CVEs.

Instead of Git submodules you could use the include feature https://fluxcd.io/flux/components/source/gitrepositories/#include

Another option is to replace the GitRepository with an OCIRepository and in CI you'll push the manifests to a container registry with flux push artifact (this is the recommend way IMO).

stefanprodan avatar Jun 17 '25 09:06 stefanprodan

@stefanprodan what do you think about this workaround suggestion? https://github.com/go-git/go-git/issues/1380#issuecomment-2979539547 Is this viable way to handle it before it's passed to go-git?

Instead of Git submodules you could the include feature https://fluxcd.io/flux/components/source/gitrepositories/#include

This is something I considered before going submodules route, but unfortunately it's not straightforward to version submodule in that way

Szpadel avatar Jun 17 '25 09:06 Szpadel

This was raised once more in #1837

matheuscscp avatar Jun 25 '25 12:06 matheuscscp