From 15dd2fcf02bf0471b53ae5411191f7804d3b7113 Mon Sep 17 00:00:00 2001
From: Tobias <tobias.gabriel@sap.com>
Date: Wed, 20 Oct 2021 13:16:49 +0200
Subject: [PATCH] feat(git): insteadOf environment variables for authentication
 (#11077)

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
---
 .../usage/getting-started/private-packages.md |   4 +-
 .../__snapshots__/artifacts.spec.ts.snap      |   5 +-
 lib/manager/gomod/artifacts.ts                |  21 ++--
 lib/util/git/auth.spec.ts                     | 106 ++++++++++++++++++
 lib/util/git/auth.ts                          |  40 +++++++
 5 files changed, 164 insertions(+), 12 deletions(-)
 create mode 100644 lib/util/git/auth.spec.ts
 create mode 100644 lib/util/git/auth.ts

diff --git a/docs/usage/getting-started/private-packages.md b/docs/usage/getting-started/private-packages.md
index ff8614e966..404bfb5851 100644
--- a/docs/usage/getting-started/private-packages.md
+++ b/docs/usage/getting-started/private-packages.md
@@ -123,8 +123,8 @@ Any `hostRules` with `hostType=packagist` are also included.
 
 ### gomod
 
-If a `github.com` token is found in `hostRules`, then it is written out to local git config prior to running `go` commands.
-The command run is `git config --global url."https://${token}@github.com/".insteadOf "https://github.com/"`.
+If a `github.com` token is found in `hostRules`, then it is written out to local [GIT*CONFIG*](https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT) variables prior to running `go` commands.
+The environment variables used are: `GIT_CONFIG_KEY_0=url.https://${token}@github.com/.insteadOf GIT_CONFIG_VALUE_0=https://github.com/ GIT_CONFIG_COUNT=1`.
 
 ### npm
 
diff --git a/lib/manager/gomod/__snapshots__/artifacts.spec.ts.snap b/lib/manager/gomod/__snapshots__/artifacts.spec.ts.snap
index 021acb2565..a446d705f8 100644
--- a/lib/manager/gomod/__snapshots__/artifacts.spec.ts.snap
+++ b/lib/manager/gomod/__snapshots__/artifacts.spec.ts.snap
@@ -239,12 +239,15 @@ Array [
     },
   },
   Object {
-    "cmd": "docker run --rm --name=renovate_go --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -e GOPATH -e GOPROXY -e GOPRIVATE -e GONOPROXY -e GONOSUMDB -e GOFLAGS -e CGO_ENABLED -w \\"/tmp/github/some/repo\\" renovate/go:latest bash -l -c \\"git config --global url.\\\\\\"https://some-token@github.com/\\\\\\".insteadOf \\\\\\"https://github.com/\\\\\\" && go get -d ./...\\"",
+    "cmd": "docker run --rm --name=renovate_go --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -e GOPATH -e GOPROXY -e GOPRIVATE -e GONOPROXY -e GONOSUMDB -e GOFLAGS -e CGO_ENABLED -e GIT_CONFIG_KEY_0 -e GIT_CONFIG_VALUE_0 -e GIT_CONFIG_COUNT -w \\"/tmp/github/some/repo\\" renovate/go:latest bash -l -c \\"go get -d ./...\\"",
     "options": Object {
       "cwd": "/tmp/github/some/repo",
       "encoding": "utf-8",
       "env": Object {
         "CGO_ENABLED": "1",
+        "GIT_CONFIG_COUNT": "1",
+        "GIT_CONFIG_KEY_0": "url.https://some-token@github.com/.insteadOf",
+        "GIT_CONFIG_VALUE_0": "https://github.com/",
         "GOFLAGS": "-modcacherw",
         "GONOPROXY": "noproxy.example.com/*",
         "GONOSUMDB": "1",
diff --git a/lib/manager/gomod/artifacts.ts b/lib/manager/gomod/artifacts.ts
index 9b6f08de39..d770c9edcc 100644
--- a/lib/manager/gomod/artifacts.ts
+++ b/lib/manager/gomod/artifacts.ts
@@ -1,5 +1,4 @@
 import is from '@sindresorhus/is';
-import { quote } from 'shlex';
 import { dirname, join } from 'upath';
 import { getGlobalConfig } from '../../config/global';
 import { PlatformId } from '../../constants';
@@ -8,6 +7,7 @@ import { logger } from '../../logger';
 import { ExecOptions, exec } from '../../util/exec';
 import { ensureCacheDir, readLocalFile, writeLocalFile } from '../../util/fs';
 import { getRepoStatus } from '../../util/git';
+import { getGitAuthenticatedEnvironmentVariables } from '../../util/git/auth';
 import { find } from '../../util/host-rules';
 import { regEx } from '../../util/regex';
 import { isValid } from '../../versioning/semver';
@@ -18,19 +18,22 @@ import type {
   UpdateArtifactsResult,
 } from '../types';
 
-function getPreCommands(): string[] | null {
+function getGitEnvironmentVariables(): NodeJS.ProcessEnv {
+  let environmentVariables: NodeJS.ProcessEnv = {};
+
   const credentials = find({
     hostType: PlatformId.Github,
     url: 'https://api.github.com/',
   });
-  let preCommands = null;
+
   if (credentials?.token) {
-    const token = quote(credentials.token);
-    preCommands = [
-      `git config --global url.\"https://${token}@github.com/\".insteadOf \"https://github.com/\"`, // eslint-disable-line no-useless-escape
-    ];
+    environmentVariables = getGitAuthenticatedEnvironmentVariables(
+      'https://github.com/',
+      credentials.token
+    );
   }
-  return preCommands;
+
+  return environmentVariables;
 }
 
 function getUpdateImportPathCmds(
@@ -127,12 +130,12 @@ export async function updateArtifacts({
         GONOSUMDB: process.env.GONOSUMDB,
         GOFLAGS: useModcacherw(config.constraints?.go) ? '-modcacherw' : null,
         CGO_ENABLED: getGlobalConfig().binarySource === 'docker' ? '0' : null,
+        ...getGitEnvironmentVariables(),
       },
       docker: {
         image: 'go',
         tagConstraint: config.constraints?.go,
         tagScheme: 'npm',
-        preCommands: getPreCommands(),
       },
     };
 
diff --git a/lib/util/git/auth.spec.ts b/lib/util/git/auth.spec.ts
new file mode 100644
index 0000000000..7e54bf89aa
--- /dev/null
+++ b/lib/util/git/auth.spec.ts
@@ -0,0 +1,106 @@
+import { getGitAuthenticatedEnvironmentVariables } from './auth';
+
+describe('util/git/auth', () => {
+  afterEach(() => {
+    delete process.env.GIT_CONFIG_COUNT;
+  });
+  describe('getGitAuthenticatedEnvironmentVariables()', () => {
+    it('returns url with token', () => {
+      expect(
+        getGitAuthenticatedEnvironmentVariables(
+          'https://github.com/',
+          'token1234'
+        )
+      ).toStrictEqual({
+        GIT_CONFIG_KEY_0: 'url.https://token1234@github.com/.insteadOf',
+        GIT_CONFIG_VALUE_0: 'https://github.com/',
+        GIT_CONFIG_COUNT: '1',
+      });
+    });
+
+    it('returns url with correctly encoded token', () => {
+      expect(
+        getGitAuthenticatedEnvironmentVariables(
+          'https://github.com/',
+          'token:1234'
+        )
+      ).toStrictEqual({
+        GIT_CONFIG_KEY_0: 'url.https://token%3A1234@github.com/.insteadOf',
+        GIT_CONFIG_VALUE_0: 'https://github.com/',
+        GIT_CONFIG_COUNT: '1',
+      });
+    });
+
+    it('returns url with token and already existing GIT_CONFIG_COUNT from parameter', () => {
+      expect(
+        getGitAuthenticatedEnvironmentVariables(
+          'https://github.com/',
+          'token1234',
+          { GIT_CONFIG_COUNT: '1' }
+        )
+      ).toStrictEqual({
+        GIT_CONFIG_KEY_1: 'url.https://token1234@github.com/.insteadOf',
+        GIT_CONFIG_VALUE_1: 'https://github.com/',
+        GIT_CONFIG_COUNT: '2',
+      });
+    });
+
+    it('returns url with token and already existing GIT_CONFIG_COUNT from parameter over environment', () => {
+      process.env.GIT_CONFIG_COUNT = '54';
+      expect(
+        getGitAuthenticatedEnvironmentVariables(
+          'https://github.com/',
+          'token1234',
+          { GIT_CONFIG_COUNT: '1' }
+        )
+      ).toStrictEqual({
+        GIT_CONFIG_KEY_1: 'url.https://token1234@github.com/.insteadOf',
+        GIT_CONFIG_VALUE_1: 'https://github.com/',
+        GIT_CONFIG_COUNT: '2',
+      });
+    });
+
+    it('returns url with token and already existing GIT_CONFIG_COUNT from environment', () => {
+      process.env.GIT_CONFIG_COUNT = '1';
+      expect(
+        getGitAuthenticatedEnvironmentVariables(
+          'https://github.com/',
+          'token1234'
+        )
+      ).toStrictEqual({
+        GIT_CONFIG_KEY_1: 'url.https://token1234@github.com/.insteadOf',
+        GIT_CONFIG_VALUE_1: 'https://github.com/',
+        GIT_CONFIG_COUNT: '2',
+      });
+    });
+
+    it('returns url with token and passthrough existing variables', () => {
+      expect(
+        getGitAuthenticatedEnvironmentVariables(
+          'https://github.com/',
+          'token1234',
+          { RANDOM_VARIABLE: 'random' }
+        )
+      ).toStrictEqual({
+        GIT_CONFIG_KEY_0: 'url.https://token1234@github.com/.insteadOf',
+        GIT_CONFIG_VALUE_0: 'https://github.com/',
+        GIT_CONFIG_COUNT: '1',
+        RANDOM_VARIABLE: 'random',
+      });
+    });
+
+    it('return url with token with invalid GIT_CONFIG_COUNT from environment', () => {
+      process.env.GIT_CONFIG_COUNT = 'notvalid';
+      expect(
+        getGitAuthenticatedEnvironmentVariables(
+          'https://github.com/',
+          'token1234'
+        )
+      ).toStrictEqual({
+        GIT_CONFIG_KEY_0: 'url.https://token1234@github.com/.insteadOf',
+        GIT_CONFIG_VALUE_0: 'https://github.com/',
+        GIT_CONFIG_COUNT: '1',
+      });
+    });
+  });
+});
diff --git a/lib/util/git/auth.ts b/lib/util/git/auth.ts
new file mode 100644
index 0000000000..3a6c56b218
--- /dev/null
+++ b/lib/util/git/auth.ts
@@ -0,0 +1,40 @@
+import { logger } from '../../logger';
+import { getHttpUrl } from './url';
+
+/*
+    Add authorization to a Git Url and returns the updated environment variables
+*/
+export function getGitAuthenticatedEnvironmentVariables(
+  gitUrl: string,
+  token: string,
+  environmentVariables?: NodeJS.ProcessEnv
+): NodeJS.ProcessEnv {
+  // check if the environmentVariables already contain a GIT_CONFIG_COUNT or if the process has one
+  const gitConfigCountEnvVariable =
+    environmentVariables?.GIT_CONFIG_COUNT || process.env.GIT_CONFIG_COUNT;
+  let gitConfigCount = 0;
+  if (gitConfigCountEnvVariable) {
+    // passthrough the gitConfigCountEnvVariable environment variable as start value of the index count
+    gitConfigCount = parseInt(gitConfigCountEnvVariable, 10);
+    if (Number.isNaN(gitConfigCount)) {
+      logger.warn(
+        `Found GIT_CONFIG_COUNT env variable, but couldn't parse the value to an integer: ${process.env.GIT_CONFIG_COUNT}. Ignoring it.`
+      );
+      gitConfigCount = 0;
+    }
+  }
+
+  const gitUrlWithToken = getHttpUrl(gitUrl, encodeURIComponent(token));
+
+  // create a shallow copy of the environmentVariables as base so we don't modify the input parameter object
+  // add the two new config key and value to the returnEnvironmentVariables object
+  // increase the CONFIG_COUNT by one and add it to the object
+  const returnEnvironmentVariables = {
+    ...environmentVariables,
+    [`GIT_CONFIG_KEY_${gitConfigCount}`]: `url.${gitUrlWithToken}.insteadOf`,
+    [`GIT_CONFIG_VALUE_${gitConfigCount}`]: gitUrl,
+    GIT_CONFIG_COUNT: (gitConfigCount + 1).toString(),
+  };
+
+  return returnEnvironmentVariables;
+}
-- 
GitLab