Gem Credentials Management with Gemstash
Tags: ruby, gemstash, gems
Published: 2023-11-14 07:01:04

## How can we efficiently manage gem credentials across the team?

When your project grows, you may reach out to commercial gems (like Sidekiq Pro) for help or want to extract some business logic into private gems.

Bundler has a [way](https://bundler.io/v2.4/man/bundle-config.1.html#CREDENTIALS-FOR-GEM-SOURCES) to set the credentials for commercial/private gems, but everyone has to set it. Each developer in the team, your build/deploy processes, and some of your jobs in the CI. It will get even worse when you have gems from multiple private sources.

That may be fine until you are forced to change (rotate) the credentials for one of the used enterprise gems (e.g., when they are leaked). Or, the worst-case, when your current credentials stop working (e.g., you forget to renew the license and end up with new credentials 😅).

## How to solve it?
So, as the title states, we can solve it with a private instance of the [Gemstash](https://github.com/rubygems/gemstash) server.

> **What is Gemstash?**  
> Gemstash is both a cache for remote servers such as [https://rubygems.org](https://rubygems.org/), and a private gem source.

With Gemstash (since version 2.5.0, where [this PR](https://github.com/rubygems/gemstash/pull/339) was merged), we can easily set credentials to private sources similarly to how we can set them using [Bundler](https://bundler.io/v2.4/man/bundle-config.1.html#CREDENTIALS-FOR-GEM-SOURCES).

That will solve two problems:
- We will have **only one place to set the credentials** for all the gems from private sources.
- We will have **all the gems cached**, so we won’t need to fetch them from the remote source every time. That can be handy when the remote source is slow, unavailable, or the credentials stop working.

## Example
To set the credentials, we have to set an ENV variable. The naming is the same as for Bundler but with the `GEMSTASH_` prefix (example below for Sidekiq gems):

```shell
GEMSTASH_GEMS__CONTRIBSYS__COM=user:pass
```

For testing, we can run the Gemstash server like this:

```shell
GEMSTASH_GEMS__CONTRIBSYS__COM=user:pass gemstash start --no-daemonize --config-file config.yml.erb
```

That will allow us to use it in our `Gemfile`, where `http://localhost:9292` is the URL to our local Gemstash server:
```ruby
source "http://localhost:9292/"

ruby "3.2.2"

source "http://localhost:9292/upstream/gems.contribsys.com" do
  gem "sidekiq-pro", "~> 5.5.7"
end
```

Notice the changes:
- On line 1, we replaced the `rubygems.org` source with our local Gemstash server. All gems will be served (and cached) by our Gemstash, which will use `rubygems.org` to fetch them [by default.](https://github.com/rubygems/gemstash/blob/main/docs/gemstash-configuration.5.md#rubygems-url)
- On line 5, we prefixed the `gems.contribsys.com` with `http://localhost:9292/upstream/`, which will use our stored credentials and fetch the gems from this source. See [docs](https://github.com/rubygems/gemstash/blob/main/docs/gemstash-multiple-sources.7.md).

It won't require credentials when running `bundle install`:
```shell
➜ bundle install           
Fetching gem metadata from http://localhost:9292/upstream/gems.contribsys.com..
Fetching gem metadata from http://localhost:9292/............
Using bundler 2.4.10
Using connection_pool 2.4.1
Using rack 2.2.7
Using redis 4.8.1
Using sidekiq 6.5.9
Fetching sidekiq-pro 5.5.8
Installing sidekiq-pro 5.5.8
Bundle complete! 1 Gemfile dependency, 6 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
```

## Securing the Gemstash server

By default, access to the Gemstash server is not protected, even for the [private gems](https://github.com/rubygems/gemstash/blob/main/docs/gemstash-private-gems.7.md#protected-fetching).

**You (or your DevOps team) are responsible for doing it!** 

Or anyone with your Gemstash server URL can fetch the private or commercial gems you are paying for.

The easiest way is to set the [Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication) for the server with one or many users. If you can automate it, you can create credentials for each developer and remove them when they leave the project.

The main benefit is that the credentials for enterprise gems won't be shared anymore, so they can not leaked that easily. If you need to rotate the credentials, you will update them in only one place: on the Gemstash server.

## Are you using Dokku?

If yes, I prepared an easy way to run Gemshash on [Dokku](https://dokku.com/): [github.com/CiTroNaK/gemstash-on-dokku](https://github.com/CiTroNaK/gemstash-on-dokku).