Restrict nova migration ssh tunnel
This change enhances the security of the migration ssh tunnel:
- The ssh authorized_keys file is only writeable by root.
- Creates a new user for migration instead of using root/nova.
- Disables SSH forwarding for this user.
- Optionally restricts the networks that this user can connect from.
- Uses an ssh wrapper command to whitelist the commands that this user can run
over ssh.
Requires the openstack-nova-migration package from
https://review.rdoproject.org/r/6327
bp tripleo-cold-migration
Change-Id: Idb56acd1e1ecb5a5fd4d942969be428cc9cbe293
(cherry picked from commit f8ca94a5b7
)
This commit is contained in:
parent
e4a293695e
commit
fd20b306b0
|
@ -67,24 +67,30 @@
|
||||||
# Expects a hash with keys 'private_key' and 'public_key'.
|
# Expects a hash with keys 'private_key' and 'public_key'.
|
||||||
# Defaults to {}
|
# Defaults to {}
|
||||||
#
|
#
|
||||||
|
# [*migration_ssh_localaddrs*]
|
||||||
|
# (Optional) Restrict ssh migration to clients connecting via this list of
|
||||||
|
# IPs.
|
||||||
|
# Defaults to [] (no restriction)
|
||||||
|
#
|
||||||
# [*libvirt_tls*]
|
# [*libvirt_tls*]
|
||||||
# (Optional) Whether or not libvird TLS service is enabled.
|
# (Optional) Whether or not libvird TLS service is enabled.
|
||||||
# Defaults to false
|
# Defaults to false
|
||||||
|
|
||||||
class tripleo::profile::base::nova (
|
class tripleo::profile::base::nova (
|
||||||
$bootstrap_node = hiera('bootstrap_nodeid', undef),
|
$bootstrap_node = hiera('bootstrap_nodeid', undef),
|
||||||
$libvirt_enabled = false,
|
$libvirt_enabled = false,
|
||||||
$manage_migration = false,
|
$manage_migration = false,
|
||||||
$messaging_driver = hiera('messaging_service_name', 'rabbit'),
|
$messaging_driver = hiera('messaging_service_name', 'rabbit'),
|
||||||
$messaging_hosts = any2array(hiera('rabbitmq_node_names', undef)),
|
$messaging_hosts = any2array(hiera('rabbitmq_node_names', undef)),
|
||||||
$messaging_password = hiera('nova::rabbit_password'),
|
$messaging_password = hiera('nova::rabbit_password'),
|
||||||
$messaging_port = hiera('nova::rabbit_port', '5672'),
|
$messaging_port = hiera('nova::rabbit_port', '5672'),
|
||||||
$messaging_username = hiera('nova::rabbit_userid', 'guest'),
|
$messaging_username = hiera('nova::rabbit_userid', 'guest'),
|
||||||
$messaging_use_ssl = hiera('nova::rabbit_use_ssl', '0'),
|
$messaging_use_ssl = hiera('nova::rabbit_use_ssl', '0'),
|
||||||
$nova_compute_enabled = false,
|
$nova_compute_enabled = false,
|
||||||
$step = hiera('step'),
|
$step = hiera('step'),
|
||||||
$migration_ssh_key = {},
|
$migration_ssh_key = {},
|
||||||
$libvirt_tls = false
|
$migration_ssh_localaddrs = [],
|
||||||
|
$libvirt_tls = false
|
||||||
) {
|
) {
|
||||||
if $::hostname == downcase($bootstrap_node) {
|
if $::hostname == downcase($bootstrap_node) {
|
||||||
$sync_db = true
|
$sync_db = true
|
||||||
|
@ -106,10 +112,22 @@ class tripleo::profile::base::nova (
|
||||||
backend => 'oslo_cache.memcache_pool',
|
backend => 'oslo_cache.memcache_pool',
|
||||||
memcache_servers => $memcache_servers,
|
memcache_servers => $memcache_servers,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class { '::nova' :
|
||||||
|
default_transport_url => os_transport_url({
|
||||||
|
'transport' => $messaging_driver,
|
||||||
|
'hosts' => $messaging_hosts,
|
||||||
|
'port' => sprintf('%s', $messaging_port),
|
||||||
|
'username' => $messaging_username,
|
||||||
|
'password' => $messaging_password,
|
||||||
|
'ssl' => $messaging_use_ssl_real,
|
||||||
|
})
|
||||||
|
}
|
||||||
include ::nova::placement
|
include ::nova::placement
|
||||||
|
}
|
||||||
|
|
||||||
if $step >= 4 and $manage_migration {
|
if $step >= 4 {
|
||||||
|
if $manage_migration {
|
||||||
# Libvirt setup (live-migration)
|
# Libvirt setup (live-migration)
|
||||||
if $libvirt_tls {
|
if $libvirt_tls {
|
||||||
class { '::nova::migration::libvirt':
|
class { '::nova::migration::libvirt':
|
||||||
|
@ -123,49 +141,75 @@ class tripleo::profile::base::nova (
|
||||||
transport => 'ssh',
|
transport => 'ssh',
|
||||||
configure_libvirt => $libvirt_enabled,
|
configure_libvirt => $libvirt_enabled,
|
||||||
configure_nova => $nova_compute_enabled,
|
configure_nova => $nova_compute_enabled,
|
||||||
client_user => 'nova',
|
client_user => 'nova_migration',
|
||||||
client_extraparams => {'keyfile' => '/var/lib/nova/.ssh/id_rsa'}
|
client_extraparams => {'keyfile' => '/etc/nova/migration/identity'}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if $migration_ssh_key != {} {
|
$services_enabled = hiera('service_names', [])
|
||||||
|
if !empty($migration_ssh_key) and 'sshd' in $services_enabled {
|
||||||
# Nova SSH tunnel setup (cold-migration)
|
# Nova SSH tunnel setup (cold-migration)
|
||||||
|
|
||||||
#TODO: Remove me when https://review.rdoproject.org/r/#/c/4008 lands
|
# Server side
|
||||||
user { 'nova':
|
if !empty($migration_ssh_localaddrs) {
|
||||||
ensure => present,
|
$allow_type = sprintf('LocalAddress %s User', join($migration_ssh_localaddrs,','))
|
||||||
shell => '/bin/bash',
|
$deny_type = 'LocalAddress'
|
||||||
|
$deny_name = sprintf('!%s', join($migration_ssh_localaddrs,',!'))
|
||||||
|
|
||||||
|
ssh::server::match_block { 'nova_migration deny':
|
||||||
|
name => $deny_name,
|
||||||
|
type => $deny_type,
|
||||||
|
order => 2,
|
||||||
|
options => {
|
||||||
|
'DenyUsers' => 'nova_migration'
|
||||||
|
},
|
||||||
|
notify => Service['sshd']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$allow_type = 'User'
|
||||||
|
}
|
||||||
|
$allow_name = 'nova_migration'
|
||||||
|
|
||||||
|
ssh::server::match_block { 'nova_migration allow':
|
||||||
|
name => $allow_name,
|
||||||
|
type => $allow_type,
|
||||||
|
order => 1,
|
||||||
|
options => {
|
||||||
|
'ForceCommand' => '/bin/nova-migration-wrapper',
|
||||||
|
'PasswordAuthentication' => 'no',
|
||||||
|
'AllowTcpForwarding' => 'no',
|
||||||
|
'X11Forwarding' => 'no',
|
||||||
|
'AuthorizedKeysFile' => '/etc/nova/migration/authorized_keys'
|
||||||
|
},
|
||||||
|
notify => Service['sshd']
|
||||||
}
|
}
|
||||||
|
|
||||||
$private_key_parts = split($migration_ssh_key['public_key'], ' ')
|
file { '/etc/nova/migration/authorized_keys':
|
||||||
$nova_public_key = {
|
content => $migration_ssh_key['public_key'],
|
||||||
'type' => $private_key_parts[0],
|
mode => '0640',
|
||||||
key => $private_key_parts[1]
|
owner => 'root',
|
||||||
|
group => 'nova_migration',
|
||||||
|
require => Package['openstack-nova-migration'],
|
||||||
}
|
}
|
||||||
$nova_private_key = {
|
|
||||||
'type' => $private_key_parts[0],
|
# Client side
|
||||||
key => $migration_ssh_key['private_key']
|
file { '/etc/nova/migration/identity':
|
||||||
|
content => $migration_ssh_key['private_key'],
|
||||||
|
mode => '0600',
|
||||||
|
owner => 'nova',
|
||||||
|
group => 'nova',
|
||||||
|
require => Package['openstack-nova-migration'],
|
||||||
}
|
}
|
||||||
|
$migration_pkg_ensure = installed
|
||||||
} else {
|
} else {
|
||||||
$nova_public_key = undef
|
$migration_pkg_ensure = absent
|
||||||
$nova_private_key = undef
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$nova_public_key = undef
|
$migration_pkg_ensure = absent
|
||||||
$nova_private_key = undef
|
|
||||||
}
|
}
|
||||||
|
package {'openstack-nova-migration':
|
||||||
class { '::nova' :
|
ensure => $migration_pkg_ensure
|
||||||
default_transport_url => os_transport_url({
|
|
||||||
'transport' => $messaging_driver,
|
|
||||||
'hosts' => $messaging_hosts,
|
|
||||||
'port' => sprintf('%s', $messaging_port),
|
|
||||||
'username' => $messaging_username,
|
|
||||||
'password' => $messaging_password,
|
|
||||||
'ssl' => $messaging_use_ssl_real,
|
|
||||||
}),
|
|
||||||
nova_public_key => $nova_public_key,
|
|
||||||
nova_private_key => $nova_private_key,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Restrict nova migration ssh tunnel
|
||||||
|
* The ssh authorized_keys file is only writeable by root.
|
||||||
|
* Creates a new user for migration instead of using root/nova.
|
||||||
|
* Disables SSH forwarding for this user.
|
||||||
|
* Restricts the networks that this user can connect from.
|
||||||
|
* Uses an ssh wrapper command to whitelist the commands that this user can run over ssh.
|
||||||
|
Adds new parameter "tripleo::profile::base::nova::migration_ssh_localaddrs" to specify which incoming IPs are allow for SSH tunnel connections.
|
|
@ -95,6 +95,9 @@ describe 'tripleo::profile::base::nova' do
|
||||||
is_expected.to contain_class('nova::cache')
|
is_expected.to contain_class('nova::cache')
|
||||||
is_expected.to contain_class('nova::placement')
|
is_expected.to contain_class('nova::placement')
|
||||||
is_expected.to_not contain_class('nova::migration::libvirt')
|
is_expected.to_not contain_class('nova::migration::libvirt')
|
||||||
|
is_expected.to contain_package('openstack-nova-migration').with(
|
||||||
|
:ensure => 'absent'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -128,6 +131,9 @@ describe 'tripleo::profile::base::nova' do
|
||||||
:configure_libvirt => params[:libvirt_enabled],
|
:configure_libvirt => params[:libvirt_enabled],
|
||||||
:configure_nova => params[:nova_compute_enabled]
|
:configure_nova => params[:nova_compute_enabled]
|
||||||
)
|
)
|
||||||
|
is_expected.to contain_package('openstack-nova-migration').with(
|
||||||
|
:ensure => 'absent'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -162,13 +168,22 @@ describe 'tripleo::profile::base::nova' do
|
||||||
:configure_libvirt => params[:libvirt_enabled],
|
:configure_libvirt => params[:libvirt_enabled],
|
||||||
:configure_nova => params[:nova_compute_enabled],
|
:configure_nova => params[:nova_compute_enabled],
|
||||||
)
|
)
|
||||||
|
is_expected.to contain_package('openstack-nova-migration').with(
|
||||||
|
:ensure => 'absent'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with step 4 with libvirt and migration ssh key' do
|
context 'with step 4 with libvirt and migration ssh key' do
|
||||||
let(:pre_condition) {
|
let(:pre_condition) do
|
||||||
'include ::nova::compute::libvirt::services'
|
<<-eof
|
||||||
}
|
include ::nova::compute::libvirt::services
|
||||||
|
class { '::ssh::server':
|
||||||
|
storeconfigs_enabled => false,
|
||||||
|
options => {}
|
||||||
|
}
|
||||||
|
eof
|
||||||
|
end
|
||||||
let(:params) { {
|
let(:params) { {
|
||||||
:step => 4,
|
:step => 4,
|
||||||
:libvirt_enabled => true,
|
:libvirt_enabled => true,
|
||||||
|
@ -185,8 +200,8 @@ describe 'tripleo::profile::base::nova' do
|
||||||
is_expected.to contain_class('nova').with(
|
is_expected.to contain_class('nova').with(
|
||||||
:default_transport_url => /.+/,
|
:default_transport_url => /.+/,
|
||||||
:notification_transport_url => /.+/,
|
:notification_transport_url => /.+/,
|
||||||
:nova_public_key => {'key' => 'bar', 'type' => 'ssh-rsa'},
|
:nova_public_key => nil,
|
||||||
:nova_private_key => {'key' => 'foo', 'type' => 'ssh-rsa'}
|
:nova_private_key => nil,
|
||||||
)
|
)
|
||||||
is_expected.to contain_class('nova::config')
|
is_expected.to contain_class('nova::config')
|
||||||
is_expected.to contain_class('nova::placement')
|
is_expected.to contain_class('nova::placement')
|
||||||
|
@ -196,13 +211,120 @@ describe 'tripleo::profile::base::nova' do
|
||||||
:configure_libvirt => params[:libvirt_enabled],
|
:configure_libvirt => params[:libvirt_enabled],
|
||||||
:configure_nova => params[:nova_compute_enabled]
|
:configure_nova => params[:nova_compute_enabled]
|
||||||
)
|
)
|
||||||
|
is_expected.to contain_ssh__server__match_block('nova_migration allow').with(
|
||||||
|
:type => 'User',
|
||||||
|
:name => 'nova_migration',
|
||||||
|
:options => {
|
||||||
|
'ForceCommand' => '/bin/nova-migration-wrapper',
|
||||||
|
'PasswordAuthentication' => 'no',
|
||||||
|
'AllowTcpForwarding' => 'no',
|
||||||
|
'X11Forwarding' => 'no',
|
||||||
|
'AuthorizedKeysFile' => '/etc/nova/migration/authorized_keys'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
is_expected.to_not contain_ssh__server__match_block('nova_migration deny')
|
||||||
|
is_expected.to contain_file('/etc/nova/migration/authorized_keys').with(
|
||||||
|
:content => 'ssh-rsa bar',
|
||||||
|
:mode => '0640',
|
||||||
|
:owner => 'root',
|
||||||
|
:group => 'nova_migration',
|
||||||
|
)
|
||||||
|
is_expected.to contain_file('/etc/nova/migration/identity').with(
|
||||||
|
:content => 'foo',
|
||||||
|
:mode => '0600',
|
||||||
|
:owner => 'nova',
|
||||||
|
:group => 'nova',
|
||||||
|
)
|
||||||
|
is_expected.to contain_package('openstack-nova-migration').with(
|
||||||
|
:ensure => 'installed'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with step 4 with libvirt and migration ssh key and migration_ssh_localaddrs' do
|
||||||
|
let(:pre_condition) do
|
||||||
|
<<-eof
|
||||||
|
include ::nova::compute::libvirt::services
|
||||||
|
class { '::ssh::server':
|
||||||
|
storeconfigs_enabled => false,
|
||||||
|
options => {}
|
||||||
|
}
|
||||||
|
eof
|
||||||
|
end
|
||||||
|
let(:params) { {
|
||||||
|
:step => 4,
|
||||||
|
:libvirt_enabled => true,
|
||||||
|
:manage_migration => true,
|
||||||
|
:nova_compute_enabled => true,
|
||||||
|
:bootstrap_node => 'node.example.com',
|
||||||
|
:messaging_hosts => [ 'localhost' ],
|
||||||
|
:messaging_password => 'foo',
|
||||||
|
:migration_ssh_key => { 'private_key' => 'foo', 'public_key' => 'ssh-rsa bar'},
|
||||||
|
:migration_ssh_localaddrs => ['127.0.0.1', '127.0.0.2']
|
||||||
|
} }
|
||||||
|
|
||||||
|
it {
|
||||||
|
is_expected.to contain_class('tripleo::profile::base::nova')
|
||||||
|
is_expected.to contain_class('nova').with(
|
||||||
|
:default_transport_url => /.+/,
|
||||||
|
:notification_transport_url => /.+/,
|
||||||
|
:nova_public_key => nil,
|
||||||
|
:nova_private_key => nil,
|
||||||
|
)
|
||||||
|
is_expected.to contain_class('nova::config')
|
||||||
|
is_expected.to contain_class('nova::placement')
|
||||||
|
is_expected.to contain_class('nova::cache')
|
||||||
|
is_expected.to contain_class('nova::migration::libvirt').with(
|
||||||
|
:transport => 'ssh',
|
||||||
|
:configure_libvirt => params[:libvirt_enabled],
|
||||||
|
:configure_nova => params[:nova_compute_enabled]
|
||||||
|
)
|
||||||
|
is_expected.to contain_ssh__server__match_block('nova_migration allow').with(
|
||||||
|
:type => 'LocalAddress 127.0.0.1,127.0.0.2 User',
|
||||||
|
:name => 'nova_migration',
|
||||||
|
:options => {
|
||||||
|
'ForceCommand' => '/bin/nova-migration-wrapper',
|
||||||
|
'PasswordAuthentication' => 'no',
|
||||||
|
'AllowTcpForwarding' => 'no',
|
||||||
|
'X11Forwarding' => 'no',
|
||||||
|
'AuthorizedKeysFile' => '/etc/nova/migration/authorized_keys'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
is_expected.to contain_ssh__server__match_block('nova_migration deny').with(
|
||||||
|
:type => 'LocalAddress',
|
||||||
|
:name => '!127.0.0.1,!127.0.0.2',
|
||||||
|
:options => {
|
||||||
|
'DenyUsers' => 'nova_migration'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
is_expected.to contain_file('/etc/nova/migration/authorized_keys').with(
|
||||||
|
:content => 'ssh-rsa bar',
|
||||||
|
:mode => '0640',
|
||||||
|
:owner => 'root',
|
||||||
|
:group => 'nova_migration',
|
||||||
|
)
|
||||||
|
is_expected.to contain_file('/etc/nova/migration/identity').with(
|
||||||
|
:content => 'foo',
|
||||||
|
:mode => '0600',
|
||||||
|
:owner => 'nova',
|
||||||
|
:group => 'nova',
|
||||||
|
)
|
||||||
|
is_expected.to contain_package('openstack-nova-migration').with(
|
||||||
|
:ensure => 'installed'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with step 4 with libvirt TLS and migration ssh key' do
|
context 'with step 4 with libvirt TLS and migration ssh key' do
|
||||||
let(:pre_condition) {
|
let(:pre_condition) do
|
||||||
'include ::nova::compute::libvirt::services'
|
<<-eof
|
||||||
}
|
include ::nova::compute::libvirt::services
|
||||||
|
class { '::ssh::server':
|
||||||
|
storeconfigs_enabled => false,
|
||||||
|
options => {}
|
||||||
|
}
|
||||||
|
eof
|
||||||
|
end
|
||||||
let(:params) { {
|
let(:params) { {
|
||||||
:step => 4,
|
:step => 4,
|
||||||
:libvirt_enabled => true,
|
:libvirt_enabled => true,
|
||||||
|
@ -220,8 +342,8 @@ describe 'tripleo::profile::base::nova' do
|
||||||
is_expected.to contain_class('nova').with(
|
is_expected.to contain_class('nova').with(
|
||||||
:default_transport_url => /.+/,
|
:default_transport_url => /.+/,
|
||||||
:notification_transport_url => /.+/,
|
:notification_transport_url => /.+/,
|
||||||
:nova_public_key => {'key' => 'bar', 'type' => 'ssh-rsa'},
|
:nova_public_key => nil,
|
||||||
:nova_private_key => {'key' => 'foo', 'type' => 'ssh-rsa'}
|
:nova_private_key => nil,
|
||||||
)
|
)
|
||||||
is_expected.to contain_class('nova::config')
|
is_expected.to contain_class('nova::config')
|
||||||
is_expected.to contain_class('nova::placement')
|
is_expected.to contain_class('nova::placement')
|
||||||
|
@ -231,6 +353,33 @@ describe 'tripleo::profile::base::nova' do
|
||||||
:configure_libvirt => params[:libvirt_enabled],
|
:configure_libvirt => params[:libvirt_enabled],
|
||||||
:configure_nova => params[:nova_compute_enabled]
|
:configure_nova => params[:nova_compute_enabled]
|
||||||
)
|
)
|
||||||
|
is_expected.to contain_ssh__server__match_block('nova_migration allow').with(
|
||||||
|
:type => 'User',
|
||||||
|
:name => 'nova_migration',
|
||||||
|
:options => {
|
||||||
|
'ForceCommand' => '/bin/nova-migration-wrapper',
|
||||||
|
'PasswordAuthentication' => 'no',
|
||||||
|
'AllowTcpForwarding' => 'no',
|
||||||
|
'X11Forwarding' => 'no',
|
||||||
|
'AuthorizedKeysFile' => '/etc/nova/migration/authorized_keys'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
is_expected.to_not contain_ssh__server__match_block('nova_migration deny')
|
||||||
|
is_expected.to contain_file('/etc/nova/migration/authorized_keys').with(
|
||||||
|
:content => 'ssh-rsa bar',
|
||||||
|
:mode => '0640',
|
||||||
|
:owner => 'root',
|
||||||
|
:group => 'nova_migration',
|
||||||
|
)
|
||||||
|
is_expected.to contain_file('/etc/nova/migration/identity').with(
|
||||||
|
:content => 'foo',
|
||||||
|
:mode => '0600',
|
||||||
|
:owner => 'nova',
|
||||||
|
:group => 'nova',
|
||||||
|
)
|
||||||
|
is_expected.to contain_package('openstack-nova-migration').with(
|
||||||
|
:ensure => 'installed'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -36,3 +36,4 @@ memcached_node_ips_v6:
|
||||||
memcached_node_ips:
|
memcached_node_ips:
|
||||||
- '127.0.0.1'
|
- '127.0.0.1'
|
||||||
horizon::secret_key: 'secrete'
|
horizon::secret_key: 'secrete'
|
||||||
|
service_names: ['sshd']
|
||||||
|
|
Loading…
Reference in New Issue