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(git/auth): Support hostrules with username and password when authenticating git commands #24081

Merged
merged 9 commits into from
Sep 4, 2023
29 changes: 29 additions & 0 deletions lib/modules/manager/git-submodules/extract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,35 @@ describe('modules/manager/git-submodules/extract', () => {
);
});

it('combined username+pwd from host rule is used to detect branch for gitlab', async () => {
gitMock.listRemote.mockResolvedValueOnce(
'ref: refs/heads/main HEAD\n5701164b9f5edba1f6ca114c491a564ffb55a964 HEAD'
);
hostRules.add({
hostType: 'gitlab',
matchHost: 'gitlab.com',
username: 'username',
password: 'password',
});
const res = await extractPackageFile('', '.gitmodules.2', {});
expect(res?.deps).toHaveLength(1);
expect(res?.deps[0].currentValue).toBe('main');
expect(gitMock.env).toHaveBeenCalledWith({
GIT_CONFIG_COUNT: '3',
GIT_CONFIG_KEY_0: 'url.https://username:[email protected]/.insteadOf',
GIT_CONFIG_KEY_1: 'url.https://username:[email protected]/.insteadOf',
GIT_CONFIG_KEY_2: 'url.https://username:[email protected]/.insteadOf',
GIT_CONFIG_VALUE_0: 'ssh://[email protected]/',
GIT_CONFIG_VALUE_1: '[email protected]:',
GIT_CONFIG_VALUE_2: 'https://gitlab.com/',
});
expect(gitMock.listRemote).toHaveBeenCalledWith([
'--symref',
'https://github.com/PowerShell/PowerShell-Docs',
'HEAD',
]);
});

it('extracts multiple submodules', async () => {
hostRules.add({ matchHost: 'github.com', token: '123test' });
hostRules.add({
Expand Down
2 changes: 1 addition & 1 deletion lib/modules/manager/git-submodules/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ The following logic is executed prior to "submodules" updating:
The token from the `hostRules` entry matching `hostType=github` and `matchHost=api.github.com` is added as the default authentication for `github.com`.
For those running against `github.com`, this token will be the default platform token.

Next, all `hostRules` with both a token and `matchHost` will be fetched, except for any github.com one from above.
Next, all `hostRules` with both a token or username/password and `matchHost` will be fetched, except for any github.com one from above.

Rules from this list are converted to environment variable directives if they match _any_ of the following characteristics:

Expand Down
2 changes: 1 addition & 1 deletion lib/modules/manager/gomod/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ The following logic is executed prior to "artifacts" updating:
The token from the `hostRules` entry matching `hostType=github` and `matchHost=api.github.com` is added as the default authentication for `github.com`.
For those running against `github.com`, this token will be the default platform token.

Next, all `hostRules` with both a token and `matchHost` will be fetched, except for any github.com one from above.
Next, all `hostRules` with both a token or username/password and `matchHost` will be fetched, except for any github.com one from above.

Rules from this list are converted to environment variable directives if they match _any_ of the following characteristics:

Expand Down
106 changes: 103 additions & 3 deletions lib/util/git/auth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,48 @@ describe('util/git/auth', () => {
});
});

it('returns url with username and password', () => {
expect(
getGitAuthenticatedEnvironmentVariables('https://example.com/', {
username: 'username',
password: 'password',
hostType: 'github',
matchHost: 'example.com',
})
).toStrictEqual({
GIT_CONFIG_COUNT: '3',
GIT_CONFIG_KEY_0:
'url.https://username:[email protected]/.insteadOf',
GIT_CONFIG_KEY_1:
'url.https://username:[email protected]/.insteadOf',
GIT_CONFIG_KEY_2:
'url.https://username:[email protected]/.insteadOf',
GIT_CONFIG_VALUE_0: 'ssh://[email protected]/',
GIT_CONFIG_VALUE_1: '[email protected]:',
GIT_CONFIG_VALUE_2: 'https://example.com/',
});
});

it('prefers token over username and password', () => {
expect(
getGitAuthenticatedEnvironmentVariables('https://github.com/', {
username: 'username',
password: 'password',
token: 'token1234',
hostType: 'github',
matchHost: 'github.com',
})
).toStrictEqual({
GIT_CONFIG_COUNT: '3',
GIT_CONFIG_KEY_0: 'url.https://ssh:[email protected]/.insteadOf',
GIT_CONFIG_KEY_1: 'url.https://git:[email protected]/.insteadOf',
GIT_CONFIG_KEY_2: 'url.https://[email protected]/.insteadOf',
GIT_CONFIG_VALUE_0: 'ssh://[email protected]/',
GIT_CONFIG_VALUE_1: '[email protected]:',
GIT_CONFIG_VALUE_2: 'https://github.com/',
});
});

it('returns url with token for different protocols', () => {
expect(
getGitAuthenticatedEnvironmentVariables('foobar://github.com/', {
Expand Down Expand Up @@ -219,10 +261,8 @@ describe('util/git/auth', () => {
getGitAuthenticatedEnvironmentVariables(
'https://gitlab.com/',
{
username: 'testing',
password: '1234',
hostType: 'gitlab',
matchHost: 'github.com',
matchHost: 'gitlab.com',
},
{ env: 'value' }
)
Expand Down Expand Up @@ -402,6 +442,48 @@ describe('util/git/auth', () => {
});
});

it('returns environment variables with username and password', () => {
add({
hostType: 'gitlab',
matchHost: 'https://gitlab.example.com',
username: 'user1234',
password: 'pass1234',
});
expect(getGitEnvironmentVariables()).toStrictEqual({
GIT_CONFIG_COUNT: '3',
GIT_CONFIG_KEY_0:
'url.https://user1234:[email protected]/.insteadOf',
GIT_CONFIG_KEY_1:
'url.https://user1234:[email protected]/.insteadOf',
GIT_CONFIG_KEY_2:
'url.https://user1234:[email protected]/.insteadOf',
GIT_CONFIG_VALUE_0: 'ssh://[email protected]/',
GIT_CONFIG_VALUE_1: '[email protected]:',
GIT_CONFIG_VALUE_2: 'https://gitlab.example.com/',
});
});

it('returns environment variables with URL encoded username and password', () => {
add({
hostType: 'gitlab',
matchHost: 'https://gitlab.example.com',
username: 'user @ :$ abc',
password: 'abc @ blub pass0:',
});
expect(getGitEnvironmentVariables()).toStrictEqual({
GIT_CONFIG_COUNT: '3',
GIT_CONFIG_KEY_0:
'url.https://user%20%40%20%3A%24%20abc:abc%20%40%20blub%20pass0%[email protected]/.insteadOf',
GIT_CONFIG_KEY_1:
'url.https://user%20%40%20%3A%24%20abc:abc%20%40%20blub%20pass0%[email protected]/.insteadOf',
GIT_CONFIG_KEY_2:
'url.https://user%20%40%20%3A%24%20abc:abc%20%40%20blub%20pass0%[email protected]/.insteadOf',
GIT_CONFIG_VALUE_0: 'ssh://[email protected]/',
GIT_CONFIG_VALUE_1: '[email protected]:',
GIT_CONFIG_VALUE_2: 'https://gitlab.example.com/',
});
});

it('returns no environment variables when hostType is not supported', () => {
add({
hostType: 'custom',
Expand All @@ -411,6 +493,24 @@ describe('util/git/auth', () => {
expect(getGitEnvironmentVariables()).toStrictEqual({});
});

it('returns no environment variables when only username is set', () => {
add({
hostType: 'custom',
matchHost: 'https://custom.example.com',
username: 'user123',
});
expect(getGitEnvironmentVariables()).toStrictEqual({});
});

it('returns no environment variables when only password is set', () => {
add({
hostType: 'custom',
matchHost: 'https://custom.example.com',
password: 'pass123',
});
expect(getGitEnvironmentVariables()).toStrictEqual({});
});

it('returns environment variables when hostType is explicitly set', () => {
add({
hostType: 'custom',
Expand Down
38 changes: 24 additions & 14 deletions lib/util/git/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ const standardGitAllowedHostTypes = [
*/
export function getGitAuthenticatedEnvironmentVariables(
originalGitUrl: string,
{ token, hostType, matchHost }: HostRule,
{ token, username, password, hostType, matchHost }: HostRule,
environmentVariables?: NodeJS.ProcessEnv
): NodeJS.ProcessEnv {
if (!token) {
if (!token && !(username && password)) {
logger.warn(
// TODO: types (#22198)
`Could not create environment variable for ${matchHost!} as token was empty`
`Could not create environment variable for ${matchHost!} as neither token or username and password was set`
);
return { ...environmentVariables };
}
Expand All @@ -58,12 +58,22 @@ export function getGitAuthenticatedEnvironmentVariables(
gitConfigCount = 0;
}
}
let authenticationRules: AuthenticationRule[];
if (token) {
authenticationRules = getAuthenticationRulesWithToken(
originalGitUrl,
hostType,
token
);
} else {
const encodedUsername = encodeURIComponent(username!);
const encodedPassword = encodeURIComponent(password!);

const authenticationRules = getAuthenticationRulesWithToken(
originalGitUrl,
hostType,
token
);
authenticationRules = getAuthenticationRules(
originalGitUrl,
`${encodedUsername}:${encodedPassword}`
);
}

// 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
Expand Down Expand Up @@ -151,15 +161,15 @@ export function getGitEnvironmentVariables(
let environmentVariables: NodeJS.ProcessEnv = {};

// hard-coded logic to use authentication for github.com based on the githubToken for api.github.com
const githubToken = find({
const gitHubHostRule = find({
viceice marked this conversation as resolved.
Show resolved Hide resolved
hostType: 'github',
url: 'https://api.github.com/',
});

if (githubToken?.token) {
if (gitHubHostRule?.token) {
environmentVariables = getGitAuthenticatedEnvironmentVariables(
'https://github.com/',
githubToken
gitHubHostRule
);
}

Expand All @@ -170,10 +180,10 @@ export function getGitEnvironmentVariables(
...additionalHostTypes,
]);

// filter rules without `matchHost` and `token` and github api github rules
// filter rules without `matchHost` and `token` or username and password and github api github rules
const hostRules = getAll()
.filter((r) => r.matchHost && r.token)
.filter((r) => !githubToken || !githubApiUrls.has(r.matchHost!));
.filter((r) => r.matchHost && (r.token ?? (r.username && r.password)))
.filter((r) => !gitHubHostRule || !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
Expand Down