3
0
Fork 0
mirror of https://github.com/actions/checkout.git synced 2024-11-22 11:29:30 +01:00

Enforce safe directory (#762)

* set safe directory when running checkout

* Update CHANGELOG.md
This commit is contained in:
Thomas Boop 2022-04-14 14:13:20 -04:00 committed by GitHub
parent add3486cc3
commit dcd71f6466
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 244 additions and 169 deletions

View file

@ -1,5 +1,9 @@
# Changelog # Changelog
## v3.0.1
- [Fixed an issue where checkout failed to run in container jobs due to the new git setting `safe.directory`](https://github.com/actions/checkout/pull/762)
- [Bumped various npm package versions](https://github.com/actions/checkout/pull/744)
## v3.0.0 ## v3.0.0
- [Update to node 16](https://github.com/actions/checkout/pull/689) - [Update to node 16](https://github.com/actions/checkout/pull/689)

View file

@ -643,10 +643,11 @@ describe('git-auth-helper tests', () => {
expect(gitConfigContent.indexOf('http.')).toBeLessThan(0) expect(gitConfigContent.indexOf('http.')).toBeLessThan(0)
}) })
const removeGlobalAuth_removesOverride = 'removeGlobalAuth removes override' const removeGlobalConfig_removesOverride =
it(removeGlobalAuth_removesOverride, async () => { 'removeGlobalConfig removes override'
it(removeGlobalConfig_removesOverride, async () => {
// Arrange // Arrange
await setup(removeGlobalAuth_removesOverride) await setup(removeGlobalConfig_removesOverride)
const authHelper = gitAuthHelper.createAuthHelper(git, settings) const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth() await authHelper.configureAuth()
await authHelper.configureGlobalAuth() await authHelper.configureGlobalAuth()
@ -655,7 +656,7 @@ describe('git-auth-helper tests', () => {
await fs.promises.stat(path.join(git.env['HOME'], '.gitconfig')) await fs.promises.stat(path.join(git.env['HOME'], '.gitconfig'))
// Act // Act
await authHelper.removeGlobalAuth() await authHelper.removeGlobalConfig()
// Assert // Assert
expect(git.env['HOME']).toBeUndefined() expect(git.env['HOME']).toBeUndefined()

169
dist/index.js vendored
View file

@ -6572,9 +6572,13 @@ class GitAuthHelper {
yield this.configureToken(); yield this.configureToken();
}); });
} }
configureGlobalAuth() { configureTempGlobalConfig(repositoryPath) {
var _a; var _a, _b;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
// Already setup global config
if (((_a = this.temporaryHomePath) === null || _a === void 0 ? void 0 : _a.length) > 0) {
return path.join(this.temporaryHomePath, '.gitconfig');
}
// Create a temp home directory // Create a temp home directory
const runnerTemp = process.env['RUNNER_TEMP'] || ''; const runnerTemp = process.env['RUNNER_TEMP'] || '';
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined'); assert.ok(runnerTemp, 'RUNNER_TEMP is not defined');
@ -6590,7 +6594,7 @@ class GitAuthHelper {
configExists = true; configExists = true;
} }
catch (err) { catch (err) {
if (((_a = err) === null || _a === void 0 ? void 0 : _a.code) !== 'ENOENT') { if (((_b = err) === null || _b === void 0 ? void 0 : _b.code) !== 'ENOENT') {
throw err; throw err;
} }
} }
@ -6601,10 +6605,25 @@ class GitAuthHelper {
else { else {
yield fs.promises.writeFile(newGitConfigPath, ''); yield fs.promises.writeFile(newGitConfigPath, '');
} }
// Override HOME
core.info(`Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes`);
this.git.setEnvironmentVariable('HOME', this.temporaryHomePath);
// Setup the workspace as a safe directory, so if we pass this into a container job with a different user it doesn't fail
// Otherwise all git commands we run in a container fail
core.info(`Adding working directory to the temporary git global config as a safe directory`);
yield this.git
.config('safe.directory', repositoryPath !== null && repositoryPath !== void 0 ? repositoryPath : this.settings.repositoryPath, true, true)
.catch(error => {
core.info(`Failed to initialize safe directory with error: ${error}`);
});
return newGitConfigPath;
});
}
configureGlobalAuth() {
return __awaiter(this, void 0, void 0, function* () {
// 'configureTempGlobalConfig' noops if already set, just returns the path
const newGitConfigPath = yield this.configureTempGlobalConfig();
try { try {
// Override HOME
core.info(`Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes`);
this.git.setEnvironmentVariable('HOME', this.temporaryHomePath);
// Configure the token // Configure the token
yield this.configureToken(newGitConfigPath, true); yield this.configureToken(newGitConfigPath, true);
// Configure HTTPS instead of SSH // Configure HTTPS instead of SSH
@ -6657,11 +6676,14 @@ class GitAuthHelper {
yield this.removeToken(); yield this.removeToken();
}); });
} }
removeGlobalAuth() { removeGlobalConfig() {
var _a;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
core.debug(`Unsetting HOME override`); if (((_a = this.temporaryHomePath) === null || _a === void 0 ? void 0 : _a.length) > 0) {
this.git.removeEnvironmentVariable('HOME'); core.debug(`Unsetting HOME override`);
yield io.rmRF(this.temporaryHomePath); this.git.removeEnvironmentVariable('HOME');
yield io.rmRF(this.temporaryHomePath);
}
}); });
} }
configureSsh() { configureSsh() {
@ -7326,40 +7348,48 @@ function getSource(settings) {
core.startGroup('Getting Git version info'); core.startGroup('Getting Git version info');
const git = yield getGitCommandManager(settings); const git = yield getGitCommandManager(settings);
core.endGroup(); core.endGroup();
// Prepare existing directory, otherwise recreate let authHelper = null;
if (isExisting) {
yield gitDirectoryHelper.prepareExistingDirectory(git, settings.repositoryPath, repositoryUrl, settings.clean, settings.ref);
}
if (!git) {
// Downloading using REST API
core.info(`The repository will be downloaded using the GitHub REST API`);
core.info(`To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH`);
if (settings.submodules) {
throw new Error(`Input 'submodules' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`);
}
else if (settings.sshKey) {
throw new Error(`Input 'ssh-key' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`);
}
yield githubApiHelper.downloadRepository(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit, settings.repositoryPath);
return;
}
// Save state for POST action
stateHelper.setRepositoryPath(settings.repositoryPath);
// Initialize the repository
if (!fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))) {
core.startGroup('Initializing the repository');
yield git.init();
yield git.remoteAdd('origin', repositoryUrl);
core.endGroup();
}
// Disable automatic garbage collection
core.startGroup('Disabling automatic garbage collection');
if (!(yield git.tryDisableAutomaticGarbageCollection())) {
core.warning(`Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.`);
}
core.endGroup();
const authHelper = gitAuthHelper.createAuthHelper(git, settings);
try { try {
if (git) {
authHelper = gitAuthHelper.createAuthHelper(git, settings);
yield authHelper.configureTempGlobalConfig();
}
// Prepare existing directory, otherwise recreate
if (isExisting) {
yield gitDirectoryHelper.prepareExistingDirectory(git, settings.repositoryPath, repositoryUrl, settings.clean, settings.ref);
}
if (!git) {
// Downloading using REST API
core.info(`The repository will be downloaded using the GitHub REST API`);
core.info(`To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH`);
if (settings.submodules) {
throw new Error(`Input 'submodules' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`);
}
else if (settings.sshKey) {
throw new Error(`Input 'ssh-key' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`);
}
yield githubApiHelper.downloadRepository(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit, settings.repositoryPath);
return;
}
// Save state for POST action
stateHelper.setRepositoryPath(settings.repositoryPath);
// Initialize the repository
if (!fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))) {
core.startGroup('Initializing the repository');
yield git.init();
yield git.remoteAdd('origin', repositoryUrl);
core.endGroup();
}
// Disable automatic garbage collection
core.startGroup('Disabling automatic garbage collection');
if (!(yield git.tryDisableAutomaticGarbageCollection())) {
core.warning(`Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.`);
}
core.endGroup();
// If we didn't initialize it above, do it now
if (!authHelper) {
authHelper = gitAuthHelper.createAuthHelper(git, settings);
}
// Configure auth // Configure auth
core.startGroup('Setting up auth'); core.startGroup('Setting up auth');
yield authHelper.configureAuth(); yield authHelper.configureAuth();
@ -7415,27 +7445,21 @@ function getSource(settings) {
core.endGroup(); core.endGroup();
// Submodules // Submodules
if (settings.submodules) { if (settings.submodules) {
try { // Temporarily override global config
// Temporarily override global config core.startGroup('Setting up auth for fetching submodules');
core.startGroup('Setting up auth for fetching submodules'); yield authHelper.configureGlobalAuth();
yield authHelper.configureGlobalAuth(); core.endGroup();
// Checkout submodules
core.startGroup('Fetching submodules');
yield git.submoduleSync(settings.nestedSubmodules);
yield git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules);
yield git.submoduleForeach('git config --local gc.auto 0', settings.nestedSubmodules);
core.endGroup();
// Persist credentials
if (settings.persistCredentials) {
core.startGroup('Persisting credentials for submodules');
yield authHelper.configureSubmoduleAuth();
core.endGroup(); core.endGroup();
// Checkout submodules
core.startGroup('Fetching submodules');
yield git.submoduleSync(settings.nestedSubmodules);
yield git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules);
yield git.submoduleForeach('git config --local gc.auto 0', settings.nestedSubmodules);
core.endGroup();
// Persist credentials
if (settings.persistCredentials) {
core.startGroup('Persisting credentials for submodules');
yield authHelper.configureSubmoduleAuth();
core.endGroup();
}
}
finally {
// Remove temporary global config override
yield authHelper.removeGlobalAuth();
} }
} }
// Get commit information // Get commit information
@ -7447,10 +7471,13 @@ function getSource(settings) {
} }
finally { finally {
// Remove auth // Remove auth
if (!settings.persistCredentials) { if (authHelper) {
core.startGroup('Removing auth'); if (!settings.persistCredentials) {
yield authHelper.removeAuth(); core.startGroup('Removing auth');
core.endGroup(); yield authHelper.removeAuth();
core.endGroup();
}
authHelper.removeGlobalConfig();
} }
} }
}); });
@ -7472,7 +7499,13 @@ function cleanup(repositoryPath) {
} }
// Remove auth // Remove auth
const authHelper = gitAuthHelper.createAuthHelper(git); const authHelper = gitAuthHelper.createAuthHelper(git);
yield authHelper.removeAuth(); try {
yield authHelper.configureTempGlobalConfig(repositoryPath);
yield authHelper.removeAuth();
}
finally {
yield authHelper.removeGlobalConfig();
}
}); });
} }
exports.cleanup = cleanup; exports.cleanup = cleanup;

View file

@ -19,8 +19,9 @@ export interface IGitAuthHelper {
configureAuth(): Promise<void> configureAuth(): Promise<void>
configureGlobalAuth(): Promise<void> configureGlobalAuth(): Promise<void>
configureSubmoduleAuth(): Promise<void> configureSubmoduleAuth(): Promise<void>
configureTempGlobalConfig(repositoryPath?: string): Promise<string>
removeAuth(): Promise<void> removeAuth(): Promise<void>
removeGlobalAuth(): Promise<void> removeGlobalConfig(): Promise<void>
} }
export function createAuthHelper( export function createAuthHelper(
@ -80,7 +81,11 @@ class GitAuthHelper {
await this.configureToken() await this.configureToken()
} }
async configureGlobalAuth(): Promise<void> { async configureTempGlobalConfig(repositoryPath?: string): Promise<string> {
// Already setup global config
if (this.temporaryHomePath?.length > 0) {
return path.join(this.temporaryHomePath, '.gitconfig')
}
// Create a temp home directory // Create a temp home directory
const runnerTemp = process.env['RUNNER_TEMP'] || '' const runnerTemp = process.env['RUNNER_TEMP'] || ''
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined') assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
@ -110,13 +115,34 @@ class GitAuthHelper {
await fs.promises.writeFile(newGitConfigPath, '') await fs.promises.writeFile(newGitConfigPath, '')
} }
try { // Override HOME
// Override HOME core.info(
core.info( `Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes`
`Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes` )
) this.git.setEnvironmentVariable('HOME', this.temporaryHomePath)
this.git.setEnvironmentVariable('HOME', this.temporaryHomePath)
// Setup the workspace as a safe directory, so if we pass this into a container job with a different user it doesn't fail
// Otherwise all git commands we run in a container fail
core.info(
`Adding working directory to the temporary git global config as a safe directory`
)
await this.git
.config(
'safe.directory',
repositoryPath ?? this.settings.repositoryPath,
true,
true
)
.catch(error => {
core.info(`Failed to initialize safe directory with error: ${error}`)
})
return newGitConfigPath
}
async configureGlobalAuth(): Promise<void> {
// 'configureTempGlobalConfig' noops if already set, just returns the path
const newGitConfigPath = await this.configureTempGlobalConfig()
try {
// Configure the token // Configure the token
await this.configureToken(newGitConfigPath, true) await this.configureToken(newGitConfigPath, true)
@ -181,10 +207,12 @@ class GitAuthHelper {
await this.removeToken() await this.removeToken()
} }
async removeGlobalAuth(): Promise<void> { async removeGlobalConfig(): Promise<void> {
core.debug(`Unsetting HOME override`) if (this.temporaryHomePath?.length > 0) {
this.git.removeEnvironmentVariable('HOME') core.debug(`Unsetting HOME override`)
await io.rmRF(this.temporaryHomePath) this.git.removeEnvironmentVariable('HOME')
await io.rmRF(this.temporaryHomePath)
}
} }
private async configureSsh(): Promise<void> { private async configureSsh(): Promise<void> {

View file

@ -36,68 +36,77 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
const git = await getGitCommandManager(settings) const git = await getGitCommandManager(settings)
core.endGroup() core.endGroup()
// Prepare existing directory, otherwise recreate let authHelper: gitAuthHelper.IGitAuthHelper | null = null
if (isExisting) { try {
await gitDirectoryHelper.prepareExistingDirectory( if (git) {
git, authHelper = gitAuthHelper.createAuthHelper(git, settings)
settings.repositoryPath, await authHelper.configureTempGlobalConfig()
repositoryUrl, }
settings.clean,
settings.ref
)
}
if (!git) { // Prepare existing directory, otherwise recreate
// Downloading using REST API if (isExisting) {
core.info(`The repository will be downloaded using the GitHub REST API`) await gitDirectoryHelper.prepareExistingDirectory(
core.info( git,
`To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH` settings.repositoryPath,
) repositoryUrl,
if (settings.submodules) { settings.clean,
throw new Error( settings.ref
`Input 'submodules' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`
)
} else if (settings.sshKey) {
throw new Error(
`Input 'ssh-key' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`
) )
} }
await githubApiHelper.downloadRepository( if (!git) {
settings.authToken, // Downloading using REST API
settings.repositoryOwner, core.info(`The repository will be downloaded using the GitHub REST API`)
settings.repositoryName, core.info(
settings.ref, `To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH`
settings.commit, )
settings.repositoryPath if (settings.submodules) {
) throw new Error(
return `Input 'submodules' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`
} )
} else if (settings.sshKey) {
throw new Error(
`Input 'ssh-key' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`
)
}
// Save state for POST action await githubApiHelper.downloadRepository(
stateHelper.setRepositoryPath(settings.repositoryPath) settings.authToken,
settings.repositoryOwner,
settings.repositoryName,
settings.ref,
settings.commit,
settings.repositoryPath
)
return
}
// Initialize the repository // Save state for POST action
if ( stateHelper.setRepositoryPath(settings.repositoryPath)
!fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))
) { // Initialize the repository
core.startGroup('Initializing the repository') if (
await git.init() !fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))
await git.remoteAdd('origin', repositoryUrl) ) {
core.startGroup('Initializing the repository')
await git.init()
await git.remoteAdd('origin', repositoryUrl)
core.endGroup()
}
// Disable automatic garbage collection
core.startGroup('Disabling automatic garbage collection')
if (!(await git.tryDisableAutomaticGarbageCollection())) {
core.warning(
`Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.`
)
}
core.endGroup() core.endGroup()
}
// Disable automatic garbage collection // If we didn't initialize it above, do it now
core.startGroup('Disabling automatic garbage collection') if (!authHelper) {
if (!(await git.tryDisableAutomaticGarbageCollection())) { authHelper = gitAuthHelper.createAuthHelper(git, settings)
core.warning( }
`Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.`
)
}
core.endGroup()
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
try {
// Configure auth // Configure auth
core.startGroup('Setting up auth') core.startGroup('Setting up auth')
await authHelper.configureAuth() await authHelper.configureAuth()
@ -170,34 +179,26 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
// Submodules // Submodules
if (settings.submodules) { if (settings.submodules) {
try { // Temporarily override global config
// Temporarily override global config core.startGroup('Setting up auth for fetching submodules')
core.startGroup('Setting up auth for fetching submodules') await authHelper.configureGlobalAuth()
await authHelper.configureGlobalAuth() core.endGroup()
core.endGroup()
// Checkout submodules // Checkout submodules
core.startGroup('Fetching submodules') core.startGroup('Fetching submodules')
await git.submoduleSync(settings.nestedSubmodules) await git.submoduleSync(settings.nestedSubmodules)
await git.submoduleUpdate( await git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules)
settings.fetchDepth, await git.submoduleForeach(
settings.nestedSubmodules 'git config --local gc.auto 0',
) settings.nestedSubmodules
await git.submoduleForeach( )
'git config --local gc.auto 0', core.endGroup()
settings.nestedSubmodules
)
core.endGroup()
// Persist credentials // Persist credentials
if (settings.persistCredentials) { if (settings.persistCredentials) {
core.startGroup('Persisting credentials for submodules') core.startGroup('Persisting credentials for submodules')
await authHelper.configureSubmoduleAuth() await authHelper.configureSubmoduleAuth()
core.endGroup() core.endGroup()
}
} finally {
// Remove temporary global config override
await authHelper.removeGlobalAuth()
} }
} }
@ -218,10 +219,13 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
) )
} finally { } finally {
// Remove auth // Remove auth
if (!settings.persistCredentials) { if (authHelper) {
core.startGroup('Removing auth') if (!settings.persistCredentials) {
await authHelper.removeAuth() core.startGroup('Removing auth')
core.endGroup() await authHelper.removeAuth()
core.endGroup()
}
authHelper.removeGlobalConfig()
} }
} }
} }
@ -244,7 +248,12 @@ export async function cleanup(repositoryPath: string): Promise<void> {
// Remove auth // Remove auth
const authHelper = gitAuthHelper.createAuthHelper(git) const authHelper = gitAuthHelper.createAuthHelper(git)
await authHelper.removeAuth() try {
await authHelper.configureTempGlobalConfig(repositoryPath)
await authHelper.removeAuth()
} finally {
await authHelper.removeGlobalConfig()
}
} }
async function getGitCommandManager( async function getGitCommandManager(