Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/next-release/bugfix-s3streamingoutput-73196.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 @@ -123,7 +124,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