Commit f2b9ec2d authored by Robert Speicher's avatar Robert Speicher

Merge branch '3340-geo-secondaries-can-have-ssh-config-for-now' into 'master'

Stop needing SSH keys for Geo primaries

Closes #3340

See merge request !2861
parents ade4c03b 1249f883
export default class GeoNodeForm { export default class GeoNodeForm {
constructor(container) { constructor(container) {
this.$container = container; this.$container = container;
this.$namespaces = this.$container.find('.js-namespaces'); this.$namespaces = this.$container.find('.js-hide-if-geo-primary');
this.$namespacesSelect = this.$namespaces.find('.select2'); this.$namespacesSelect = this.$namespaces.find('.select2');
this.$primaryCheckbox = this.$container.find("input[type='checkbox']"); this.$primaryCheckbox = this.$container.find("input[type='checkbox']");
this.$primaryCheckbox.on('change', () => this.onPrimaryCheckboxChange()); this.$primaryCheckbox.on('change', () => this.onPrimaryCheckboxChange());
......
class GeoNode < ActiveRecord::Base class GeoNode < ActiveRecord::Base
include Presentable include Presentable
belongs_to :geo_node_key, dependent: :destroy # rubocop: disable Cop/ActiveRecordDependent belongs_to :geo_node_key, inverse_of: :geo_node, dependent: :destroy # rubocop: disable Cop/ActiveRecordDependent
belongs_to :oauth_application, class_name: 'Doorkeeper::Application', dependent: :destroy # rubocop: disable Cop/ActiveRecordDependent belongs_to :oauth_application, class_name: 'Doorkeeper::Application', dependent: :destroy # rubocop: disable Cop/ActiveRecordDependent
has_many :geo_node_namespace_links has_many :geo_node_namespace_links
...@@ -22,6 +22,8 @@ class GeoNode < ActiveRecord::Base ...@@ -22,6 +22,8 @@ class GeoNode < ActiveRecord::Base
validates :access_key, presence: true validates :access_key, presence: true
validates :encrypted_secret_access_key, presence: true validates :encrypted_secret_access_key, presence: true
validates :geo_node_key, presence: true, if: :secondary?
after_initialize :build_dependents after_initialize :build_dependents
after_save :expire_cache! after_save :expire_cache!
after_destroy :expire_cache! after_destroy :expire_cache!
...@@ -172,13 +174,15 @@ class GeoNode < ActiveRecord::Base ...@@ -172,13 +174,15 @@ class GeoNode < ActiveRecord::Base
end end
def build_dependents def build_dependents
unless persisted? build_geo_node_key if new_record? && secondary? && geo_node_key.nil?
self.build_geo_node_key unless geo_node_key.present?
end
end end
def update_dependents_attributes def update_dependents_attributes
self.geo_node_key&.title = "Geo node: #{self.url}" if primary?
self.geo_node_key = nil
else
self.geo_node_key&.title = "Geo node: #{self.url}"
end
if self.primary? if self.primary?
self.oauth_application = nil self.oauth_application = nil
......
class GeoNodeKey < Key class GeoNodeKey < Key
has_one :geo_node has_one :geo_node, inverse_of: :geo_node_key
def orphaned? def orphaned?
self.geo_nodes.length == 0 self.geo_nodes.length == 0
......
...@@ -12,8 +12,9 @@ ...@@ -12,8 +12,9 @@
.col-sm-10 .col-sm-10
= form.text_field :url, class: 'form-control' = form.text_field :url, class: 'form-control'
= form.fields_for :geo_node_key, geo_node.geo_node_key, include_id: !disable_key_edit do |fg| = form.fields_for :geo_node_key, geo_node.geo_node_key, include_id: !disable_key_edit do |fg|
.form-group .form-group.js-hide-if-geo-primary{ class: ('hidden' unless geo_node.secondary?) }
= fg.label :key, 'Public Key', class: 'control-label' = fg.label :key, 'Public Key', class: 'control-label'
.col-sm-10 .col-sm-10
= fg.text_area :key, class: 'form-control thin_area', rows: 5, disabled: disable_key_edit = fg.text_area :key, class: 'form-control thin_area', rows: 5, disabled: disable_key_edit
...@@ -22,7 +23,7 @@ ...@@ -22,7 +23,7 @@
Paste the ssh public key used by the node you are adding. Read more about it Paste the ssh public key used by the node you are adding. Read more about it
= link_to 'here', help_page_path('gitlab-geo/configuration.html', anchor: 'step-5-enabling-the-secondary-gitlab-node') = link_to 'here', help_page_path('gitlab-geo/configuration.html', anchor: 'step-5-enabling-the-secondary-gitlab-node')
.form-group.js-namespaces{ class: ('hidden' unless geo_node.new_record? || geo_node.secondary?) } .form-group.js-hide-if-geo-primary{ class: ('hidden' unless geo_node.secondary?) }
= form.label :namespace_ids, 'Namespaces to replicate', class: 'control-label' = form.label :namespace_ids, 'Namespaces to replicate', class: 'control-label'
.col-sm-10 .col-sm-10
= form.select :namespace_ids, namespaces_options(geo_node.namespace_ids), { include_hidden: true }, multiple: true, class: 'select2 select-wide', data: { field: 'namespace_ids' } = form.select :namespace_ids, namespaces_options(geo_node.namespace_ids), { include_hidden: true }, multiple: true, class: 'select2 select-wide', data: { field: 'namespace_ids' }
......
---
title: Geo primary nodes no longer require SSH keys
merge_request: 2861
author:
type: changed
...@@ -62,15 +62,13 @@ logins opened on all nodes as we will be moving back and forth. ...@@ -62,15 +62,13 @@ logins opened on all nodes as we will be moving back and forth.
sudo -i sudo -i
``` ```
1. Added in GitLab 9.1: Execute the command below to define the node as primary Geo node: 1. Execute the command below to define the node as primary Geo node:
``` ```
gitlab-ctl set-geo-primary-node gitlab-ctl set-geo-primary-node
``` ```
This command will use your defined `external_url` in `gitlab.rb` and pre-generated SSH key pairs. This command will use your defined `external_url` in `gitlab.rb`
Read more in [additional info for SSH key pairs](#additional-information-for-the-ssh-key-pairs).
### Step 2. Updating the `known_hosts` file of the secondary nodes ### Step 2. Updating the `known_hosts` file of the secondary nodes
...@@ -282,29 +280,14 @@ Just omit the first step that sets up the primary node. ...@@ -282,29 +280,14 @@ Just omit the first step that sets up the primary node.
## Additional information for the SSH key pairs ## Additional information for the SSH key pairs
When adding a new Geo node, you must provide an SSH public key of the user that When adding a new **secondary** Geo node, you must provide an SSH public key for
your GitLab instance runs on (unless changed, should be the user `git`). This the system user that your GitLab instance runs as (unless changed, should be the
user will act as a "normal user" who fetches from the primary Geo node. user `git`). This user will act as a "normal user" who fetches from the primary
Geo node.
If for any reason you generate the key using a different name from the default
`id_rsa`, or you want to generate an extra key only for the repository
synchronization feature, you can do so, but you have to create/modify your
`~/.ssh/config` (for the `git` user).
This is an example on how to change the default key for all remote hosts: Omnibus automatically generates `~git/.ssh/id_rsa` and `~git/.ssh/id_rsa.pub`
files on secondary Geo nodes. Primaries do not need these files, and you should
```bash not create them manually.
Host * # Match all remote hosts
IdentityFile ~/.ssh/mycustom.key # The location of your private key
```
This is how to change it for an specific host:
```bash
Host example.com # The FQDN of the primary Geo node
HostName example.com # The FQDN of the primary Geo node
IdentityFile ~/.ssh/mycustom.key # The location of your private key
```
### Upgrading Geo ### Upgrading Geo
......
...@@ -63,32 +63,12 @@ logins opened on all nodes as we will be moving back and forth. ...@@ -63,32 +63,12 @@ logins opened on all nodes as we will be moving back and forth.
sudo -i sudo -i
``` ```
1. (Source install only): Create a new SSH key pair for the primary node. Choose the default location 1. Add this node as the Geo primary by running:
and leave the password blank by hitting 'Enter' three times:
```bash ```bash
sudo -u git -H ssh-keygen -b 4096 -C 'Primary GitLab Geo node' bundle exec rake geo:set_primary_node
``` ```
Read more in [additional info for SSH key pairs](#additional-information-for-the-ssh-key-pairs).
1. Get the contents of `id_rsa.pub` for the git user:
```
sudo -u git cat /home/git/.ssh/id_rsa.pub
```
1. Visit the primary node's **Admin Area ➔ Geo Nodes** (`/admin/geo_nodes`) in
your browser.
1. Add the primary node by providing its full URL and the public SSH key
you created previously. Make sure to check the box 'This is a primary node'
when adding it.
![Add new primary Geo node](img/geo_nodes_add_new.png)
1. Click the **Add node** button.
### Step 2. Updating the `known_hosts` file of the secondary nodes ### Step 2. Updating the `known_hosts` file of the secondary nodes
1. SSH into the **secondary** node and login as root: 1. SSH into the **secondary** node and login as root:
...@@ -318,7 +298,7 @@ Point your users to the [after setup steps](after_setup.md). ...@@ -318,7 +298,7 @@ Point your users to the [after setup steps](after_setup.md).
## Adding another secondary Geo node ## Adding another secondary Geo node
To add another Geo node in an already Geo configured infrastructure, just follow To add another Geo node in an already Geo configured infrastructure, just follow
[the steps starting form step 2](#step-2-updating-the-known_hosts-file-of-the-secondary-nodes). [the steps starting from step 2](#step-2-updating-the-known_hosts-file-of-the-secondary-nodes).
Just omit the first step that sets up the primary node. Just omit the first step that sets up the primary node.
## Additional information for the SSH key pairs ## Additional information for the SSH key pairs
......
...@@ -21,19 +21,25 @@ You must make the changes in the exact specific order: ...@@ -21,19 +21,25 @@ You must make the changes in the exact specific order:
1. Take down your primary node (or make sure it will not go up during this 1. Take down your primary node (or make sure it will not go up during this
process or you may lose data) process or you may lose data)
2. Wait for any database replication to finish 1. Wait for any database replication to finish
3. Promote the Postgres in your secondary node as primary 1. Promote the Postgres in your secondary node as primary
4. Log-in to your secondary node with a user with `sudo` permission 1. Modify the `gitlab.rb` for both nodes to reflect their new statuses
5. Open the interactive rails console: `sudo gitlab-rails console` and execute: 1. Log-in to your secondary node with a user with `sudo` permission
1. **Remove** the Geo SSH client keys (this is very important!):
```bash
sudo rm ~git/.ssh/id_rsa ~git/.ssh/id_rsa.pub
```
1. Open the interactive rails console: `sudo gitlab-rails console` and execute:
* List your primary node and note down it's id: * List your primary node and note down it's id:
```ruby ```ruby
Gitlab::Geo.primary_node Gitlab::Geo.primary_node
``` ```
* Turn your primary into a secondary: * Remove the old primary node:
```ruby ```ruby
Gitlab::Geo.primary_node.update(primary: false) Gitlab::Geo.primary_node.destroy
``` ```
* List your secondary nodes and note down the id of the one you want to promote: * List your secondary nodes and note down the id of the one you want to promote:
...@@ -51,12 +57,11 @@ You must make the changes in the exact specific order: ...@@ -51,12 +57,11 @@ You must make the changes in the exact specific order:
Gitlab::Geo.primary_node.oauth_application.destroy! Gitlab::Geo.primary_node.oauth_application.destroy!
Gitlab::Geo.primary_node.system_hook.destroy! Gitlab::Geo.primary_node.system_hook.destroy!
``` ```
* And refresh your old primary node to behave correctly as secondary (assuming id is `1`)
```ruby
GeoNode.find(1).save!
```
* To exit the interactive console, type: `exit` * To exit the interactive console, type: `exit`
6. Rsync everything in `/var/opt/gitlab/gitlab-rails/uploads` and 1. Rsync everything in `/var/opt/gitlab/gitlab-rails/uploads` and
`/var/opt/gitlab/gitlab-rails/shared` from your old node to the new one. `/var/opt/gitlab/gitlab-rails/shared` from your old node to the new one.
To bring your old primary node back into use as a working secondary, you need to
run `gitlab-ctl reconfigure` against the node and then follow the
[setup instructions](README.md) again, as if for a secondary node, from step 3.
...@@ -10,10 +10,10 @@ module SystemCheck ...@@ -10,10 +10,10 @@ module SystemCheck
].freeze ].freeze
set_name 'Git user has default SSH configuration?' set_name 'Git user has default SSH configuration?'
set_skip_reason 'skipped (git user is not present or configured)' set_skip_reason 'skipped (Geo secondary, or git user is not present / configured)'
def skip? def skip?
!home_dir || !File.directory?(home_dir) Gitlab::Geo.secondary? || !home_dir || !File.directory?(home_dir)
end end
def check? def check?
......
...@@ -79,33 +79,21 @@ namespace :geo do ...@@ -79,33 +79,21 @@ namespace :geo do
end end
desc 'Make this node the Geo primary' desc 'Make this node the Geo primary'
task :set_primary_node, [:ssh_key_filename] => :environment do |_, args| task set_primary_node: :environment do
filename = args[:ssh_key_filename]
abort 'GitLab Geo is not supported with this license. Please contact sales@gitlab.com.' unless Gitlab::Geo.license_allows? abort 'GitLab Geo is not supported with this license. Please contact sales@gitlab.com.' unless Gitlab::Geo.license_allows?
abort 'You must specify a filename of an SSH public key' unless filename.present?
abort 'GitLab Geo primary node already present' if Gitlab::Geo.primary_node.present? abort 'GitLab Geo primary node already present' if Gitlab::Geo.primary_node.present?
public_key = load_ssh_public_key(filename) set_primary_geo_node
abort "Invalid SSH public key in #{filename}, aborting" unless public_key
set_primary_geo_node(public_key)
end
def load_ssh_public_key(filename)
File.open(filename).read
rescue => e
puts "Error opening #{filename}: #{e}".color(:red)
nil
end end
def set_primary_geo_node(public_key) def set_primary_geo_node
params = { schema: Gitlab.config.gitlab.protocol, params = {
host: Gitlab.config.gitlab.host, schema: Gitlab.config.gitlab.protocol,
port: Gitlab.config.gitlab.port, host: Gitlab.config.gitlab.host,
relative_url_root: Gitlab.config.gitlab.relative_url_root, port: Gitlab.config.gitlab.port,
primary: true, relative_url_root: Gitlab.config.gitlab.relative_url_root,
geo_node_key_attributes: { key: public_key } } primary: true
}
node = GeoNode.new(params) node = GeoNode.new(params)
puts "Saving primary GeoNode with URL #{node.url}".color(:green) puts "Saving primary GeoNode with URL #{node.url}".color(:green)
......
...@@ -432,7 +432,8 @@ namespace :gitlab do ...@@ -432,7 +432,8 @@ namespace :gitlab do
SystemCheck::Geo::GeoDatabaseConfiguredCheck, SystemCheck::Geo::GeoDatabaseConfiguredCheck,
SystemCheck::Geo::DatabaseReplicationCheck, SystemCheck::Geo::DatabaseReplicationCheck,
SystemCheck::Geo::HttpConnectionCheck, SystemCheck::Geo::HttpConnectionCheck,
SystemCheck::Geo::ClocksSynchronizationCheck SystemCheck::Geo::ClocksSynchronizationCheck,
SystemCheck::App::GitUserDefaultSSHConfigCheck
] ]
SystemCheck.run('Geo', checks) SystemCheck.run('Geo', checks)
......
...@@ -7,6 +7,7 @@ FactoryGirl.define do ...@@ -7,6 +7,7 @@ FactoryGirl.define do
trait :primary do trait :primary do
primary true primary true
port { Gitlab.config.gitlab.port } port { Gitlab.config.gitlab.port }
geo_node_key nil
end end
trait :current do trait :current do
......
...@@ -34,6 +34,15 @@ describe SystemCheck::App::GitUserDefaultSSHConfigCheck do ...@@ -34,6 +34,15 @@ describe SystemCheck::App::GitUserDefaultSSHConfigCheck do
it { is_expected.to eq(expected_result) } it { is_expected.to eq(expected_result) }
end end
# EE-only
it 'skips Geo secondaries' do
stub_user
stub_home_dir
allow(Gitlab::Geo).to receive(:secondary?).and_return(true)
is_expected.to be_truthy
end
end end
describe '#check?' do describe '#check?' do
......
require 'spec_helper' require 'spec_helper'
describe GeoNode, type: :model do describe GeoNode, type: :model do
subject(:new_node) { create(:geo_node, schema: 'https', host: 'localhost', port: 3000, relative_url_root: 'gitlab', primary: false) } let(:new_node) { create(:geo_node, schema: 'https', host: 'localhost', port: 3000, relative_url_root: 'gitlab') }
subject(:new_primary_node) { create(:geo_node, schema: 'https', host: 'localhost', port: 3000, relative_url_root: 'gitlab', primary: true) } let(:new_primary_node) { create(:geo_node, :primary, schema: 'https', host: 'localhost', port: 3000, relative_url_root: 'gitlab') }
subject(:empty_node) { described_class.new } let(:empty_node) { described_class.new }
subject(:primary_node) { create(:geo_node, :primary) } let(:primary_node) { create(:geo_node, :primary) }
subject(:node) { create(:geo_node) } let(:node) { create(:geo_node) }
let(:dummy_url) { 'https://localhost:3000/gitlab' } let(:dummy_url) { 'https://localhost:3000/gitlab' }
let(:url_helpers) { Gitlab::Routing.url_helpers } let(:url_helpers) { Gitlab::Routing.url_helpers }
...@@ -19,9 +19,12 @@ describe GeoNode, type: :model do ...@@ -19,9 +19,12 @@ describe GeoNode, type: :model do
it { is_expected.to have_many(:namespaces).through(:geo_node_namespace_links) } it { is_expected.to have_many(:namespaces).through(:geo_node_namespace_links) }
end end
context 'default values' do context 'validations' do
subject { described_class.new } it { expect(new_node).to validate_presence_of(:geo_node_key) }
it { expect(new_primary_node).not_to validate_presence_of(:geo_node_key) }
end
context 'default values' do
let(:gitlab_host) { 'gitlabhost' } let(:gitlab_host) { 'gitlabhost' }
before do before do
...@@ -29,23 +32,23 @@ describe GeoNode, type: :model do ...@@ -29,23 +32,23 @@ describe GeoNode, type: :model do
end end
it 'defines a default schema' do it 'defines a default schema' do
expect(subject.schema).to eq('http') expect(empty_node.schema).to eq('http')
end end
it 'defines a default host' do it 'defines a default host' do
expect(subject.host).to eq(gitlab_host) expect(empty_node.host).to eq(gitlab_host)
end end
it 'defines a default port' do it 'defines a default port' do
expect(subject.port).to eq(80) expect(empty_node.port).to eq(80)
end end
it 'defines a default relative_url_root' do it 'defines a default relative_url_root' do
expect(subject.relative_url_root).to eq('') expect(empty_node.relative_url_root).to eq('')
end end
it 'defines a default primary flag' do it 'defines a default primary flag' do
expect(subject.primary).to eq(false) expect(empty_node.primary).to eq(false)
end end
end end
...@@ -100,7 +103,7 @@ describe GeoNode, type: :model do ...@@ -100,7 +103,7 @@ describe GeoNode, type: :model do
let(:new_node) { FactoryGirl.build(:geo_node) } let(:new_node) { FactoryGirl.build(:geo_node) }
it 'expires cache when saved' do it 'expires cache when saved' do
expect(new_node).to receive(:expire_cache!) expect(new_node).to receive(:expire_cache!).at_least(:once)
new_node.save! new_node.save!
end end
...@@ -315,4 +318,34 @@ describe GeoNode, type: :model do ...@@ -315,4 +318,34 @@ describe GeoNode, type: :model do
end end
end end
end end
describe '#geo_node_key' do
context 'primary node' do
it 'cannot be set' do
node = new_primary_node
expect(node.geo_node_key).to be_nil
node.geo_node_key = build(:geo_node_key)
expect(node).to be_valid
node.save!
expect(node.geo_node_key(true)).to be_nil
end
end
context 'secondary node' do
it 'is automatically set' do
node = build(:geo_node, url: 'http://example.com/')
expect(node.geo_node_key).to be_present
expect(node.geo_node_key.title).not_to include('example.com')
node.save!
expect(node.geo_node_key.title).to eq('Geo node: http://example.com/')
end
end
end
end end
...@@ -6,32 +6,23 @@ describe 'geo rake tasks' do ...@@ -6,32 +6,23 @@ describe 'geo rake tasks' do
end end
describe 'set_primary_node task' do describe 'set_primary_node task' do
let(:ssh_key) { 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUkxk8m9rVYZ1q4/5xpg3TwTM9QFw3TinPFkyWsiACFKjor3byV6g3vHWTuIS70E7wk2JTXGL0wdrfUG6iQDJuP0BYNxjkluB14nIAfPuXN7V73QY/cqvHogw5o6pPRFD+Szke6FzouNQ70Z/qrM1k7me3e9DMuscMMrMTOR2HLKppNQyP4Jp0WJOyncdWB2NxKXTezy/ZnHv+BdhC0q0JW3huIx9qkBCHio7x8BdyJLMF9KxNYIuCkbP3exs5wgb+qGrjSri6LfAVq8dJ2VYibWxdsUG6iITJF+G4qbcyQjgiMLbxCfNd9bjwmkxSGvFn2EPsAFKzxyAvYFWb/y91 test@host' }
before do before do
expect(Gitlab::Geo).to receive(:license_allows?).and_return(true) expect(Gitlab::Geo).to receive(:license_allows?).and_return(true)
stub_config_setting(protocol: 'https') stub_config_setting(protocol: 'https')
end end
it 'creates a GeoNode' do it 'creates a GeoNode' do
begin expect(GeoNode.count).to eq(0)
file = Tempfile.new('geo-test-')
file.write(ssh_key) run_rake_task('geo:set_primary_node')
path = file.path
file.close
expect(GeoNode.count).to eq(0) expect(GeoNode.count).to eq(1)
run_rake_task('geo:set_primary_node', path) node = GeoNode.first
expect(GeoNode.count).to eq(1) expect(node.schema).to eq('https')
node = GeoNode.first expect(node.primary).to be_truthy
expect(node.schema).to eq('https') expect(node.geo_node_key).to be_nil
expect(node.primary).to be_truthy
expect(node.geo_node_key.key).to eq(ssh_key)
ensure
file.unlink
end
end end
end end
end end
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