December 5, 2023

Working as a consultant or an independent freelancer in a multi-tenant environment has its perks, but handling workspaces for multiple clients simultaneously presents its inconveniences. Amidst the sea of emails and organization hell, I'll focus on managing SSH and Git credentials for platforms like GitHub, GitLab, and Azure DevOps.

Reflecting on past experiences, I had numerous accidentals credentials access, difficulty in identifying which key corresponds to which client, and maintaining the overall security of sensitive information are common issues faced by consultants. In this article, I'll share practical solutions I've implemented to optimize multi-tenant environment management for a seamless workflow with a security-first approach.

HTTPS vs. SSH: The Big Small Debate

Firstly, let's touch on the HTTPS versus SSH debate – a significant discussion among developers of all ages. While HTTPS is not inherently less secure than SSH, it does present different considerations that I won't delve into here. From personal experience, I find HTTPS more user-friendly, especially for open-source projects. With no explicit authentication setup, you clone, login and it works – no SSH key configuration, algorithm choices, additional password management or tweaks to your client's SSH config for older server requirements.

However, when managing multiple credentials for a single domain, as in the case of GitLab, HTTPS falls short in flexibility. Some organizations use self-hosting with a distinct domain for each tenant, while others opt for the managed version. Here's where SSH shines – it's more flexible but admittedly more complex. Using HTTPS for personal and SSH for work works well when dealing with a single personal and work credential. Scaling this approach for multiple clients requires more thought.

Many default to using id_rsa or id_ed25519, or id_ed25519_sk for a touch of flair. Everything, including SSH, ssh-agent, and Git, uses this id_{algorithm} key by default, which is convenient but poses challenges when credentials are shared between multiple accounts on services like GitLab, which globally detects duplicate credentials, and futher remembering which account use it, just count the number of keys that are still in your GitHub settings page.

Distinct Credentials for Each Client

At this juncture, it becomes essential to establish distinct credentials for each client, such as sfeir_ed25519. While various solutions exist on your beloved Stack Overflow, I'll present my somewhat sophisticated SSH and Git configuration version. Firstly, since SSH config doesn't inherently support URL matching, like:

Host github.com/sfeir
  IdentityFile ~/.ssh/sfeir_ed25519

Host github.com/infinity-horizons
  IdentityFile ~/.ssh/infinityhorizons_ed25519

simply because the protocol was designed to connect to machines, not URLs. A commonly found solution involves defining a custom, non-existing domain for each tenant, using it with your tools, and forwarding it to the actual real host.

Host github.sfeir.internal
  HostName github.com
  IdentityFile ~/.ssh/sfeir_ed25519

Host github.infinity-horizons.internal
  HostName github.com
  IdentityFile ~/.ssh/infinityhorizons_ed25519

While any domain could be used, I find it more convenient to follow the top-level domain de facto standard for internal use cases. While not officially recognized by the IANA standard, I recommend it over the standard .local, which is often utilized for multicast purposes such as mDNS and can lead to confusion. If you're a nerdy for standards, you can refer to the details in RFC6762 and RFC6761. In case your git command encounters issues, a helpful troubleshooting approach is to set GIT_TRACE=1 and GIT_SSH_COMMAND environment variable – a lifesaver in many instances.

Enhancing Convenience: URL Rewriting

While we've technically addressed our issue, there's an additional enhancement worth considering. Now, to re-enable the ability to copy and paste URLs provided by platforms like GitLab or GitHub, you can take it a step further. The following technique proves particularly advantageous in situations where internal packages or token-only authentication are in play. If you're acquainted with url.<base>.insteadOf, it serves as a convenient means to rewrite a URL akin to a middleman proxy. However, remember that it only works for prefixes. Therefore, you need to set up different instances for URLs that may have an ssh:// prefix.

[url "[email protected]/sfeir"]
   insteadOf = "ssh://[email protected]/sfeir"

And with that, I've shared my tricks for the day. Now you can confidently showcase your newfound efficiency, akin to an Arch Linux enthusiast, among your colleagues. Additionally, I'll always express my affection for Nix; each of these configurations could easily be centralized through home-manager modules. However, I encourage you to explore one of the many dotfile management options available.

Another valuable consideration is the use of devcontainer for heightened security. In my practice, I often isolate cloud provider authentications to enhance security levels and… well, because you known, the database stories. For more insights into this approach, you can refer to my enlightening article.