5

Background: We use Bitbucket Server (soon to upgrade/switch to Bitbucket DataCenter). In our authentication setup, we have user passwords disabled (web auth is via a different means), so for Bitbucket/Git purposes, we use Bitbucket Personal Access Tokens (PAT). (SSH key authentication is not an option in our organization.) For cloning/pushing/etc., we have been using our username and PAT when prompted for credentials. This works, but for reasons I can't get into regarding our authentication setup, it is creating performance issues related to our automated builds and also large user-interactive local builds.

Rather than use HTTP(S) Basic authentication, Bitbucket also supports HTTP(S) Bearer token authentication, where you supply just the PAT without the username. Doing this solves our performance issues. Doing so on REST API requests is easy. Doing so on Git is trickier. This Bitbucket documentation recommends doing this:

$ git clone -c http.extraHeader='Authorization: Bearer xxxx' https://bitbucketserver.com/scm/projectname/teamsinspace.git

This works! But it exposes the PAT to others via command history, etc. A more secure variety that doesn't expose the PAT also works:

$ git clone --config-env=http.extraHeader=GIT_AUTHORIZATION_HEADER https://bitbucketserver.com/scm/projectname/teamsinspace.git

Where GIT_AUTHORIZATION_HEADER is Authorization: Bearer xxxx. But this still has some downsides, namely that it can't employ .gitconfig and must be added to every command, and that users have to configure this ahead of time and can't be prompted to enter the PAT that gets stored securely in the Git credential helper.

GitLab provides a "magic username," OAuth2, that can be provided with the token through Basic authentication that essentially tells the server "ignore the username, authenticate with the token only." Bitbucket also provides a similar "magic username," x-token-auth, but it appears to either be Cloud-only or limited to Repository Access Tokens or both, because we have tried to use it and it just results in authentication errors. Access to such a feature would allow us to hard-code the magic username in .gitconfig and the developer would merely get prompted for the "password for x-token-auth@our-server.com," which would be the PAT. But, alas, we can't use this feature.

About 14 years ago there was a discussion on a Git mailing list about supporting Bearer token authentication, and even a patch proposed, but it doesn't look like it ever went anywhere. I could, in theory, apply a modernized version of that patch and recompile Git, but that is really not an ideal solution for our developer organization.

I looked into creating a Git credential helper (which I've actually created before for other reasons) and a Git host provider, but everything credential-related baked into Git is hard-coded to username/password only, so those don't work.

So what I'm looking for / hoping for is some kind of way to create a helper, provider, or plugin of some type that will be able to specify, override, or replace the HTTP Authorization header to make it Bearer instead of Basic, either using the password that comes from built-in Git credential stuff or prompting for something new if necessary, without having to compile our own customized Git fork. Is this at all possible, or should I just give up and use --config-env?

Nick Williams
  • 2,864
  • 5
  • 29
  • 43
  • Would [Git 2.42](https://stackoverflow.com/a/76009757/6309) help? – VonC Jun 19 '23 at 17:30
  • The [current GCM](https://github.com/search?q=repo%3Agit-ecosystem%2Fgit-credential-manager+Bearer+&type=code) already supports `Bearer` for GitLab or BitBucket. The username would be the magic one. Everything would be stored in the underlying credential OS manager, not in `.gitconfig` (which would only declare the helper) – VonC Jun 19 '23 at 17:38
  • @VonC: 2.42 does not help. If Git supports Bearer tokens, it is not documented, and it does not work. As mentioned above, I have attempted to auth with the magic username `x-token-auth` documented in Bitbucket, and it does not work. My searching indicates that the `x-token-auth` magic username works only with Bitbucket Cloud and not Bitbucket Server / Data Center. – Nick Williams Jun 20 '23 at 00:59
  • Did you try with the regular username? And token as password in basic auth? – VonC Jun 20 '23 at 01:16
  • @VonC: Yes, but as mentioned in the first paragraph of my question, while this works, it is causing performance issues for us that using Bearer token auth solves. – Nick Williams Jun 20 '23 at 14:16
  • Performance reason because of having to go to the credential manager? Sorry, I missed the "for reasons I can't get into regarding our authentication setup". OK. – VonC Jun 20 '23 at 15:04
  • Server performance, specifically, not credential manager performance. As in, due to our specific server configuration, which cannot be improved any time soon, `Authorization: Bearer` is much faster than `Authorization: Basic` for Bitbucket API/SCM operations. That's about all the details I can go into there. – Nick Williams Jun 20 '23 at 17:54

2 Answers2

3

Definitely not proud of this but have had to resort to something dirty things in CI environments, also because of "reasons"...

Neither of these solutions are ideal, but the simplest would be to create a shell alias or in your gitconfig and add to your .bashrc/.zshrc that reads the token from stdin

## example that shows header is set correctly but does not show up actual value in command line history  
## replace `whoami` command with command/script to retrieve token
$ GIT_CURL_VERBOSE=1 git -c http.extraHeader="Hello: $(whoami)" ls-remote https://github.com/deric4/github-action-runner-reference
...
15:43:52.454783 http.c:701              == Info: using HTTP/2
15:43:52.454838 http.c:701              == Info: h2h3 [:method: GET]
15:43:52.454844 http.c:701              == Info: h2h3 [:path: /deric4/github-action-runner-reference/info/refs?service=git-upload-pack]
15:43:52.454849 http.c:701              == Info: h2h3 [:scheme: https]
15:43:52.454853 http.c:701              == Info: h2h3 [:authority: github.com]
15:43:52.454857 http.c:701              == Info: h2h3 [user-agent: git/2.39.2 (Apple Git-143)]
15:43:52.454861 http.c:701              == Info: h2h3 [accept: */*]
15:43:52.454865 http.c:701              == Info: h2h3 [accept-encoding: deflate, gzip]
15:43:52.454869 http.c:701              == Info: h2h3 [hello: deric4]
15:43:52.454873 http.c:701              == Info: h2h3 [pragma: no-cache]
15:43:52.454876 http.c:701              == Info: h2h3 [git-protocol: version=2]
...
# .gitconfig
[alias]
    myalias = "!f() { GIT_CURL_VERBOSE=1 git -c http.extraHeader=\"Hello: $(whoami)\" ls-remote $1 ; }; f"

A more drastic approach would be to implement a custom remote helper in addition/place of credential helper.

I was able to dig up an old example that i'm not proud of but got the job done:

This would allow for dynamically getting and setting config/credentials based on execution context...

git clone accesstoken://some-profile@foo-bar-baz

// Note: that this was written in go 1.12 and hadn't looked at it since then so...
func main() {

    remoteName := os.Args[1]
    repoUrl := os.Args[2]

    u, err := url.Parse(repoUrl)
    if err != nil {
        log.Fatal(err)
    }
    
    // replaced complex steps to just getting the env var
    accessToken := os.Getenv("GIT_ACCESS_TOKEN")

    // get username so it can be reset when password is added
    username := u.User.Username()

    // construct <username>:<password>
    u.User = url.UserPassword(username, accessToken)

    // convert from accesstoken -> https
    u.Scheme = "https"

    cmd := exec.Command("git", "remote-http", remoteName, u.String())
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Stdin = os.Stdin

    err = cmd.Run()
    if err != nil {
        log.Fatal(err)
    }
}

but in your case

    // custom logic to get bearer token
    
    cmd := exec.Command("git", "-c", "http.extraHeader='Bearer: <token from somewhere>'", "remote-http", remoteName, u.String())

deric4
  • 1,095
  • 7
  • 11
  • Thanks for this. I, too, have come up with ways to hack around it (though I'm using `--config-env=http.extraHeader=ENV_VAR` to keep the token out of command history / the process list). This isn't really the "answer" I was looking for, but it does provide solutions, and it's the best/only answer provided, so I'll award the bounty. Thanks again! – Nick Williams Jun 26 '23 at 17:27
0

We use environment variables to avoid changing config files, see this https://github.com/microsoft/azure-pipelines-agent/issues/1601#issuecomment-1683714305

Nielsen
  • 23
  • 3