Git SSH setup for VisualStudio
Tags: ssh, git, devops
Published: 2021-12-01 11:21:31


---
title: Git SSH setup for VisualStudio 
published: true
description: Every now and then we need to set ourselves up a new dev machine. And 99% of the time, that means setting up git source control. We believe that password &hellip; Continue reading &#8220;Git SSH setup for VisualStudio&#8221;
tags: ssh, git, devops
canonical_url: https://blog.wiseowls.co.nz/index.php/2021/12/02/git-ssh-setup-for-visualstudio/
cover_image: https://blog.wiseowls.co.nz/wp-content/uploads/2021/09/alternate-skate-_tH3YCjPCCE-unsplash-scaled.jpg
---

Every now and then we need to set ourselves up a new dev machine. And 99% of the time, that means setting up [git](https://git-scm.com/) source control. We believe that password authentication is a no-no, so we needed a quick way to bootstrap fresh Windows 10 install to use SSH key pairs.

This Is The Way
---------------

Setting things up would involve making sure OpenSSH is installed, `ssh-agent` is running and key pair is generated and registered with the agent. Finally, we’d go to `http://dev.azure.com/{orgname}/_usersSettings/keys` and paste public key in. This however is a laborious task, and most sources online seem to suggest doing it that way. We decided to simplify:

```powershell
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/tkhadimullin/win-ssh-bootstrap/master/install.ps1'))
```


this will download and run the following:

```powershell
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
    Write-Warning  "Running as non-Admin user. Skipping environment checks"
} else {
    $capability = Get-WindowsCapability -Online | Where-Object Name -like "OpenSSH.Client*"

    if($capability.State -ne "Installed") {
        Write-Information "Installing OpenSSH client"
        Add-WindowsCapability -Online -Name $capability.Name
    } else {
        Write-Information "OpenSSH client installed"
    }

    $sshAgent = Get-Service ssh-agent
    if($sshAgent.Status -eq "Stopped") {$sshAgent | Start-Service}
    if($sshAgent.StartType -eq "Disabled") {$sshAgent | Set-Service -StartupType Automatic }
}

if([String]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable("GIT_SSH"))) {
    [Environment]::SetEnvironmentVariable("GIT_SSH", "$((Get-Command ssh).Source)", [System.EnvironmentVariableTarget]::User)
}

$keyPath = Join-Path $env:Userprofile ".ssh\id_rsa" {
 # Assuming file name here
if(-not (Test-Path $keyPath)) { 
    ssh-keygen -q -f $keyPath -C "autogenerated_key" -N """" # empty password
    ssh-add -q -f $keyPath
} 

$line = Get-Content -Path "$($keyPath).pub" | Select-Object -First 1 # assuming file name and key index

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = 'Your SSH Key'
$form.Size = New-Object System.Drawing.Size(600,150)
$form.StartPosition = 'CenterScreen'

$okButton = New-Object System.Windows.Forms.Button
$okButton.Location = New-Object System.Drawing.Point(260,70)
$okButton.Size = New-Object System.Drawing.Size(75,23)
$okButton.Text = 'OK'
$okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $okButton
$form.Controls.Add($okButton)

$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,10)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.Text = 'Copy your key and paste into ADO:'
$form.Controls.Add($label)

$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Point(10,30)
$textBox.Size = New-Object System.Drawing.Size(560,40)
$textBox.Text = $line
$textBox.ReadOnly = $true

$form.Controls.Add($textBox)
$form.Add_Shown({$textBox.Select()})
$form.Topmost = $true
$form.ShowDialog()
```


This script will take care of prerequisites (if run as admin) or try to generate a key in case everything else is done. Then it’ll paint a small window with public key:

![script output window](https://blog.wiseowls.co.nz/wp-content/uploads/2021/12/image-1.png)The script makes a couple of assumptions about existing keys and will just roll with defaults. Nothing fancy at all. We also wanted to automate posting to ADO, but that did not happen (see below).

Setting up Visual Studio
------------------------

Next order of business was to set up the IDE. It appears, Visual Studio would default to using password credentials, unless we set a `GIT_SSH` environment variable and point it to `ssh.exe` from OpenSSH distribution. The script will take care of that too.

Posting public key to Azure DevOps (not really)
-----------------------------------------------

ADO [does not have an API](https://stackoverflow.com/questions/63057083/add-ssh-key-to-azure-devops-pipeline-user-via-devops-rest-api) for managing SSH keys. Therefore, generating PATs and service credentials will not going to help. We can try to make it happen by reverse engineering the front-end call and hoping it’s isolated enough for us to be able to repeat the procedure. Turns out, it’s indeed a matter of sending payload to `https://dev.azure.com/{org}/_apis/Contribution/HierarchyQuery` – this looks like a common message bus for [ADO Extensions](https://docs.microsoft.com/en-us/azure/devops/extend/develop/contributions-overview?view=azure-devops) to post updates to:

```json
{
    "contributionIds": [
        "ms.vss-token-web.personal-access-token-issue-session-token-provider"
    ],
    "dataProviderContext": {
        "properties": {
            "displayName": "key-name",
            "publicData": "ssh-rsa Aaaaaaaaaaaaaabbbbbb key-comment",
            "validFrom": "2021-11-30T08:00:00.000Z",
            "validTo": "2026-11-30T08:00:00.000Z",
            "scope": "app_token",
            "targetAccounts": [
                "xxxxxxxx-xxxx-xxxxx-xxxx-xxxxxxxxxxxx"
            ],
            "isPublic": true
        }
    }
}
```


The first issue waits us right in the payload: `dataProviderContext.targetAccounts` needs a value, but we could not find where to fetch it from. It’s loaded along with other content on the page, but opening it kind of eliminates the purpose of automating this task. And unfortunately, that’s not the only obstacle we’ve hit there.

Authentication
--------------

Front end relies on cookies to authenticate this request. We found that the only one we really need is `UserAuthentication`:

![postman cookie view](https://blog.wiseowls.co.nz/wp-content/uploads/2021/12/image.png)The value is standard JWT, issued by `app.vstoken.visualstudio.com`. Getting it requires us to register an app and have users go through oAuth flow. Also, since ADO works on concept of tenants and organisations, it is tricky to get the correct tenancy without interactive login. It seems doable, but we have deemed it to be not worth the effort. <sad\_face\_emoji\_here>

Conclusion
----------

Despite not being able to reach our fully automated nirvana, we’ve got to a state where we’d prep the system for SSH and surface the public key to copy-paste. It seems that reverse engineering the ADO frontend and extracting token from there is very much achievable, but at the stage we’d not pursue it. Publishing the code [on GitHub](https://github.com/tkhadimullin/win-ssh-bootstrap) gives us a faint hope the Community may push it across the line.