Skip to content

Commit e3180a7

Browse files
aahallalAhmad Hallal
andauthored
Tighten CodeDeploy config file permissions (#10205)
Co-authored-by: Ahmad Hallal <aahallal@amazon.com>
1 parent af2c5fb commit e3180a7

3 files changed

Lines changed: 79 additions & 15 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "bugfix",
3+
"category": "codedeploy",
4+
"description": "Tighten file permissions for CodeDeploy configuration file"
5+
}

awscli/customizations/codedeploy/register.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313

14+
import os
1415
import sys
1516

1617
from awscli.customizations.codedeploy.systems import DEFAULT_CONFIG_FILE
@@ -162,13 +163,24 @@ def _create_config(self, params):
162163
f'Creating the on-premises instance configuration file named {DEFAULT_CONFIG_FILE}'
163164
'...'
164165
)
165-
with open(DEFAULT_CONFIG_FILE, 'w') as f:
166-
f.write(
167-
'---\n'
168-
f'region: {params.region}\n'
169-
f'iam_user_arn: {params.iam_user_arn}\n'
170-
f'aws_access_key_id: {params.access_key_id}\n'
171-
f'aws_secret_access_key: {params.secret_access_key}\n'
166+
try:
167+
fd = os.open(
168+
DEFAULT_CONFIG_FILE,
169+
os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
170+
0o600,
171+
)
172+
with os.fdopen(fd, 'w') as f:
173+
os.chmod(DEFAULT_CONFIG_FILE, 0o600)
174+
f.write(
175+
'---\n'
176+
f'region: {params.region}\n'
177+
f'iam_user_arn: {params.iam_user_arn}\n'
178+
f'aws_access_key_id: {params.access_key_id}\n'
179+
f'aws_secret_access_key: {params.secret_access_key}\n'
180+
)
181+
except OSError as e:
182+
raise RuntimeError(
183+
f'Failed to create config file {DEFAULT_CONFIG_FILE}: {e}'
172184
)
173185
sys.stdout.write('DONE\n')
174186

tests/unit/customizations/codedeploy/test_register.py

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,22 @@ def setUp(self):
5555
self.globals.endpoint_url = self.endpoint_url
5656
self.globals.verify_ssl = False
5757

58-
self.open_patcher = mock.patch(
59-
'awscli.customizations.codedeploy.register.open',
58+
self.os_open_patcher = mock.patch(
59+
'awscli.customizations.codedeploy.register.os.open',
60+
return_value=3,
61+
)
62+
self.os_open = self.os_open_patcher.start()
63+
64+
self.os_fdopen_patcher = mock.patch(
65+
'awscli.customizations.codedeploy.register.os.fdopen',
6066
mock.mock_open(),
61-
create=True,
6267
)
63-
self.open = self.open_patcher.start()
68+
self.os_fdopen = self.os_fdopen_patcher.start()
69+
70+
self.os_chmod_patcher = mock.patch(
71+
'awscli.customizations.codedeploy.register.os.chmod'
72+
)
73+
self.os_chmod = self.os_chmod_patcher.start()
6474

6575
self.codedeploy = mock.MagicMock()
6676

@@ -80,7 +90,9 @@ def setUp(self):
8090
self.register = Register(self.session)
8191

8292
def tearDown(self):
83-
self.open_patcher.stop()
93+
self.os_open_patcher.stop()
94+
self.os_fdopen_patcher.stop()
95+
self.os_chmod_patcher.stop()
8496

8597
def test_register_throws_on_invalid_region(self):
8698
self.globals.region = None
@@ -149,8 +161,13 @@ def test_register_with_no_iam_user_arn(self):
149161
self.assertEqual(self.policy_name, self.args.policy_name)
150162
self.assertIn('policy_document', self.args)
151163
self.assertEqual(self.policy_document, self.args.policy_document)
152-
self.open.assert_called_with(self.config_file, 'w')
153-
self.open().write.assert_called_with(
164+
self.os_open.assert_called_with(
165+
self.config_file,
166+
mock.ANY,
167+
0o600,
168+
)
169+
self.os_fdopen.assert_called_with(3, 'w')
170+
self.os_fdopen().write.assert_called_with(
154171
'---\n'
155172
f'region: {self.region}\n'
156173
f'iam_user_arn: {self.iam_user_arn}\n'
@@ -167,7 +184,7 @@ def test_register_with_iam_user_arn(self):
167184
self.assertFalse(self.register.iam.create_user.called)
168185
self.assertFalse(self.register.iam.create_access_key.called)
169186
self.assertFalse(self.register.iam.put_user_policy.called)
170-
self.assertFalse(self.open.called)
187+
self.assertFalse(self.os_open.called)
171188
self.register.codedeploy.register_on_premises_instance.assert_called_with(
172189
instanceName=self.instance_name, iamUserArn=self.iam_user_arn
173190
)
@@ -192,6 +209,36 @@ def test_register_with_tags(self):
192209
tags=self.tags, instanceNames=[self.instance_name]
193210
)
194211

212+
def test_create_config_raises_runtime_error_on_open_failure(self):
213+
self.args.iam_user_arn = None
214+
self.os_open.side_effect = OSError('permission denied')
215+
with self.assertRaisesRegex(
216+
RuntimeError, 'Failed to create config file'
217+
):
218+
self.register._create_config(self.args)
219+
220+
def test_create_config_raises_runtime_error_on_chmod_failure(self):
221+
self.args.iam_user_arn = None
222+
self.os_chmod.side_effect = OSError('permission denied')
223+
with self.assertRaisesRegex(
224+
RuntimeError, 'Failed to create config file'
225+
):
226+
self.register._create_config(self.args)
227+
228+
def test_create_config_uses_restricted_permissions(self):
229+
self.args.iam_user_arn = None
230+
self.register._run_main(self.args, self.globals)
231+
self.os_open.assert_called_with(
232+
self.config_file,
233+
mock.ANY,
234+
0o600,
235+
)
236+
237+
def test_create_config_chmods_existing_file(self):
238+
self.args.iam_user_arn = None
239+
self.register._run_main(self.args, self.globals)
240+
self.os_chmod.assert_called_with(self.config_file, 0o600)
241+
195242

196243
if __name__ == "__main__":
197244
unittest.main()

0 commit comments

Comments
 (0)