Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetching private gems using bundler's credentials #61

Open
burke opened this issue Aug 20, 2019 · 4 comments
Open

Fetching private gems using bundler's credentials #61

burke opened this issue Aug 20, 2019 · 4 comments

Comments

@burke
Copy link

burke commented Aug 20, 2019

So a few months back, I added support for bundix to look up bundler credentials to fetch gems requiring authentication. Today I took a run at teaching nix to do this without the runtime assistance of bundix, and this is what I came up with. It's pretty rough but Technically Works™.

Anyone know how I could do this better? Is there a form I could shape this into that would make sense to merge into bundix?

I'm happy to pour a bunch of work into this if someone can point me in the right direction.

I'm going to want to come up with similar solutions for gems fetched from private git remotes.

# gemset.nix
let

  hostnameFromURL = url:
    builtins.elemAt (
      builtins.match "https?://([^/]+)/.*" url # [ "packages.shopify.io" ]
    ) 0; # "packages.shopify.io"

  bundlerVarFromHostname = hostname:
    with import <nixpkgs> { }; "BUNDLE_" + lib.toUpper (
      builtins.replaceStrings ["."] ["__"] hostname # "packages__shopify__io"
    ); # "BUNDLE_PACKAGES__SHOPIFY__IO"

  tokenFromHomeBundlerConfig = var:
    if   builtins.getEnv var != ""
    then builtins.getEnv var
    else builtins.elemAt (
      builtins.match ".*\n${var}: ([^\n]+)\n.*" (
        builtins.readFile ((builtins.getEnv "HOME") + "/.bundle/config")
      )
    ) 0;

  netrcEntryForGemURL = url:
    let
      hostname = hostnameFromURL url;
      var = bundlerVarFromHostname hostname;
      token = tokenFromHomeBundlerConfig var;
    in
      "machine ${hostname}\n\tlogin ${token}\n";

  fetchurlWithBundlerAuthentication = { url, sha256 }:
    let
      entry = netrcEntryForGemURL url;
    in
      with import <nixpkgs> {}; fetchurl {
        url = url;
        sha256 = sha256;

        netrcPhase = ''
          cat > netrc <<EOF
          ${entry}
          EOF
        '';
      };

in

{
  abc = {
    dependencies = ["def"];
    groups = ["development" "test"];
    platforms = [];
    source = {
      remotes = ["https://packages.shopify.io/shopify/gems"];
      sha256 = "0000000000000000000000000000000000000000000000000001";
      type = "gem";
    };
    src = fetchurlWithBundlerAuthentication {
      url = "https://packages.shopify.io/shopify/gems/gems/abc-0.0.1.gem";
      sha256 = "0000000000000000000000000000000000000000000000000001";
    };
    version = "0.0.1";
  };
}
@burke
Copy link
Author

burke commented Aug 20, 2019

Hm, I refined this to:

let

  hostnameFromURL = url: # String -> String
    builtins.elemAt (
      builtins.match "https?://([^/]+)/.*" url # [ "packages.shopify.io" ]
    ) 0; # "packages.shopify.io"

  bundlerVarFromHostname = hostname: # String -> String
    with import <nixpkgs> { }; "BUNDLE_" + lib.toUpper (
      builtins.replaceStrings ["."] ["__"] hostname # "packages__shopify__io"
    ); # "BUNDLE_PACKAGES__SHOPIFY__IO"

  lookUpBundlerConfig = var: # String -> String
    if   builtins.getEnv var != ""
    then builtins.getEnv var
    else builtins.elemAt (
      builtins.match ".*\n${var}: ([^\n]+)\n.*" (
        builtins.readFile ((builtins.getEnv "HOME") + "/.bundle/config")
      )
    ) 0;

  injectAuth = url: # String -> String
    let
      hostname = hostnameFromURL url;
      var = bundlerVarFromHostname hostname;
      token = lookUpBundlerConfig var;
    in
      builtins.replaceStrings ["://"] ["://${token}@"] url;

in

{
  abc = {
    dependencies = ["def"];
    groups = ["development" "test"];
    platforms = [];
    source = {
      remotes = [(injectAuth "https://packages.shopify.io/shopify/gems")];
      sha256 = "0000000000000000000000000000000000000000000000000001";
      type = "gem";
    };
    version = "0.0.1";
  };
}

This is probably progress in the right direction, but this feels like a weird place for this code to live.

@zimbatm
Copy link
Member

zimbatm commented Aug 21, 2019

One issue with this approach is that the secrets will be written to the /nix/store which is readable by all the users on the machine. What some people do is rotate the token frequently to limit the window of attack.

Another approach would be to change the fetchers to use buitlins.fetchurl and builtins.fetchgit instead. Those are executed at evaluation time and also read from the user's ~/.netrc by default. To do that you will probably have to introduce an extension to <nixpkgs/pkgs/development/ruby-modules/gem/default.nix> to allow to pass custom fetchers.

@burke
Copy link
Author

burke commented Aug 21, 2019

Thanks. I like that idea. I'll play with that when I get a chance.

@bugeats
Copy link

bugeats commented Nov 17, 2021

I've got a Gemfile with private repos accessed over https. In a normal flow, I would just set bundler to use a personal access token like so:

bundle config GITHUB__COM <token>:x-oauth-basic

This of course doesn't work with bundix. I'm highly interested in finding a solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants