Commit 02878551 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge branch 'support-gitaly-tls' into 'master'

Support tls communication in gitaly

See merge request gitlab-org/gitlab-ce!22602
parents a9049532 907f0ce8
---
title: Support tls communication in gitaly
merge_request: 22602
author:
type: added
...@@ -612,7 +612,7 @@ production: &base ...@@ -612,7 +612,7 @@ production: &base
storages: # You must have at least a `default` storage path. storages: # You must have at least a `default` storage path.
default: default:
path: /home/git/repositories/ path: /home/git/repositories/
gitaly_address: unix:/home/git/gitlab/tmp/sockets/private/gitaly.socket # TCP connections are supported too (e.g. tcp://host:port) gitaly_address: unix:/home/git/gitlab/tmp/sockets/private/gitaly.socket # TCP connections are supported too (e.g. tcp://host:port). TLS connections are also supported using the system certificate pool (eg: tls://host:port).
# gitaly_token: 'special token' # Optional: override global gitaly.token for this storage. # gitaly_token: 'special token' # Optional: override global gitaly.token for this storage.
## Backup settings ## Backup settings
......
...@@ -133,6 +133,11 @@ gitaly['storage'] = [ ...@@ -133,6 +133,11 @@ gitaly['storage'] = [
{ 'name' => 'default', 'path' => '/mnt/gitlab/default/repositories' }, { 'name' => 'default', 'path' => '/mnt/gitlab/default/repositories' },
{ 'name' => 'storage1', 'path' => '/mnt/gitlab/storage1/repositories' }, { 'name' => 'storage1', 'path' => '/mnt/gitlab/storage1/repositories' },
] ]
# To use tls for gitaly you need to add
gitaly['tls_listen_addr'] = "0.0.0.0:9999"
gitaly['certificate_path'] = "path/to/cert.pem"
gitaly['key_path'] = "path/to/key.pem"
``` ```
Source installations: Source installations:
...@@ -140,6 +145,11 @@ Source installations: ...@@ -140,6 +145,11 @@ Source installations:
```toml ```toml
# /home/git/gitaly/config.toml # /home/git/gitaly/config.toml
listen_addr = '0.0.0.0:8075' listen_addr = '0.0.0.0:8075'
tls_listen_addr = '0.0.0.0:9999'
[tls]
certificate_path = /path/to/cert.pem
key_path = /path/to/key.pem
[auth] [auth]
token = 'abc123secret' token = 'abc123secret'
...@@ -205,6 +215,70 @@ Gitaly logs on your Gitaly server (`sudo gitlab-ctl tail gitaly` or ...@@ -205,6 +215,70 @@ Gitaly logs on your Gitaly server (`sudo gitlab-ctl tail gitaly` or
coming in. One sure way to trigger a Gitaly request is to clone a coming in. One sure way to trigger a Gitaly request is to clone a
repository from your GitLab server over HTTP. repository from your GitLab server over HTTP.
## TLS support
Gitaly supports TLS credentials for GRPC authentication. To be able to communicate
with a gitaly instance that listens for secure connections you will need to use `tls://` url
scheme in the `gitaly_address` of the corresponding storage entry in the gitlab configuration.
The admin needs to bring their own certificate as we do not provide that automatically.
The certificate to be used needs to be installed on all gitaly nodes and on all client nodes that communicate with it following procedures described in [GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates)
### Example TLS configuration
### Omnibus installations:
#### On client nodes:
```ruby
# /etc/gitlab/gitlab.rb
git_data_dirs({
'default' => { 'path' => '/mnt/gitlab/default', 'gitaly_address' => 'tls://gitaly.internal:9999' },
'storage1' => { 'path' => '/mnt/gitlab/storage1', 'gitaly_address' => 'tls://gitaly.internal:9999' },
})
gitlab_rails['gitaly_token'] = 'abc123secret'
```
#### On gitaly server nodes:
```ruby
gitaly['tls_listen_addr'] = "0.0.0.0:9999"
gitaly['certificate_path'] = "path/to/cert.pem"
gitaly['key_path'] = "path/to/key.pem"
```
### Source installations:
#### On client nodes:
```yaml
# /home/git/gitlab/config/gitlab.yml
gitlab:
repositories:
storages:
default:
path: /mnt/gitlab/default/repositories
gitaly_address: tls://gitaly.internal:9999
storage1:
path: /mnt/gitlab/storage1/repositories
gitaly_address: tls://gitaly.internal:9999
gitaly:
token: 'abc123secret'
```
#### On gitaly server nodes:
```toml
# /home/git/gitaly/config.toml
tls_listen_addr = '0.0.0.0:9999'
[tls]
certificate_path = '/path/to/cert.pem'
key_path = '/path/to/key.pem'
```
## Disabling or enabling the Gitaly service in a cluster environment ## Disabling or enabling the Gitaly service in a cluster environment
If you are running Gitaly [as a remote If you are running Gitaly [as a remote
......
...@@ -26,6 +26,7 @@ module Gitlab ...@@ -26,6 +26,7 @@ module Gitlab
end end
end end
PEM_REGEX = /\-+BEGIN CERTIFICATE\-+.+?\-+END CERTIFICATE\-+/m
SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION' SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION'
MAXIMUM_GITALY_CALLS = 35 MAXIMUM_GITALY_CALLS = 35
CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze
...@@ -50,11 +51,42 @@ module Gitlab ...@@ -50,11 +51,42 @@ module Gitlab
@stubs[storage][name] ||= begin @stubs[storage][name] ||= begin
klass = stub_class(name) klass = stub_class(name)
addr = stub_address(storage) addr = stub_address(storage)
klass.new(addr, :this_channel_is_insecure) creds = stub_creds(storage)
klass.new(addr, creds)
end end
end end
end end
def self.stub_cert_paths
cert_paths = Dir["#{OpenSSL::X509::DEFAULT_CERT_DIR}/*"]
cert_paths << OpenSSL::X509::DEFAULT_CERT_FILE if File.exist? OpenSSL::X509::DEFAULT_CERT_FILE
cert_paths
end
def self.stub_certs
return @certs if @certs
@certs = stub_cert_paths.flat_map do |cert_file|
File.read(cert_file).scan(PEM_REGEX).map do |cert|
begin
OpenSSL::X509::Certificate.new(cert).to_pem
rescue OpenSSL::OpenSSLError => e
Rails.logger.error "Could not load certificate #{cert_file} #{e}"
Gitlab::Sentry.track_exception(e, extra: { cert_file: cert_file })
nil
end
end.compact
end.uniq.join("\n")
end
def self.stub_creds(storage)
if URI(address(storage)).scheme == 'tls'
GRPC::Core::ChannelCredentials.new stub_certs
else
:this_channel_is_insecure
end
end
def self.stub_class(name) def self.stub_class(name)
if name == :health_check if name == :health_check
Grpc::Health::V1::Health::Stub Grpc::Health::V1::Health::Stub
...@@ -64,9 +96,7 @@ module Gitlab ...@@ -64,9 +96,7 @@ module Gitlab
end end
def self.stub_address(storage) def self.stub_address(storage)
addr = address(storage) address(storage).sub(%r{^tcp://|^tls://}, '')
addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp'
addr
end end
def self.clear_stubs! def self.clear_stubs!
...@@ -88,8 +118,8 @@ module Gitlab ...@@ -88,8 +118,8 @@ module Gitlab
raise "storage #{storage.inspect} is missing a gitaly_address" raise "storage #{storage.inspect} is missing a gitaly_address"
end end
unless URI(address).scheme.in?(%w(tcp unix)) unless URI(address).scheme.in?(%w(tcp unix tls))
raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix'" raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix' or 'tls'"
end end
address address
......
...@@ -3,6 +3,20 @@ require 'spec_helper' ...@@ -3,6 +3,20 @@ require 'spec_helper'
# We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want # We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want
# those stubs while testing the GitalyClient itself. # those stubs while testing the GitalyClient itself.
describe Gitlab::GitalyClient do describe Gitlab::GitalyClient do
let(:sample_cert) { Rails.root.join('spec/fixtures/clusters/sample_cert.pem').to_s }
before do
allow(described_class)
.to receive(:stub_cert_paths)
.and_return([sample_cert])
end
def stub_repos_storages(address)
allow(Gitlab.config.repositories).to receive(:storages).and_return({
'default' => { 'gitaly_address' => address }
})
end
describe '.stub_class' do describe '.stub_class' do
it 'returns the gRPC health check stub' do it 'returns the gRPC health check stub' do
expect(described_class.stub_class(:health_check)).to eq(::Grpc::Health::V1::Health::Stub) expect(described_class.stub_class(:health_check)).to eq(::Grpc::Health::V1::Health::Stub)
...@@ -15,12 +29,8 @@ describe Gitlab::GitalyClient do ...@@ -15,12 +29,8 @@ describe Gitlab::GitalyClient do
describe '.stub_address' do describe '.stub_address' do
it 'returns the same result after being called multiple times' do it 'returns the same result after being called multiple times' do
address = 'localhost:9876' address = 'tcp://localhost:9876'
prefixed_address = "tcp://#{address}" stub_repos_storages address
allow(Gitlab.config.repositories).to receive(:storages).and_return({
'default' => { 'gitaly_address' => prefixed_address }
})
2.times do 2.times do
expect(described_class.stub_address('default')).to eq('localhost:9876') expect(described_class.stub_address('default')).to eq('localhost:9876')
...@@ -28,6 +38,45 @@ describe Gitlab::GitalyClient do ...@@ -28,6 +38,45 @@ describe Gitlab::GitalyClient do
end end
end end
describe '.stub_certs' do
it 'skips certificates if OpenSSLError is raised and report it' do
expect(Rails.logger).to receive(:error).at_least(:once)
expect(Gitlab::Sentry)
.to receive(:track_exception)
.with(
a_kind_of(OpenSSL::X509::CertificateError),
extra: { cert_file: a_kind_of(String) }).at_least(:once)
expect(OpenSSL::X509::Certificate)
.to receive(:new)
.and_raise(OpenSSL::X509::CertificateError).at_least(:once)
expect(described_class.stub_certs).to be_a(String)
end
end
describe '.stub_creds' do
it 'returns :this_channel_is_insecure if unix' do
address = 'unix:/tmp/gitaly.sock'
stub_repos_storages address
expect(described_class.stub_creds('default')).to eq(:this_channel_is_insecure)
end
it 'returns :this_channel_is_insecure if tcp' do
address = 'tcp://localhost:9876'
stub_repos_storages address
expect(described_class.stub_creds('default')).to eq(:this_channel_is_insecure)
end
it 'returns Credentials object if tls' do
address = 'tls://localhost:9876'
stub_repos_storages address
expect(described_class.stub_creds('default')).to be_a(GRPC::Core::ChannelCredentials)
end
end
describe '.stub' do describe '.stub' do
# Notice that this is referring to gRPC "stubs", not rspec stubs # Notice that this is referring to gRPC "stubs", not rspec stubs
before do before do
...@@ -37,9 +86,19 @@ describe Gitlab::GitalyClient do ...@@ -37,9 +86,19 @@ describe Gitlab::GitalyClient do
context 'when passed a UNIX socket address' do context 'when passed a UNIX socket address' do
it 'passes the address as-is to GRPC' do it 'passes the address as-is to GRPC' do
address = 'unix:/tmp/gitaly.sock' address = 'unix:/tmp/gitaly.sock'
allow(Gitlab.config.repositories).to receive(:storages).and_return({ stub_repos_storages address
'default' => { 'gitaly_address' => address }
}) expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
described_class.stub(:commit_service, 'default')
end
end
context 'when passed a TLS address' do
it 'strips tls:// prefix before passing it to GRPC::Core::Channel initializer' do
address = 'localhost:9876'
prefixed_address = "tls://#{address}"
stub_repos_storages prefixed_address
expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args) expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
...@@ -51,10 +110,7 @@ describe Gitlab::GitalyClient do ...@@ -51,10 +110,7 @@ describe Gitlab::GitalyClient do
it 'strips tcp:// prefix before passing it to GRPC::Core::Channel initializer' do it 'strips tcp:// prefix before passing it to GRPC::Core::Channel initializer' do
address = 'localhost:9876' address = 'localhost:9876'
prefixed_address = "tcp://#{address}" prefixed_address = "tcp://#{address}"
stub_repos_storages prefixed_address
allow(Gitlab.config.repositories).to receive(:storages).and_return({
'default' => { 'gitaly_address' => prefixed_address }
})
expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args) expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
......
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