git-ssh-wrapper 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -1,10 +1,49 @@
1
1
  # GitSSHWrapper
2
2
 
3
- Encapsulate the code you need to write out a permissive GIT_SSH script that
3
+ Encapsulate the code you need to write out a permissive GIT\_SSH script that
4
4
  can be used to connect git to protected git@github.com repositories.
5
5
 
6
- ## Example
6
+ Includes two bin scripts, `git-ssh-wrapper` and `git-ssh` that can be used
7
+ inline to call git with GIT\_SSH set properly. See examples below.
7
8
 
9
+ ## What it does
10
+
11
+ This gem provides a simple way to connect to git servers using keys that have
12
+ not been added to your authentication agent with ssh-add, or keys which you
13
+ only have saved as a string instead of written to a file.
14
+
15
+ This is especially useful for scripts that need to automate connections to a
16
+ server using keys that are not intended to be part of the system on which the
17
+ script is running.
18
+
19
+ This script is designed to *always work* even if hosts keys change or the ssh
20
+ agent is being too paranoid or having a bad day.
21
+
22
+ A common use case is connecting to github.com to retrieve repositories,
23
+ submodules, or ref listings using read-only "deploy keys" or bundling a Gemfile
24
+ that contains private repositories accessible by a certain deploy key.
25
+
26
+ ## Command Line
27
+
28
+ You can use the included command line tool to call git commands directly.
29
+
30
+ $ git-ssh-wrapper ~/.ssh/id_rsa git fetch origin
31
+ $ git merge origin/master
32
+ $ git-ssh-wrapper ~/.ssh/id_rsa git push origin master
33
+
34
+ $ git-ssh-wrapper ~/.ssh/id_rsa bundle install
35
+
36
+ A shortcut command `git-ssh` is also included that inserts `git` automatically.
37
+
38
+ $ git-ssh ~/.ssh/id_rsa fetch origin # git fetch origin
39
+
40
+ You'll probably use this version if you're writing commands by hand.
41
+
42
+ ## Ruby Example
43
+
44
+ Accessing git servers programatically in ruby:
45
+
46
+ # :log_level default is 'INFO'
8
47
  def get_refs
9
48
  wrapper = GitSSHWrapper.new(:private_key_path => '~/.ssh/id_rsa', :log_level => 'ERROR')
10
49
  `env #{wrapper.git_ssh} git ls-remote git@github.com:martinemde/git-ssh-wrapper.git`
@@ -14,16 +53,46 @@ can be used to connect git to protected git@github.com repositories.
14
53
 
15
54
  OR
16
55
 
17
- # :log_level default in 'INFO'
18
56
  def get_refs
19
- GitSSHWrapper.new(:private_key_path => '~/.ssh/id_rsa') do |wrapper|
20
- `env #{wrapper.cmd_prefix} git ls-remote git@github.com:martinemde/git-ssh-wrapper.git`
57
+ private_key_data_string = get_key_data_somehow
58
+ GitSSHWrapper.with_wrapper(:private_key => private_key_data_string) do |wrapper|
59
+ wrapper.set_env
60
+ `git ls-remote git@github.com:martinemde/git-ssh-wrapper.git`
21
61
  end
22
62
  end
23
63
 
24
64
  OR
25
65
 
26
66
  wrapper = GitSSHWrapper.new(:private_key => Pathname.new('id_rsa').read)
67
+ `git ls-remote git@github.com:martinemde/git-ssh-wrapper.git`
68
+ wrapper.unlink
27
69
 
28
70
  The wrapper creates Tempfiles when it is initialized. They will be cleaned at
29
- program exit, or you can unlink them by calling #unlink like the example above.
71
+ program exit, or you can unlink them by calling #unlink.
72
+
73
+ ## How it works
74
+
75
+ When connecting to a git server using ssh, if the GIT\_SSH environment variable
76
+ is set, git will use $GIT\_SSH instead of `ssh` to connect.
77
+
78
+ The script generated will look something like this:
79
+ (as long as I've kept this documentation up-to-date properly)
80
+
81
+ unset SSH_AUTH_SOCK
82
+ ssh -o CheckHostIP=no \
83
+ -o IdentitiesOnly=yes \
84
+ -o LogLevel=LOG_LEVEL \
85
+ -o StrictHostKeyChecking=no \
86
+ -o PasswordAuthentication=no \
87
+ -o UserKnownHostsFile=TEMPFILE \
88
+ -o IdentityFile=PRIVATE_KEY_PATH \
89
+ $*
90
+
91
+ The result is an ssh connection that won't use your ssh-added keys, won't prompt
92
+ for passwords, doesn't save known hosts and doesn't require strict host key
93
+ checking.
94
+
95
+ A tempfile is generated to absorb known hosts to prevent these constant warnings:
96
+ `Warning: Permanently added 'xxx' (RSA) to the list of known hosts.`
97
+
98
+ The tempfile is cleaned when the wrapper is unlinked or the program exits.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path('../lib', File.dirname(__FILE__))
3
+ require 'git-ssh-wrapper/cli'
4
+ GitSSHWrapper::CLI.git_ssh
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path('../lib', File.dirname(__FILE__))
3
+ require 'git-ssh-wrapper/cli'
4
+ GitSSHWrapper::CLI.wrapper
@@ -1,7 +1,10 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ $:.unshift File.expand_path('lib', File.dirname(__FILE__))
3
+ require 'git-ssh-wrapper/version'
4
+
2
5
  Gem::Specification.new do |s|
3
6
  s.name = "git-ssh-wrapper"
4
- s.version = "0.1.0"
7
+ s.version = GitSSHWrapper::VERSION
5
8
  s.authors = ["Martin Emde"]
6
9
  s.email = ["martin.emde@gmail.com"]
7
10
  s.homepage = "http://github.org/martinemde/git-ssh-wrapper"
@@ -10,7 +13,6 @@ Gem::Specification.new do |s|
10
13
 
11
14
  s.add_development_dependency "rake"
12
15
  s.add_development_dependency "rspec", '~> 2.0'
13
- s.add_development_dependency "open4"
14
16
 
15
17
  s.files = `git ls-files`.split("\n")
16
18
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -12,14 +12,12 @@ class GitSSHWrapper
12
12
  SAFE_MODE = 0600
13
13
  EXEC_MODE = 0700
14
14
 
15
-
16
- def self.tempfile(content, mode=SAFE_MODE)
17
- file = Tempfile.new("git-ssh-wrapper")
18
- file << content
19
- file.chmod(mode)
20
- file.flush
21
- file.close
22
- file
15
+ # command given as an array for Kernel#system
16
+ def self.run(command, options)
17
+ with_wrapper(options) do |wrapper|
18
+ wrapper.set_env
19
+ system *command
20
+ end
23
21
  end
24
22
 
25
23
  def self.with_wrapper(options)
@@ -52,10 +50,16 @@ class GitSSHWrapper
52
50
  alias git_ssh cmd_prefix
53
51
 
54
52
  def set_env
53
+ @old_git_ssh = ENV['GIT_SSH']
55
54
  ENV['GIT_SSH'] = path
56
55
  end
57
56
 
57
+ def unset_env
58
+ ENV['GIT_SSH'] = @old_git_ssh
59
+ end
60
+
58
61
  def unlink
62
+ unset_env
59
63
  @tempfiles.each { |file| file.unlink }.clear
60
64
  end
61
65
 
@@ -65,12 +69,20 @@ class GitSSHWrapper
65
69
  tempfile(<<-SCRIPT, EXEC_MODE)
66
70
  #!/bin/sh
67
71
  unset SSH_AUTH_SOCK
68
- ssh -o 'CheckHostIP no' -o 'StrictHostKeyChecking no' -o 'PasswordAuthentication no' -o 'LogLevel #{log_level}' -o 'IdentityFile #{private_key_path}' -o 'IdentitiesOnly yes' -o 'UserKnownHostsFile /dev/null' $*
72
+ ssh -o CheckHostIP=no -o IdentitiesOnly=yes -o LogLevel=#{log_level} -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o UserKnownHostsFile=#{known_hosts_file} -o IdentityFile=#{private_key_path} $*
69
73
  SCRIPT
70
74
  end
71
75
 
76
+ def known_hosts_file
77
+ @known_hosts_file ||= tempfile('')
78
+ end
79
+
72
80
  def tempfile(content, mode=SAFE_MODE)
73
- file = self.class.tempfile(content, mode)
81
+ file = Tempfile.new("git-ssh-wrapper")
82
+ file << content
83
+ file.chmod(mode)
84
+ file.flush
85
+ file.close
74
86
  @tempfiles << file
75
87
  file.path
76
88
  end
@@ -0,0 +1,79 @@
1
+ require 'git-ssh-wrapper'
2
+ require 'git-ssh-wrapper/version'
3
+
4
+ class GitSSHWrapper::CLI
5
+ # Calls any command with GIT_SSH set.
6
+ #
7
+ # Examples of argv:
8
+ # [key_path, git, fetch, origin]
9
+ # [key_path, some/script, that, calls, git]
10
+ def self.wrapper(args=ARGV)
11
+ new(*args).call
12
+ end
13
+
14
+ # Inserts git into the command if it's not there.
15
+ # The command must be a git command.
16
+ #
17
+ # Examples of argv:
18
+ # [key_path, fetch, origin]
19
+ # [key_path, git, fetch, origin]
20
+ def self.git_ssh(args=ARGV)
21
+ key, *command = *args
22
+ command.unshift('git') unless command.first == 'git'
23
+ new(key, *command).call
24
+ end
25
+
26
+ attr_reader :key, :command
27
+
28
+ def initialize(*args)
29
+ @key, *@command = *args
30
+ end
31
+
32
+ def call
33
+ if %w[--help -h help].include?(key)
34
+ print_help
35
+ end
36
+
37
+ if %w[--version -v].include?(key)
38
+ print_version
39
+ end
40
+
41
+ if key.nil? || command.empty?
42
+ error
43
+ elsif !File.exist?(key)
44
+ error "private key not found: #{key.inspect}"
45
+ end
46
+
47
+ GitSSHWrapper.with_wrapper(:private_key_path => key) do |wrapper|
48
+ wrapper.set_env
49
+ system *command
50
+ exit $?.exitstatus
51
+ end
52
+
53
+ exit 1
54
+ end
55
+
56
+ def print_help
57
+ puts "Run remote git commands using only the specified ssh private key."
58
+ puts
59
+ puts usage
60
+ exit 0
61
+ end
62
+
63
+ def print_version
64
+ puts "git-ssh-wrapper version #{GitSSHWrapper::VERSION}"
65
+ exit 0
66
+ end
67
+
68
+ def bin
69
+ File.basename($0)
70
+ end
71
+
72
+ def usage
73
+ "usage:\t#{bin} ssh.key command"
74
+ end
75
+
76
+ def error(message=nil)
77
+ abort [message,usage].compact.join("\n")
78
+ end
79
+ end
@@ -0,0 +1,3 @@
1
+ class GitSSHWrapper
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'git-ssh script' do
4
+ let(:bin) { GIT_SSH_BIN }
5
+ let(:usage) { "usage:\tgit-ssh ssh.key command\n" }
6
+
7
+ it "prints usage information with no args" do
8
+ run_fails(bin).should == usage
9
+ end
10
+
11
+ it "prints help on -h, --help, or help" do
12
+ help = "Run remote git commands using only the specified ssh private key.\n\n#{usage}"
13
+ run_succeeds(bin, '-h').should == help
14
+ run_succeeds(bin, 'help').should == help
15
+ run_succeeds(bin, '--help').should == help
16
+ end
17
+
18
+ it "aborts if you don't specify a key" do
19
+ run_fails(bin, 'git st').should == "private key not found: \"git\"\n#{usage}"
20
+ end
21
+
22
+ it "just calls git without args if you don't specify a command" do
23
+ run_fails(bin, private_key_path).should == `git`
24
+ end
25
+
26
+ it "allows access to secure github repositories" do
27
+ run_succeeds(bin, private_key_path, 'ls-remote git@github.com:martinemde/git-ssh-wrapper.git refs/heads/master')
28
+ end
29
+
30
+ it "exits with the status of the child command" do
31
+ run_succeeds(bin, private_key_path, 'status')
32
+ run_fails(bin, private_key_path, 'notfound')
33
+ end
34
+
35
+ it "does not delete the keyfile" do
36
+ run_succeeds(bin, private_key_path, 'status')
37
+ private_key_path.should exist
38
+ end
39
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'git-ssh-wrapper script' do
4
+ let(:print_env) { ROOT_PATH.join('spec/print_env') }
5
+ let(:bin) { WRAPPER_BIN }
6
+ let(:usage) { "usage:\tgit-ssh-wrapper ssh.key command\n" }
7
+
8
+ it "prints usage information with no args" do
9
+ run_fails(bin).should == usage
10
+ end
11
+
12
+ it "prints help on -h, --help, or help" do
13
+ help = "Run remote git commands using only the specified ssh private key.\n\n#{usage}"
14
+ run_succeeds(bin, '-h').should == help
15
+ run_succeeds(bin, 'help').should == help
16
+ run_succeeds(bin, '--help').should == help
17
+ end
18
+
19
+ it "aborts if you don't specify a key" do
20
+ run_fails(bin, 'git st').should == "private key not found: \"git\"\n#{usage}"
21
+ end
22
+
23
+ it "aborts if you didn't specify a command" do
24
+ run_fails(bin, private_key_path).should == usage
25
+ end
26
+
27
+ it "allows access to secure github repositories" do
28
+ run_succeeds(bin, private_key_path, 'git ls-remote git@github.com:martinemde/git-ssh-wrapper.git refs/heads/master')
29
+ end
30
+
31
+ it "sets the GIT_SSH environment variable" do
32
+ run_succeeds(bin, private_key_path, print_env_script).chomp.should =~ /git-ssh-wrapper/ # the tempfile includes this in the name
33
+ end
34
+
35
+ it "cleans up after execution" do
36
+ run_succeeds(bin, private_key_path, 'true')
37
+ `#{print_env_script}`.chomp.should be_empty
38
+ ENV['GIT_SSH'].should be_nil
39
+ end
40
+
41
+ it "exits with the status of the child command" do
42
+ run_succeeds(bin, private_key_path, 'true')
43
+ run_fails(bin, private_key_path, 'false')
44
+ end
45
+
46
+ it "does not delete the keyfile" do
47
+ run_succeeds(bin, private_key_path, 'true')
48
+ private_key_path.should exist
49
+ end
50
+ end
@@ -18,7 +18,7 @@ describe GitSSHWrapper do
18
18
  it "disappears when unlinked" do
19
19
  pathname = subject.pathname
20
20
  subject.unlink
21
- pathname.should_not be_exist # ;_; syntax h8
21
+ pathname.should_not exist
22
22
  end
23
23
  end
24
24
 
@@ -34,10 +34,9 @@ describe GitSSHWrapper do
34
34
  it_should_behave_like "a GIT_SSH wrapper"
35
35
 
36
36
  it "should not delete the keyfile when unlinked" do
37
- pathname = Pathname.new(private_key_path)
38
- pathname.should be_exist
37
+ private_key_path.should exist
39
38
  subject.unlink
40
- pathname.should be_exist
39
+ private_key_path.should exist
41
40
  end
42
41
  end
43
42
 
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+ if [[ -x "$GIT_SSH" ]]; then
3
+ echo $GIT_SSH
4
+ fi
@@ -8,18 +8,50 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
8
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
9
9
  require 'git-ssh-wrapper'
10
10
  require 'rspec'
11
- require 'open4'
12
11
 
13
- module TestPrivateKey
12
+ ROOT_PATH = Pathname.new("..").expand_path(File.dirname(__FILE__))
13
+ PRIVATE_KEY_PATH = ROOT_PATH.join('spec/test_key').realpath.freeze
14
+ PRIVATE_KEY = PRIVATE_KEY_PATH.read.freeze
15
+ GIT_SSH_BIN = ROOT_PATH.join('bin/git-ssh').freeze
16
+ WRAPPER_BIN = ROOT_PATH.join('bin/git-ssh-wrapper').freeze
17
+ PRINT_ENV_SCRIPT = ROOT_PATH.join('spec/print_env').freeze
18
+
19
+ module SpecHelpers
20
+ def exist
21
+ be_exist
22
+ end
23
+
14
24
  def private_key
15
- private_key_path.read
25
+ PRIVATE_KEY
16
26
  end
17
27
 
18
28
  def private_key_path
19
- Pathname.new('spec/test_key').realpath
29
+ PRIVATE_KEY_PATH
30
+ end
31
+
32
+ def print_env_script
33
+ PRINT_ENV_SCRIPT
34
+ end
35
+
36
+ def run_succeeds(bin, *args)
37
+ cmd = ([bin] + args).flatten.join(' ')
38
+ ret = `#{cmd} 2>&1`
39
+ if !$?.success?
40
+ fail "Expected exit status 0, got #{$?.exitstatus}.\n\t#{cmd}\n\t#{ret}"
41
+ end
42
+ ret
43
+ end
44
+
45
+ def run_fails(bin, *args)
46
+ cmd = ([bin] + args).flatten.join(' ')
47
+ ret = `#{cmd} 2>&1`
48
+ if $?.success?
49
+ fail "Expected failure.\n\t#{cmd}\n\t#{ret}"
50
+ end
51
+ ret
20
52
  end
21
53
  end
22
54
 
23
55
  RSpec.configure do |config|
24
- config.include TestPrivateKey
56
+ config.include SpecHelpers
25
57
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-ssh-wrapper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-18 00:00:00.000000000 Z
12
+ date: 2012-11-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70113546137280 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70113546137280
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: rspec
27
- requirement: &70113546135600 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ~>
@@ -32,23 +37,19 @@ dependencies:
32
37
  version: '2.0'
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *70113546135600
36
- - !ruby/object:Gem::Dependency
37
- name: open4
38
- requirement: &70113546148800 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
39
41
  none: false
40
42
  requirements:
41
- - - ! '>='
43
+ - - ~>
42
44
  - !ruby/object:Gem::Version
43
- version: '0'
44
- type: :development
45
- prerelease: false
46
- version_requirements: *70113546148800
45
+ version: '2.0'
47
46
  description: Generate a permissive GIT_SSH wrapper script using a private key string
48
47
  or file for use with git commands that need ssh.
49
48
  email:
50
49
  - martin.emde@gmail.com
51
- executables: []
50
+ executables:
51
+ - git-ssh
52
+ - git-ssh-wrapper
52
53
  extensions: []
53
54
  extra_rdoc_files: []
54
55
  files:
@@ -57,9 +58,16 @@ files:
57
58
  - LICENSE
58
59
  - README.md
59
60
  - Rakefile
61
+ - bin/git-ssh
62
+ - bin/git-ssh-wrapper
60
63
  - git-ssh-wrapper.gemspec
61
64
  - lib/git-ssh-wrapper.rb
65
+ - lib/git-ssh-wrapper/cli.rb
66
+ - lib/git-ssh-wrapper/version.rb
67
+ - spec/bin_git_ssh_spec.rb
68
+ - spec/bin_wrapper_spec.rb
62
69
  - spec/git_ssh_wrapper_spec.rb
70
+ - spec/print_env
63
71
  - spec/spec_helper.rb
64
72
  - spec/test_key
65
73
  - spec/test_key.pub
@@ -77,7 +85,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
85
  version: '0'
78
86
  segments:
79
87
  - 0
80
- hash: -3599100337838962590
88
+ hash: -3272220434625762096
81
89
  required_rubygems_version: !ruby/object:Gem::Requirement
82
90
  none: false
83
91
  requirements:
@@ -86,15 +94,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
94
  version: '0'
87
95
  segments:
88
96
  - 0
89
- hash: -3599100337838962590
97
+ hash: -3272220434625762096
90
98
  requirements: []
91
99
  rubyforge_project:
92
- rubygems_version: 1.8.15
100
+ rubygems_version: 1.8.24
93
101
  signing_key:
94
102
  specification_version: 3
95
103
  summary: Generate a permissive GIT_SSH wrapper script on the fly
96
104
  test_files:
105
+ - spec/bin_git_ssh_spec.rb
106
+ - spec/bin_wrapper_spec.rb
97
107
  - spec/git_ssh_wrapper_spec.rb
108
+ - spec/print_env
98
109
  - spec/spec_helper.rb
99
110
  - spec/test_key
100
111
  - spec/test_key.pub