When we are working for multiple clients or handling multiple client projects often times we see that each client has their git server in GitHub or GitLab or Bitbucket either in self-hosted model or subscribed to GitHub enterprise versions. Each client’s git server URL is different, and we are provided with a unique git account username.

Let me explain the way I do it. I generally work on some projects that live in GitHub, GitLab. In order to push code from my machine, I have to authenticate myself with GitHub, GitLab. There are 2 ways to authenticate either with https or ssh. These options can be observed when we to try to clone a repo from GitHub or GitLab. SSH based authentication is recommended over HTTPS for some of these reasons.

  • Security: SSH authentication provides a more secure way of authentication as it uses public-key cryptography to authenticate the user. This means that the private key is never transmitted over the network, making it less susceptible to eavesdropping or man-in-the-middle attacks.
  • Convenience: Once we have set up SSH authentication, we can easily connect to your remote server without having to enter your password every time. This is especially useful when we need to perform multiple operations on the server, as it saves we time and effort.
  • Speed: SSH authentication is generally faster than HTTPS, as it does not require the overhead of SSL/TLS encryption. This means that we can transfer files or perform operations on the server more quickly.
  • Control: With SSH authentication, we have more control over who can access your server, as we can configure the authorized_keys file to only allow specific users or IP addresses to connect. This is not possible with HTTPS.

Here are the steps I usually follow to set up SSH keys with git servers.

  1. Create SSH Keys
    • Generate keys(private, public) using the command

      ssh-keygen -t ed25519 -C "srk-GitHub"

      • In the above command -C “srk-GitHub” is optional, and it is a comment that gets added to your public key. The real essence of this comment can be observed when we add private keys to ssh-agent’s memory, and when we list all keys, we can see this comment at the end of each key.
      • If we don’t pass the comment with -C random text is appended in your *.pub file at the end of the line.
      • It’s a good practice to always add the comment with -C “your-comment” while generating the ssh keys
    • Now, enter the private key file name with absolute or relative path. Example: id_ed25519. Also, enter a passphrase for your private key. Creating ssh key
    • After the successful command execution 2 files will be generated
      • Private key - with no file extension
      • Public key - with file extension as *.pub
    • Private Public keys generated
  2. Add SSH key to GitHub/GitLab/Bitbucket Account
    • Copy your public key and upload it to your GitHub account under ssh keys and GPG keys, click on create a new SSH Key.
    • Before Upload
      • Before upload of public key to GitHub
    • After Upload
      • After upload of public key to GitHub
  3. Test your SSH Connection
    • Login to git server UI and create a repo to quickly verify connectivity Example repo: test
    • Clone the repo to your machine using ssh
      • git clone git@GitHub.com:sairaghavak/test.git
      • Syntax:

        git clone git@<host/aliasName/FQDN>:<username>/<repo-name>.git

    • Once the repo is cloned to your machine, update any file usually README.md, commit, and push.
    • Now, it should ask you to enter password for your private key.
    • After successful password entry, it will push your code to GitHub upstream server.
    • Alternatively, we can add keys to ssh-agent by using ssh-add command, and to verify or validate the SSH connection we can use the command ssh -T git@<hostname-registered-in-~/.ssh/config> file


Managing multiple SSH Keys

From the above steps it’s clear that we have to enter password of your private key every time we want to push the code. On top of that if we are working on multiple projects we have to remember all private key passwords and enter them when we push code for each project.

To overcome this, we have to use ssh-agent an identity manager to manage all your identities/SSH Keys

  • Create a config file under ~/.ssh directory i.e., ~/.ssh/config
  • Here is how my ssh config would look like

    # sairaghavak customized config
    HashKnownHosts=yes # the host address is hashed in known_hosts file under ~/.ssh/known_hosts
    
    # HostName or alias name for sairaghavak GitHub
    Host srk-GitHub # alias name
      AddKeysToAgent yes # we can add your private keys to ssh-agent utility
      Hostname GitHub.com # Actual server address
      user git # recommended to keep the user as git always
      IdentityFile ~/.ssh/srk/GitHub/id_ed25519 # your private key absolute path
      ForwardAgent Yes # Sometimes we might want to we share/use the same ssh-agent between Host OS and DevContainer
    
    # HostName or alias name for sairaghavak gitlab
    Host srk-gitlab
      AddKeysToAgent yes
      Hostname gitlab.com
      user git
      IdentityFile ~/.ssh/srk/gitlab/id_ed25519
      ForwardAgent Yes
    

    Note: With the above config the command to clone repo has to be changed. Example: git clone git@srk-GitHub:sairaghavak/test.git

  • Start ssh-agent
    • Windows
      • On Windows we have to go to service.msc and make the OpenSSH authentication to be automatically started on startup, and click on start so that ssh-agent is running.

        Start SSH Agent as Windows service

      • The command to know the ssh-agent status is ps ssh-agent on Windows Powershell.

        Know if you ssh-agent is running on Windows

    • Linux
      • To start an SSH Agent use the following command eval $(ssh-agent -s)
      • To check if ssh-agent is started use the command echo $SSH_AUTH_SOCK. That should print a response like /tmp/ssh-XXXXXXkF9FDx/agent.4238
  • Optional Step: Delete Keys: Delete all the existing keys to start from clean slate
    • ssh-add -D will delete all your identities
    • ssh-add -d <path-to-your-private-key-or-identity-file> will delete a specific identity
  • Add keys: Once, ssh-agent is up and running now we can add all our identities/private keys as a one time setup using ssh-add utility
    • ssh-add <path-to-your-private-key-or-identity-file>
    • Example: ssh-add ~/.ssh/srk/GitHub/id_ed25519 it will prompt us to enter password for private key, and it will add this identity
    • Add identities using ssh-add
    • Similarly we can add all we SSH private keys or identities to ssh-add utility
  • List Keys: After, all the identities are added, we can review them using command
    • ssh-add -l which will list all the identities which we have added, it will read from ssh-agent’s memory. List all identities using ssh-add -l
  • Note: ssh-agent generally stores identities in memory, meaning whenever the service is killed or terminated all the identities are removed. This has been the behavior of ssh-agent in Linux. However, in windows OpenSSH, the identities remain even after system shutdowns and restarts.


Validating SSH Connection

  • To validate your SSH connectivity with upstream GitHub we use the following command
    • ssh -T git@srk-GitHub
    • Summary of how this command works:
      • the SSH client (ssh) reads your ~/.ssh/config file to see if there is a Host section that matches srk-GitHub. If there is, it uses the configuration settings specified in that section (including any specified IdentityFile) to try to authenticate with the server.
      • If there is no Host section in the ~/.ssh/config file, then the client will look for the default location of the private key (~/.ssh/id_rsa), and check if the corresponding public key is associated with your GitHub account.
      • When the key is already added to the ssh-agent, the authentication is performed using the key without any need for user intervention. Else if the key is not added to the ssh-agent, then the client will prompt we to enter the passphrase to unlock the private key.
      • If the authentication is successful, then the server responds with a welcome message and the connection is established. If the authentication fails, the SSH client will exit with an error message.
    • At this step, it has to add the upstream GitHub server ipaddress and will add an entry in your known_hosts file in ~/.ssh/known_hosts
    • Finally, it should print a message like this Hi sairaghavak! we've successfully authenticated, but GitHub does not provide shell access
    • Validating ssh connection with git

Git Configs

  • Git configs can be created at global/system level and can be inherited and overridden in each cloned repository.
  • Here are my sample git configs
    • Global git config: ~/.gitconfig

        [core]
            editor = code --wait # Upon git commit, it will open a new tab in foreground within currently running vscode to enter commit message and only after closing this tab, the commit message on bash prompt is released and freezed.
            eol = lf # end of line(eol) is line feed(lf)
            autocrlf = input # input if your repo is cloned on linux or (mac/OSX), for windows use value true: Read: https://docs.GitHub.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings?platform=windows
            sshCommand = C:/Windows/System32/OpenSSH/ssh.exe # This is on windows, good to mention specific targeted ssh util path. There could be multiple ssh clients installed on your machine. The value on linux could be /usr/bin/ssh or the absolute path of your ssh
        [pull]
           rebase = false # false meaning, when you do git pull, it will do merge instead of rebase. For further info, read more or merge vs rebase strategies.
      
    • Local git config inside a repository: ~/.git/config

        # Repository specific git config overrides and inherits props from ~/.gitconfig
        [user]
            name = <git-registered-account-username>
            email = <git-registered-email-linked-with-git-username>
        [core]
            editor = code --wait
            eol = lf
            autocrlf = input # if your repo is cloned on linux or (mac/osx), otherwise if it is cloned on windows it is true
            sshCommand = /usr/bin/ssh # on linux, this value is the result of command which ssh
        [remote "origin"]
            url = git@<host-or-alias-name as defined in ~/.ssh/config>:<git-username>/<repo-name>.git
            fetch = +refs/heads/*:refs/remotes/origin/*
        [branch "main"]
            remote = origin
            merge = refs/heads/main
      

References:

  1. SSH
  2. CRLF vs. LF
  3. autoclrf in git
  4. SO Answer: CRLF vs LF
  5. SO Answer: How autocrlf works
  6. SO Answer: Summarized autocrlf values