Merge ~pzakha/cloud-init:userdata into cloud-init:master
- Git
- lp:~pzakha/cloud-init
- userdata
- Merge into master
Status: | Merged |
---|---|
Approved by: | Ryan Harper |
Approved revision: | 67f7991583a71105e07c7555a4d2e06d51d6bb73 |
Merge reported by: | Server Team CI bot |
Merged at revision: | not available |
Proposed branch: | ~pzakha/cloud-init:userdata |
Merge into: | cloud-init:master |
Diff against target: |
254 lines (+103/-12) 5 files modified
cloudinit/config/cc_ssh.py (+13/-2) cloudinit/config/tests/test_ssh.py (+37/-9) cloudinit/stages.py (+5/-1) doc/rtd/topics/format.rst (+8/-0) tests/unittests/test_data.py (+40/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ryan Harper | Approve | ||
Server Team CI bot | continuous-integration | Approve | |
Review via email: mp+367721@code.launchpad.net |
Commit message
Description of the change
Add config for ssh-key import and consuming user-data
This patch enables control over SSH public-key import and
discarding supplied user-data (both disabled by default).
allow-userdata: false
ssh:
allow_
This feature enables closed appliances to prevent customers
from unintentionally breaking the appliance which were
not designed for user interaction.
The downstream change for this is here:
https:/
Dan Watkins (oddbloke) wrote : | # |
Scott Moser (smoser) wrote : | # |
I'm not opposed to this in principal. But I'd like to warn that this is not "drm". The end user can probably find a way of getting root on your image if they really want. You'll just be making it more difficult to do so.
That said, I do understand the desire to not have a user "break" your supported functionality unintentionally and then asking for support.
Pavel Zakharov (pzakha) wrote : | # |
Thanks for the reviews.
Yes I understand that if the user really wants to access the system, they will find a way to do so. The intent of this is to make it non-trivial. Your reasoning is spot on.
Ryan Harper (raharper) : | # |
Pavel Zakharov (pzakha) wrote : | # |
I think I've addressed the review feedback, let me know what are the next steps.
Ryan Harper (raharper) wrote : | # |
Thanks for updating the branch. I didn't see a unittest for the change. Let me know if you need help there.
I definitely would like to see us document this behavior. At a min, this branch can update:
cloudinit/
The large block comment at the top is in RTD format, please add the allow_public_keys field, default value and describe what it does.
And I think the best location to mention disabling user-data is in:
doc/rtd/
under the Network section. It mentions that this stage consumes all user-data.
Here you could add a
**Note**: Cloud-init system config (/etc/cloud/
user-data via the following key: allow_userdata , etc
And document the default value and what it does.
Pavel Zakharov (pzakha) wrote : | # |
Alright, I'm a bit busy right now, but I should be able to post an update in a few weeks.
Ryan Harper (raharper) wrote : | # |
I've marked this branch work-in-progress. Once you've updated this branch please move the state back to NeedsReview. Thanks!
Pavel Zakharov (pzakha) wrote : | # |
I've updated the code, looking for another round of reviews!
Pavel Zakharov (pzakha) wrote : | # |
Could someone take another look at this?
Ryan Harper (raharper) wrote : | # |
Thanks for the update.
I'm wondering if these two config changes should be in separate commits.
I've some in-line comments, suggestions.
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:8913c393d58
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
Ryan Harper (raharper) wrote : | # |
If you can rebase this to master, that'll fix up the current CI issue unrelated to your changes.
- 33f9d36... by Pavel Zakharov <email address hidden>
-
Review feedback, fix tests
Pavel Zakharov (pzakha) wrote : | # |
I've rebased the code on master, and fixed the failing unit tests.
@raharper, I think I fixed most of the phrasing, but I didn't quite understand your feedback regarding the "Disabling User-Data" section.
Ryan Harper (raharper) wrote : | # |
Thanks for rebasing. I'll look at it again and comment. I think the important part was to not say
"cloud-init will silently ignore"
when your code *logs* that we're ignoring user-data as requested, which is what I want; we want to leave a breadcrumb in the log file for folks to see why their user-data was not processed.
- 67f7991... by Pavel Zakharov <email address hidden>
-
Reword the Disable User-Data section
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:67f7991583a
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Preview Diff
1 | diff --git a/cloudinit/config/cc_ssh.py b/cloudinit/config/cc_ssh.py | |||
2 | index fdd8f4d..050285a 100755 | |||
3 | --- a/cloudinit/config/cc_ssh.py | |||
4 | +++ b/cloudinit/config/cc_ssh.py | |||
5 | @@ -56,9 +56,13 @@ root login is disabled, and root login opts are set to:: | |||
6 | 56 | no-port-forwarding,no-agent-forwarding,no-X11-forwarding | 56 | no-port-forwarding,no-agent-forwarding,no-X11-forwarding |
7 | 57 | 57 | ||
8 | 58 | Authorized keys for the default user/first user defined in ``users`` can be | 58 | Authorized keys for the default user/first user defined in ``users`` can be |
10 | 59 | specified using `ssh_authorized_keys``. Keys should be specified as a list of | 59 | specified using ``ssh_authorized_keys``. Keys should be specified as a list of |
11 | 60 | public keys. | 60 | public keys. |
12 | 61 | 61 | ||
13 | 62 | Importing ssh public keys for the default user (defined in ``users``)) is | ||
14 | 63 | enabled by default. This feature may be disabled by setting | ||
15 | 64 | ``allow_publish_ssh_keys: false``. | ||
16 | 65 | |||
17 | 62 | .. note:: | 66 | .. note:: |
18 | 63 | see the ``cc_set_passwords`` module documentation to enable/disable ssh | 67 | see the ``cc_set_passwords`` module documentation to enable/disable ssh |
19 | 64 | password authentication | 68 | password authentication |
20 | @@ -91,6 +95,7 @@ public keys. | |||
21 | 91 | ssh_authorized_keys: | 95 | ssh_authorized_keys: |
22 | 92 | - ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEA3FSyQwBI6Z+nCSjUU ... | 96 | - ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEA3FSyQwBI6Z+nCSjUU ... |
23 | 93 | - ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3I7VUf2l5gSn5uavROsc5HRDpZ ... | 97 | - ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3I7VUf2l5gSn5uavROsc5HRDpZ ... |
24 | 98 | allow_public_ssh_keys: <true/false> | ||
25 | 94 | ssh_publish_hostkeys: | 99 | ssh_publish_hostkeys: |
26 | 95 | enabled: <true/false> (Defaults to true) | 100 | enabled: <true/false> (Defaults to true) |
27 | 96 | blacklist: <list of key types> (Defaults to [dsa]) | 101 | blacklist: <list of key types> (Defaults to [dsa]) |
28 | @@ -207,7 +212,13 @@ def handle(_name, cfg, cloud, log, _args): | |||
29 | 207 | disable_root_opts = util.get_cfg_option_str(cfg, "disable_root_opts", | 212 | disable_root_opts = util.get_cfg_option_str(cfg, "disable_root_opts", |
30 | 208 | ssh_util.DISABLE_USER_OPTS) | 213 | ssh_util.DISABLE_USER_OPTS) |
31 | 209 | 214 | ||
33 | 210 | keys = cloud.get_public_ssh_keys() or [] | 215 | keys = [] |
34 | 216 | if util.get_cfg_option_bool(cfg, 'allow_public_ssh_keys', True): | ||
35 | 217 | keys = cloud.get_public_ssh_keys() or [] | ||
36 | 218 | else: | ||
37 | 219 | log.debug('Skipping import of publish ssh keys per ' | ||
38 | 220 | 'config setting: allow_public_ssh_keys=False') | ||
39 | 221 | |||
40 | 211 | if "ssh_authorized_keys" in cfg: | 222 | if "ssh_authorized_keys" in cfg: |
41 | 212 | cfgkeys = cfg["ssh_authorized_keys"] | 223 | cfgkeys = cfg["ssh_authorized_keys"] |
42 | 213 | keys.extend(cfgkeys) | 224 | keys.extend(cfgkeys) |
43 | diff --git a/cloudinit/config/tests/test_ssh.py b/cloudinit/config/tests/test_ssh.py | |||
44 | index e778984..0c55441 100644 | |||
45 | --- a/cloudinit/config/tests/test_ssh.py | |||
46 | +++ b/cloudinit/config/tests/test_ssh.py | |||
47 | @@ -5,6 +5,9 @@ import os.path | |||
48 | 5 | from cloudinit.config import cc_ssh | 5 | from cloudinit.config import cc_ssh |
49 | 6 | from cloudinit import ssh_util | 6 | from cloudinit import ssh_util |
50 | 7 | from cloudinit.tests.helpers import CiTestCase, mock | 7 | from cloudinit.tests.helpers import CiTestCase, mock |
51 | 8 | import logging | ||
52 | 9 | |||
53 | 10 | LOG = logging.getLogger(__name__) | ||
54 | 8 | 11 | ||
55 | 9 | MODPATH = "cloudinit.config.cc_ssh." | 12 | MODPATH = "cloudinit.config.cc_ssh." |
56 | 10 | 13 | ||
57 | @@ -87,7 +90,7 @@ class TestHandleSsh(CiTestCase): | |||
58 | 87 | cc_ssh.PUBLISH_HOST_KEYS = False | 90 | cc_ssh.PUBLISH_HOST_KEYS = False |
59 | 88 | cloud = self.tmp_cloud( | 91 | cloud = self.tmp_cloud( |
60 | 89 | distro='ubuntu', metadata={'public-keys': keys}) | 92 | distro='ubuntu', metadata={'public-keys': keys}) |
62 | 90 | cc_ssh.handle("name", cfg, cloud, None, None) | 93 | cc_ssh.handle("name", cfg, cloud, LOG, None) |
63 | 91 | options = ssh_util.DISABLE_USER_OPTS.replace("$USER", "NONE") | 94 | options = ssh_util.DISABLE_USER_OPTS.replace("$USER", "NONE") |
64 | 92 | options = options.replace("$DISABLE_USER", "root") | 95 | options = options.replace("$DISABLE_USER", "root") |
65 | 93 | m_glob.assert_called_once_with('/etc/ssh/ssh_host_*key*') | 96 | m_glob.assert_called_once_with('/etc/ssh/ssh_host_*key*') |
66 | @@ -103,6 +106,31 @@ class TestHandleSsh(CiTestCase): | |||
67 | 103 | @mock.patch(MODPATH + "glob.glob") | 106 | @mock.patch(MODPATH + "glob.glob") |
68 | 104 | @mock.patch(MODPATH + "ug_util.normalize_users_groups") | 107 | @mock.patch(MODPATH + "ug_util.normalize_users_groups") |
69 | 105 | @mock.patch(MODPATH + "os.path.exists") | 108 | @mock.patch(MODPATH + "os.path.exists") |
70 | 109 | def test_dont_allow_public_ssh_keys(self, m_path_exists, m_nug, | ||
71 | 110 | m_glob, m_setup_keys): | ||
72 | 111 | """Test allow_public_ssh_keys=False ignores ssh public keys from | ||
73 | 112 | platform. | ||
74 | 113 | """ | ||
75 | 114 | cfg = {"allow_public_ssh_keys": False} | ||
76 | 115 | keys = ["key1"] | ||
77 | 116 | user = "clouduser" | ||
78 | 117 | m_glob.return_value = [] # Return no matching keys to prevent removal | ||
79 | 118 | # Mock os.path.exits to True to short-circuit the key writing logic | ||
80 | 119 | m_path_exists.return_value = True | ||
81 | 120 | m_nug.return_value = ({user: {"default": user}}, {}) | ||
82 | 121 | cloud = self.tmp_cloud( | ||
83 | 122 | distro='ubuntu', metadata={'public-keys': keys}) | ||
84 | 123 | cc_ssh.handle("name", cfg, cloud, LOG, None) | ||
85 | 124 | |||
86 | 125 | options = ssh_util.DISABLE_USER_OPTS.replace("$USER", user) | ||
87 | 126 | options = options.replace("$DISABLE_USER", "root") | ||
88 | 127 | self.assertEqual([mock.call(set(), user), | ||
89 | 128 | mock.call(set(), "root", options=options)], | ||
90 | 129 | m_setup_keys.call_args_list) | ||
91 | 130 | |||
92 | 131 | @mock.patch(MODPATH + "glob.glob") | ||
93 | 132 | @mock.patch(MODPATH + "ug_util.normalize_users_groups") | ||
94 | 133 | @mock.patch(MODPATH + "os.path.exists") | ||
95 | 106 | def test_handle_no_cfg_and_default_root(self, m_path_exists, m_nug, | 134 | def test_handle_no_cfg_and_default_root(self, m_path_exists, m_nug, |
96 | 107 | m_glob, m_setup_keys): | 135 | m_glob, m_setup_keys): |
97 | 108 | """Test handle with no config and a default distro user.""" | 136 | """Test handle with no config and a default distro user.""" |
98 | @@ -115,7 +143,7 @@ class TestHandleSsh(CiTestCase): | |||
99 | 115 | m_nug.return_value = ({user: {"default": user}}, {}) | 143 | m_nug.return_value = ({user: {"default": user}}, {}) |
100 | 116 | cloud = self.tmp_cloud( | 144 | cloud = self.tmp_cloud( |
101 | 117 | distro='ubuntu', metadata={'public-keys': keys}) | 145 | distro='ubuntu', metadata={'public-keys': keys}) |
103 | 118 | cc_ssh.handle("name", cfg, cloud, None, None) | 146 | cc_ssh.handle("name", cfg, cloud, LOG, None) |
104 | 119 | 147 | ||
105 | 120 | options = ssh_util.DISABLE_USER_OPTS.replace("$USER", user) | 148 | options = ssh_util.DISABLE_USER_OPTS.replace("$USER", user) |
106 | 121 | options = options.replace("$DISABLE_USER", "root") | 149 | options = options.replace("$DISABLE_USER", "root") |
107 | @@ -140,7 +168,7 @@ class TestHandleSsh(CiTestCase): | |||
108 | 140 | m_nug.return_value = ({user: {"default": user}}, {}) | 168 | m_nug.return_value = ({user: {"default": user}}, {}) |
109 | 141 | cloud = self.tmp_cloud( | 169 | cloud = self.tmp_cloud( |
110 | 142 | distro='ubuntu', metadata={'public-keys': keys}) | 170 | distro='ubuntu', metadata={'public-keys': keys}) |
112 | 143 | cc_ssh.handle("name", cfg, cloud, None, None) | 171 | cc_ssh.handle("name", cfg, cloud, LOG, None) |
113 | 144 | 172 | ||
114 | 145 | options = ssh_util.DISABLE_USER_OPTS.replace("$USER", user) | 173 | options = ssh_util.DISABLE_USER_OPTS.replace("$USER", user) |
115 | 146 | options = options.replace("$DISABLE_USER", "root") | 174 | options = options.replace("$DISABLE_USER", "root") |
116 | @@ -165,7 +193,7 @@ class TestHandleSsh(CiTestCase): | |||
117 | 165 | cloud = self.tmp_cloud( | 193 | cloud = self.tmp_cloud( |
118 | 166 | distro='ubuntu', metadata={'public-keys': keys}) | 194 | distro='ubuntu', metadata={'public-keys': keys}) |
119 | 167 | cloud.get_public_ssh_keys = mock.Mock(return_value=keys) | 195 | cloud.get_public_ssh_keys = mock.Mock(return_value=keys) |
121 | 168 | cc_ssh.handle("name", cfg, cloud, None, None) | 196 | cc_ssh.handle("name", cfg, cloud, LOG, None) |
122 | 169 | 197 | ||
123 | 170 | self.assertEqual([mock.call(set(keys), user), | 198 | self.assertEqual([mock.call(set(keys), user), |
124 | 171 | mock.call(set(keys), "root", options="")], | 199 | mock.call(set(keys), "root", options="")], |
125 | @@ -196,7 +224,7 @@ class TestHandleSsh(CiTestCase): | |||
126 | 196 | cfg = {} | 224 | cfg = {} |
127 | 197 | expected_call = [self.test_hostkeys[key_type] for key_type | 225 | expected_call = [self.test_hostkeys[key_type] for key_type |
128 | 198 | in ['ecdsa', 'ed25519', 'rsa']] | 226 | in ['ecdsa', 'ed25519', 'rsa']] |
130 | 199 | cc_ssh.handle("name", cfg, cloud, None, None) | 227 | cc_ssh.handle("name", cfg, cloud, LOG, None) |
131 | 200 | self.assertEqual([mock.call(expected_call)], | 228 | self.assertEqual([mock.call(expected_call)], |
132 | 201 | cloud.datasource.publish_host_keys.call_args_list) | 229 | cloud.datasource.publish_host_keys.call_args_list) |
133 | 202 | 230 | ||
134 | @@ -225,7 +253,7 @@ class TestHandleSsh(CiTestCase): | |||
135 | 225 | cfg = {'ssh_publish_hostkeys': {'enabled': True}} | 253 | cfg = {'ssh_publish_hostkeys': {'enabled': True}} |
136 | 226 | expected_call = [self.test_hostkeys[key_type] for key_type | 254 | expected_call = [self.test_hostkeys[key_type] for key_type |
137 | 227 | in ['ecdsa', 'ed25519', 'rsa']] | 255 | in ['ecdsa', 'ed25519', 'rsa']] |
139 | 228 | cc_ssh.handle("name", cfg, cloud, None, None) | 256 | cc_ssh.handle("name", cfg, cloud, LOG, None) |
140 | 229 | self.assertEqual([mock.call(expected_call)], | 257 | self.assertEqual([mock.call(expected_call)], |
141 | 230 | cloud.datasource.publish_host_keys.call_args_list) | 258 | cloud.datasource.publish_host_keys.call_args_list) |
142 | 231 | 259 | ||
143 | @@ -252,7 +280,7 @@ class TestHandleSsh(CiTestCase): | |||
144 | 252 | cloud.datasource.publish_host_keys = mock.Mock() | 280 | cloud.datasource.publish_host_keys = mock.Mock() |
145 | 253 | 281 | ||
146 | 254 | cfg = {'ssh_publish_hostkeys': {'enabled': False}} | 282 | cfg = {'ssh_publish_hostkeys': {'enabled': False}} |
148 | 255 | cc_ssh.handle("name", cfg, cloud, None, None) | 283 | cc_ssh.handle("name", cfg, cloud, LOG, None) |
149 | 256 | self.assertFalse(cloud.datasource.publish_host_keys.call_args_list) | 284 | self.assertFalse(cloud.datasource.publish_host_keys.call_args_list) |
150 | 257 | cloud.datasource.publish_host_keys.assert_not_called() | 285 | cloud.datasource.publish_host_keys.assert_not_called() |
151 | 258 | 286 | ||
152 | @@ -282,7 +310,7 @@ class TestHandleSsh(CiTestCase): | |||
153 | 282 | 'blacklist': ['dsa', 'rsa']}} | 310 | 'blacklist': ['dsa', 'rsa']}} |
154 | 283 | expected_call = [self.test_hostkeys[key_type] for key_type | 311 | expected_call = [self.test_hostkeys[key_type] for key_type |
155 | 284 | in ['ecdsa', 'ed25519']] | 312 | in ['ecdsa', 'ed25519']] |
157 | 285 | cc_ssh.handle("name", cfg, cloud, None, None) | 313 | cc_ssh.handle("name", cfg, cloud, LOG, None) |
158 | 286 | self.assertEqual([mock.call(expected_call)], | 314 | self.assertEqual([mock.call(expected_call)], |
159 | 287 | cloud.datasource.publish_host_keys.call_args_list) | 315 | cloud.datasource.publish_host_keys.call_args_list) |
160 | 288 | 316 | ||
161 | @@ -312,6 +340,6 @@ class TestHandleSsh(CiTestCase): | |||
162 | 312 | 'blacklist': []}} | 340 | 'blacklist': []}} |
163 | 313 | expected_call = [self.test_hostkeys[key_type] for key_type | 341 | expected_call = [self.test_hostkeys[key_type] for key_type |
164 | 314 | in ['dsa', 'ecdsa', 'ed25519', 'rsa']] | 342 | in ['dsa', 'ecdsa', 'ed25519', 'rsa']] |
166 | 315 | cc_ssh.handle("name", cfg, cloud, None, None) | 343 | cc_ssh.handle("name", cfg, cloud, LOG, None) |
167 | 316 | self.assertEqual([mock.call(expected_call)], | 344 | self.assertEqual([mock.call(expected_call)], |
168 | 317 | cloud.datasource.publish_host_keys.call_args_list) | 345 | cloud.datasource.publish_host_keys.call_args_list) |
169 | diff --git a/cloudinit/stages.py b/cloudinit/stages.py | |||
170 | index 5012988..18ca3b9 100644 | |||
171 | --- a/cloudinit/stages.py | |||
172 | +++ b/cloudinit/stages.py | |||
173 | @@ -549,7 +549,11 @@ class Init(object): | |||
174 | 549 | with events.ReportEventStack("consume-user-data", | 549 | with events.ReportEventStack("consume-user-data", |
175 | 550 | "reading and applying user-data", | 550 | "reading and applying user-data", |
176 | 551 | parent=self.reporter): | 551 | parent=self.reporter): |
178 | 552 | self._consume_userdata(frequency) | 552 | if util.get_cfg_option_bool(self.cfg, 'allow_userdata', True): |
179 | 553 | self._consume_userdata(frequency) | ||
180 | 554 | else: | ||
181 | 555 | LOG.debug('allow_userdata = False: discarding user-data') | ||
182 | 556 | |||
183 | 553 | with events.ReportEventStack("consume-vendor-data", | 557 | with events.ReportEventStack("consume-vendor-data", |
184 | 554 | "reading and applying vendor-data", | 558 | "reading and applying vendor-data", |
185 | 555 | parent=self.reporter): | 559 | parent=self.reporter): |
186 | diff --git a/doc/rtd/topics/format.rst b/doc/rtd/topics/format.rst | |||
187 | index 7605040..f9f4ba6 100644 | |||
188 | --- a/doc/rtd/topics/format.rst | |||
189 | +++ b/doc/rtd/topics/format.rst | |||
190 | @@ -196,6 +196,14 @@ Example | |||
191 | 196 | 196 | ||
192 | 197 | Also this `blog`_ post offers another example for more advanced usage. | 197 | Also this `blog`_ post offers another example for more advanced usage. |
193 | 198 | 198 | ||
194 | 199 | Disabling User-Data | ||
195 | 200 | =================== | ||
196 | 201 | |||
197 | 202 | Cloud-init can be configured to ignore any user-data provided to instance. | ||
198 | 203 | This allows custom images to prevent users from accidentally breaking closed | ||
199 | 204 | appliances. Setting ``allow_userdata: false`` in the configuration will disable | ||
200 | 205 | cloud-init from processing user-data. | ||
201 | 206 | |||
202 | 199 | .. [#] See your cloud provider for applicable user-data size limitations... | 207 | .. [#] See your cloud provider for applicable user-data size limitations... |
203 | 200 | .. _blog: http://foss-boss.blogspot.com/2011/01/advanced-cloud-init-custom-handlers.html | 208 | .. _blog: http://foss-boss.blogspot.com/2011/01/advanced-cloud-init-custom-handlers.html |
204 | 201 | .. vi: textwidth=78 | 209 | .. vi: textwidth=78 |
205 | diff --git a/tests/unittests/test_data.py b/tests/unittests/test_data.py | |||
206 | index 3efe7ad..97d1dc2 100644 | |||
207 | --- a/tests/unittests/test_data.py | |||
208 | +++ b/tests/unittests/test_data.py | |||
209 | @@ -524,6 +524,46 @@ c: 4 | |||
210 | 524 | self.assertEqual(cfg.get('password'), 'gocubs') | 524 | self.assertEqual(cfg.get('password'), 'gocubs') |
211 | 525 | self.assertEqual(cfg.get('locale'), 'chicago') | 525 | self.assertEqual(cfg.get('locale'), 'chicago') |
212 | 526 | 526 | ||
213 | 527 | @mock.patch('cloudinit.util.read_conf_with_confd') | ||
214 | 528 | def test_dont_allow_user_data(self, mock_cfg): | ||
215 | 529 | mock_cfg.return_value = {"allow_userdata": False} | ||
216 | 530 | |||
217 | 531 | # test that user-data is ignored but vendor-data is kept | ||
218 | 532 | user_blob = ''' | ||
219 | 533 | #cloud-config-jsonp | ||
220 | 534 | [ | ||
221 | 535 | { "op": "add", "path": "/baz", "value": "qux" }, | ||
222 | 536 | { "op": "add", "path": "/bar", "value": "qux2" } | ||
223 | 537 | ] | ||
224 | 538 | ''' | ||
225 | 539 | vendor_blob = ''' | ||
226 | 540 | #cloud-config-jsonp | ||
227 | 541 | [ | ||
228 | 542 | { "op": "add", "path": "/baz", "value": "quxA" }, | ||
229 | 543 | { "op": "add", "path": "/bar", "value": "quxB" }, | ||
230 | 544 | { "op": "add", "path": "/foo", "value": "quxC" } | ||
231 | 545 | ] | ||
232 | 546 | ''' | ||
233 | 547 | self.reRoot() | ||
234 | 548 | initer = stages.Init() | ||
235 | 549 | initer.datasource = FakeDataSource(user_blob, vendordata=vendor_blob) | ||
236 | 550 | initer.read_cfg() | ||
237 | 551 | initer.initialize() | ||
238 | 552 | initer.fetch() | ||
239 | 553 | initer.instancify() | ||
240 | 554 | initer.update() | ||
241 | 555 | initer.cloudify().run('consume_data', | ||
242 | 556 | initer.consume_data, | ||
243 | 557 | args=[PER_INSTANCE], | ||
244 | 558 | freq=PER_INSTANCE) | ||
245 | 559 | mods = stages.Modules(initer) | ||
246 | 560 | (_which_ran, _failures) = mods.run_section('cloud_init_modules') | ||
247 | 561 | cfg = mods.cfg | ||
248 | 562 | self.assertIn('vendor_data', cfg) | ||
249 | 563 | self.assertEqual('quxA', cfg['baz']) | ||
250 | 564 | self.assertEqual('quxB', cfg['bar']) | ||
251 | 565 | self.assertEqual('quxC', cfg['foo']) | ||
252 | 566 | |||
253 | 527 | 567 | ||
254 | 528 | class TestConsumeUserDataHttp(TestConsumeUserData, helpers.HttprettyTestCase): | 568 | class TestConsumeUserDataHttp(TestConsumeUserData, helpers.HttprettyTestCase): |
255 | 529 | 569 |
Hi Pavel, thanks for the contribution to cloud-init! I'm flitting between meetings, so I'm not able to assess whether these changes are broadly appropriate. That said, I do have an inline comment on how the changes could be expressed a little more concisely. I'll revisit this later today for a more full review.