Read-only mirror — the public repositories of the Forgejo instance, refreshed every 10 minutes. The live instance (fg.dieschoenewolke.de) is reachable only over the VPN; clone here with https://git.dieschoenewolke.de/git/<owner>/<repo>.git.
summaryrefslogtreecommitdiff
path: root/src/git-auth-helper.ts
diff options
context:
space:
mode:
authoreric sciple <ericsciple@users.noreply.github.com>2020-03-05 14:21:59 -0500
committerGitHub <noreply@github.com>2020-03-05 14:21:59 -0500
commit7ca5766bfacc6c307d7e69645aa94ac44318af46 (patch)
tree6cf0884441af3df11112f769de7e2bd6b2db727e /src/git-auth-helper.ts
parent05e00fbe0759a9339f189796ba5ceb3c4c78a556 (diff)
add support for submodules (#173)
Diffstat (limited to 'src/git-auth-helper.ts')
-rw-r--r--src/git-auth-helper.ts154
1 files changed, 130 insertions, 24 deletions
diff --git a/src/git-auth-helper.ts b/src/git-auth-helper.ts
index 7f7f11f..dd76fe9 100644
--- a/src/git-auth-helper.ts
+++ b/src/git-auth-helper.ts
@@ -5,6 +5,7 @@ import * as fs from 'fs'
import * as io from '@actions/io'
import * as os from 'os'
import * as path from 'path'
+import * as regexpHelper from './regexp-helper'
import * as stateHelper from './state-helper'
import {default as uuid} from 'uuid/v4'
import {IGitCommandManager} from './git-command-manager'
@@ -12,11 +13,13 @@ import {IGitSourceSettings} from './git-source-settings'
const IS_WINDOWS = process.platform === 'win32'
const HOSTNAME = 'github.com'
-const EXTRA_HEADER_KEY = `http.https://${HOSTNAME}/.extraheader`
export interface IGitAuthHelper {
configureAuth(): Promise<void>
+ configureGlobalAuth(): Promise<void>
+ configureSubmoduleAuth(): Promise<void>
removeAuth(): Promise<void>
+ removeGlobalAuth(): Promise<void>
}
export function createAuthHelper(
@@ -27,8 +30,12 @@ export function createAuthHelper(
}
class GitAuthHelper {
- private git: IGitCommandManager
- private settings: IGitSourceSettings
+ private readonly git: IGitCommandManager
+ private readonly settings: IGitSourceSettings
+ private readonly tokenConfigKey: string = `http.https://${HOSTNAME}/.extraheader`
+ private readonly tokenPlaceholderConfigValue: string
+ private temporaryHomePath = ''
+ private tokenConfigValue: string
constructor(
gitCommandManager: IGitCommandManager,
@@ -36,6 +43,15 @@ class GitAuthHelper {
) {
this.git = gitCommandManager
this.settings = gitSourceSettings || (({} as unknown) as IGitSourceSettings)
+
+ // Token auth header
+ 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}`
}
async configureAuth(): Promise<void> {
@@ -46,48 +62,132 @@ class GitAuthHelper {
await this.configureToken()
}
+ async configureGlobalAuth(): Promise<void> {
+ // Create a temp home directory
+ const runnerTemp = process.env['RUNNER_TEMP'] || ''
+ assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
+ const uniqueId = uuid()
+ this.temporaryHomePath = path.join(runnerTemp, uniqueId)
+ await 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')
+ let configExists = false
+ try {
+ await fs.promises.stat(gitConfigPath)
+ configExists = true
+ } catch (err) {
+ if (err.code !== 'ENOENT') {
+ throw err
+ }
+ }
+ if (configExists) {
+ core.info(`Copying '${gitConfigPath}' to '${newGitConfigPath}'`)
+ await io.cp(gitConfigPath, newGitConfigPath)
+ } else {
+ await fs.promises.writeFile(newGitConfigPath, '')
+ }
+
+ // Configure the token
+ try {
+ core.info(
+ `Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes`
+ )
+ this.git.setEnvironmentVariable('HOME', this.temporaryHomePath)
+ await this.configureToken(newGitConfigPath, true)
+ } catch (err) {
+ // Unset in case somehow written to the real global config
+ core.info(
+ 'Encountered an error when attempting to configure token. Attempting unconfigure.'
+ )
+ await this.git.tryConfigUnset(this.tokenConfigKey, true)
+ throw err
+ }
+ }
+
+ async configureSubmoduleAuth(): Promise<void> {
+ 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(
+ `git config "${this.tokenConfigKey}" "${this.tokenPlaceholderConfigValue}" && 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}'`)
+ this.replaceTokenPlaceholder(configPath)
+ }
+ }
+ }
+
async removeAuth(): Promise<void> {
await this.removeToken()
}
- private async configureToken(): Promise<void> {
+ async removeGlobalAuth(): Promise<void> {
+ core.info(`Unsetting HOME override`)
+ this.git.removeEnvironmentVariable('HOME')
+ await io.rmRF(this.temporaryHomePath)
+ }
+
+ private async configureToken(
+ configPath?: string,
+ globalConfig?: boolean
+ ): Promise<void> {
+ // 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
- const placeholder = `AUTHORIZATION: basic ***`
- await this.git.config(EXTRA_HEADER_KEY, placeholder)
+ await this.git.config(
+ this.tokenConfigKey,
+ this.tokenPlaceholderConfigValue,
+ globalConfig
+ )
- // Determine the basic credential value
- const basicCredential = Buffer.from(
- `x-access-token:${this.settings.authToken}`,
- 'utf8'
- ).toString('base64')
- core.setSecret(basicCredential)
+ // Replace the placeholder
+ await this.replaceTokenPlaceholder(configPath || '')
+ }
- // Replace the value in the config file
- const configPath = path.join(
- this.git.getWorkingDirectory(),
- '.git',
- 'config'
- )
+ 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(placeholder)
+ const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue)
if (
placeholderIndex < 0 ||
- placeholderIndex != content.lastIndexOf(placeholder)
+ placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue)
) {
- throw new Error('Unable to replace auth placeholder in .git/config')
+ throw new Error(`Unable to replace auth placeholder in ${configPath}`)
}
+ assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined')
content = content.replace(
- placeholder,
- `AUTHORIZATION: basic ${basicCredential}`
+ this.tokenPlaceholderConfigValue,
+ this.tokenConfigValue
)
await fs.promises.writeFile(configPath, content)
}
private async removeToken(): Promise<void> {
// HTTP extra header
- await this.removeGitConfig(EXTRA_HEADER_KEY)
+ await this.removeGitConfig(this.tokenConfigKey)
}
private async removeGitConfig(configKey: string): Promise<void> {
@@ -98,5 +198,11 @@ class GitAuthHelper {
// Load the config contents
core.warning(`Failed to remove '${configKey}' from the git config`)
}
+
+ const pattern = regexpHelper.escape(configKey)
+ await this.git.submoduleForeach(
+ `git config --local --name-only --get-regexp ${pattern} && git config --local --unset-all ${configKey} || :`,
+ true
+ )
}
}