summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOliver Walsh <owalsh@redhat.com>2017-04-19 14:39:42 +0100
committerOliver Walsh <owalsh@redhat.com>2017-06-06 21:40:41 +0100
commiteed662fbcfe87d0f09eb55bac90482f54430de1b (patch)
tree4712f9830b8d1f8b172f7e8abde4e56988545f7f
parent1ef971dd4f62bd6e2e9f1ac2ab8288ed87b0a2b6 (diff)
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 f8ca94a5b7c7658631f5b0a9b010251ebbcff65e) (cherry picked from commit fd20b306b0bb4be2f5b251b45daeda5d215fb618)
Notes
Notes (review): Code-Review+2: Alex Schultz <aschultz@redhat.com> Workflow+1: Alex Schultz <aschultz@redhat.com> Verified+2: Jenkins Submitted-by: Jenkins Submitted-at: Wed, 07 Jun 2017 17:57:04 +0000 Reviewed-on: https://review.openstack.org/471433 Project: openstack/puppet-tripleo Branch: refs/heads/stable/newton
-rw-r--r--manifests/profile/base/nova.pp111
-rw-r--r--releasenotes/notes/cold_migration_security-1543136408c76459.yaml10
-rw-r--r--spec/classes/tripleo_profile_base_nova_spec.rb179
-rw-r--r--spec/fixtures/hieradata/default.yaml1
4 files changed, 250 insertions, 51 deletions
diff --git a/manifests/profile/base/nova.pp b/manifests/profile/base/nova.pp
index b6c1910..885714c 100644
--- a/manifests/profile/base/nova.pp
+++ b/manifests/profile/base/nova.pp
@@ -51,20 +51,26 @@
51# Expects a hash with keys 'private_key' and 'public_key'. 51# Expects a hash with keys 'private_key' and 'public_key'.
52# Defaults to {} 52# Defaults to {}
53# 53#
54# [*migration_ssh_localaddrs*]
55# (Optional) Restrict ssh migration to clients connecting via this list of
56# IPs.
57# Defaults to [] (no restriction)
58#
54# [*libvirt_tls*] 59# [*libvirt_tls*]
55# (Optional) Whether or not libvird TLS service is enabled. 60# (Optional) Whether or not libvird TLS service is enabled.
56# Defaults to false 61# Defaults to false
57 62
58class tripleo::profile::base::nova ( 63class tripleo::profile::base::nova (
59 $bootstrap_node = hiera('bootstrap_nodeid', undef), 64 $bootstrap_node = hiera('bootstrap_nodeid', undef),
60 $libvirt_enabled = false, 65 $libvirt_enabled = false,
61 $manage_migration = false, 66 $manage_migration = false,
62 $nova_compute_enabled = false, 67 $nova_compute_enabled = false,
63 $step = hiera('step'), 68 $step = hiera('step'),
64 $rabbit_hosts = hiera('rabbitmq_node_ips', undef), 69 $rabbit_hosts = hiera('rabbitmq_node_ips', undef),
65 $rabbit_port = hiera('nova::rabbit_port', 5672), 70 $rabbit_port = hiera('nova::rabbit_port', 5672),
66 $migration_ssh_key = {}, 71 $migration_ssh_key = {},
67 $libvirt_tls = false 72 $migration_ssh_localaddrs = [],
73 $libvirt_tls = false
68) { 74) {
69 if $::hostname == downcase($bootstrap_node) { 75 if $::hostname == downcase($bootstrap_node) {
70 $sync_db = true 76 $sync_db = true
@@ -80,15 +86,19 @@ class tripleo::profile::base::nova (
80 86
81 if $step >= 4 or ($step >= 3 and $sync_db) { 87 if $step >= 4 or ($step >= 3 and $sync_db) {
82 $rabbit_endpoints = suffix(any2array(normalize_ip_for_uri($rabbit_hosts)), ":${rabbit_port}") 88 $rabbit_endpoints = suffix(any2array(normalize_ip_for_uri($rabbit_hosts)), ":${rabbit_port}")
89 class { '::nova' :
90 rabbit_hosts => $rabbit_endpoints,
91 }
83 include ::nova::config 92 include ::nova::config
84 class { '::nova::cache': 93 class { '::nova::cache':
85 enabled => true, 94 enabled => true,
86 backend => 'oslo_cache.memcache_pool', 95 backend => 'oslo_cache.memcache_pool',
87 memcache_servers => $memcache_servers, 96 memcache_servers => $memcache_servers,
88 } 97 }
98 }
89 99
90 if $step >= 4 and $manage_migration { 100 if $step >= 4 {
91 101 if $manage_migration {
92 # Libvirt setup (live-migration) 102 # Libvirt setup (live-migration)
93 if $libvirt_tls { 103 if $libvirt_tls {
94 class { '::nova::migration::libvirt': 104 class { '::nova::migration::libvirt':
@@ -102,44 +112,77 @@ class tripleo::profile::base::nova (
102 transport => 'ssh', 112 transport => 'ssh',
103 configure_libvirt => $libvirt_enabled, 113 configure_libvirt => $libvirt_enabled,
104 configure_nova => $nova_compute_enabled, 114 configure_nova => $nova_compute_enabled,
105 client_user => 'nova', 115 client_user => 'nova_migration',
106 client_extraparams => { 116 client_extraparams => {
107 'keyfile' => '/var/lib/nova/.ssh/id_rsa' 117 'keyfile' => '/etc/nova/migration/identity'
108 } 118 }
109 } 119 }
110 } 120 }
111 121
112 if $migration_ssh_key != {} { 122 $services_enabled = hiera('service_names', [])
123 if !empty($migration_ssh_key) and 'sshd' in $services_enabled {
113 # Nova SSH tunnel setup (cold-migration) 124 # Nova SSH tunnel setup (cold-migration)
114 125
115 #TODO: Remove me when https://review.rdoproject.org/r/#/c/4008 lands 126 # Server side
116 user { 'nova': 127 if !empty($migration_ssh_localaddrs) {
117 ensure => present, 128 $allow_type = sprintf('LocalAddress %s User', join($migration_ssh_localaddrs,','))
118 shell => '/bin/bash', 129 $deny_type = 'LocalAddress'
130 $deny_name = sprintf('!%s', join($migration_ssh_localaddrs,',!'))
131
132 ssh::server::match_block { 'nova_migration deny':
133 name => $deny_name,
134 type => $deny_type,
135 order => 2,
136 options => {
137 'DenyUsers' => 'nova_migration'
138 },
139 notify => Service['sshd']
140 }
141 }
142 else {
143 $allow_type = 'User'
144 }
145 $allow_name = 'nova_migration'
146
147 ssh::server::match_block { 'nova_migration allow':
148 name => $allow_name,
149 type => $allow_type,
150 order => 1,
151 options => {
152 'ForceCommand' => '/bin/nova-migration-wrapper',
153 'PasswordAuthentication' => 'no',
154 'AllowTcpForwarding' => 'no',
155 'X11Forwarding' => 'no',
156 'AuthorizedKeysFile' => '/etc/nova/migration/authorized_keys'
157 },
158 notify => Service['sshd']
119 } 159 }
120 160
121 $private_key_parts = split($migration_ssh_key['public_key'], ' ') 161 file { '/etc/nova/migration/authorized_keys':
122 $nova_public_key = { 162 content => $migration_ssh_key['public_key'],
123 'type' => $private_key_parts[0], 163 mode => '0640',
124 key => $private_key_parts[1] 164 owner => 'root',
165 group => 'nova_migration',
166 require => Package['openstack-nova-migration'],
125 } 167 }
126 $nova_private_key = { 168
127 'type' => $private_key_parts[0], 169 # Client side
128 key => $migration_ssh_key['private_key'] 170 file { '/etc/nova/migration/identity':
171 content => $migration_ssh_key['private_key'],
172 mode => '0600',
173 owner => 'nova',
174 group => 'nova',
175 require => Package['openstack-nova-migration'],
129 } 176 }
177 $migration_pkg_ensure = installed
130 } else { 178 } else {
131 $nova_public_key = undef 179 $migration_pkg_ensure = absent
132 $nova_private_key = undef
133 } 180 }
134 } else { 181 } else {
135 $nova_public_key = undef 182 $migration_pkg_ensure = absent
136 $nova_private_key = undef
137 } 183 }
138 184 package {'openstack-nova-migration':
139 class { '::nova' : 185 ensure => $migration_pkg_ensure
140 rabbit_hosts => $rabbit_endpoints,
141 nova_public_key => $nova_public_key,
142 nova_private_key => $nova_private_key,
143 } 186 }
144 } 187 }
145} 188}
diff --git a/releasenotes/notes/cold_migration_security-1543136408c76459.yaml b/releasenotes/notes/cold_migration_security-1543136408c76459.yaml
new file mode 100644
index 0000000..aaea57e
--- /dev/null
+++ b/releasenotes/notes/cold_migration_security-1543136408c76459.yaml
@@ -0,0 +1,10 @@
1---
2features:
3 - |
4 Restrict nova migration ssh tunnel
5 * The ssh authorized_keys file is only writeable by root.
6 * Creates a new user for migration instead of using root/nova.
7 * Disables SSH forwarding for this user.
8 * Restricts the networks that this user can connect from.
9 * Uses an ssh wrapper command to whitelist the commands that this user can run over ssh.
10 Adds new parameter "tripleo::profile::base::nova::migration_ssh_localaddrs" to specify which incoming IPs are allow for SSH tunnel connections.
diff --git a/spec/classes/tripleo_profile_base_nova_spec.rb b/spec/classes/tripleo_profile_base_nova_spec.rb
index 92511fb..c1d82e6 100644
--- a/spec/classes/tripleo_profile_base_nova_spec.rb
+++ b/spec/classes/tripleo_profile_base_nova_spec.rb
@@ -22,7 +22,7 @@ describe 'tripleo::profile::base::nova' do
22 context 'with step less than 3' do 22 context 'with step less than 3' do
23 let(:params) { { 23 let(:params) { {
24 :step => 1, 24 :step => 1,
25 :rabbit_hosts => [ '127.0.0.1' ], 25 :rabbit_hosts => [ '127.0.0.1' ],
26 } } 26 } }
27 27
28 it { 28 it {
@@ -37,13 +37,13 @@ describe 'tripleo::profile::base::nova' do
37 let(:params) { { 37 let(:params) { {
38 :step => 3, 38 :step => 3,
39 :bootstrap_node => 'node.example.com', 39 :bootstrap_node => 'node.example.com',
40 :rabbit_hosts => [ '127.0.0.1' ], 40 :rabbit_hosts => [ '127.0.0.1' ],
41 } } 41 } }
42 42
43 it { 43 it {
44 is_expected.to contain_class('tripleo::profile::base::nova') 44 is_expected.to contain_class('tripleo::profile::base::nova')
45 is_expected.to contain_class('nova').with( 45 is_expected.to contain_class('nova').with(
46 :rabbit_hosts => ['127.0.0.1:5672'] 46 :rabbit_hosts => ['127.0.0.1:5672']
47 47
48 ) 48 )
49 is_expected.to contain_class('nova::config') 49 is_expected.to contain_class('nova::config')
@@ -59,7 +59,7 @@ describe 'tripleo::profile::base::nova' do
59 let(:params) { { 59 let(:params) { {
60 :step => 3, 60 :step => 3,
61 :bootstrap_node => 'other.example.com', 61 :bootstrap_node => 'other.example.com',
62 :rabbit_hosts => [ '127.0.0.1' ], 62 :rabbit_hosts => [ '127.0.0.1' ],
63 } } 63 } }
64 64
65 it { 65 it {
@@ -74,7 +74,7 @@ describe 'tripleo::profile::base::nova' do
74 let(:params) { { 74 let(:params) { {
75 :step => 4, 75 :step => 4,
76 :bootstrap_node => 'other.example.com', 76 :bootstrap_node => 'other.example.com',
77 :rabbit_hosts => [ '127.0.0.1' ], 77 :rabbit_hosts => [ '127.0.0.1' ],
78 } } 78 } }
79 79
80 it { 80 it {
@@ -87,6 +87,9 @@ describe 'tripleo::profile::base::nova' do
87 is_expected.to contain_class('nova::config') 87 is_expected.to contain_class('nova::config')
88 is_expected.to contain_class('nova::cache') 88 is_expected.to contain_class('nova::cache')
89 is_expected.to_not contain_class('nova::migration::libvirt') 89 is_expected.to_not contain_class('nova::migration::libvirt')
90 is_expected.to contain_package('openstack-nova-migration').with(
91 :ensure => 'absent'
92 )
90 } 93 }
91 end 94 end
92 95
@@ -100,7 +103,7 @@ describe 'tripleo::profile::base::nova' do
100 :manage_migration => true, 103 :manage_migration => true,
101 :nova_compute_enabled => true, 104 :nova_compute_enabled => true,
102 :bootstrap_node => 'node.example.com', 105 :bootstrap_node => 'node.example.com',
103 :rabbit_hosts => [ '127.0.0.1' ], 106 :rabbit_hosts => [ '127.0.0.1' ],
104 } } 107 } }
105 108
106 it { 109 it {
@@ -117,6 +120,9 @@ describe 'tripleo::profile::base::nova' do
117 :configure_libvirt => params[:libvirt_enabled], 120 :configure_libvirt => params[:libvirt_enabled],
118 :configure_nova => params[:nova_compute_enabled] 121 :configure_nova => params[:nova_compute_enabled]
119 ) 122 )
123 is_expected.to contain_package('openstack-nova-migration').with(
124 :ensure => 'absent'
125 )
120 } 126 }
121 end 127 end
122 128
@@ -148,13 +154,22 @@ describe 'tripleo::profile::base::nova' do
148 :configure_libvirt => params[:libvirt_enabled], 154 :configure_libvirt => params[:libvirt_enabled],
149 :configure_nova => params[:nova_compute_enabled], 155 :configure_nova => params[:nova_compute_enabled],
150 ) 156 )
157 is_expected.to contain_package('openstack-nova-migration').with(
158 :ensure => 'absent'
159 )
151 } 160 }
152 end 161 end
153 162
154 context 'with step 4 with libvirt and migration ssh key' do 163 context 'with step 4 with libvirt and migration ssh key' do
155 let(:pre_condition) { 164 let(:pre_condition) do
156 'include ::nova::compute::libvirt::services' 165 <<-eof
157 } 166 include ::nova::compute::libvirt::services
167 class { '::ssh::server':
168 storeconfigs_enabled => false,
169 options => {}
170 }
171 eof
172 end
158 let(:params) { { 173 let(:params) { {
159 :step => 4, 174 :step => 4,
160 :libvirt_enabled => true, 175 :libvirt_enabled => true,
@@ -169,8 +184,8 @@ describe 'tripleo::profile::base::nova' do
169 is_expected.to contain_class('tripleo::profile::base::nova') 184 is_expected.to contain_class('tripleo::profile::base::nova')
170 is_expected.to contain_class('nova').with( 185 is_expected.to contain_class('nova').with(
171 :rabbit_hosts => /.+/, 186 :rabbit_hosts => /.+/,
172 :nova_public_key => {'key' => 'bar', 'type' => 'ssh-rsa'}, 187 :nova_public_key => nil,
173 :nova_private_key => {'key' => 'foo', 'type' => 'ssh-rsa'} 188 :nova_private_key => nil,
174 ) 189 )
175 is_expected.to contain_class('nova::config') 190 is_expected.to contain_class('nova::config')
176 is_expected.to contain_class('nova::cache') 191 is_expected.to contain_class('nova::cache')
@@ -179,13 +194,117 @@ describe 'tripleo::profile::base::nova' do
179 :configure_libvirt => params[:libvirt_enabled], 194 :configure_libvirt => params[:libvirt_enabled],
180 :configure_nova => params[:nova_compute_enabled] 195 :configure_nova => params[:nova_compute_enabled]
181 ) 196 )
197 is_expected.to contain_ssh__server__match_block('nova_migration allow').with(
198 :type => 'User',
199 :name => 'nova_migration',
200 :options => {
201 'ForceCommand' => '/bin/nova-migration-wrapper',
202 'PasswordAuthentication' => 'no',
203 'AllowTcpForwarding' => 'no',
204 'X11Forwarding' => 'no',
205 'AuthorizedKeysFile' => '/etc/nova/migration/authorized_keys'
206 }
207 )
208 is_expected.to_not contain_ssh__server__match_block('nova_migration deny')
209 is_expected.to contain_file('/etc/nova/migration/authorized_keys').with(
210 :content => 'ssh-rsa bar',
211 :mode => '0640',
212 :owner => 'root',
213 :group => 'nova_migration',
214 )
215 is_expected.to contain_file('/etc/nova/migration/identity').with(
216 :content => 'foo',
217 :mode => '0600',
218 :owner => 'nova',
219 :group => 'nova',
220 )
221 is_expected.to contain_package('openstack-nova-migration').with(
222 :ensure => 'installed'
223 )
182 } 224 }
183 end 225 end
184 226
185 context 'with step 4 with libvirt TLS and migration ssh key' do 227 context 'with step 4 with libvirt and migration ssh key and migration_ssh_localaddrs' do
186 let(:pre_condition) { 228 let(:pre_condition) do
187 'include ::nova::compute::libvirt::services' 229 <<-eof
230 include ::nova::compute::libvirt::services
231 class { '::ssh::server':
232 storeconfigs_enabled => false,
233 options => {}
234 }
235 eof
236 end
237 let(:params) { {
238 :step => 4,
239 :libvirt_enabled => true,
240 :manage_migration => true,
241 :nova_compute_enabled => true,
242 :bootstrap_node => 'node.example.com',
243 :rabbit_hosts => [ '127.0.0.1' ],
244 :migration_ssh_key => { 'private_key' => 'foo', 'public_key' => 'ssh-rsa bar'},
245 :migration_ssh_localaddrs => ['127.0.0.1', '127.0.0.2']
246 } }
247
248 it {
249 is_expected.to contain_class('tripleo::profile::base::nova')
250 is_expected.to contain_class('nova').with(
251 :rabbit_hosts => /.+/,
252 :nova_public_key => nil,
253 :nova_private_key => nil,
254 )
255 is_expected.to contain_class('nova::config')
256 is_expected.to contain_class('nova::cache')
257 is_expected.to contain_class('nova::migration::libvirt').with(
258 :transport => 'ssh',
259 :configure_libvirt => params[:libvirt_enabled],
260 :configure_nova => params[:nova_compute_enabled]
261 )
262 is_expected.to contain_ssh__server__match_block('nova_migration allow').with(
263 :type => 'LocalAddress 127.0.0.1,127.0.0.2 User',
264 :name => 'nova_migration',
265 :options => {
266 'ForceCommand' => '/bin/nova-migration-wrapper',
267 'PasswordAuthentication' => 'no',
268 'AllowTcpForwarding' => 'no',
269 'X11Forwarding' => 'no',
270 'AuthorizedKeysFile' => '/etc/nova/migration/authorized_keys'
271 }
272 )
273 is_expected.to contain_ssh__server__match_block('nova_migration deny').with(
274 :type => 'LocalAddress',
275 :name => '!127.0.0.1,!127.0.0.2',
276 :options => {
277 'DenyUsers' => 'nova_migration'
278 }
279 )
280 is_expected.to contain_file('/etc/nova/migration/authorized_keys').with(
281 :content => 'ssh-rsa bar',
282 :mode => '0640',
283 :owner => 'root',
284 :group => 'nova_migration',
285 )
286 is_expected.to contain_file('/etc/nova/migration/identity').with(
287 :content => 'foo',
288 :mode => '0600',
289 :owner => 'nova',
290 :group => 'nova',
291 )
292 is_expected.to contain_package('openstack-nova-migration').with(
293 :ensure => 'installed'
294 )
188 } 295 }
296 end
297
298 context 'with step 4 with libvirt TLS and migration ssh key' do
299 let(:pre_condition) do
300 <<-eof
301 include ::nova::compute::libvirt::services
302 class { '::ssh::server':
303 storeconfigs_enabled => false,
304 options => {}
305 }
306 eof
307 end
189 let(:params) { { 308 let(:params) { {
190 :step => 4, 309 :step => 4,
191 :libvirt_enabled => true, 310 :libvirt_enabled => true,
@@ -201,9 +320,8 @@ describe 'tripleo::profile::base::nova' do
201 is_expected.to contain_class('tripleo::profile::base::nova') 320 is_expected.to contain_class('tripleo::profile::base::nova')
202 is_expected.to contain_class('nova').with( 321 is_expected.to contain_class('nova').with(
203 :rabbit_hosts => /.+/, 322 :rabbit_hosts => /.+/,
204 :notification_transport_url => /.+/, 323 :nova_public_key => nil,
205 :nova_public_key => {'key' => 'bar', 'type' => 'ssh-rsa'}, 324 :nova_private_key => nil,
206 :nova_private_key => {'key' => 'foo', 'type' => 'ssh-rsa'}
207 ) 325 )
208 is_expected.to contain_class('nova::config') 326 is_expected.to contain_class('nova::config')
209 is_expected.to contain_class('nova::cache') 327 is_expected.to contain_class('nova::cache')
@@ -212,6 +330,33 @@ describe 'tripleo::profile::base::nova' do
212 :configure_libvirt => params[:libvirt_enabled], 330 :configure_libvirt => params[:libvirt_enabled],
213 :configure_nova => params[:nova_compute_enabled] 331 :configure_nova => params[:nova_compute_enabled]
214 ) 332 )
333 is_expected.to contain_ssh__server__match_block('nova_migration allow').with(
334 :type => 'User',
335 :name => 'nova_migration',
336 :options => {
337 'ForceCommand' => '/bin/nova-migration-wrapper',
338 'PasswordAuthentication' => 'no',
339 'AllowTcpForwarding' => 'no',
340 'X11Forwarding' => 'no',
341 'AuthorizedKeysFile' => '/etc/nova/migration/authorized_keys'
342 }
343 )
344 is_expected.to_not contain_ssh__server__match_block('nova_migration deny')
345 is_expected.to contain_file('/etc/nova/migration/authorized_keys').with(
346 :content => 'ssh-rsa bar',
347 :mode => '0640',
348 :owner => 'root',
349 :group => 'nova_migration',
350 )
351 is_expected.to contain_file('/etc/nova/migration/identity').with(
352 :content => 'foo',
353 :mode => '0600',
354 :owner => 'nova',
355 :group => 'nova',
356 )
357 is_expected.to contain_package('openstack-nova-migration').with(
358 :ensure => 'installed'
359 )
215 } 360 }
216 end 361 end
217 362
diff --git a/spec/fixtures/hieradata/default.yaml b/spec/fixtures/hieradata/default.yaml
index 5349425..6ba85a1 100644
--- a/spec/fixtures/hieradata/default.yaml
+++ b/spec/fixtures/hieradata/default.yaml
@@ -7,3 +7,4 @@ memcached_node_ips_v6:
7 - '::1' 7 - '::1'
8memcached_node_ips: 8memcached_node_ips:
9 - '127.0.0.1' 9 - '127.0.0.1'
10service_names: ['sshd']