Gem Credentials Management with Gemstash

ruby, gems, gemstash

13 Nov 2023 ・ Featured at Ruby Weekly #678

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 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 commercial 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 server.

What is Gemstash?
Gemstash is both a cache for remote servers such as https://rubygems.org, and a private gem source.

With Gemstash (since version 2.5.0, where this PR was merged), we can easily set credentials to private sources similarly to how we can set them using Bundler.

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):

GEMSTASH_GEMS__CONTRIBSYS__COM=user:pass

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

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:

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.
  • 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.

It won’t require credentials when running bundle install:

➜ 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.

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 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 commercial 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: github.com/CiTroNaK/gemstash-on-dokku.


Do you like it? You can subscribe to RSS (you know how), or follow me on Mastodon.