Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add gomod git auth #509

Open
wants to merge 47 commits into
base: main
Choose a base branch
from

Conversation

pmengelbert
Copy link
Contributor

See Commit Descriptions

@pmengelbert pmengelbert requested a review from a team as a code owner January 29, 2025 19:38
@cpuguy83 cpuguy83 added this to the v0.12.0 milestone Jan 29, 2025
@pmengelbert
Copy link
Contributor Author

  • Write docs for feature, including auto-propagating the auth from the git source

Sorry, something went wrong.

@pmengelbert pmengelbert force-pushed the pmengelbert/troubleshoot_git_auth/2 branch from 91abd07 to d66c8cd Compare February 4, 2025 17:32
@@ -1,14 +1,20 @@
package dalec

import (
"bytes"
_ "embed"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is unused?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

gomodCacheDir = "/go/pkg/mod"
gomodCacheDir = "/go/pkg/mod"
gitConfigMountpoint = "/dev/shm/git"
scriptRelativePath = "/go_mod_download.sh"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give this variable a better name?
Truly, I think you can define the const inside the function you are using it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

const (
fourKB = 4096
workDir = "/work/src"
scriptMountPoint = "/tmp/mnt"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's give this a better path to make sure it's not overwriting anything in the builder image (just in case).
In other places I've done something like /tmp/dalec/internal/...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -54,6 +72,101 @@ func withGomod(g *SourceGenerator, srcSt, worker llb.State, opts ...llb.Constrai
}
}

func (g *SourceGenerator) gitconfigGeneratorScript() llb.State {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered utilizing GIT_ASKPASS or just more generally git credential helpers?
I think it would probably be worthwhile to handle secrets with a helper of some sort that can just read secrets directly from /run/secrets (where buildkit puts them) instead of trying to deal with the configuration file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK GIT_ASKPASS won't work with github sources since username/password authentication is not allowed by github. I'll look into the credential helpers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL about GIT_ASKPASS

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@cpuguy83 cpuguy83 modified the milestones: v0.12.0, vNext Feb 13, 2025
@pmengelbert pmengelbert force-pushed the pmengelbert/troubleshoot_git_auth/2 branch 2 times, most recently from abed044 to a088848 Compare February 13, 2025 16:41
@pmengelbert
Copy link
Contributor Author

pmengelbert commented Feb 13, 2025

  • Fix rebase disaster.

Sorry, something went wrong.

Copy link
Contributor

@MorrisLaw MorrisLaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good for my change requests; just have to get Brian G's approval too

@pmengelbert pmengelbert force-pushed the pmengelbert/troubleshoot_git_auth/2 branch from 2588022 to 3149a03 Compare February 21, 2025 18:30
@pmengelbert
Copy link
Contributor Author

@cpuguy83 This should be ready for another round of review. I'll probably have to modify the tests, but I'd like to get your thoughts on the overall approach.

source.go Outdated
gm.Auth[host] = gomodAuth
}

// func (gm *GeneratorGomod) fillDefaults
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@pmengelbert pmengelbert force-pushed the pmengelbert/troubleshoot_git_auth/2 branch from 2c71e12 to 2c1b3a9 Compare February 24, 2025 19:23
@pmengelbert pmengelbert requested a review from cpuguy83 February 24, 2025 19:23
var errs []error
fill := func(k, v string) {
if v != "" {
if _, err := fmt.Fprintln(&buf, k+"="+v); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that bytes.Buffer will never return an error on write and this can all be simplified to not bother with error handling.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return llb.Scratch().File(llb.Copy(f, srcPath, dalec.GitCredentialHelperGomod)), nil
}

func GomodGitCredentialHelperGetter(client gwclient.Client) func() (llb.State, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this just be a closer in gateway.go rather than giving it an exported function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

)

func main() {
cmd := filepath.Base(os.Args[0])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was it not possible to tell git to use a subcommand?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this. There are now 2 subcommands, frontend and credential-helper. If the subcommand is blank, it behaves as though the subcommand were frontend.

return func(in llb.State) llb.State {
workDir := "/work/src"
const (
fourKB = 4096
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't look like this is used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this and other unused consts.

}

var b bytes.Buffer
enc := gob.NewEncoder(&b)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why gob?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to avoid yaml messiness -- we've already got a go struct, why not keep it that way rather than translating it into another form and back?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly because its a weird encoding and is actually less efficient than the stdlib json encoding.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By using the filepath to convey the necessary information.

continue
}

fmt.Fprintf(&script, `git config --global credential."https://%s".helper "gomod %s"`, host, configPath)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's give this a name that's more dalec specific?
dalec-git-credentials?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can give it an absolute path also, so I'm just giving it the path to the frontend.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

}

for secret := range secrets {
llb.AddSecret("/run/secrets/"+secret, llb.SecretID(secret)).SetRunOption(ei)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What it help simplify things to mount the secret such that we can find it by host name instead of having to encode configs?
e.g. something like /run/secrets/dalec-git-auth/<host>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking the same thing earlier today. I'll set the destination path on the secrets instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to encode the auth type also (token or header).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

source.go Outdated
@@ -18,6 +19,11 @@ import (
"github.com/pkg/errors"
)

const (
GitCredentialHelperGomod = "git-credential-gomod"
GitCredentialHelperGomodSrcDir = "/usr/bin"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem like this is used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this and others.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem like this got removed?

source.go Outdated
Resolver llb.ImageMetaResolver
Forward ForwarderFunc
GetContext func(string, ...llb.LocalOption) (*llb.State, error)
GitCredentialHelpers map[string]GitCredHelperGetter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't there only ever be one helper here and it doesn't seem like it should be tied to any particular need (e.g. gomod).
This is how we make our buildkit secrets available to git, shouldn't need much more than that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this to a func() (llb.RunOption, error). The error is thrown if the frontend state can't be acquired. The runopt mounts the frontend at /usr/local/bin/frontend and can be used anywhere the credential helper is needed.

@pmengelbert pmengelbert force-pushed the pmengelbert/troubleshoot_git_auth/2 branch from 7157dec to 0b2fb91 Compare February 26, 2025 18:58
@pmengelbert pmengelbert requested a review from cpuguy83 February 26, 2025 19:01
Copy link
Member

@cpuguy83 cpuguy83 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's think about how we can test these changes in the integration suite.
I think if we can run a service on the host that is accessible to buildkit it shouldn't be a problem (other than just setting up multiple different git services).

args := flag.Args()[2:]
// skip os.Args[0] and "credential-helper"
gomodMain(args)
case "", frontendBasename:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, I think we can remove the frontend subcommand and just assume if no subcommand is provided then it should be the frontend.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@@ -292,6 +307,16 @@ func (b *BuildxEnv) RunTest(ctx context.Context, t *testing.T, f TestFunc, opts
withProjectRoot(t, &so)
withResolveLocal(&so)

withSecrets(&so,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like secrets should be a TestRunnerOpt

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also doesn't seem like we're actually using this in any tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It got added to the main RunTest. I'll move it to the test proper as a TestRunnerOpt.

Copy link
Contributor Author

@pmengelbert pmengelbert Mar 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind. This was from before I got rid of the integration tests. I changed it to use TestRunnerOpts and I'll use it when I add the integration tests.

@pmengelbert
Copy link
Contributor Author

pmengelbert commented Mar 4, 2025

Let's think about how we can test these changes in the integration suite. I think if we can run a service on the host that is accessible to buildkit it shouldn't be a problem (other than just setting up multiple different git services).

I don't know how we do this without monkeying around with network namespaces. If that's the case, the tests would only run on linux. Setting the namespaces such that the frontend can communicate with a service running on the host requires root privileges.

@pmengelbert pmengelbert force-pushed the pmengelbert/troubleshoot_git_auth/2 branch from 98c11a6 to bf5b5cc Compare March 7, 2025 23:39
Dockerfile Outdated
@@ -1,4 +1,4 @@
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.22@sha256:9e5243001d0f43d6e1c556a0ce8eedc1bc80eb37b5210036b289d2c601bc08b6 AS go
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.24@sha256:7c2024496593127e761a4610d628e7f653168342e7b60b21d6745adc236b0bd9 AS go
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should stick with 1.23 here (which, IIRC dependabot updated this week)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

@cpuguy83 cpuguy83 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test can't run locally for me due to this error (beyond needing to add the platform to the image refs used).

go: host.docker.internal/user/private.git@v0.0.0: no secure protocol found for repository

.gitmodules Outdated
@@ -0,0 +1,3 @@
[submodule "test/fixtures/git/user/private"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something we need to commit to the repo?
I feel like we should be able to create a repo on the fly without submodules.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed, now creating repo on the fly

if err != nil {
return nil, nil, err
}

res, err := client.Solve(ctx, gwclient.SolveRequest{
Definition: def.ToPB(),
FrontendOpt: map[string]string{
"add-hosts": "host.docker.internal=10.0.2.2",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably be copied in from the client opts rather than hard-coding it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mistake that I left this in, thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

@@ -74,6 +74,9 @@ func ForwarderFromClient(ctx context.Context, client gwclient.Client) dalec.Forw
req, err := newSolveRequest(
toDockerfile(ctx, st, dockerfileDt, spec, dalec.ProgressGroup("prepare dockerfile to forward to frontend")),
copyForForward(ctx, client),
func(sr *gwclient.SolveRequest) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Started doing something here, then took another approach. Left it in by mistake.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

@@ -41,19 +50,108 @@ func withGomod(g *SourceGenerator, srcSt, worker llb.State, opts ...llb.Constrai
paths = []string{"."}
}

sort.Strings(paths)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

paths is already ordered by the user.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

source.go Outdated
@@ -18,6 +19,11 @@ import (
"github.com/pkg/errors"
)

const (
GitCredentialHelperGomod = "git-credential-gomod"
GitCredentialHelperGomodSrcDir = "/usr/bin"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem like this got removed?

return pid
}

func runGitServer(addr string, port int) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you pass through a *testing.T shutdown the listener in a t.Cleanup function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

},
}

outsideAddr, insideAddr := getGitServerAddrs(ctx, t, c)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we are using these values anywhere other than runGitServer, maybe it makes sense to have runGitServer handle all the things here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

insideAddr gets added as an extra host a few lines down.

}))
}

func initGomodWorker(c gwclient.Client, host string, port int) llb.State {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why we need to configure git this way?

Copy link
Contributor Author

@pmengelbert pmengelbert Mar 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because you can't have a port number in a go module name. We tell git to use host:port so that when go invokes git to fetch the module, git knows to use host:port instead of just host. This is all because the git server is not running on 443/80, because we're starting our own git server on localhost and can't use those ports without privileges.

@pmengelbert pmengelbert force-pushed the pmengelbert/troubleshoot_git_auth/2 branch from 922a79c to 7a679a8 Compare March 10, 2025 16:29
@pmengelbert
Copy link
Contributor Author

The test can't run locally for me due to this error (beyond needing to add the platform to the image refs used).

go: host.docker.internal/user/private.git@v0.0.0: no secure protocol found for repository

Can you please try this again with the latest changes? I was experiencing the same error this morning but I believe it's fixed now.

Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
The LLB has changed substantially since we adoped git credential
helpers.

We now check:
- SSH sockets will be mounted
- Secrets will be mounted as files (not provided in the environment)
- The gomod credential helper will be mounted
- The auth config and `go mod download` wrapper script will be mounted

Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Instead, use the `gob` package to encode/decode the bytes. This is to
avoid errors that might arise from the marshaling/unmarshaling to YAML.

Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
- Use the filepath of the secret mount to communicate with the
  credential helper
- Clean up the credential helper
- Handle cli flags properly
- Modified the unit tests to reflect changes to llb operations
- Various other small fixes as requested

Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Some of the unit tests do not use valid hosts.

Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
@pmengelbert pmengelbert force-pushed the pmengelbert/troubleshoot_git_auth/2 branch from 8aaf10c to a86107f Compare March 11, 2025 16:26
Also improve variable naming in credential helper.

Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants