mirror of
https://github.com/actions/checkout.git
synced 2024-11-15 13:43:50 +08:00
Use git-credential-store instead of .extraheader
This commit is contained in:
parent
b80ff79f17
commit
6606fcd2c4
@ -71,6 +71,9 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
|
||||
# Default: true
|
||||
persist-credentials: ''
|
||||
|
||||
# Custom git credential helper
|
||||
custom-credential-helper: ''
|
||||
|
||||
# Relative path under $GITHUB_WORKSPACE to place the repository
|
||||
path: ''
|
||||
|
||||
|
@ -52,6 +52,8 @@ inputs:
|
||||
persist-credentials:
|
||||
description: 'Whether to configure the token or SSH key with the local git config'
|
||||
default: true
|
||||
custom-credential-helper:
|
||||
description: 'Custom git credential helper'
|
||||
path:
|
||||
description: 'Relative path under $GITHUB_WORKSPACE to place the repository'
|
||||
clean:
|
||||
|
90
dist/index.js
vendored
90
dist/index.js
vendored
@ -166,13 +166,16 @@ class GitAuthHelper {
|
||||
this.temporaryHomePath = '';
|
||||
this.git = gitCommandManager;
|
||||
this.settings = gitSourceSettings || {};
|
||||
// Token auth header
|
||||
this.credentialConfigKey = `credential.helper`;
|
||||
const runnerTemp = process.env['RUNNER_TEMP'] || '';
|
||||
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined');
|
||||
const uniqueId = (0, uuid_1.v4)();
|
||||
this.credentialStorePath = path.join(runnerTemp, `${uniqueId}_credential_store`);
|
||||
this.credentialConfigValue = `store --file ${this.credentialStorePath}`;
|
||||
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 ***`;
|
||||
this.tokenConfigValue = `AUTHORIZATION: basic ${basicCredential}`;
|
||||
serverUrl.username = `x-access-token`;
|
||||
serverUrl.password = this.settings.authToken;
|
||||
this.tokenCredential = serverUrl.href;
|
||||
// Instead of SSH URL
|
||||
this.insteadOfKey = `url.${serverUrl.origin}/.insteadOf`; // "origin" is SCHEME://HOSTNAME[:PORT]
|
||||
this.insteadOfValues.push(`git@${serverUrl.hostname}:`);
|
||||
@ -246,7 +249,7 @@ 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);
|
||||
yield this.git.tryConfigUnset(this.credentialConfigKey, true);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
@ -256,18 +259,12 @@ class GitAuthHelper {
|
||||
// Remove possible previous HTTPS instead of SSH
|
||||
yield this.removeGitConfig(this.insteadOfKey, true);
|
||||
if (this.settings.persistCredentials) {
|
||||
// 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
|
||||
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);
|
||||
// Replace the placeholder
|
||||
const configPaths = output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || [];
|
||||
for (const configPath of configPaths) {
|
||||
core.debug(`Replacing token placeholder in '${configPath}'`);
|
||||
yield this.replaceTokenPlaceholder(configPath);
|
||||
if (this.settings.customCredentialHelper) {
|
||||
yield this.git.submoduleForeach(`sh -c "git config --local --add '${this.credentialConfigKey}' '${this.settings.customCredentialHelper}' && git config --local 'credential.useHttpPath' 'true'"`, this.settings.nestedSubmodules);
|
||||
}
|
||||
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 --add '${this.credentialConfigKey}' '${this.credentialConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`, this.settings.nestedSubmodules);
|
||||
if (this.settings.sshKey) {
|
||||
// Configure core.sshCommand
|
||||
yield this.git.submoduleForeach(`git config --local '${SSH_COMMAND_KEY}' '${this.sshCommand}'`, this.settings.nestedSubmodules);
|
||||
@ -306,7 +303,7 @@ class GitAuthHelper {
|
||||
const runnerTemp = process.env['RUNNER_TEMP'] || '';
|
||||
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined');
|
||||
const uniqueId = (0, uuid_1.v4)();
|
||||
this.sshKeyPath = path.join(runnerTemp, uniqueId);
|
||||
this.sshKeyPath = path.join(runnerTemp, `${uniqueId}_ssh_key`);
|
||||
stateHelper.setSshKeyPath(this.sshKeyPath);
|
||||
yield fs.promises.mkdir(runnerTemp, { recursive: true });
|
||||
yield fs.promises.writeFile(this.sshKeyPath, this.settings.sshKey.trim() + '\n', { mode: 0o600 });
|
||||
@ -357,30 +354,17 @@ class GitAuthHelper {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// Validate args
|
||||
assert.ok((configPath && globalConfig) || (!configPath && !globalConfig), 'Unexpected configureToken parameter combinations');
|
||||
stateHelper.setCredentialStorePath(this.credentialStorePath);
|
||||
yield fs.promises.writeFile(this.credentialStorePath, this.tokenCredential);
|
||||
// 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');
|
||||
let content = (yield fs.promises.readFile(configPath)).toString();
|
||||
const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue);
|
||||
if (placeholderIndex < 0 ||
|
||||
placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue)) {
|
||||
throw new Error(`Unable to replace auth placeholder in ${configPath}`);
|
||||
if (this.settings.customCredentialHelper) {
|
||||
yield this.git.config(this.credentialConfigKey, this.settings.customCredentialHelper, globalConfig, true);
|
||||
yield this.git.config('credential.useHttpPath', 'true', globalConfig);
|
||||
}
|
||||
assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined');
|
||||
content = content.replace(this.tokenPlaceholderConfigValue, this.tokenConfigValue);
|
||||
yield fs.promises.writeFile(configPath, content);
|
||||
yield this.git.config(this.credentialConfigKey, this.credentialConfigValue, globalConfig, true);
|
||||
});
|
||||
}
|
||||
removeSsh() {
|
||||
@ -413,8 +397,19 @@ class GitAuthHelper {
|
||||
}
|
||||
removeToken() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// HTTP extra header
|
||||
yield this.removeGitConfig(this.tokenConfigKey);
|
||||
var _a;
|
||||
// Credential Helper
|
||||
const credentialStorePath = this.credentialStorePath || stateHelper.CredentialStorePath;
|
||||
if (credentialStorePath) {
|
||||
try {
|
||||
yield io.rmRF(credentialStorePath);
|
||||
}
|
||||
catch (err) {
|
||||
core.debug(`${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
|
||||
core.warning(`Failed to remove credential store '${credentialStorePath}'`);
|
||||
}
|
||||
}
|
||||
yield this.removeGitConfig(this.credentialConfigKey);
|
||||
});
|
||||
}
|
||||
removeGitConfig(configKey_1) {
|
||||
@ -1826,6 +1821,8 @@ function getInputs() {
|
||||
// Persist credentials
|
||||
result.persistCredentials =
|
||||
(core.getInput('persist-credentials') || 'false').toUpperCase() === 'TRUE';
|
||||
// Custom credential helper
|
||||
result.customCredentialHelper = core.getInput('custom-credential-helper');
|
||||
// Workflow organization ID
|
||||
result.workflowOrganizationId =
|
||||
yield workflowContextHelper.getOrganizationId();
|
||||
@ -2347,7 +2344,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.setSafeDirectory = exports.setSshKnownHostsPath = exports.setSshKeyPath = exports.setRepositoryPath = exports.SshKnownHostsPath = exports.SshKeyPath = exports.PostSetSafeDirectory = exports.RepositoryPath = exports.IsPost = void 0;
|
||||
exports.setCredentialStorePath = exports.setSafeDirectory = exports.setSshKnownHostsPath = exports.setSshKeyPath = exports.setRepositoryPath = exports.CredentialStorePath = exports.SshKnownHostsPath = exports.SshKeyPath = exports.PostSetSafeDirectory = exports.RepositoryPath = exports.IsPost = void 0;
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
/**
|
||||
* Indicates whether the POST action is running
|
||||
@ -2369,6 +2366,10 @@ exports.SshKeyPath = core.getState('sshKeyPath');
|
||||
* The SSH known hosts path for the POST action. The value is empty during the MAIN action.
|
||||
*/
|
||||
exports.SshKnownHostsPath = core.getState('sshKnownHostsPath');
|
||||
/**
|
||||
* The credential store path for git-credential-store
|
||||
*/
|
||||
exports.CredentialStorePath = core.getState('credentialStorePath');
|
||||
/**
|
||||
* Save the repository path so the POST action can retrieve the value.
|
||||
*/
|
||||
@ -2402,6 +2403,13 @@ exports.setSafeDirectory = setSafeDirectory;
|
||||
if (!exports.IsPost) {
|
||||
core.saveState('isPost', 'true');
|
||||
}
|
||||
/**
|
||||
* Save the credential store path so the POST action can retrieve the value.
|
||||
*/
|
||||
function setCredentialStorePath(credentialStorePath) {
|
||||
core.saveState('credentialStorePath', credentialStorePath);
|
||||
}
|
||||
exports.setCredentialStorePath = setCredentialStorePath;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
@ -34,9 +34,10 @@ 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 credentialConfigKey: string
|
||||
private readonly credentialConfigValue: string
|
||||
private readonly tokenCredential: string
|
||||
private readonly credentialStorePath: string
|
||||
private readonly insteadOfKey: string
|
||||
private readonly insteadOfValues: string[] = []
|
||||
private sshCommand = ''
|
||||
@ -51,16 +52,20 @@ class GitAuthHelper {
|
||||
this.git = gitCommandManager
|
||||
this.settings = gitSourceSettings || ({} as unknown as IGitSourceSettings)
|
||||
|
||||
// Token auth header
|
||||
this.credentialConfigKey = `credential.helper`
|
||||
const runnerTemp = process.env['RUNNER_TEMP'] || ''
|
||||
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
|
||||
const uniqueId = uuid()
|
||||
this.credentialStorePath = path.join(
|
||||
runnerTemp,
|
||||
`${uniqueId}_credential_store`
|
||||
)
|
||||
this.credentialConfigValue = `store --file ${this.credentialStorePath}`
|
||||
|
||||
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 ***`
|
||||
this.tokenConfigValue = `AUTHORIZATION: basic ${basicCredential}`
|
||||
serverUrl.username = `x-access-token`
|
||||
serverUrl.password = this.settings.authToken
|
||||
this.tokenCredential = serverUrl.href
|
||||
|
||||
// Instead of SSH URL
|
||||
this.insteadOfKey = `url.${serverUrl.origin}/.insteadOf` // "origin" is SCHEME://HOSTNAME[:PORT]
|
||||
@ -143,7 +148,7 @@ class GitAuthHelper {
|
||||
core.info(
|
||||
'Encountered an error when attempting to configure token. Attempting unconfigure.'
|
||||
)
|
||||
await this.git.tryConfigUnset(this.tokenConfigKey, true)
|
||||
await this.git.tryConfigUnset(this.credentialConfigKey, true)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@ -153,23 +158,19 @@ class GitAuthHelper {
|
||||
await this.removeGitConfig(this.insteadOfKey, true)
|
||||
|
||||
if (this.settings.persistCredentials) {
|
||||
// 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
|
||||
const output = await this.git.submoduleForeach(
|
||||
if (this.settings.customCredentialHelper) {
|
||||
await this.git.submoduleForeach(
|
||||
`sh -c "git config --local --add '${this.credentialConfigKey}' '${this.settings.customCredentialHelper}' && git config --local 'credential.useHttpPath' 'true'"`,
|
||||
this.settings.nestedSubmodules
|
||||
)
|
||||
}
|
||||
|
||||
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 --add '${this.credentialConfigKey}' '${this.credentialConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`,
|
||||
this.settings.nestedSubmodules
|
||||
)
|
||||
|
||||
// Replace the placeholder
|
||||
const configPaths: string[] =
|
||||
output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || []
|
||||
for (const configPath of configPaths) {
|
||||
core.debug(`Replacing token placeholder in '${configPath}'`)
|
||||
await this.replaceTokenPlaceholder(configPath)
|
||||
}
|
||||
|
||||
if (this.settings.sshKey) {
|
||||
// Configure core.sshCommand
|
||||
await this.git.submoduleForeach(
|
||||
@ -210,7 +211,7 @@ class GitAuthHelper {
|
||||
const runnerTemp = process.env['RUNNER_TEMP'] || ''
|
||||
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
|
||||
const uniqueId = uuid()
|
||||
this.sshKeyPath = path.join(runnerTemp, uniqueId)
|
||||
this.sshKeyPath = path.join(runnerTemp, `${uniqueId}_ssh_key`)
|
||||
stateHelper.setSshKeyPath(this.sshKeyPath)
|
||||
await fs.promises.mkdir(runnerTemp, {recursive: true})
|
||||
await fs.promises.writeFile(
|
||||
@ -282,40 +283,31 @@ class GitAuthHelper {
|
||||
'Unexpected configureToken parameter combinations'
|
||||
)
|
||||
|
||||
stateHelper.setCredentialStorePath(this.credentialStorePath)
|
||||
await fs.promises.writeFile(this.credentialStorePath, this.tokenCredential)
|
||||
|
||||
// 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
|
||||
)
|
||||
if (this.settings.customCredentialHelper) {
|
||||
await this.git.config(
|
||||
this.credentialConfigKey,
|
||||
this.settings.customCredentialHelper,
|
||||
globalConfig,
|
||||
true
|
||||
)
|
||||
|
||||
// Replace the placeholder
|
||||
await this.replaceTokenPlaceholder(configPath || '')
|
||||
}
|
||||
|
||||
private async replaceTokenPlaceholder(configPath: string): Promise<void> {
|
||||
assert.ok(configPath, 'configPath is not defined')
|
||||
let content = (await fs.promises.readFile(configPath)).toString()
|
||||
const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue)
|
||||
if (
|
||||
placeholderIndex < 0 ||
|
||||
placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue)
|
||||
) {
|
||||
throw new Error(`Unable to replace auth placeholder in ${configPath}`)
|
||||
await this.git.config('credential.useHttpPath', 'true', globalConfig)
|
||||
}
|
||||
assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined')
|
||||
content = content.replace(
|
||||
this.tokenPlaceholderConfigValue,
|
||||
this.tokenConfigValue
|
||||
|
||||
await this.git.config(
|
||||
this.credentialConfigKey,
|
||||
this.credentialConfigValue,
|
||||
globalConfig,
|
||||
true
|
||||
)
|
||||
await fs.promises.writeFile(configPath, content)
|
||||
}
|
||||
|
||||
private async removeSsh(): Promise<void> {
|
||||
@ -346,8 +338,20 @@ class GitAuthHelper {
|
||||
}
|
||||
|
||||
private async removeToken(): Promise<void> {
|
||||
// HTTP extra header
|
||||
await this.removeGitConfig(this.tokenConfigKey)
|
||||
// Credential Helper
|
||||
const credentialStorePath =
|
||||
this.credentialStorePath || stateHelper.CredentialStorePath
|
||||
if (credentialStorePath) {
|
||||
try {
|
||||
await io.rmRF(credentialStorePath)
|
||||
} catch (err) {
|
||||
core.debug(`${(err as any)?.message ?? err}`)
|
||||
core.warning(
|
||||
`Failed to remove credential store '${credentialStorePath}'`
|
||||
)
|
||||
}
|
||||
}
|
||||
await this.removeGitConfig(this.credentialConfigKey)
|
||||
}
|
||||
|
||||
private async removeGitConfig(
|
||||
|
@ -104,6 +104,11 @@ export interface IGitSourceSettings {
|
||||
*/
|
||||
persistCredentials: boolean
|
||||
|
||||
/**
|
||||
* Use following command/script as value for "credential.<URL>.helper"
|
||||
*/
|
||||
customCredentialHelper: string | undefined
|
||||
|
||||
/**
|
||||
* Organization ID for the currently running workflow (used for auth settings)
|
||||
*/
|
||||
|
@ -149,6 +149,9 @@ export async function getInputs(): Promise<IGitSourceSettings> {
|
||||
result.persistCredentials =
|
||||
(core.getInput('persist-credentials') || 'false').toUpperCase() === 'TRUE'
|
||||
|
||||
// Custom credential helper
|
||||
result.customCredentialHelper = core.getInput('custom-credential-helper')
|
||||
|
||||
// Workflow organization ID
|
||||
result.workflowOrganizationId =
|
||||
await workflowContextHelper.getOrganizationId()
|
||||
|
@ -25,6 +25,11 @@ export const SshKeyPath = core.getState('sshKeyPath')
|
||||
*/
|
||||
export const SshKnownHostsPath = core.getState('sshKnownHostsPath')
|
||||
|
||||
/**
|
||||
* The credential store path for git-credential-store
|
||||
*/
|
||||
export const CredentialStorePath = core.getState('credentialStorePath')
|
||||
|
||||
/**
|
||||
* Save the repository path so the POST action can retrieve the value.
|
||||
*/
|
||||
@ -58,3 +63,10 @@ export function setSafeDirectory() {
|
||||
if (!IsPost) {
|
||||
core.saveState('isPost', 'true')
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the credential store path so the POST action can retrieve the value.
|
||||
*/
|
||||
export function setCredentialStorePath(credentialStorePath: string) {
|
||||
core.saveState('credentialStorePath', credentialStorePath)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user