Commit fda23577 authored by Yorick Peterse's avatar Yorick Peterse

Merge branch 'use-ssh-key-internal-api' into 'master'

Use ssh key internal api to build the authorized-keys command on openssh 6.9

See merge request !42
parents 91e72255 38b8600f
v2.6.14
- Add support for ssh AuthorizedKeysCommand query by key
v2.6.13 v2.6.13
- Add push-branches command - Add push-branches command
- Add delete-remote-branches command - Add delete-remote-branches command
......
#!/usr/bin/env ruby
#
# GitLab shell authorized_keys. Query GitLab API to get the authorized command for a given ssh key fingerprint
#
# Ex.
# /bin/authorized_keys BASE64-KEY
#
# Returns
# command="/bin/gitlab-shell key-#",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAAAADAQA...
#
key = ARGV[0]
abort "# No key provided" if key.nil? || key.empty?
require_relative "../lib/gitlab_init"
require_relative "../lib/gitlab_net"
require_relative "../lib/gitlab_keys"
authorized_key = GitlabNet.new.authorized_key(key)
if authorized_key.nil?
puts "# No key was found for #{key}"
else
puts GitlabKey.new.key_line("key-#{authorized_key['id']}", authorized_key["key"])
end
...@@ -11,6 +11,7 @@ class GitlabKeys ...@@ -11,6 +11,7 @@ class GitlabKeys
@key_id = ARGV.shift @key_id = ARGV.shift
@key = ARGV.shift @key = ARGV.shift
@auth_file = GitlabConfig.new.auth_file @auth_file = GitlabConfig.new.auth_file
@gitlab_key = GitlabKey.new
end end
def exec def exec
...@@ -32,7 +33,7 @@ class GitlabKeys ...@@ -32,7 +33,7 @@ class GitlabKeys
def add_key def add_key
lock do lock do
$logger.info "Adding key #{@key_id} => #{@key.inspect}" $logger.info "Adding key #{@key_id} => #{@key.inspect}"
auth_line = key_line(@key_id, @key) auth_line = @gitlab_key.key_line(@key_id, @key)
open(auth_file, 'a') { |file| file.puts(auth_line) } open(auth_file, 'a') { |file| file.puts(auth_line) }
end end
true true
...@@ -59,7 +60,7 @@ class GitlabKeys ...@@ -59,7 +60,7 @@ class GitlabKeys
abort("#{$0}: invalid input #{input.inspect}") unless tokens.count == 2 abort("#{$0}: invalid input #{input.inspect}") unless tokens.count == 2
key_id, public_key = tokens key_id, public_key = tokens
$logger.info "Adding key #{key_id} => #{public_key.inspect}" $logger.info "Adding key #{key_id} => #{public_key.inspect}"
file.puts(key_line(key_id, public_key)) file.puts(@gitlab_key.key_line(key_id, public_key))
end end
end end
end end
...@@ -70,20 +71,12 @@ class GitlabKeys ...@@ -70,20 +71,12 @@ class GitlabKeys
$stdin $stdin
end end
def key_command(key_id)
"#{ROOT_PATH}/bin/gitlab-shell #{key_id}"
end
def key_line(key_id, public_key)
auth_line = "command=\"#{key_command(key_id)}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{public_key}"
end
def rm_key def rm_key
lock do lock do
$logger.info "Removing key #{@key_id}" $logger.info "Removing key #{@key_id}"
open(auth_file, 'r+') do |f| open(auth_file, 'r+') do |f|
while line = f.gets do while line = f.gets do
next unless line.start_with?("command=\"#{key_command(@key_id)}\"") next unless line.start_with?("command=\"#{@gitlab_key.command(@key_id)}\"")
f.seek(-line.length, IO::SEEK_CUR) f.seek(-line.length, IO::SEEK_CUR)
# Overwrite the line with #'s. Because the 'line' variable contains # Overwrite the line with #'s. Because the 'line' variable contains
# a terminating '\n', we write line.length - 1 '#' characters. # a terminating '\n', we write line.length - 1 '#' characters.
...@@ -115,3 +108,14 @@ class GitlabKeys ...@@ -115,3 +108,14 @@ class GitlabKeys
@lock_file ||= auth_file + '.lock' @lock_file ||= auth_file + '.lock'
end end
end end
class GitlabKey
def command(key_id)
"#{ROOT_PATH}/bin/gitlab-shell #{key_id}"
end
def key_line(key_id, public_key)
"command=\"#{command(key_id)}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{public_key}"
end
end
...@@ -56,6 +56,13 @@ class GitlabNet ...@@ -56,6 +56,13 @@ class GitlabNet
get("#{host}/check", read_timeout: CHECK_TIMEOUT) get("#{host}/check", read_timeout: CHECK_TIMEOUT)
end end
def authorized_key(key)
resp = get("#{host}/authorized_keys?key=#{URI.escape(key, '+/=')}")
JSON.parse(resp.body) if resp.code == "200"
rescue
nil
end
protected protected
def config def config
......
...@@ -8,8 +8,8 @@ describe GitlabNet, vcr: true do ...@@ -8,8 +8,8 @@ describe GitlabNet, vcr: true do
let(:changes) { ['0000000000000000000000000000000000000000 92d0970eefd7acb6d548878925ce2208cfe2d2ec refs/heads/branch4'] } let(:changes) { ['0000000000000000000000000000000000000000 92d0970eefd7acb6d548878925ce2208cfe2d2ec refs/heads/branch4'] }
before do before do
gitlab_net.stub!(:host).and_return('https://dev.gitlab.org/api/v3/internal') gitlab_net.stub(:host).and_return('https://dev.gitlab.org/api/v3/internal')
gitlab_net.stub!(:secret_token).and_return('a123') gitlab_net.stub(:secret_token).and_return('a123')
end end
describe :check do describe :check do
...@@ -76,6 +76,36 @@ describe GitlabNet, vcr: true do ...@@ -76,6 +76,36 @@ describe GitlabNet, vcr: true do
end end
end end
describe :authorized_key do
let (:ssh_key) { "AAAAB3NzaC1yc2EAAAADAQABAAACAQDPKPqqnqQ9PDFw65cO7iHXrKw6ucSZg8Bd2CZ150Yy1YRDPJOWeRNCnddS+M/Lk" }
it "should return nil when the resource is not implemented" do
VCR.use_cassette("ssh-key-not-implemented") do
result = gitlab_net.authorized_key("whatever")
result.should be_nil
end
end
it "should return nil when the fingerprint is not found" do
VCR.use_cassette("ssh-key-not-found") do
result = gitlab_net.authorized_key("whatever")
result.should be_nil
end
end
it "should return a ssh key with a valid fingerprint" do
VCR.use_cassette("ssh-key-ok") do
result = gitlab_net.authorized_key(ssh_key)
result.should eq({
"created_at" => "2016-03-04T18:27:36.959Z",
"id" => 2,
"key" => "ssh-rsa a-made=up-rsa-key dummy@gitlab.com",
"title" => "some key title"
})
end
end
end
describe :check_access do describe :check_access do
context 'ssh key with access to project' do context 'ssh key with access to project' do
it 'should allow pull access for dev.gitlab.org' do it 'should allow pull access for dev.gitlab.org' do
...@@ -142,7 +172,7 @@ describe GitlabNet, vcr: true do ...@@ -142,7 +172,7 @@ describe GitlabNet, vcr: true do
describe :http_client_for do describe :http_client_for do
subject { gitlab_net.send :http_client_for, URI('https://localhost/') } subject { gitlab_net.send :http_client_for, URI('https://localhost/') }
before do before do
gitlab_net.stub! :cert_store gitlab_net.stub :cert_store
gitlab_net.send(:config).stub(:http_settings) { {'self_signed_cert' => true} } gitlab_net.send(:config).stub(:http_settings) { {'self_signed_cert' => true} }
end end
......
---
http_interactions:
- request:
method: get
uri: https://dev.gitlab.org/api/v3/internal/authorized_keys?key=whatever
body:
encoding: US-ASCII
string: secret_token=a123
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 404
message: Not Found
headers:
Server:
- nginx
Date:
- Mon, 07 Mar 2016 12:09:59 GMT
Content-Type:
- text/html; charset=utf-8
Connection:
- keep-alive
Cache-Control:
- no-cache
Set-Cookie:
- _gitlab_session=a924e63729e538c9efe10fa8338077d7; path=/; expires=Mon, 14
Mar 2016 12:09:59 -0000; secure; HttpOnly
Status:
- 404 Not Found
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 275ad011-515f-4239-80be-e537fd7c9086
X-Runtime:
- '2.169401'
X-Xss-Protection:
- 1; mode=block
http_version:
recorded_at: Mon, 07 Mar 2016 12:10:00 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: get
uri: https://dev.gitlab.org/api/v3/internal/authorized_keys?key=whatever
body:
encoding: US-ASCII
string: secret_token=a123
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 501
message: Not Implemented
headers:
Server:
- nginx
Date:
- Mon, 07 Mar 2016 12:09:59 GMT
Content-Type:
- text/html; charset=utf-8
Connection:
- keep-alive
Cache-Control:
- no-cache
Set-Cookie:
- _gitlab_session=a924e63729e538c9efe10fa8338077d7; path=/; expires=Mon, 14
Mar 2016 12:09:59 -0000; secure; HttpOnly
Status:
- 501 Not Implemented
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- 275ad011-515f-4239-80be-e537fd7c9086
X-Runtime:
- '2.169401'
X-Xss-Protection:
- 1; mode=block
http_version:
recorded_at: Mon, 07 Mar 2016 12:10:00 GMT
recorded_with: VCR 2.4.0
---
http_interactions:
- request:
method: get
uri: https://dev.gitlab.org/api/v3/internal/authorized_keys?key=AAAAB3NzaC1yc2EAAAADAQABAAACAQDPKPqqnqQ9PDFw65cO7iHXrKw6ucSZg8Bd2CZ150Yy1YRDPJOWeRNCnddS+M/Lk
body:
encoding: US-ASCII
string: secret_token=a123
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Content-Type:
- application/x-www-form-urlencoded
response:
status:
code: 200
message: OK
headers:
Server:
- nginx/1.1.19
Date:
- Wed, 03 Sep 2014 11:27:35 GMT
Content-Type:
- application/json
Connection:
- keep-alive
Status:
- 200 OK
Cache-Control:
- max-age=0, private, must-revalidate
body:
encoding: UTF-8
string: '{"id":2, "title":"some key title", "key":"ssh-rsa a-made=up-rsa-key dummy@gitlab.com", "created_at":"2016-03-04T18:27:36.959Z"}'
http_version:
recorded_at: Mon, 07 Mar 2016 12:10:00 GMT
recorded_with: VCR 2.4.0
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment