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-11 15:55:17 -0400
committerGitHub <noreply@github.com>2020-03-11 15:55:17 -0400
commit1b406b27feed694fb3a05a638c92124514baa6ea (patch)
tree05a8d7cd5c1748d2e83782e43edb2c274882ea90 /src/git-auth-helper.ts
parentad8315eb967265115b59c3b9dc1516f8ac5917ba (diff)
add ssh support (#163)
Diffstat (limited to 'src/git-auth-helper.ts')
-rw-r--r--src/git-auth-helper.ts146
1 files changed, 131 insertions, 15 deletions
diff --git a/src/git-auth-helper.ts b/src/git-auth-helper.ts
index 3f36ff8..7418c7c 100644
--- a/src/git-auth-helper.ts
+++ b/src/git-auth-helper.ts
@@ -13,6 +13,7 @@ import {IGitSourceSettings} from './git-source-settings'
const IS_WINDOWS = process.platform === 'win32'
const HOSTNAME = 'github.com'
+const SSH_COMMAND_KEY = 'core.sshCommand'
export interface IGitAuthHelper {
configureAuth(): Promise<void>
@@ -36,6 +37,8 @@ class GitAuthHelper {
private readonly tokenPlaceholderConfigValue: string
private readonly insteadOfKey: string = `url.https://${HOSTNAME}/.insteadOf`
private readonly insteadOfValue: string = `git@${HOSTNAME}:`
+ private sshKeyPath = ''
+ private sshKnownHostsPath = ''
private temporaryHomePath = ''
private tokenConfigValue: string
@@ -61,6 +64,7 @@ class GitAuthHelper {
await this.removeAuth()
// Configure new values
+ await this.configureSsh()
await this.configureToken()
}
@@ -106,7 +110,9 @@ class GitAuthHelper {
// Configure HTTPS instead of SSH
await this.git.tryConfigUnset(this.insteadOfKey, true)
- await this.git.config(this.insteadOfKey, this.insteadOfValue, true)
+ if (!this.settings.sshKey) {
+ await this.git.config(this.insteadOfKey, this.insteadOfValue, true)
+ }
} catch (err) {
// Unset in case somehow written to the real global config
core.info(
@@ -118,17 +124,15 @@ class GitAuthHelper {
}
async configureSubmoduleAuth(): Promise<void> {
+ // Remove possible previous HTTPS instead of SSH
+ 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 commands = [
- `git config --local "${this.tokenConfigKey}" "${this.tokenPlaceholderConfigValue}"`,
- `git config --local "${this.insteadOfKey}" "${this.insteadOfValue}"`,
- `git config --local --show-origin --name-only --get-regexp remote.origin.url`
- ]
const output = await this.git.submoduleForeach(
- commands.join(' && '),
+ `git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url`,
this.settings.nestedSubmodules
)
@@ -139,10 +143,19 @@ class GitAuthHelper {
core.debug(`Replacing token placeholder in '${configPath}'`)
this.replaceTokenPlaceholder(configPath)
}
+
+ // Configure HTTPS instead of SSH
+ if (!this.settings.sshKey) {
+ await this.git.submoduleForeach(
+ `git config --local '${this.insteadOfKey}' '${this.insteadOfValue}'`,
+ this.settings.nestedSubmodules
+ )
+ }
}
}
async removeAuth(): Promise<void> {
+ await this.removeSsh()
await this.removeToken()
}
@@ -152,6 +165,77 @@ class GitAuthHelper {
await io.rmRF(this.temporaryHomePath)
}
+ private async configureSsh(): Promise<void> {
+ if (!this.settings.sshKey) {
+ return
+ }
+
+ // Write key
+ const runnerTemp = process.env['RUNNER_TEMP'] || ''
+ assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
+ const uniqueId = uuid()
+ this.sshKeyPath = path.join(runnerTemp, uniqueId)
+ stateHelper.setSshKeyPath(this.sshKeyPath)
+ await fs.promises.mkdir(runnerTemp, {recursive: true})
+ await fs.promises.writeFile(
+ this.sshKeyPath,
+ this.settings.sshKey.trim() + '\n',
+ {mode: 0o600}
+ )
+
+ // Remove inherited permissions on Windows
+ if (IS_WINDOWS) {
+ const icacls = await io.which('icacls.exe')
+ await exec.exec(
+ `"${icacls}" "${this.sshKeyPath}" /grant:r "${process.env['USERDOMAIN']}\\${process.env['USERNAME']}:F"`
+ )
+ await exec.exec(`"${icacls}" "${this.sshKeyPath}" /inheritance:r`)
+ }
+
+ // Write known hosts
+ const userKnownHostsPath = path.join(os.homedir(), '.ssh', 'known_hosts')
+ let userKnownHosts = ''
+ try {
+ userKnownHosts = (
+ await fs.promises.readFile(userKnownHostsPath)
+ ).toString()
+ } catch (err) {
+ if (err.code !== 'ENOENT') {
+ throw err
+ }
+ }
+ let knownHosts = ''
+ if (userKnownHosts) {
+ knownHosts += `# Begin from ${userKnownHostsPath}\n${userKnownHosts}\n# End from ${userKnownHostsPath}\n`
+ }
+ if (this.settings.sshKnownHosts) {
+ knownHosts += `# Begin from input known hosts\n${this.settings.sshKnownHosts}\n# end from input known hosts\n`
+ }
+ knownHosts += `# Begin implicitly added github.com\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n# End implicitly added github.com\n`
+ this.sshKnownHostsPath = path.join(runnerTemp, `${uniqueId}_known_hosts`)
+ stateHelper.setSshKnownHostsPath(this.sshKnownHostsPath)
+ await fs.promises.writeFile(this.sshKnownHostsPath, knownHosts)
+
+ // Configure GIT_SSH_COMMAND
+ const sshPath = await io.which('ssh', true)
+ let sshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(
+ this.sshKeyPath
+ )}"`
+ if (this.settings.sshStrict) {
+ sshCommand += ' -o StrictHostKeyChecking=yes -o CheckHostIP=no'
+ }
+ sshCommand += ` -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(
+ this.sshKnownHostsPath
+ )}"`
+ core.info(`Temporarily overriding GIT_SSH_COMMAND=${sshCommand}`)
+ this.git.setEnvironmentVariable('GIT_SSH_COMMAND', sshCommand)
+
+ // Configure core.sshCommand
+ if (this.settings.persistCredentials) {
+ await this.git.config(SSH_COMMAND_KEY, sshCommand)
+ }
+ }
+
private async configureToken(
configPath?: string,
globalConfig?: boolean
@@ -198,23 +282,55 @@ class GitAuthHelper {
await fs.promises.writeFile(configPath, content)
}
+ private async removeSsh(): Promise<void> {
+ // SSH key
+ const keyPath = this.sshKeyPath || stateHelper.SshKeyPath
+ if (keyPath) {
+ try {
+ await io.rmRF(keyPath)
+ } catch (err) {
+ core.debug(err.message)
+ core.warning(`Failed to remove SSH key '${keyPath}'`)
+ }
+ }
+
+ // SSH known hosts
+ const knownHostsPath =
+ this.sshKnownHostsPath || stateHelper.SshKnownHostsPath
+ if (knownHostsPath) {
+ try {
+ await io.rmRF(knownHostsPath)
+ } catch {
+ // Intentionally empty
+ }
+ }
+
+ // SSH command
+ await this.removeGitConfig(SSH_COMMAND_KEY)
+ }
+
private async removeToken(): Promise<void> {
// HTTP extra header
await this.removeGitConfig(this.tokenConfigKey)
}
- private async removeGitConfig(configKey: string): Promise<void> {
- if (
- (await this.git.configExists(configKey)) &&
- !(await this.git.tryConfigUnset(configKey))
- ) {
- // Load the config contents
- core.warning(`Failed to remove '${configKey}' from the git config`)
+ private async removeGitConfig(
+ configKey: string,
+ submoduleOnly: boolean = false
+ ): Promise<void> {
+ if (!submoduleOnly) {
+ if (
+ (await this.git.configExists(configKey)) &&
+ !(await this.git.tryConfigUnset(configKey))
+ ) {
+ // 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} || :`,
+ `git config --local --name-only --get-regexp '${pattern}' && git config --local --unset-all '${configKey}' || :`,
true
)
}