-
Notifications
You must be signed in to change notification settings - Fork 3k
Expand file tree
/
Copy pathauth.ts
More file actions
254 lines (230 loc) · 8.75 KB
/
auth.ts
File metadata and controls
254 lines (230 loc) · 8.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
import { isEmptyString } from '@sindresorhus/is';
import { PLATFORM_HOST_TYPES } from '../../constants/platforms.ts';
import { logger } from '../../logger/index.ts';
import type { HostRule } from '../../types/index.ts';
import { detectPlatform } from '../common.ts';
import { getEnv } from '../env.ts';
import { find, getAll } from '../host-rules.ts';
import { regEx } from '../regex.ts';
import { createURLFromHostOrURL, isHttpUrl } from '../url.ts';
import type { AuthenticationRule } from './types.ts';
import { parseGitUrl } from './url.ts';
const githubApiUrls = new Set([
'github.com',
'api.github.com',
'https://api.github.com',
'https://api.github.com/',
]);
/**
* Add authorization to a Git Url and returns a new environment variables object
* @returns a new NodeJS.ProcessEnv object without modifying any input parameters
*/
export function getGitAuthenticatedEnvironmentVariables(
originalGitUrl: string,
{ token, username, password, hostType, matchHost }: HostRule,
environmentVariables?: NodeJS.ProcessEnv,
): NodeJS.ProcessEnv {
if (!token && !(username && password)) {
logger.warn(
{ host: matchHost },
`Could not create environment variable for host as neither token or username and password was set`,
);
return { ...environmentVariables };
}
const env = getEnv();
// check if the environmentVariables already contain a GIT_CONFIG_COUNT or if the process has one
const gitConfigCountEnvVariable =
environmentVariables?.GIT_CONFIG_COUNT ?? 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(
{
GIT_CONFIG_COUNT: env.GIT_CONFIG_COUNT,
},
`Found GIT_CONFIG_COUNT env variable, but couldn't parse the value to an integer. Ignoring it.`,
);
gitConfigCount = 0;
}
}
let authenticationRules: AuthenticationRule[];
if (token) {
authenticationRules = getAuthenticationRulesWithToken(
originalGitUrl,
hostType,
token,
);
} else {
const encodedUsername = encodeURIComponent(username!);
const encodedPassword = encodeURIComponent(password!);
authenticationRules = getAuthenticationRules(
originalGitUrl,
hostType,
`${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
// increase the CONFIG_COUNT by one for each rule and add it to the object
const newEnvironmentVariables = {
...environmentVariables,
};
for (const rule of authenticationRules) {
newEnvironmentVariables[`GIT_CONFIG_KEY_${gitConfigCount}`] =
`url.${rule.url}.insteadOf`;
newEnvironmentVariables[`GIT_CONFIG_VALUE_${gitConfigCount}`] =
rule.insteadOf;
gitConfigCount++;
}
newEnvironmentVariables.GIT_CONFIG_COUNT = gitConfigCount.toString();
return newEnvironmentVariables;
}
function getAuthenticationRulesWithToken(
url: string,
hostType: string | undefined | null,
authToken: string,
): AuthenticationRule[] {
let token = authToken;
let type = hostType;
type ??= detectPlatform(url);
if (type === 'gitlab') {
token = `gitlab-ci-token:${authToken}`;
}
return getAuthenticationRules(url, type, token);
}
/**
* Generates the authentication rules for later git usage for the given host
* @link https://coolaj86.com/articles/vanilla-devops-git-credentials-cheatsheet/
* @param gitUrl Git repository URL
* @param hostType Git host type
* @param token Authentication token or `username:password` string
*/
export function getAuthenticationRules(
gitUrl: string,
hostType: string | undefined | null,
token: string,
): AuthenticationRule[] {
const authenticationRules = [];
const hasUser = token.split(':').length > 1;
const insteadUrl = parseGitUrl(gitUrl);
let sshPort = insteadUrl.port;
if (hostType === 'bitbucket-server') {
// For Bitbucket Server/Data Center, `source` must be `bitbucket-server`
// to generate HTTP(s) URLs correctly.
// https://github.com/IonicaBizau/git-url-parse/blob/28828546c148d58bbcff61409915a4e1e8f7eb11/lib/index.js#L304
insteadUrl.source = 'bitbucket-server';
// sshPort is string, wrong type!
// https://github.com/DefinitelyTyped/DefinitelyTyped/pull/72361
// https://github.com/IonicaBizau/parse-path/blob/87f71f273da90f85f6845937a70b7c032eeae4e3/lib/index.js#L45
// v8 ignore next -- TODO: add test #40625
if (!sshPort || isEmptyString(sshPort)) {
// By default, bitbucket-server SSH port is 7999.
// For non-default port, the generated auth config will likely be incorrect.
sshPort = '7999';
}
}
const url = { ...insteadUrl };
const protocol = regEx(/^https?$/).test(url.protocol)
? url.protocol
: 'https';
// ssh protocol with user if empty
url.token = hasUser ? token : `ssh:${token}`;
authenticationRules.push({
url: url.toString(protocol),
// only edge case, need to stringify ourself because the exact syntax is not supported by the library
// https://github.com/IonicaBizau/git-url-parse/blob/246c9119fb42c2ea1c280028fe77c53eb34c190c/lib/index.js#L246
insteadOf: `ssh://git@${insteadUrl.resource}${
sshPort ? `:${sshPort}` : ''
}/${insteadUrl.full_name}${insteadUrl.git_suffix ? '.git' : ''}`,
});
// alternative ssh protocol with user if empty
url.token = hasUser ? token : `git:${token}`;
authenticationRules.push({
url: url.toString(protocol),
insteadOf: { ...insteadUrl, port: sshPort }.toString('ssh'),
});
// https protocol with no user as default fallback
url.token = token;
authenticationRules.push({
url: url.toString(protocol),
insteadOf: insteadUrl.toString(protocol),
});
if (hostType === 'gerrit') {
// Gerrit requires /a/ prefix in the URL path for authenticated HTTP access.
// Modify replacement URLs to include /a/ and add an extra rule so URLs
// that already contain /a/ are not doubled (git uses longest-prefix match).
const httpsInsteadOf = authenticationRules[2].insteadOf;
for (const rule of authenticationRules) {
rule.url = addGerritAuthPrefix(rule.url);
}
authenticationRules.push({
url: authenticationRules[2].url,
insteadOf: addGerritAuthPrefix(httpsInsteadOf),
});
}
return authenticationRules;
}
function addGerritAuthPrefix(urlStr: string): string {
const u = new URL(urlStr);
u.pathname = u.pathname.replace(/\/?$/, '/a/');
return u.href;
}
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 gitHubHostRule = find({
hostType: 'github',
url: 'https://api.github.com/',
});
if (gitHubHostRule?.token) {
environmentVariables = getGitAuthenticatedEnvironmentVariables(
'https://github.com/',
gitHubHostRule,
);
}
// construct the Set of allowed hostTypes consisting of the standard Git provides
// plus additionalHostTypes, which are provided as parameter
const gitAllowedHostTypes = new Set<string>([
...PLATFORM_HOST_TYPES,
...additionalHostTypes,
]);
// filter rules without `matchHost` and `token` or username and password and github api github rules
const hostRules = getAll()
.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
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 (isHttpUrl(httpUrl)) {
logger.trace(`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;
}