diff --git a/lib/modules/manager/gomod/artifacts.ts b/lib/modules/manager/gomod/artifacts.ts index 0b6de5e39d2d0cf1824ed344948498eb0d2103a6..a5606467dba9bf24715a67a7e0428744f9bce232 100644 --- a/lib/modules/manager/gomod/artifacts.ts +++ b/lib/modules/manager/gomod/artifacts.ts @@ -3,10 +3,8 @@ import semver from 'semver'; import { quote } from 'shlex'; import upath from 'upath'; import { GlobalConfig } from '../../../config/global'; -import type { PlatformId } from '../../../constants'; import { TEMPORARY_ERROR } from '../../../constants/error-messages'; import { logger } from '../../../logger'; -import type { HostRule } from '../../../types'; import { exec } from '../../../util/exec'; import type { ExecOptions } from '../../../util/exec/types'; import { @@ -16,10 +14,8 @@ import { writeLocalFile, } from '../../../util/fs'; import { getRepoStatus } from '../../../util/git'; -import { getGitAuthenticatedEnvironmentVariables } from '../../../util/git/auth'; -import { find, getAll } from '../../../util/host-rules'; +import { getGitEnvironmentVariables } from '../../../util/git/auth'; import { regEx } from '../../../util/regex'; -import { createURLFromHostOrURL, validateUrl } from '../../../util/url'; import { isValid } from '../../versioning/semver'; import type { PackageDependency, @@ -28,94 +24,8 @@ import type { UpdateArtifactsResult, } from '../types'; -const githubApiUrls = new Set([ - 'github.com', - 'api.github.com', - 'https://api.github.com', - 'https://api.github.com/', -]); - const { major, valid } = semver; -function getGitEnvironmentVariables(): NodeJS.ProcessEnv { - let environmentVariables: NodeJS.ProcessEnv = {}; - - // hard-coded logic to use authentication for github.com based on the githubToken for api.github.com - const githubToken = find({ - hostType: 'github', - url: 'https://api.github.com/', - }); - - if (githubToken?.token) { - environmentVariables = getGitAuthenticatedEnvironmentVariables( - 'https://github.com/', - githubToken - ); - } - - // get extra host rules for other git-based Go Module hosts - // filter rules without `matchHost` and `token` and github api github rules - const hostRules = getAll() - .filter((r) => r.matchHost && r.token) - .filter((r) => !githubToken || !githubApiUrls.has(r.matchHost!)); - - const goGitAllowedHostType = new Set<string>([ - // All known git platforms - 'azure', - 'bitbucket', - 'bitbucket-server', - 'gitea', - 'github', - 'gitlab', - ] satisfies PlatformId[]); - - // for each hostRule without hostType we add additional authentication variables to the environmentVariables - for (const hostRule of hostRules) { - if (hostRule.hostType === 'go' || !hostRule.hostType) { - environmentVariables = addAuthFromHostRule( - hostRule, - environmentVariables - ); - } - } - - // for each hostRule with hostType we add additional authentication variables to the environmentVariables - for (const hostRule of hostRules) { - if (hostRule.hostType && goGitAllowedHostType.has(hostRule.hostType)) { - environmentVariables = addAuthFromHostRule( - hostRule, - environmentVariables - ); - } - } - return environmentVariables; -} - -function addAuthFromHostRule( - hostRule: HostRule, - env: NodeJS.ProcessEnv -): NodeJS.ProcessEnv { - let environmentVariables = env; - const httpUrl = createURLFromHostOrURL(hostRule.matchHost!)?.toString(); - if (validateUrl(httpUrl)) { - logger.debug( - // TODO: types (#7154) - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Adding Git authentication for Go Module retrieval for ${httpUrl} using token auth.` - ); - environmentVariables = getGitAuthenticatedEnvironmentVariables( - httpUrl!, - hostRule, - environmentVariables - ); - } else { - logger.warn( - `Could not parse registryUrl ${hostRule.matchHost!} or not using http(s). Ignoring` - ); - } - return environmentVariables; -} - function getUpdateImportPathCmds( updatedDeps: PackageDependency[], { constraints }: UpdateArtifactsConfig @@ -285,7 +195,7 @@ export async function updateArtifacts({ GOINSECURE: process.env.GOINSECURE, GOFLAGS: useModcacherw(goConstraints) ? '-modcacherw' : null, CGO_ENABLED: GlobalConfig.get('binarySource') === 'docker' ? '0' : null, - ...getGitEnvironmentVariables(), + ...getGitEnvironmentVariables(['go']), }, docker: {}, toolConstraints: [ diff --git a/lib/util/git/auth.spec.ts b/lib/util/git/auth.spec.ts index b26d72eaedbd52e3732ad9c51c156bc63a3e9178..4c7ec1fdfcce186e664a79f71113396b774ff2a5 100644 --- a/lib/util/git/auth.spec.ts +++ b/lib/util/git/auth.spec.ts @@ -1,4 +1,8 @@ -import { getGitAuthenticatedEnvironmentVariables } from './auth'; +import { add, clear } from '../host-rules'; +import { + getGitAuthenticatedEnvironmentVariables, + getGitEnvironmentVariables, +} from './auth'; describe('util/git/auth', () => { afterEach(() => { @@ -307,4 +311,132 @@ describe('util/git/auth', () => { }); }); }); + + describe('getGitEnvironmentVariables()', () => { + beforeEach(() => { + clear(); + }); + + it('returns empty object if no environment variables exist', () => { + expect(getGitEnvironmentVariables()).toStrictEqual({}); + }); + + it('returns environment variables with token if hostRule for api.github.com exists', () => { + add({ + hostType: 'github', + matchHost: 'api.github.com', + token: 'token123', + }); + expect(getGitEnvironmentVariables()).toStrictEqual({ + GIT_CONFIG_COUNT: '3', + GIT_CONFIG_KEY_0: 'url.https://ssh:token123@github.com/.insteadOf', + GIT_CONFIG_KEY_1: 'url.https://git:token123@github.com/.insteadOf', + GIT_CONFIG_KEY_2: 'url.https://token123@github.com/.insteadOf', + GIT_CONFIG_VALUE_0: 'ssh://git@github.com/', + GIT_CONFIG_VALUE_1: 'git@github.com:', + GIT_CONFIG_VALUE_2: 'https://github.com/', + }); + }); + + it('returns environment variables with token if hostRule for multiple hostsRules', () => { + add({ + hostType: 'github', + matchHost: 'api.github.com', + token: 'token123', + }); + add({ + hostType: 'gitlab', + matchHost: 'https://gitlab.example.com', + token: 'token234', + }); + add({ + hostType: 'github', + matchHost: 'https://github.example.com', + token: 'token345', + }); + expect(getGitEnvironmentVariables()).toStrictEqual({ + GIT_CONFIG_COUNT: '9', + GIT_CONFIG_KEY_0: 'url.https://ssh:token123@github.com/.insteadOf', + GIT_CONFIG_KEY_1: 'url.https://git:token123@github.com/.insteadOf', + GIT_CONFIG_KEY_2: 'url.https://token123@github.com/.insteadOf', + GIT_CONFIG_KEY_3: + 'url.https://gitlab-ci-token:token234@gitlab.example.com/.insteadOf', + GIT_CONFIG_KEY_4: + 'url.https://gitlab-ci-token:token234@gitlab.example.com/.insteadOf', + GIT_CONFIG_KEY_5: + 'url.https://gitlab-ci-token:token234@gitlab.example.com/.insteadOf', + GIT_CONFIG_KEY_6: + 'url.https://ssh:token345@github.example.com/.insteadOf', + GIT_CONFIG_KEY_7: + 'url.https://git:token345@github.example.com/.insteadOf', + GIT_CONFIG_KEY_8: 'url.https://token345@github.example.com/.insteadOf', + GIT_CONFIG_VALUE_0: 'ssh://git@github.com/', + GIT_CONFIG_VALUE_1: 'git@github.com:', + GIT_CONFIG_VALUE_2: 'https://github.com/', + GIT_CONFIG_VALUE_3: 'ssh://git@gitlab.example.com/', + GIT_CONFIG_VALUE_4: 'git@gitlab.example.com:', + GIT_CONFIG_VALUE_5: 'https://gitlab.example.com/', + GIT_CONFIG_VALUE_6: 'ssh://git@github.example.com/', + GIT_CONFIG_VALUE_7: 'git@github.example.com:', + GIT_CONFIG_VALUE_8: 'https://github.example.com/', + }); + }); + + it('returns environment variables with token if hostRule is for Gitlab', () => { + add({ + hostType: 'gitlab', + matchHost: 'https://gitlab.example.com', + token: 'token123', + }); + expect(getGitEnvironmentVariables()).toStrictEqual({ + GIT_CONFIG_COUNT: '3', + GIT_CONFIG_KEY_0: + 'url.https://gitlab-ci-token:token123@gitlab.example.com/.insteadOf', + GIT_CONFIG_KEY_1: + 'url.https://gitlab-ci-token:token123@gitlab.example.com/.insteadOf', + GIT_CONFIG_KEY_2: + 'url.https://gitlab-ci-token:token123@gitlab.example.com/.insteadOf', + GIT_CONFIG_VALUE_0: 'ssh://git@gitlab.example.com/', + GIT_CONFIG_VALUE_1: 'git@gitlab.example.com:', + GIT_CONFIG_VALUE_2: 'https://gitlab.example.com/', + }); + }); + + it('returns no environment variables when hostType is not supported', () => { + add({ + hostType: 'custom', + matchHost: 'https://custom.example.com', + token: 'token123', + }); + expect(getGitEnvironmentVariables()).toStrictEqual({}); + }); + + it('returns environment variables when hostType is explicitly set', () => { + add({ + hostType: 'custom', + matchHost: 'https://custom.example.com', + token: 'token123', + }); + expect(getGitEnvironmentVariables(['custom'])).toStrictEqual({ + GIT_CONFIG_COUNT: '3', + GIT_CONFIG_KEY_0: + 'url.https://ssh:token123@custom.example.com/.insteadOf', + GIT_CONFIG_KEY_1: + 'url.https://git:token123@custom.example.com/.insteadOf', + GIT_CONFIG_KEY_2: 'url.https://token123@custom.example.com/.insteadOf', + GIT_CONFIG_VALUE_0: 'ssh://git@custom.example.com/', + GIT_CONFIG_VALUE_1: 'git@custom.example.com:', + GIT_CONFIG_VALUE_2: 'https://custom.example.com/', + }); + }); + + it('returns empty environment variables when matchHost contains invalid protocol', () => { + add({ + hostType: 'github', + matchHost: 'invalid://*.github.example.com', + token: 'token123', + }); + expect(getGitEnvironmentVariables(['custom'])).toStrictEqual({}); + }); + }); }); diff --git a/lib/util/git/auth.ts b/lib/util/git/auth.ts index 31e6e639c7ee7baacfe5511f612ebe808d98f92b..cd0045901ad8e5a240209acd243d98418af4da9d 100644 --- a/lib/util/git/auth.ts +++ b/lib/util/git/auth.ts @@ -1,10 +1,30 @@ +import type { PlatformId } from '../../constants/platforms'; import { logger } from '../../logger'; import type { HostRule } from '../../types'; import { detectPlatform } from '../common'; +import { find, getAll } from '../host-rules'; import { regEx } from '../regex'; +import { createURLFromHostOrURL, validateUrl } from '../url'; import type { AuthenticationRule } from './types'; import { parseGitUrl } from './url'; +const githubApiUrls = new Set([ + 'github.com', + 'api.github.com', + 'https://api.github.com', + 'https://api.github.com/', +]); + +const standardGitAllowedHostTypes = [ + // All known git platforms + 'azure', + 'bitbucket', + 'bitbucket-server', + 'gitea', + 'github', + 'gitlab', +] satisfies PlatformId[]; + /** * Add authorization to a Git Url and returns a new environment variables object * @returns a new NodeJS.ProcessEnv object without modifying any input parameters @@ -124,3 +144,71 @@ export function getAuthenticationRules( return authenticationRules; } + +export function getGitEnvironmentVariables( + additionalHostTypes: string[] = [] +): NodeJS.ProcessEnv { + let environmentVariables: NodeJS.ProcessEnv = {}; + + // hard-coded logic to use authentication for github.com based on the githubToken for api.github.com + const githubToken = find({ + hostType: 'github', + url: 'https://api.github.com/', + }); + + if (githubToken?.token) { + environmentVariables = getGitAuthenticatedEnvironmentVariables( + 'https://github.com/', + githubToken + ); + } + + // construct the Set of allowed hostTypes consisting of the standard Git provides + // plus additionalHostTypes, which are provided as parameter + const gitAllowedHostTypes = new Set<string>([ + ...standardGitAllowedHostTypes, + ...additionalHostTypes, + ]); + + // filter rules without `matchHost` and `token` and github api github rules + const hostRules = getAll() + .filter((r) => r.matchHost && r.token) + .filter((r) => !githubToken || !githubApiUrls.has(r.matchHost!)); + + // for each hostRule without hostType we add additional authentication variables to the environmentVariables + // for each hostRule with hostType we add additional authentication variables to the environmentVariables + for (const hostRule of hostRules) { + if (!hostRule.hostType || gitAllowedHostTypes.has(hostRule.hostType)) { + environmentVariables = addAuthFromHostRule( + hostRule, + environmentVariables + ); + } + } + return environmentVariables; +} + +function addAuthFromHostRule( + hostRule: HostRule, + env: NodeJS.ProcessEnv +): NodeJS.ProcessEnv { + let environmentVariables = env; + const httpUrl = createURLFromHostOrURL(hostRule.matchHost!)?.toString(); + if (validateUrl(httpUrl)) { + logger.trace( + // TODO: types (#7154) + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Adding Git authentication for ${httpUrl} using token auth.` + ); + environmentVariables = getGitAuthenticatedEnvironmentVariables( + httpUrl!, + hostRule, + environmentVariables + ); + } else { + logger.debug( + `Could not parse registryUrl ${hostRule.matchHost!} or not using http(s). Ignoring` + ); + } + return environmentVariables; +}