diff --git a/dist/index.js b/dist/index.js index 4556295..03e28d5 100644 --- a/dist/index.js +++ b/dist/index.js @@ -159,11 +159,11 @@ class GitAuthHelper { this.sshKeyPath = ''; this.sshKnownHostsPath = ''; this.temporaryHomePath = ''; + this.gitConfigPath = ''; this.git = gitCommandManager; this.settings = gitSourceSettings || {}; // Token auth header const serverUrl = urlHelper.getServerUrl(this.settings.githubServerUrl); - this.tokenConfigKey = `http.${serverUrl.origin}/.extraheader`; // "origin" is SCHEME://HOSTNAME[:PORT] const basicCredential = Buffer.from(`x-access-token:${this.settings.authToken}`, 'utf8').toString('base64'); core.setSecret(basicCredential); this.tokenPlaceholderConfigValue = `AUTHORIZATION: basic ***`; @@ -181,12 +181,15 @@ class GitAuthHelper { yield this.removeAuth(); // Configure new values yield this.configureSsh(); - yield this.configureToken(); + yield this.configureCredentialsHelper(); }); } configureTempGlobalConfig() { var _a, _b; return __awaiter(this, void 0, void 0, function* () { + if (!!this.gitConfigPath) { + return this.gitConfigPath; + } // Already setup global config if (((_a = this.temporaryHomePath) === null || _a === void 0 ? void 0 : _a.length) > 0) { return path.join(this.temporaryHomePath, '.gitconfig'); @@ -199,7 +202,7 @@ class GitAuthHelper { yield fs.promises.mkdir(this.temporaryHomePath, { recursive: true }); // Copy the global git config const gitConfigPath = path.join(process.env['HOME'] || os.homedir(), '.gitconfig'); - const newGitConfigPath = path.join(this.temporaryHomePath, '.gitconfig'); + this.gitConfigPath = path.join(this.temporaryHomePath, '.gitconfig'); let configExists = false; try { yield fs.promises.stat(gitConfigPath); @@ -211,16 +214,31 @@ class GitAuthHelper { } } if (configExists) { - core.info(`Copying '${gitConfigPath}' to '${newGitConfigPath}'`); - yield io.cp(gitConfigPath, newGitConfigPath); + core.info(`Copying '${gitConfigPath}' to '${this.gitConfigPath}'`); + yield io.cp(gitConfigPath, this.gitConfigPath); } else { - yield fs.promises.writeFile(newGitConfigPath, ''); + yield fs.promises.writeFile(this.gitConfigPath, ''); } // Override HOME core.info(`Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes`); this.git.setEnvironmentVariable('HOME', this.temporaryHomePath); - return newGitConfigPath; + return this.gitConfigPath; + }); + } + configureCredentialsHelper() { + return __awaiter(this, void 0, void 0, function* () { + if (this.settings.lfs) { + core.info(`lfs disabled, skipping custom credentials helper`); + return; + } + const newGitConfigPath = yield this.configureTempGlobalConfig(); + const credentialHelper = ` + [credential] + helper = "!f() { echo username=x-access-token; echo password=${this.tokenConfigValue}; };f" + `; + core.info(`Configuring git to use a custom credential helper for aut to handle git lfs`); + yield fs.promises.appendFile(newGitConfigPath, credentialHelper); }); } configureGlobalAuth() { @@ -229,7 +247,6 @@ class GitAuthHelper { const newGitConfigPath = yield this.configureTempGlobalConfig(); try { // Configure the token - yield this.configureToken(newGitConfigPath, true); // Configure HTTPS instead of SSH yield this.git.tryConfigUnset(this.insteadOfKey, true); if (!this.settings.sshKey) { @@ -241,7 +258,6 @@ class GitAuthHelper { catch (err) { // Unset in case somehow written to the real global config core.info('Encountered an error when attempting to configure token. Attempting unconfigure.'); - yield this.git.tryConfigUnset(this.tokenConfigKey, true); throw err; } }); @@ -256,7 +272,7 @@ class GitAuthHelper { // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing const output = yield this.git.submoduleForeach( // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline - `sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`, this.settings.nestedSubmodules); + `sh -c "git config --local --show-origin --name-only --get-regexp remote.origin.url"`, this.settings.nestedSubmodules); // Replace the placeholder const configPaths = output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || []; for (const configPath of configPaths) { @@ -279,7 +295,6 @@ class GitAuthHelper { removeAuth() { return __awaiter(this, void 0, void 0, function* () { yield this.removeSsh(); - yield this.removeToken(); }); } removeGlobalConfig() { @@ -349,22 +364,6 @@ class GitAuthHelper { } }); } - configureToken(configPath, globalConfig) { - return __awaiter(this, void 0, void 0, function* () { - // Validate args - assert.ok((configPath && globalConfig) || (!configPath && !globalConfig), 'Unexpected configureToken parameter combinations'); - // Default config path - if (!configPath && !globalConfig) { - configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config'); - } - // Configure a placeholder value. This approach avoids the credential being captured - // by process creation audit events, which are commonly logged. For more information, - // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing - yield this.git.config(this.tokenConfigKey, this.tokenPlaceholderConfigValue, globalConfig); - // Replace the placeholder - yield this.replaceTokenPlaceholder(configPath || ''); - }); - } replaceTokenPlaceholder(configPath) { return __awaiter(this, void 0, void 0, function* () { assert.ok(configPath, 'configPath is not defined'); @@ -407,12 +406,6 @@ class GitAuthHelper { yield this.removeGitConfig(SSH_COMMAND_KEY); }); } - removeToken() { - return __awaiter(this, void 0, void 0, function* () { - // HTTP extra header - yield this.removeGitConfig(this.tokenConfigKey); - }); - } removeGitConfig(configKey, submoduleOnly = false) { return __awaiter(this, void 0, void 0, function* () { if (!submoduleOnly) { diff --git a/src/git-auth-helper.ts b/src/git-auth-helper.ts index 364a04e..0866aae 100644 --- a/src/git-auth-helper.ts +++ b/src/git-auth-helper.ts @@ -20,6 +20,7 @@ export interface IGitAuthHelper { configureGlobalAuth(): Promise configureSubmoduleAuth(): Promise configureTempGlobalConfig(): Promise + configureCredentialsHelper(): Promise removeAuth(): Promise removeGlobalConfig(): Promise } @@ -34,7 +35,6 @@ export function createAuthHelper( class GitAuthHelper { private readonly git: IGitCommandManager private readonly settings: IGitSourceSettings - private readonly tokenConfigKey: string private readonly tokenConfigValue: string private readonly tokenPlaceholderConfigValue: string private readonly insteadOfKey: string @@ -43,6 +43,7 @@ class GitAuthHelper { private sshKeyPath = '' private sshKnownHostsPath = '' private temporaryHomePath = '' + private gitConfigPath = '' constructor( gitCommandManager: IGitCommandManager, @@ -53,7 +54,6 @@ class GitAuthHelper { // Token auth header const serverUrl = urlHelper.getServerUrl(this.settings.githubServerUrl) - this.tokenConfigKey = `http.${serverUrl.origin}/.extraheader` // "origin" is SCHEME://HOSTNAME[:PORT] const basicCredential = Buffer.from( `x-access-token:${this.settings.authToken}`, 'utf8' @@ -78,10 +78,13 @@ class GitAuthHelper { // Configure new values await this.configureSsh() - await this.configureToken() + await this.configureCredentialsHelper() } async configureTempGlobalConfig(): Promise { + if (!!this.gitConfigPath) { + return this.gitConfigPath + } // Already setup global config if (this.temporaryHomePath?.length > 0) { return path.join(this.temporaryHomePath, '.gitconfig') @@ -98,7 +101,7 @@ class GitAuthHelper { process.env['HOME'] || os.homedir(), '.gitconfig' ) - const newGitConfigPath = path.join(this.temporaryHomePath, '.gitconfig') + this.gitConfigPath = path.join(this.temporaryHomePath, '.gitconfig') let configExists = false try { await fs.promises.stat(gitConfigPath) @@ -109,10 +112,10 @@ class GitAuthHelper { } } if (configExists) { - core.info(`Copying '${gitConfigPath}' to '${newGitConfigPath}'`) - await io.cp(gitConfigPath, newGitConfigPath) + core.info(`Copying '${gitConfigPath}' to '${this.gitConfigPath}'`) + await io.cp(gitConfigPath, this.gitConfigPath) } else { - await fs.promises.writeFile(newGitConfigPath, '') + await fs.promises.writeFile(this.gitConfigPath, '') } // Override HOME @@ -121,7 +124,25 @@ class GitAuthHelper { ) this.git.setEnvironmentVariable('HOME', this.temporaryHomePath) - return newGitConfigPath + return this.gitConfigPath + } + + async configureCredentialsHelper(): Promise { + if (this.settings.lfs) { + core.info(`lfs disabled, skipping custom credentials helper`) + return + } + const newGitConfigPath = await this.configureTempGlobalConfig() + + const credentialHelper = ` + [credential] + helper = "!f() { echo username=x-access-token; echo password=${this.tokenConfigValue}; };f" + ` + + core.info( + `Configuring git to use a custom credential helper for aut to handle git lfs` + ) + await fs.promises.appendFile(newGitConfigPath, credentialHelper) } async configureGlobalAuth(): Promise { @@ -129,8 +150,6 @@ class GitAuthHelper { const newGitConfigPath = await this.configureTempGlobalConfig() try { // Configure the token - await this.configureToken(newGitConfigPath, true) - // Configure HTTPS instead of SSH await this.git.tryConfigUnset(this.insteadOfKey, true) if (!this.settings.sshKey) { @@ -143,7 +162,6 @@ class GitAuthHelper { core.info( 'Encountered an error when attempting to configure token. Attempting unconfigure.' ) - await this.git.tryConfigUnset(this.tokenConfigKey, true) throw err } } @@ -158,7 +176,7 @@ class GitAuthHelper { // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing const output = await this.git.submoduleForeach( // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline - `sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`, + `sh -c "git config --local --show-origin --name-only --get-regexp remote.origin.url"`, this.settings.nestedSubmodules ) @@ -190,7 +208,6 @@ class GitAuthHelper { async removeAuth(): Promise { await this.removeSsh() - await this.removeToken() } async removeGlobalConfig(): Promise { @@ -272,34 +289,6 @@ class GitAuthHelper { } } - private async configureToken( - configPath?: string, - globalConfig?: boolean - ): Promise { - // Validate args - assert.ok( - (configPath && globalConfig) || (!configPath && !globalConfig), - 'Unexpected configureToken parameter combinations' - ) - - // Default config path - if (!configPath && !globalConfig) { - configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config') - } - - // Configure a placeholder value. This approach avoids the credential being captured - // by process creation audit events, which are commonly logged. For more information, - // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing - await this.git.config( - this.tokenConfigKey, - this.tokenPlaceholderConfigValue, - globalConfig - ) - - // Replace the placeholder - await this.replaceTokenPlaceholder(configPath || '') - } - private async replaceTokenPlaceholder(configPath: string): Promise { assert.ok(configPath, 'configPath is not defined') let content = (await fs.promises.readFile(configPath)).toString() @@ -345,11 +334,6 @@ class GitAuthHelper { await this.removeGitConfig(SSH_COMMAND_KEY) } - private async removeToken(): Promise { - // HTTP extra header - await this.removeGitConfig(this.tokenConfigKey) - } - private async removeGitConfig( configKey: string, submoduleOnly: boolean = false