From: Jan Provaznik <jprovazn(a)redhat.com>
When instance is started unique ssh key is generated and uploaded to instance.
New instance key is not generated with deltacloud api because api returns only
private part of key but with openssl lib.
This patch requires net_scp and delayed_job gems and delayedjob worker should be
started with: 'rake jobs:work' command
---
src/app/models/instance.rb | 25 +++++++++++++++++++++++
src/app/models/instance_key.rb | 29 +++++++++++++++++++++++++++
src/app/models/instance_observer.rb | 17 ++++++++-------
src/app/models/provider_account_observer.rb | 2 +-
src/config/environment.rb | 1 +
src/dbomatic/dbomatic | 5 ----
src/features/support/custom.rb | 6 +++++
src/spec/models/instance_observer_spec.rb | 28 +++++++++++++-------------
8 files changed, 85 insertions(+), 28 deletions(-)
diff --git a/src/app/models/instance.rb b/src/app/models/instance.rb
index 8a29cc7..bce169c 100644
--- a/src/app/models/instance.rb
+++ b/src/app/models/instance.rb
@@ -222,6 +222,31 @@ class Instance < ActiveRecord::Base
end
end
+ def create_unique_key
+ return unless self.provider_account and self.provider_account.instance_key
+ # deltacloud/ec2 api's create_key method returns only private part of
+ # key -> we don't know public part, so we generate new ssh key
+ # and replace whole authorized_keys file
+ key_name = "#{self.name}_rsa_#{Time.now.to_f}"
+ self.instance_key =
InstanceKey.new(self.provider_account.instance_key.attributes.merge({
+ :instance_key_owner => self,
+ :name => key_name
+ }))
+ begin
+ self.instance_key.replace_key(self.public_addresses,
self.provider_account.instance_key.pem)
+ self.events.create!(:summary => "successfully updated ssh key",
:event_time => Time.now)
+ rescue
+ msg = "failed to upload ssh key: #{$!}"
+ self.last_error = msg
+ self.events.create!(:summary => msg, :event_time => Time.now)
+ ensure
+ # if replace_key fails, we still save instance_key - should be copy of
+ # provider_account key which was used when launching instance
+ self.instance_key.save!
+ self.save!
+ end
+ end
+
def self.get_user_instances_stats(user)
stats = {
:running_instances => 0,
diff --git a/src/app/models/instance_key.rb b/src/app/models/instance_key.rb
index fd13e74..0246c9e 100644
--- a/src/app/models/instance_key.rb
+++ b/src/app/models/instance_key.rb
@@ -33,8 +33,37 @@
# Filters added to this controller apply to all controllers in the application.
# Likewise, all the methods added will be available for all controllers.
#
+
+require 'openssl'
+require 'base64'
+
class InstanceKey < ActiveRecord::Base
belongs_to :instance_key_owner, :polymorphic => true
+ def replace_key(addr, old_pem)
+ key = generate_ssh_key
+ replace_on_server(addr, old_pem, key[:public])
+ self.pem = key[:private]
+ end
+
+ private
+
+ def replace_on_server(addr, old_pem, new_pub)
+ provider_type = self.instance_key_owner.provider_account.provider.provider_type
+ Net::SCP::start(addr, provider_type.ssh_user, :key_data => [old_pem], :keys =>
[]) do |scp|
+ scp.upload! StringIO.new(new_pub), File.join(provider_type.home_dir,
'/.ssh/authorized_keys')
+ end
+ end
+
+ def generate_ssh_key
+ key = OpenSSL::PKey::RSA.generate(1024)
+ writer = Net::SSH::Buffer.new
+ writer.write_key key
+ ssh_key = Base64.encode64( writer.to_s ).strip.gsub( /[\n\r\t ]/, "" )
+ {
+ :private => key.export,
+ :public => "#{key.ssh_type} #{ssh_key}
#{ENV['USER']}@#{ENV['HOSTNAME']}"
+ }
+ end
end
diff --git a/src/app/models/instance_observer.rb b/src/app/models/instance_observer.rb
index 0bc8852..365efca 100644
--- a/src/app/models/instance_observer.rb
+++ b/src/app/models/instance_observer.rb
@@ -61,14 +61,15 @@ class InstanceObserver < ActiveRecord::Observer
end
end
- #def before_update(instance)
- # # we try to generate key only when instance is running
- # # and instance_key is not generated yet
- # return if instance.state != Instance::STATE_RUNNING or instance.instance_key
- # if key = instance.provider_account.generate_auth_key
- # instance.instance_key = InstanceKey.create!(:pem => key.pem, :name =>
key.id, :instance_key_owner => instance)
- # end
- #end
+ def after_update(instance)
+ # we try to generate unique key only when instance is running
+ # and provider_account for this instance has instance_key (provider account
+ # instance_key is used as default ssh key when instance is launched)
+ if instance.state_changed? and instance.state == Instance::STATE_RUNNING and
+ not instance.instance_key and instance.provider_account and
instance.provider_account.instance_key
+ instance.delay.create_unique_key
+ end
+ end
end
diff --git a/src/app/models/provider_account_observer.rb
b/src/app/models/provider_account_observer.rb
index 396743c..6cd50e2 100644
--- a/src/app/models/provider_account_observer.rb
+++ b/src/app/models/provider_account_observer.rb
@@ -7,7 +7,7 @@ class ProviderAccountObserver < ActiveRecord::Observer
create_bucket(account)
end
if key = account.generate_auth_key
- account.update_attribute(:instance_key, InstanceKey.create!(:pem => key.pem,
:name => key.id, :instance_key_owner => account))
+ account.update_attribute(:instance_key, InstanceKey.create!(:pem =>
key.pem.first, :name => key.id, :instance_key_owner => account))
end
end
diff --git a/src/config/environment.rb b/src/config/environment.rb
index cedcb32..1b7844e 100644
--- a/src/config/environment.rb
+++ b/src/config/environment.rb
@@ -55,6 +55,7 @@ Rails::Initializer.run do |config|
config.gem 'rack-restful_submit', :version => '1.1.2'
config.gem 'sunspot_rails', :lib => 'sunspot/rails'
config.gem 'delayed_job', :version => '~>2.0.4'
+ config.gem 'net-scp', :lib => 'net/scp'
config.middleware.swap Rack::MethodOverride, 'Rack::RestfulSubmit'
diff --git a/src/dbomatic/dbomatic b/src/dbomatic/dbomatic
index 63b3301..5becb37 100755
--- a/src/dbomatic/dbomatic
+++ b/src/dbomatic/dbomatic
@@ -214,11 +214,6 @@ class CondorEventLog < Nokogiri::XML::SAX::Document
inst.provider_account_id = provider_account.id
- # FIXME: we are updating the instance_key_id here, but this is really not
- # the right way or place to do this. This will have to be revisited when
- # we come up with a real key management architecture
- InstanceKey.create(:instance_key_owner=>inst, :name =>
provider_account.instance_key.name, :pem => provider_account.instance_key.pem)
-
inst.save!
end
diff --git a/src/features/support/custom.rb b/src/features/support/custom.rb
index 1bc2c33..c2744de 100644
--- a/src/features/support/custom.rb
+++ b/src/features/support/custom.rb
@@ -58,3 +58,9 @@ Template.class_eval do
true
end
end
+
+InstanceKey.class_eval do
+ def replace_on_server(addr, new)
+ true
+ end
+end
diff --git a/src/spec/models/instance_observer_spec.rb
b/src/spec/models/instance_observer_spec.rb
index 5738bd7..1f64dd5 100644
--- a/src/spec/models/instance_observer_spec.rb
+++ b/src/spec/models/instance_observer_spec.rb
@@ -154,18 +154,18 @@ describe InstanceObserver do
end
end
- #it "should generate instance key when instance is running" do
- # client = mock('DeltaCloud', :null_object => true)
- # key = mock('Key', :null_object => true)
- # key.stub!(:pem).and_return("PEM")
- # key.stub!(:id).and_return("1_user")
- # client.stub!(:"feature?").and_return(true)
- # client.stub!(:"create_key").and_return(key)
- # @cloud_account.stub!(:connect).and_return(client)
- # @instance.stub!(:cloud_account).and_return(@cloud_account)
-
- # @instance.state = Instance::STATE_RUNNING
- # @instance.save!
- # @instance.instance_key.should_not == nil
- #end
+ it "should generate instance key when instance is running" do
+ client = mock('DeltaCloud', :null_object => true)
+ client.stub!(:"feature?").and_return(true)
+ @provider_account.stub!(:connect).and_return(client)
+ @instance.stub!(:provider_account).and_return(@provider_account)
+
+ @instance.instance_key = Factory(:instance_key, :name => 'key1',
:instance_key_owner => @instance)
+ @instance.instance_key.stub!(:replace_on_server).and_return(true)
+ @provider_account.instance_key = Factory(:instance_key, :name => 'key1',
:instance_key_owner => @provider_account)
+
+ @instance.state = Instance::STATE_RUNNING
+ @instance.save!
+ @instance.instance_key.name.should != @provider_account.instance_key.name
+ end
end
--
1.7.4