Skip to content
5 changes: 5 additions & 0 deletions .changes/next-release/bugfix-s3streamingoutput-59585.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "bugfix",
"category": "s3, streaming output",
"description": "Output files created by S3 Select and streaming output commands are now created with owner-only permissions (0600). Existing files are also tightened to 0600 when overwritten."
}
5 changes: 4 additions & 1 deletion awscli/customizations/s3events.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"""Add S3 specific event streaming output arg."""

import os
import stat

from awscli.arguments import CustomArgument

Expand Down Expand Up @@ -124,7 +125,9 @@ def save_file(self, parsed, **kwargs):
fd = os.open(
self._output_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600
)
os.chmod(self._output_file, 0o600)
if stat.S_ISREG(os.fstat(fd).st_mode):
# Only chmod regular files; skip special files like /dev/null
os.chmod(self._output_file, 0o600)
with os.fdopen(fd, 'wb') as fp:
for event in event_stream:
if 'Records' in event:
Expand Down
5 changes: 4 additions & 1 deletion awscli/customizations/streamingoutputarg.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import os
import stat

from botocore.model import Shape

Expand Down Expand Up @@ -109,7 +110,9 @@ def save_file(self, parsed, **kwargs):
fd = os.open(
self._output_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600
)
os.chmod(self._output_file, 0o600)
if stat.S_ISREG(os.fstat(fd).st_mode):
# Only chmod regular files; skip special files like /dev/null
os.chmod(self._output_file, 0o600)
with os.fdopen(fd, 'wb') as fp:
data = body.read(buffer_size)
while data:
Expand Down
21 changes: 21 additions & 0 deletions tests/functional/s3api/test_select_object_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,27 @@ def test_output_file_permissions(self):
# Mask file type bits to isolate permission bits (rwxrwxrwx)
self.assertEqual(os.stat(filename).st_mode & 0o777, 0o600)

@skip_if_windows('chmod is not supported on Windows')
def test_output_does_not_chmod_non_regular_files(self):
cmdline = self.prefix + [
'--bucket',
'mybucket',
'--key',
'mykey',
'--expression',
'SELECT * FROM S3Object',
'--expression-type',
'SQL',
'--input-serialization',
'{"CSV": {}}',
'--output-serialization',
'{"CSV": {}}',
'/dev/null',
]
original_mode = os.stat('/dev/null').st_mode & 0o777
self.assert_params_for_cmd(cmdline, ignore_params=True)
self.assertEqual(os.stat('/dev/null').st_mode & 0o777, original_mode)

def test_errors_are_propagated(self):
self.http_response.status_code = 400
self.parsed_response = {
Expand Down
14 changes: 14 additions & 0 deletions tests/functional/test_streaming_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,17 @@ def test_streaming_output_file_permissions(self):
self.assert_params_for_cmd(cmdline % outpath, ignore_params=True)
# Mask file type bits to isolate permission bits (rwxrwxrwx)
self.assertEqual(os.stat(outpath).st_mode & 0o777, 0o600)

@skip_if_windows('chmod is not supported on Windows')
def test_streaming_output_does_not_chmod_non_regular_files(self):
cmdline = (
'kinesis-video-media get-media --stream-name test-stream '
'--start-selector StartSelectorType=EARLIEST %s'
)
self.parsed_response = {
'ContentType': 'video/webm',
'Payload': BytesIO(b'testbody'),
}
original_mode = os.stat('/dev/null').st_mode & 0o777
self.assert_params_for_cmd(cmdline % '/dev/null', ignore_params=True)
self.assertEqual(os.stat('/dev/null').st_mode & 0o777, original_mode)
Loading