From 3cdfd7a20a2a8c483a58d9cff78f52b52e16989d Mon Sep 17 00:00:00 2001
From: Matt Palmer <9059517+56KBs@users.noreply.github.com>
Date: Tue, 21 Dec 2021 13:09:49 +0000
Subject: [PATCH] fix(manager/gomod): GitLab Private Authentication (#13216)

---
 lib/manager/gomod/artifacts.spec.ts | 20 +++++--
 lib/manager/gomod/artifacts.ts      | 10 ++--
 lib/util/git/auth.spec.ts           | 87 ++++++++++++++++++++++-------
 lib/util/git/auth.ts                | 28 ++++++++--
 4 files changed, 112 insertions(+), 33 deletions(-)

diff --git a/lib/manager/gomod/artifacts.spec.ts b/lib/manager/gomod/artifacts.spec.ts
index e29babff35..d84c82d52b 100644
--- a/lib/manager/gomod/artifacts.spec.ts
+++ b/lib/manager/gomod/artifacts.spec.ts
@@ -3,6 +3,7 @@ import { envMock, exec, mockExecAll } from '../../../test/exec-util';
 import { env, fs, git, mocked } from '../../../test/util';
 import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
+import { PlatformId } from '../../constants/platforms';
 import * as docker from '../../util/exec/docker';
 import type { StatusResult } from '../../util/git/types';
 import * as _hostRules from '../../util/host-rules';
@@ -222,6 +223,7 @@ describe('manager/gomod/artifacts', () => {
       {
         token: 'some-enterprise-token',
         matchHost: 'github.enterprise.com',
+        hostType: PlatformId.Github,
       },
     ]);
     fs.readLocalFile.mockResolvedValueOnce('Current go.sum');
@@ -263,6 +265,7 @@ describe('manager/gomod/artifacts', () => {
       {
         token: 'some-enterprise-token',
         matchHost: 'gitlab.enterprise.com',
+        hostType: PlatformId.Gitlab,
       },
     ]);
     fs.readLocalFile.mockResolvedValueOnce('Current go.sum');
@@ -287,7 +290,7 @@ describe('manager/gomod/artifacts', () => {
             env: expect.objectContaining({
               GIT_CONFIG_COUNT: '1',
               GIT_CONFIG_KEY_0:
-                'url.https://some-enterprise-token@gitlab.enterprise.com/.insteadOf',
+                'url.https://gitlab-ci-token:some-enterprise-token@gitlab.enterprise.com/.insteadOf',
               GIT_CONFIG_VALUE_0: 'https://gitlab.enterprise.com/',
             }),
           }),
@@ -302,10 +305,12 @@ describe('manager/gomod/artifacts', () => {
       {
         token: 'some-enterprise-token-repo1',
         matchHost: 'https://gitlab.enterprise.com/repo1',
+        hostType: PlatformId.Gitlab,
       },
       {
         token: 'some-enterprise-token-repo2',
         matchHost: 'https://gitlab.enterprise.com/repo2',
+        hostType: PlatformId.Gitlab,
       },
     ]);
     fs.readLocalFile.mockResolvedValueOnce('Current go.sum');
@@ -330,9 +335,9 @@ describe('manager/gomod/artifacts', () => {
             env: expect.objectContaining({
               GIT_CONFIG_COUNT: '2',
               GIT_CONFIG_KEY_0:
-                'url.https://some-enterprise-token-repo1@gitlab.enterprise.com/repo1.insteadOf',
+                'url.https://gitlab-ci-token:some-enterprise-token-repo1@gitlab.enterprise.com/repo1.insteadOf',
               GIT_CONFIG_KEY_1:
-                'url.https://some-enterprise-token-repo2@gitlab.enterprise.com/repo2.insteadOf',
+                'url.https://gitlab-ci-token:some-enterprise-token-repo2@gitlab.enterprise.com/repo2.insteadOf',
               GIT_CONFIG_VALUE_0: 'https://gitlab.enterprise.com/repo1',
               GIT_CONFIG_VALUE_1: 'https://gitlab.enterprise.com/repo2',
             }),
@@ -348,10 +353,12 @@ describe('manager/gomod/artifacts', () => {
       {
         token: 'some-token',
         matchHost: 'ssh://github.enterprise.com',
+        hostType: PlatformId.Github,
       },
       {
         token: 'some-gitlab-token',
         matchHost: 'gitlab.enterprise.com',
+        hostType: PlatformId.Gitlab,
       },
     ]);
     fs.readLocalFile.mockResolvedValueOnce('Current go.sum');
@@ -376,7 +383,7 @@ describe('manager/gomod/artifacts', () => {
             env: expect.objectContaining({
               GIT_CONFIG_COUNT: '1',
               GIT_CONFIG_KEY_0:
-                'url.https://some-gitlab-token@gitlab.enterprise.com/.insteadOf',
+                'url.https://gitlab-ci-token:some-gitlab-token@gitlab.enterprise.com/.insteadOf',
               GIT_CONFIG_VALUE_0: 'https://gitlab.enterprise.com/',
             }),
           }),
@@ -394,14 +401,17 @@ describe('manager/gomod/artifacts', () => {
       {
         token: 'some-token',
         matchHost: 'api.github.com',
+        hostType: PlatformId.Github,
       },
       {
         token: 'some-enterprise-token',
         matchHost: 'github.enterprise.com',
+        hostType: PlatformId.Github,
       },
       {
         token: 'some-gitlab-token',
         matchHost: 'gitlab.enterprise.com',
+        hostType: PlatformId.Gitlab,
       },
     ]);
     fs.readLocalFile.mockResolvedValueOnce('Current go.sum');
@@ -431,7 +441,7 @@ describe('manager/gomod/artifacts', () => {
               GIT_CONFIG_KEY_2:
                 'url.https://some-enterprise-token@github.enterprise.com/.insteadOf',
               GIT_CONFIG_KEY_3:
-                'url.https://some-gitlab-token@gitlab.enterprise.com/.insteadOf',
+                'url.https://gitlab-ci-token:some-gitlab-token@gitlab.enterprise.com/.insteadOf',
               GIT_CONFIG_VALUE_0: 'https://github.com/',
               GIT_CONFIG_VALUE_1: 'https://api.github.com/',
               GIT_CONFIG_VALUE_2: 'https://github.enterprise.com/',
diff --git a/lib/manager/gomod/artifacts.ts b/lib/manager/gomod/artifacts.ts
index dbb2083ef9..d53839f2dc 100644
--- a/lib/manager/gomod/artifacts.ts
+++ b/lib/manager/gomod/artifacts.ts
@@ -23,16 +23,16 @@ import type {
 function getGitEnvironmentVariables(): NodeJS.ProcessEnv {
   let environmentVariables: NodeJS.ProcessEnv = {};
 
-  // hard-coded logic to use authentication for github.com based on the credentials for api.github.com
-  const credentials = find({
+  // hard-coded logic to use authentication for github.com based on the githubToken for api.github.com
+  const githubToken = find({
     hostType: PlatformId.Github,
     url: 'https://api.github.com/',
   });
 
-  if (credentials?.token) {
+  if (githubToken?.token) {
     environmentVariables = getGitAuthenticatedEnvironmentVariables(
       'https://github.com/',
-      credentials.token
+      githubToken
     );
   }
 
@@ -65,7 +65,7 @@ function getGitEnvironmentVariables(): NodeJS.ProcessEnv {
         );
         environmentVariables = getGitAuthenticatedEnvironmentVariables(
           httpUrl,
-          hostRule.token,
+          hostRule,
           environmentVariables
         );
       } else {
diff --git a/lib/util/git/auth.spec.ts b/lib/util/git/auth.spec.ts
index 6e3d5690a8..86d0c3fcf4 100644
--- a/lib/util/git/auth.spec.ts
+++ b/lib/util/git/auth.spec.ts
@@ -1,3 +1,4 @@
+import { PlatformId } from '../../constants';
 import { getGitAuthenticatedEnvironmentVariables } from './auth';
 
 describe('util/git/auth', () => {
@@ -7,10 +8,11 @@ describe('util/git/auth', () => {
   describe('getGitAuthenticatedEnvironmentVariables()', () => {
     it('returns url with token', () => {
       expect(
-        getGitAuthenticatedEnvironmentVariables(
-          'https://github.com/',
-          'token1234'
-        )
+        getGitAuthenticatedEnvironmentVariables('https://github.com/', {
+          token: 'token1234',
+          hostType: PlatformId.Github,
+          matchHost: 'github.com',
+        })
       ).toStrictEqual({
         GIT_CONFIG_KEY_0: 'url.https://token1234@github.com/.insteadOf',
         GIT_CONFIG_VALUE_0: 'https://github.com/',
@@ -20,10 +22,11 @@ describe('util/git/auth', () => {
 
     it('returns correct url if token already contains GitHub App username', () => {
       expect(
-        getGitAuthenticatedEnvironmentVariables(
-          'https://github.com/',
-          'x-access-token:token1234'
-        )
+        getGitAuthenticatedEnvironmentVariables('https://github.com/', {
+          token: 'x-access-token:token1234',
+          hostType: PlatformId.Github,
+          matchHost: 'github.com',
+        })
       ).toStrictEqual({
         GIT_CONFIG_KEY_0:
           'url.https://x-access-token:token1234@github.com/.insteadOf',
@@ -36,7 +39,11 @@ describe('util/git/auth', () => {
       expect(
         getGitAuthenticatedEnvironmentVariables(
           'https://github.com/',
-          'token1234',
+          {
+            token: 'token1234',
+            hostType: PlatformId.Github,
+            matchHost: 'github.com',
+          },
           { GIT_CONFIG_COUNT: '1' }
         )
       ).toStrictEqual({
@@ -51,7 +58,11 @@ describe('util/git/auth', () => {
       expect(
         getGitAuthenticatedEnvironmentVariables(
           'https://github.com/',
-          'token1234',
+          {
+            token: 'token1234',
+            hostType: PlatformId.Github,
+            matchHost: 'github.com',
+          },
           { GIT_CONFIG_COUNT: '1' }
         )
       ).toStrictEqual({
@@ -64,10 +75,11 @@ describe('util/git/auth', () => {
     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'
-        )
+        getGitAuthenticatedEnvironmentVariables('https://github.com/', {
+          token: 'token1234',
+          hostType: PlatformId.Github,
+          matchHost: 'github.com',
+        })
       ).toStrictEqual({
         GIT_CONFIG_KEY_1: 'url.https://token1234@github.com/.insteadOf',
         GIT_CONFIG_VALUE_1: 'https://github.com/',
@@ -79,7 +91,11 @@ describe('util/git/auth', () => {
       expect(
         getGitAuthenticatedEnvironmentVariables(
           'https://github.com/',
-          'token1234',
+          {
+            token: 'token1234',
+            hostType: PlatformId.Github,
+            matchHost: 'github.com',
+          },
           { RANDOM_VARIABLE: 'random' }
         )
       ).toStrictEqual({
@@ -93,15 +109,48 @@ describe('util/git/auth', () => {
     it('return url with token with invalid GIT_CONFIG_COUNT from environment', () => {
       process.env.GIT_CONFIG_COUNT = 'notvalid';
       expect(
-        getGitAuthenticatedEnvironmentVariables(
-          'https://github.com/',
-          'token1234'
-        )
+        getGitAuthenticatedEnvironmentVariables('https://github.com/', {
+          token: 'token1234',
+          hostType: PlatformId.Github,
+          matchHost: 'github.com',
+        })
       ).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 token containing username for GitLab token', () => {
+      expect(
+        getGitAuthenticatedEnvironmentVariables('https://gitlab.com/', {
+          token: 'token1234',
+          hostType: PlatformId.Gitlab,
+          matchHost: 'github.com',
+        })
+      ).toStrictEqual({
+        GIT_CONFIG_KEY_0:
+          'url.https://gitlab-ci-token:token1234@gitlab.com/.insteadOf',
+        GIT_CONFIG_VALUE_0: 'https://gitlab.com/',
+        GIT_CONFIG_COUNT: '1',
+      });
+    });
+
+    it('returns original environment variables when no token is set', () => {
+      expect(
+        getGitAuthenticatedEnvironmentVariables(
+          'https://gitlab.com/',
+          {
+            username: 'testing',
+            password: '1234',
+            hostType: PlatformId.Gitlab,
+            matchHost: 'github.com',
+          },
+          { env: 'value' }
+        )
+      ).toStrictEqual({
+        env: 'value',
+      });
+    });
   });
 });
diff --git a/lib/util/git/auth.ts b/lib/util/git/auth.ts
index 0d04ef526c..49d77dfb61 100644
--- a/lib/util/git/auth.ts
+++ b/lib/util/git/auth.ts
@@ -1,4 +1,6 @@
+import { PlatformId } from '../../constants';
 import { logger } from '../../logger';
+import type { HostRule } from '../../types';
 import { getHttpUrl } from './url';
 
 /**
@@ -7,9 +9,16 @@ import { getHttpUrl } from './url';
  */
 export function getGitAuthenticatedEnvironmentVariables(
   gitUrl: string,
-  token: string,
+  { token, hostType, matchHost }: HostRule,
   environmentVariables?: NodeJS.ProcessEnv
 ): NodeJS.ProcessEnv {
+  if (!token) {
+    logger.warn(
+      `Could not create environment variable for ${matchHost} as token was empty`
+    );
+    return { ...environmentVariables };
+  }
+
   // 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;
@@ -25,17 +34,28 @@ export function getGitAuthenticatedEnvironmentVariables(
     }
   }
 
-  const gitUrlWithToken = getHttpUrl(gitUrl, token);
+  const gitUrlWithToken = getUrlWithToken(gitUrl, hostType, 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 = {
+  return {
     ...environmentVariables,
     [`GIT_CONFIG_KEY_${gitConfigCount}`]: `url.${gitUrlWithToken}.insteadOf`,
     [`GIT_CONFIG_VALUE_${gitConfigCount}`]: gitUrl,
     GIT_CONFIG_COUNT: (gitConfigCount + 1).toString(),
   };
+}
+
+function getUrlWithToken(
+  gitUrl: string,
+  hostType: string,
+  authToken: string
+): string {
+  let token = authToken;
+  if (hostType === PlatformId.Gitlab) {
+    token = `gitlab-ci-token:${token}`;
+  }
 
-  return returnEnvironmentVariables;
+  return getHttpUrl(gitUrl, token);
 }
-- 
GitLab