diff --git a/README.md b/README.md
index 9233a25..df3355c 100644
--- a/README.md
+++ b/README.md
@@ -45,14 +45,40 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
     # Otherwise, defaults to `master`.
     ref: ''
 
-    # Auth token used to fetch the repository. The token is stored in the local git
-    # config, which enables your scripts to run authenticated git commands. The
-    # post-job step removes the token from the git config. [Learn more about creating
-    # and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
+    # Personal access token (PAT) used to fetch the repository. The PAT is configured
+    # with the local git config, which enables your scripts to run authenticated git
+    # commands. The post-job step removes the PAT.
+    #
+    # We recommend creating a service account with the least permissions necessary.
+    # Also when generating a new PAT, select the least scopes necessary.
+    #
+    # [Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
+    #
     # Default: ${{ github.token }}
     token: ''
 
-    # Whether to persist the token in the git config
+    # SSH key used to fetch the repository. SSH key is configured with the local git
+    # config, which enables your scripts to run authenticated git commands. The
+    # post-job step removes the SSH key.
+    #
+    # We recommend creating a service account with the least permissions necessary.
+    #
+    # [Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
+    ssh-key: ''
+
+    # Known hosts in addition to the user and global host key database. The public SSH
+    # keys for a host may be obtained using the utility `ssh-keyscan`. For example,
+    # `ssh-keyscan github.com`. The public key for github.com is always implicitly
+    # added.
+    ssh-known-hosts: ''
+
+    # Whether to perform strict host key checking. When true, adds the options
+    # `StrictHostKeyChecking=yes` and `CheckHostIP=no` to the SSH command line. Use
+    # the input `ssh-known-hosts` to configure additional hosts.
+    # Default: true
+    ssh-strict: ''
+
+    # Whether to configure the token or SSH key with the local git config
     # Default: true
     persist-credentials: ''
 
@@ -73,6 +99,10 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
 
     # Whether to checkout submodules: `true` to checkout submodules or `recursive` to
     # recursively checkout submodules.
+    #
+    # When the `ssh-key` input is not provided, SSH URLs beginning with
+    # `git@github.com:` are converted to HTTPS.
+    #
     # Default: false
     submodules: ''
 ```
diff --git a/__test__/git-auth-helper.test.ts b/__test__/git-auth-helper.test.ts
index 68926f2..dc03ab3 100644
--- a/__test__/git-auth-helper.test.ts
+++ b/__test__/git-auth-helper.test.ts
@@ -2,10 +2,13 @@ import * as core from '@actions/core'
 import * as fs from 'fs'
 import * as gitAuthHelper from '../lib/git-auth-helper'
 import * as io from '@actions/io'
+import * as os from 'os'
 import * as path from 'path'
+import * as stateHelper from '../lib/state-helper'
 import {IGitCommandManager} from '../lib/git-command-manager'
 import {IGitSourceSettings} from '../lib/git-source-settings'
 
+const isWindows = process.platform === 'win32'
 const testWorkspace = path.join(__dirname, '_temp', 'git-auth-helper')
 const originalRunnerTemp = process.env['RUNNER_TEMP']
 const originalHome = process.env['HOME']
@@ -16,9 +19,13 @@ let runnerTemp: string
 let tempHomedir: string
 let git: IGitCommandManager & {env: {[key: string]: string}}
 let settings: IGitSourceSettings
+let sshPath: string
 
 describe('git-auth-helper tests', () => {
   beforeAll(async () => {
+    // SSH
+    sshPath = await io.which('ssh')
+
     // Clear test workspace
     await io.rmRF(testWorkspace)
   })
@@ -32,6 +39,12 @@ describe('git-auth-helper tests', () => {
     jest.spyOn(core, 'warning').mockImplementation(jest.fn())
     jest.spyOn(core, 'info').mockImplementation(jest.fn())
     jest.spyOn(core, 'debug').mockImplementation(jest.fn())
+
+    // Mock state helper
+    jest.spyOn(stateHelper, 'setSshKeyPath').mockImplementation(jest.fn())
+    jest
+      .spyOn(stateHelper, 'setSshKnownHostsPath')
+      .mockImplementation(jest.fn())
   })
 
   afterEach(() => {
@@ -108,6 +121,52 @@ describe('git-auth-helper tests', () => {
     }
   )
 
+  const configureAuth_copiesUserKnownHosts =
+    'configureAuth copies user known hosts'
+  it(configureAuth_copiesUserKnownHosts, async () => {
+    if (!sshPath) {
+      process.stdout.write(
+        `Skipped test "${configureAuth_copiesUserKnownHosts}". Executable 'ssh' not found in the PATH.\n`
+      )
+      return
+    }
+
+    // Arange
+    await setup(configureAuth_copiesUserKnownHosts)
+    expect(settings.sshKey).toBeTruthy() // sanity check
+
+    // Mock fs.promises.readFile
+    const realReadFile = fs.promises.readFile
+    jest.spyOn(fs.promises, 'readFile').mockImplementation(
+      async (file: any, options: any): Promise<Buffer> => {
+        const userKnownHostsPath = path.join(
+          os.homedir(),
+          '.ssh',
+          'known_hosts'
+        )
+        if (file === userKnownHostsPath) {
+          return Buffer.from('some-domain.com ssh-rsa ABCDEF')
+        }
+
+        return await realReadFile(file, options)
+      }
+    )
+
+    // Act
+    const authHelper = gitAuthHelper.createAuthHelper(git, settings)
+    await authHelper.configureAuth()
+
+    // Assert known hosts
+    const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
+    const actualSshKnownHostsContent = (
+      await fs.promises.readFile(actualSshKnownHostsPath)
+    ).toString()
+    expect(actualSshKnownHostsContent).toMatch(
+      /some-domain\.com ssh-rsa ABCDEF/
+    )
+    expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
+  })
+
   const configureAuth_registersBasicCredentialAsSecret =
     'configureAuth registers basic credential as secret'
   it(configureAuth_registersBasicCredentialAsSecret, async () => {
@@ -129,6 +188,173 @@ describe('git-auth-helper tests', () => {
     expect(setSecretSpy).toHaveBeenCalledWith(expectedSecret)
   })
 
+  const setsSshCommandEnvVarWhenPersistCredentialsFalse =
+    'sets SSH command env var when persist-credentials false'
+  it(setsSshCommandEnvVarWhenPersistCredentialsFalse, async () => {
+    if (!sshPath) {
+      process.stdout.write(
+        `Skipped test "${setsSshCommandEnvVarWhenPersistCredentialsFalse}". Executable 'ssh' not found in the PATH.\n`
+      )
+      return
+    }
+
+    // Arrange
+    await setup(setsSshCommandEnvVarWhenPersistCredentialsFalse)
+    settings.persistCredentials = false
+    const authHelper = gitAuthHelper.createAuthHelper(git, settings)
+
+    // Act
+    await authHelper.configureAuth()
+
+    // Assert git env var
+    const actualKeyPath = await getActualSshKeyPath()
+    const actualKnownHostsPath = await getActualSshKnownHostsPath()
+    const expectedSshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(
+      actualKeyPath
+    )}" -o StrictHostKeyChecking=yes -o CheckHostIP=no -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(
+      actualKnownHostsPath
+    )}"`
+    expect(git.setEnvironmentVariable).toHaveBeenCalledWith(
+      'GIT_SSH_COMMAND',
+      expectedSshCommand
+    )
+
+    // Asserty git config
+    const gitConfigLines = (await fs.promises.readFile(localGitConfigPath))
+      .toString()
+      .split('\n')
+      .filter(x => x)
+    expect(gitConfigLines).toHaveLength(1)
+    expect(gitConfigLines[0]).toMatch(/^http\./)
+  })
+
+  const configureAuth_setsSshCommandWhenPersistCredentialsTrue =
+    'sets SSH command when persist-credentials true'
+  it(configureAuth_setsSshCommandWhenPersistCredentialsTrue, async () => {
+    if (!sshPath) {
+      process.stdout.write(
+        `Skipped test "${configureAuth_setsSshCommandWhenPersistCredentialsTrue}". Executable 'ssh' not found in the PATH.\n`
+      )
+      return
+    }
+
+    // Arrange
+    await setup(configureAuth_setsSshCommandWhenPersistCredentialsTrue)
+    const authHelper = gitAuthHelper.createAuthHelper(git, settings)
+
+    // Act
+    await authHelper.configureAuth()
+
+    // Assert git env var
+    const actualKeyPath = await getActualSshKeyPath()
+    const actualKnownHostsPath = await getActualSshKnownHostsPath()
+    const expectedSshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(
+      actualKeyPath
+    )}" -o StrictHostKeyChecking=yes -o CheckHostIP=no -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(
+      actualKnownHostsPath
+    )}"`
+    expect(git.setEnvironmentVariable).toHaveBeenCalledWith(
+      'GIT_SSH_COMMAND',
+      expectedSshCommand
+    )
+
+    // Asserty git config
+    expect(git.config).toHaveBeenCalledWith(
+      'core.sshCommand',
+      expectedSshCommand
+    )
+  })
+
+  const configureAuth_writesExplicitKnownHosts = 'writes explicit known hosts'
+  it(configureAuth_writesExplicitKnownHosts, async () => {
+    if (!sshPath) {
+      process.stdout.write(
+        `Skipped test "${configureAuth_writesExplicitKnownHosts}". Executable 'ssh' not found in the PATH.\n`
+      )
+      return
+    }
+
+    // Arrange
+    await setup(configureAuth_writesExplicitKnownHosts)
+    expect(settings.sshKey).toBeTruthy() // sanity check
+    settings.sshKnownHosts = 'my-custom-host.com ssh-rsa ABC123'
+    const authHelper = gitAuthHelper.createAuthHelper(git, settings)
+
+    // Act
+    await authHelper.configureAuth()
+
+    // Assert known hosts
+    const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
+    const actualSshKnownHostsContent = (
+      await fs.promises.readFile(actualSshKnownHostsPath)
+    ).toString()
+    expect(actualSshKnownHostsContent).toMatch(
+      /my-custom-host\.com ssh-rsa ABC123/
+    )
+    expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
+  })
+
+  const configureAuth_writesSshKeyAndImplicitKnownHosts =
+    'writes SSH key and implicit known hosts'
+  it(configureAuth_writesSshKeyAndImplicitKnownHosts, async () => {
+    if (!sshPath) {
+      process.stdout.write(
+        `Skipped test "${configureAuth_writesSshKeyAndImplicitKnownHosts}". Executable 'ssh' not found in the PATH.\n`
+      )
+      return
+    }
+
+    // Arrange
+    await setup(configureAuth_writesSshKeyAndImplicitKnownHosts)
+    expect(settings.sshKey).toBeTruthy() // sanity check
+    const authHelper = gitAuthHelper.createAuthHelper(git, settings)
+
+    // Act
+    await authHelper.configureAuth()
+
+    // Assert SSH key
+    const actualSshKeyPath = await getActualSshKeyPath()
+    expect(actualSshKeyPath).toBeTruthy()
+    const actualSshKeyContent = (
+      await fs.promises.readFile(actualSshKeyPath)
+    ).toString()
+    expect(actualSshKeyContent).toBe(settings.sshKey + '\n')
+    if (!isWindows) {
+      expect((await fs.promises.stat(actualSshKeyPath)).mode & 0o777).toBe(
+        0o600
+      )
+    }
+
+    // Assert known hosts
+    const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
+    const actualSshKnownHostsContent = (
+      await fs.promises.readFile(actualSshKnownHostsPath)
+    ).toString()
+    expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
+  })
+
+  const configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet =
+    'configureGlobalAuth configures URL insteadOf when SSH key not set'
+  it(configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet, async () => {
+    // Arrange
+    await setup(configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet)
+    settings.sshKey = ''
+    const authHelper = gitAuthHelper.createAuthHelper(git, settings)
+
+    // Act
+    await authHelper.configureAuth()
+    await authHelper.configureGlobalAuth()
+
+    // Assert temporary global config
+    expect(git.env['HOME']).toBeTruthy()
+    const configContent = (
+      await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
+    ).toString()
+    expect(
+      configContent.indexOf(`url.https://github.com/.insteadOf git@github.com`)
+    ).toBeGreaterThanOrEqual(0)
+  })
+
   const configureGlobalAuth_copiesGlobalGitConfig =
     'configureGlobalAuth copies global git config'
   it(configureGlobalAuth_copiesGlobalGitConfig, async () => {
@@ -211,6 +437,67 @@ describe('git-auth-helper tests', () => {
     }
   )
 
+  const configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrueAndSshKeyNotSet =
+    'configureSubmoduleAuth configures token when persist credentials true and SSH key not set'
+  it(
+    configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrueAndSshKeyNotSet,
+    async () => {
+      // Arrange
+      await setup(
+        configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrueAndSshKeyNotSet
+      )
+      settings.sshKey = ''
+      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
+      await authHelper.configureAuth()
+      const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
+      mockSubmoduleForeach.mockClear() // reset calls
+
+      // Act
+      await authHelper.configureSubmoduleAuth()
+
+      // Assert
+      expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3)
+      expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
+        /unset-all.*insteadOf/
+      )
+      expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
+      expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(/url.*insteadOf/)
+    }
+  )
+
+  const configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrueAndSshKeySet =
+    'configureSubmoduleAuth configures token when persist credentials true and SSH key set'
+  it(
+    configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrueAndSshKeySet,
+    async () => {
+      if (!sshPath) {
+        process.stdout.write(
+          `Skipped test "${configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrueAndSshKeySet}". Executable 'ssh' not found in the PATH.\n`
+        )
+        return
+      }
+
+      // Arrange
+      await setup(
+        configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrueAndSshKeySet
+      )
+      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
+      await authHelper.configureAuth()
+      const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
+      mockSubmoduleForeach.mockClear() // reset calls
+
+      // Act
+      await authHelper.configureSubmoduleAuth()
+
+      // Assert
+      expect(mockSubmoduleForeach).toHaveBeenCalledTimes(2)
+      expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
+        /unset-all.*insteadOf/
+      )
+      expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
+    }
+  )
+
   const configureSubmoduleAuth_doesNotConfigureTokenWhenPersistCredentialsFalse =
     'configureSubmoduleAuth does not configure token when persist credentials false'
   it(
@@ -223,37 +510,135 @@ describe('git-auth-helper tests', () => {
       settings.persistCredentials = false
       const authHelper = gitAuthHelper.createAuthHelper(git, settings)
       await authHelper.configureAuth()
-      ;(git.submoduleForeach as jest.Mock<any, any>).mockClear() // reset calls
+      const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
+      mockSubmoduleForeach.mockClear() // reset calls
 
       // Act
       await authHelper.configureSubmoduleAuth()
 
       // Assert
-      expect(git.submoduleForeach).not.toHaveBeenCalled()
+      expect(mockSubmoduleForeach).toBeCalledTimes(1)
+      expect(mockSubmoduleForeach.mock.calls[0][0] as string).toMatch(
+        /unset-all.*insteadOf/
+      )
     }
   )
 
-  const configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrue =
-    'configureSubmoduleAuth configures token when persist credentials true'
+  const configureSubmoduleAuth_doesNotConfigureUrlInsteadOfWhenPersistCredentialsTrueAndSshKeySet =
+    'configureSubmoduleAuth does not configure URL insteadOf when persist credentials true and SSH key set'
   it(
-    configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrue,
+    configureSubmoduleAuth_doesNotConfigureUrlInsteadOfWhenPersistCredentialsTrueAndSshKeySet,
     async () => {
+      if (!sshPath) {
+        process.stdout.write(
+          `Skipped test "${configureSubmoduleAuth_doesNotConfigureUrlInsteadOfWhenPersistCredentialsTrueAndSshKeySet}". Executable 'ssh' not found in the PATH.\n`
+        )
+        return
+      }
+
       // Arrange
       await setup(
-        configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrue
+        configureSubmoduleAuth_doesNotConfigureUrlInsteadOfWhenPersistCredentialsTrueAndSshKeySet
       )
       const authHelper = gitAuthHelper.createAuthHelper(git, settings)
       await authHelper.configureAuth()
-      ;(git.submoduleForeach as jest.Mock<any, any>).mockClear() // reset calls
+      const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
+      mockSubmoduleForeach.mockClear() // reset calls
 
       // Act
       await authHelper.configureSubmoduleAuth()
 
       // Assert
-      expect(git.submoduleForeach).toHaveBeenCalledTimes(1)
+      expect(mockSubmoduleForeach).toHaveBeenCalledTimes(2)
+      expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
+        /unset-all.*insteadOf/
+      )
+      expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
     }
   )
 
+  const configureSubmoduleAuth_removesUrlInsteadOfWhenPersistCredentialsFalse =
+    'configureSubmoduleAuth removes URL insteadOf when persist credentials false'
+  it(
+    configureSubmoduleAuth_removesUrlInsteadOfWhenPersistCredentialsFalse,
+    async () => {
+      // Arrange
+      await setup(
+        configureSubmoduleAuth_removesUrlInsteadOfWhenPersistCredentialsFalse
+      )
+      settings.persistCredentials = false
+      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
+      await authHelper.configureAuth()
+      const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
+      mockSubmoduleForeach.mockClear() // reset calls
+
+      // Act
+      await authHelper.configureSubmoduleAuth()
+
+      // Assert
+      expect(mockSubmoduleForeach).toBeCalledTimes(1)
+      expect(mockSubmoduleForeach.mock.calls[0][0] as string).toMatch(
+        /unset-all.*insteadOf/
+      )
+    }
+  )
+
+  const removeAuth_removesSshCommand = 'removeAuth removes SSH command'
+  it(removeAuth_removesSshCommand, async () => {
+    if (!sshPath) {
+      process.stdout.write(
+        `Skipped test "${removeAuth_removesSshCommand}". Executable 'ssh' not found in the PATH.\n`
+      )
+      return
+    }
+
+    // Arrange
+    await setup(removeAuth_removesSshCommand)
+    const authHelper = gitAuthHelper.createAuthHelper(git, settings)
+    await authHelper.configureAuth()
+    let gitConfigContent = (
+      await fs.promises.readFile(localGitConfigPath)
+    ).toString()
+    expect(gitConfigContent.indexOf('core.sshCommand')).toBeGreaterThanOrEqual(
+      0
+    ) // sanity check
+    const actualKeyPath = await getActualSshKeyPath()
+    expect(actualKeyPath).toBeTruthy()
+    await fs.promises.stat(actualKeyPath)
+    const actualKnownHostsPath = await getActualSshKnownHostsPath()
+    expect(actualKnownHostsPath).toBeTruthy()
+    await fs.promises.stat(actualKnownHostsPath)
+
+    // Act
+    await authHelper.removeAuth()
+
+    // Assert git config
+    gitConfigContent = (
+      await fs.promises.readFile(localGitConfigPath)
+    ).toString()
+    expect(gitConfigContent.indexOf('core.sshCommand')).toBeLessThan(0)
+
+    // Assert SSH key file
+    try {
+      await fs.promises.stat(actualKeyPath)
+      throw new Error('SSH key should have been deleted')
+    } catch (err) {
+      if (err.code !== 'ENOENT') {
+        throw err
+      }
+    }
+
+    // Assert known hosts file
+    try {
+      await fs.promises.stat(actualKnownHostsPath)
+      throw new Error('SSH known hosts should have been deleted')
+    } catch (err) {
+      if (err.code !== 'ENOENT') {
+        throw err
+      }
+    }
+  })
+
   const removeAuth_removesToken = 'removeAuth removes token'
   it(removeAuth_removesToken, async () => {
     // Arrange
@@ -401,6 +786,36 @@ async function setup(testName: string): Promise<void> {
     ref: 'refs/heads/master',
     repositoryName: 'my-repo',
     repositoryOwner: 'my-org',
-    repositoryPath: ''
+    repositoryPath: '',
+    sshKey: sshPath ? 'some ssh private key' : '',
+    sshKnownHosts: '',
+    sshStrict: true
   }
 }
+
+async function getActualSshKeyPath(): Promise<string> {
+  let actualTempFiles = (await fs.promises.readdir(runnerTemp))
+    .sort()
+    .map(x => path.join(runnerTemp, x))
+  if (actualTempFiles.length === 0) {
+    return ''
+  }
+
+  expect(actualTempFiles).toHaveLength(2)
+  expect(actualTempFiles[0].endsWith('_known_hosts')).toBeFalsy()
+  return actualTempFiles[0]
+}
+
+async function getActualSshKnownHostsPath(): Promise<string> {
+  let actualTempFiles = (await fs.promises.readdir(runnerTemp))
+    .sort()
+    .map(x => path.join(runnerTemp, x))
+  if (actualTempFiles.length === 0) {
+    return ''
+  }
+
+  expect(actualTempFiles).toHaveLength(2)
+  expect(actualTempFiles[1].endsWith('_known_hosts')).toBeTruthy()
+  expect(actualTempFiles[1].startsWith(actualTempFiles[0])).toBeTruthy()
+  return actualTempFiles[1]
+}
diff --git a/action.yml b/action.yml
index a411037..7d5412d 100644
--- a/action.yml
+++ b/action.yml
@@ -1,6 +1,6 @@
 name: 'Checkout'
 description: 'Checkout a Git repository at a particular version'
-inputs: 
+inputs:
   repository:
     description: 'Repository name with owner. For example, actions/checkout'
     default: ${{ github.repository }}
@@ -11,13 +11,42 @@ inputs:
       event.  Otherwise, defaults to `master`.
   token:
     description: >
-      Auth token used to fetch the repository. The token is stored in the local
-      git config, which enables your scripts to run authenticated git commands.
-      The post-job step removes the token from the git config. [Learn more about
-      creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
+      Personal access token (PAT) used to fetch the repository. The PAT is configured
+      with the local git config, which enables your scripts to run authenticated git
+      commands. The post-job step removes the PAT.
+
+
+      We recommend creating a service account with the least permissions necessary.
+      Also when generating a new PAT, select the least scopes necessary.
+
+
+      [Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
     default: ${{ github.token }}
+  ssh-key:
+    description: >
+      SSH key used to fetch the repository. SSH key is configured with the local
+      git config, which enables your scripts to run authenticated git commands.
+      The post-job step removes the SSH key.
+
+
+      We recommend creating a service account with the least permissions necessary.
+
+
+      [Learn more about creating and using
+      encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
+  ssh-known-hosts:
+    description: >
+      Known hosts in addition to the user and global host key database. The public
+      SSH keys for a host may be obtained using the utility `ssh-keyscan`. For example,
+      `ssh-keyscan github.com`. The public key for github.com is always implicitly added.
+  ssh-strict:
+    description: >
+      Whether to perform strict host key checking. When true, adds the options `StrictHostKeyChecking=yes`
+      and `CheckHostIP=no` to the SSH command line. Use the input `ssh-known-hosts` to
+      configure additional hosts.
+    default: true
   persist-credentials:
-    description: 'Whether to persist the token in the git config'
+    description: 'Whether to configure the token or SSH key with the local git config'
     default: true
   path:
     description: 'Relative path under $GITHUB_WORKSPACE to place the repository'
@@ -34,6 +63,10 @@ inputs:
     description: >
       Whether to checkout submodules: `true` to checkout submodules or `recursive` to
       recursively checkout submodules.
+
+
+      When the `ssh-key` input is not provided, SSH URLs beginning with `git@github.com:` are
+      converted to HTTPS.
     default: false
 runs:
   using: node12
diff --git a/dist/index.js b/dist/index.js
index 3082946..706f035 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -2621,6 +2621,14 @@ exports.IsPost = !!process.env['STATE_isPost'];
  * The repository path for the POST action. The value is empty during the MAIN action.
  */
 exports.RepositoryPath = process.env['STATE_repositoryPath'] || '';
+/**
+ * The SSH key path for the POST action. The value is empty during the MAIN action.
+ */
+exports.SshKeyPath = process.env['STATE_sshKeyPath'] || '';
+/**
+ * The SSH known hosts path for the POST action. The value is empty during the MAIN action.
+ */
+exports.SshKnownHostsPath = process.env['STATE_sshKnownHostsPath'] || '';
 /**
  * Save the repository path so the POST action can retrieve the value.
  */
@@ -2628,6 +2636,20 @@ function setRepositoryPath(repositoryPath) {
     coreCommand.issueCommand('save-state', { name: 'repositoryPath' }, repositoryPath);
 }
 exports.setRepositoryPath = setRepositoryPath;
+/**
+ * Save the SSH key path so the POST action can retrieve the value.
+ */
+function setSshKeyPath(sshKeyPath) {
+    coreCommand.issueCommand('save-state', { name: 'sshKeyPath' }, sshKeyPath);
+}
+exports.setSshKeyPath = setSshKeyPath;
+/**
+ * Save the SSH known hosts path so the POST action can retrieve the value.
+ */
+function setSshKnownHostsPath(sshKnownHostsPath) {
+    coreCommand.issueCommand('save-state', { name: 'sshKnownHostsPath' }, sshKnownHostsPath);
+}
+exports.setSshKnownHostsPath = setSshKnownHostsPath;
 // Publish a variable so that when the POST action runs, it can determine it should run the cleanup logic.
 // This is necessary since we don't have a separate entry point.
 if (!exports.IsPost) {
@@ -5080,14 +5102,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
 Object.defineProperty(exports, "__esModule", { value: true });
 const assert = __importStar(__webpack_require__(357));
 const core = __importStar(__webpack_require__(470));
+const exec = __importStar(__webpack_require__(986));
 const fs = __importStar(__webpack_require__(747));
 const io = __importStar(__webpack_require__(1));
 const os = __importStar(__webpack_require__(87));
 const path = __importStar(__webpack_require__(622));
 const regexpHelper = __importStar(__webpack_require__(528));
+const stateHelper = __importStar(__webpack_require__(153));
 const v4_1 = __importDefault(__webpack_require__(826));
 const IS_WINDOWS = process.platform === 'win32';
 const HOSTNAME = 'github.com';
+const SSH_COMMAND_KEY = 'core.sshCommand';
 function createAuthHelper(git, settings) {
     return new GitAuthHelper(git, settings);
 }
@@ -5097,6 +5122,8 @@ class GitAuthHelper {
         this.tokenConfigKey = `http.https://${HOSTNAME}/.extraheader`;
         this.insteadOfKey = `url.https://${HOSTNAME}/.insteadOf`;
         this.insteadOfValue = `git@${HOSTNAME}:`;
+        this.sshKeyPath = '';
+        this.sshKnownHostsPath = '';
         this.temporaryHomePath = '';
         this.git = gitCommandManager;
         this.settings = gitSourceSettings || {};
@@ -5111,6 +5138,7 @@ class GitAuthHelper {
             // Remove possible previous values
             yield this.removeAuth();
             // Configure new values
+            yield this.configureSsh();
             yield this.configureToken();
         });
     }
@@ -5150,7 +5178,9 @@ class GitAuthHelper {
                 yield this.configureToken(newGitConfigPath, true);
                 // Configure HTTPS instead of SSH
                 yield this.git.tryConfigUnset(this.insteadOfKey, true);
-                yield this.git.config(this.insteadOfKey, this.insteadOfValue, true);
+                if (!this.settings.sshKey) {
+                    yield this.git.config(this.insteadOfKey, this.insteadOfValue, true);
+                }
             }
             catch (err) {
                 // Unset in case somehow written to the real global config
@@ -5162,27 +5192,29 @@ class GitAuthHelper {
     }
     configureSubmoduleAuth() {
         return __awaiter(this, void 0, void 0, function* () {
+            // 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 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 = yield this.git.submoduleForeach(commands.join(' && '), this.settings.nestedSubmodules);
+                const output = yield this.git.submoduleForeach(`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}'`);
                     this.replaceTokenPlaceholder(configPath);
                 }
+                // Configure HTTPS instead of SSH
+                if (!this.settings.sshKey) {
+                    yield this.git.submoduleForeach(`git config --local '${this.insteadOfKey}' '${this.insteadOfValue}'`, this.settings.nestedSubmodules);
+                }
             }
         });
     }
     removeAuth() {
         return __awaiter(this, void 0, void 0, function* () {
+            yield this.removeSsh();
             yield this.removeToken();
         });
     }
@@ -5193,6 +5225,62 @@ class GitAuthHelper {
             yield io.rmRF(this.temporaryHomePath);
         });
     }
+    configureSsh() {
+        return __awaiter(this, void 0, void 0, function* () {
+            if (!this.settings.sshKey) {
+                return;
+            }
+            // Write key
+            const runnerTemp = process.env['RUNNER_TEMP'] || '';
+            assert.ok(runnerTemp, 'RUNNER_TEMP is not defined');
+            const uniqueId = v4_1.default();
+            this.sshKeyPath = path.join(runnerTemp, uniqueId);
+            stateHelper.setSshKeyPath(this.sshKeyPath);
+            yield fs.promises.mkdir(runnerTemp, { recursive: true });
+            yield fs.promises.writeFile(this.sshKeyPath, this.settings.sshKey.trim() + '\n', { mode: 0o600 });
+            // Remove inherited permissions on Windows
+            if (IS_WINDOWS) {
+                const icacls = yield io.which('icacls.exe');
+                yield exec.exec(`"${icacls}" "${this.sshKeyPath}" /grant:r "${process.env['USERDOMAIN']}\\${process.env['USERNAME']}:F"`);
+                yield exec.exec(`"${icacls}" "${this.sshKeyPath}" /inheritance:r`);
+            }
+            // Write known hosts
+            const userKnownHostsPath = path.join(os.homedir(), '.ssh', 'known_hosts');
+            let userKnownHosts = '';
+            try {
+                userKnownHosts = (yield 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);
+            yield fs.promises.writeFile(this.sshKnownHostsPath, knownHosts);
+            // Configure GIT_SSH_COMMAND
+            const sshPath = yield 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) {
+                yield this.git.config(SSH_COMMAND_KEY, sshCommand);
+            }
+        });
+    }
     configureToken(configPath, globalConfig) {
         return __awaiter(this, void 0, void 0, function* () {
             // Validate args
@@ -5223,21 +5311,50 @@ class GitAuthHelper {
             yield fs.promises.writeFile(configPath, content);
         });
     }
+    removeSsh() {
+        return __awaiter(this, void 0, void 0, function* () {
+            // SSH key
+            const keyPath = this.sshKeyPath || stateHelper.SshKeyPath;
+            if (keyPath) {
+                try {
+                    yield 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 {
+                    yield io.rmRF(knownHostsPath);
+                }
+                catch (_a) {
+                    // Intentionally empty
+                }
+            }
+            // SSH command
+            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) {
+    removeGitConfig(configKey, submoduleOnly = false) {
         return __awaiter(this, void 0, void 0, function* () {
-            if ((yield this.git.configExists(configKey)) &&
-                !(yield this.git.tryConfigUnset(configKey))) {
-                // Load the config contents
-                core.warning(`Failed to remove '${configKey}' from the git config`);
+            if (!submoduleOnly) {
+                if ((yield this.git.configExists(configKey)) &&
+                    !(yield this.git.tryConfigUnset(configKey))) {
+                    // Load the config contents
+                    core.warning(`Failed to remove '${configKey}' from the git config`);
+                }
             }
             const pattern = regexpHelper.escape(configKey);
-            yield this.git.submoduleForeach(`git config --local --name-only --get-regexp ${pattern} && git config --local --unset-all ${configKey} || :`, true);
+            yield this.git.submoduleForeach(`git config --local --name-only --get-regexp '${pattern}' && git config --local --unset-all '${configKey}' || :`, true);
         });
     }
 }
@@ -5680,7 +5797,9 @@ function getSource(settings) {
     return __awaiter(this, void 0, void 0, function* () {
         // Repository URL
         core.info(`Syncing repository: ${settings.repositoryOwner}/${settings.repositoryName}`);
-        const repositoryUrl = `https://${hostname}/${encodeURIComponent(settings.repositoryOwner)}/${encodeURIComponent(settings.repositoryName)}`;
+        const repositoryUrl = settings.sshKey
+            ? `git@${hostname}:${encodeURIComponent(settings.repositoryOwner)}/${encodeURIComponent(settings.repositoryName)}.git`
+            : `https://${hostname}/${encodeURIComponent(settings.repositoryOwner)}/${encodeURIComponent(settings.repositoryName)}`;
         // Remove conflicting file path
         if (fsHelper.fileExistsSync(settings.repositoryPath)) {
             yield io.rmRF(settings.repositoryPath);
@@ -13940,6 +14059,11 @@ function getInputs() {
     core.debug(`recursive submodules = ${result.nestedSubmodules}`);
     // Auth token
     result.authToken = core.getInput('token');
+    // SSH
+    result.sshKey = core.getInput('ssh-key');
+    result.sshKnownHosts = core.getInput('ssh-known-hosts');
+    result.sshStrict =
+        (core.getInput('ssh-strict') || 'true').toUpperCase() === 'TRUE';
     // Persist credentials
     result.persistCredentials =
         (core.getInput('persist-credentials') || 'false').toUpperCase() === 'TRUE';
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
     )
   }
diff --git a/src/git-source-provider.ts b/src/git-source-provider.ts
index 90f97c9..7ce9fb7 100644
--- a/src/git-source-provider.ts
+++ b/src/git-source-provider.ts
@@ -18,9 +18,13 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
   core.info(
     `Syncing repository: ${settings.repositoryOwner}/${settings.repositoryName}`
   )
-  const repositoryUrl = `https://${hostname}/${encodeURIComponent(
-    settings.repositoryOwner
-  )}/${encodeURIComponent(settings.repositoryName)}`
+  const repositoryUrl = settings.sshKey
+    ? `git@${hostname}:${encodeURIComponent(
+        settings.repositoryOwner
+      )}/${encodeURIComponent(settings.repositoryName)}.git`
+    : `https://${hostname}/${encodeURIComponent(
+        settings.repositoryOwner
+      )}/${encodeURIComponent(settings.repositoryName)}`
 
   // Remove conflicting file path
   if (fsHelper.fileExistsSync(settings.repositoryPath)) {
diff --git a/src/git-source-settings.ts b/src/git-source-settings.ts
index e411fad..04d548c 100644
--- a/src/git-source-settings.ts
+++ b/src/git-source-settings.ts
@@ -10,5 +10,8 @@ export interface IGitSourceSettings {
   submodules: boolean
   nestedSubmodules: boolean
   authToken: string
+  sshKey: string
+  sshKnownHosts: string
+  sshStrict: boolean
   persistCredentials: boolean
 }
diff --git a/src/input-helper.ts b/src/input-helper.ts
index 3769350..11a1ab6 100644
--- a/src/input-helper.ts
+++ b/src/input-helper.ts
@@ -112,6 +112,12 @@ export function getInputs(): IGitSourceSettings {
   // Auth token
   result.authToken = core.getInput('token')
 
+  // SSH
+  result.sshKey = core.getInput('ssh-key')
+  result.sshKnownHosts = core.getInput('ssh-known-hosts')
+  result.sshStrict =
+    (core.getInput('ssh-strict') || 'true').toUpperCase() === 'TRUE'
+
   // Persist credentials
   result.persistCredentials =
     (core.getInput('persist-credentials') || 'false').toUpperCase() === 'TRUE'
diff --git a/src/misc/generate-docs.ts b/src/misc/generate-docs.ts
index 884d606..defda5a 100644
--- a/src/misc/generate-docs.ts
+++ b/src/misc/generate-docs.ts
@@ -59,13 +59,17 @@ function updateUsage(
 
     // Constrain the width of the description
     const width = 80
-    let description = input.description as string
+    let description = (input.description as string)
+      .trimRight()
+      .replace(/\r\n/g, '\n') // Convert CR to LF
+      .replace(/ +/g, ' ') //    Squash consecutive spaces
+      .replace(/ \n/g, '\n') //  Squash space followed by newline
     while (description) {
       // Longer than width? Find a space to break apart
       let segment: string = description
       if (description.length > width) {
         segment = description.substr(0, width + 1)
-        while (!segment.endsWith(' ') && segment) {
+        while (!segment.endsWith(' ') && !segment.endsWith('\n') && segment) {
           segment = segment.substr(0, segment.length - 1)
         }
 
@@ -77,15 +81,30 @@ function updateUsage(
         segment = description
       }
 
-      description = description.substr(segment.length) // Remaining
-      segment = segment.trimRight() // Trim the trailing space
-      newReadme.push(`    # ${segment}`)
+      // Check for newline
+      const newlineIndex = segment.indexOf('\n')
+      if (newlineIndex >= 0) {
+        segment = segment.substr(0, newlineIndex + 1)
+      }
+
+      // Append segment
+      newReadme.push(`    # ${segment}`.trimRight())
+
+      // Remaining
+      description = description.substr(segment.length)
     }
 
-    // Input and default
     if (input.default !== undefined) {
+      // Append blank line if description had paragraphs
+      if ((input.description as string).trimRight().match(/\n[ ]*\r?\n/)) {
+        newReadme.push(`    #`)
+      }
+
+      // Default
       newReadme.push(`    # Default: ${input.default}`)
     }
+
+    // Input name
     newReadme.push(`    ${key}: ''`)
 
     firstInput = false
diff --git a/src/state-helper.ts b/src/state-helper.ts
index da15d86..3c657b1 100644
--- a/src/state-helper.ts
+++ b/src/state-helper.ts
@@ -11,6 +11,17 @@ export const IsPost = !!process.env['STATE_isPost']
 export const RepositoryPath =
   (process.env['STATE_repositoryPath'] as string) || ''
 
+/**
+ * The SSH key path for the POST action. The value is empty during the MAIN action.
+ */
+export const SshKeyPath = (process.env['STATE_sshKeyPath'] as string) || ''
+
+/**
+ * The SSH known hosts path for the POST action. The value is empty during the MAIN action.
+ */
+export const SshKnownHostsPath =
+  (process.env['STATE_sshKnownHostsPath'] as string) || ''
+
 /**
  * Save the repository path so the POST action can retrieve the value.
  */
@@ -22,6 +33,24 @@ export function setRepositoryPath(repositoryPath: string) {
   )
 }
 
+/**
+ * Save the SSH key path so the POST action can retrieve the value.
+ */
+export function setSshKeyPath(sshKeyPath: string) {
+  coreCommand.issueCommand('save-state', {name: 'sshKeyPath'}, sshKeyPath)
+}
+
+/**
+ * Save the SSH known hosts path so the POST action can retrieve the value.
+ */
+export function setSshKnownHostsPath(sshKnownHostsPath: string) {
+  coreCommand.issueCommand(
+    'save-state',
+    {name: 'sshKnownHostsPath'},
+    sshKnownHostsPath
+  )
+}
+
 // Publish a variable so that when the POST action runs, it can determine it should run the cleanup logic.
 // This is necessary since we don't have a separate entry point.
 if (!IsPost) {