git-ssh-wrapper 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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