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
6 changes: 3 additions & 3 deletions apps/files_external/lib/Lib/Storage/AmazonS3.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ private function headObject(string $key): array|false {
$this->objectCache[$key] = $this->getConnection()->headObject([
'Bucket' => $this->bucket,
'Key' => $key
] + $this->getSSECParameters())->toArray();
] + $this->getServerSideEncryptionParameters())->toArray();
} catch (S3Exception $e) {
if ($e->getStatusCode() >= 500) {
throw $e;
Expand Down Expand Up @@ -210,7 +210,7 @@ public function mkdir(string $path): bool {
'Key' => $path . '/',
'Body' => '',
'ContentType' => FileInfo::MIMETYPE_FOLDER
] + $this->getSSECParameters());
] + $this->getServerSideEncryptionParameters());
$this->testTimeout();
} catch (S3Exception $e) {
$this->logger->error($e->getMessage(), [
Expand Down Expand Up @@ -513,7 +513,7 @@ public function touch(string $path, ?int $mtime = null): bool {
'Body' => '',
'ContentType' => $mimeType,
'MetadataDirective' => 'REPLACE',
] + $this->getSSECParameters());
] + $this->getServerSideEncryptionParameters());
$this->testTimeout();
} catch (S3Exception $e) {
$this->logger->error($e->getMessage(), [
Expand Down
14 changes: 7 additions & 7 deletions lib/private/Files/ObjectStore/S3.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function initiateMultipartUpload(string $urn): string {
$upload = $this->getConnection()->createMultipartUpload([
'Bucket' => $this->bucket,
'Key' => $urn,
] + $this->getSSECParameters());
] + $this->getServerSideEncryptionParameters());
$uploadId = $upload->get('UploadId');
if ($uploadId === null) {
throw new Exception('No upload id returned');
Expand All @@ -50,7 +50,7 @@ public function uploadMultipartPart(string $urn, string $uploadId, int $partId,
'ContentLength' => $size,
'PartNumber' => $partId,
'UploadId' => $uploadId,
] + $this->getSSECParameters());
] + $this->getServerSideEncryptionParameters());
}

public function getMultipartUploads(string $urn, string $uploadId): array {
Expand All @@ -65,7 +65,7 @@ public function getMultipartUploads(string $urn, string $uploadId): array {
'UploadId' => $uploadId,
'MaxParts' => 1000,
'PartNumberMarker' => $partNumberMarker,
] + $this->getSSECParameters());
] + $this->getServerSideEncryptionParameters());
$parts = array_merge($parts, $result->get('Parts') ?? []);
$isTruncated = $result->get('IsTruncated');
$partNumberMarker = $result->get('NextPartNumberMarker');
Expand All @@ -80,11 +80,11 @@ public function completeMultipartUpload(string $urn, string $uploadId, array $re
'Key' => $urn,
'UploadId' => $uploadId,
'MultipartUpload' => ['Parts' => $result],
] + $this->getSSECParameters());
] + $this->getServerSideEncryptionParameters());
$stat = $this->getConnection()->headObject([
'Bucket' => $this->bucket,
'Key' => $urn,
] + $this->getSSECParameters());
] + $this->getServerSideEncryptionParameters());
return (int)$stat->get('ContentLength');
}

Expand Down Expand Up @@ -113,7 +113,7 @@ public function getObjectMetaData(string $urn): array {
$object = $this->getConnection()->headObject([
'Bucket' => $this->bucket,
'Key' => $urn
] + $this->getSSECParameters())->toArray();
] + $this->getServerSideEncryptionParameters())->toArray();
return [
'mtime' => $object['LastModified'],
'etag' => trim($object['ETag'], '"'),
Expand All @@ -125,7 +125,7 @@ public function listObjects(string $prefix = ''): \Iterator {
$results = $this->getConnection()->getPaginator('ListObjectsV2', [
'Bucket' => $this->bucket,
'Prefix' => $prefix,
] + $this->getSSECParameters());
] + $this->getServerSideEncryptionParameters());

foreach ($results as $result) {
if (is_array($result['Contents'])) {
Expand Down
74 changes: 74 additions & 0 deletions lib/private/Files/ObjectStore/S3ConnectionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,80 @@ protected function getSSECParameters(bool $copy = false): array {
];
}

/**
* Get SSE-KMS key ID from configuration
* @return string|null KMS key ARN/ID or null for bucket default key
*/
protected function getSSEKMSKeyId(): ?string {
if (isset($this->params['sse_kms_key_id']) && !empty($this->params['sse_kms_key_id'])) {
return $this->params['sse_kms_key_id'];
}
return null;
}

/**
* Check if SSE-KMS is enabled
* @return bool
*/
protected function isSSEKMSEnabled(): bool {
return !empty($this->params['sse_kms_enabled']) && $this->params['sse_kms_enabled'] === true;
}

/**
* Get SSE-KMS parameters for S3 operations
*
* When SSE-KMS is enabled, AWS S3 encrypts objects server-side using
* AWS Key Management Service (KMS) keys. This provides:
* - Centralized key management via AWS KMS
* - Audit trail of key usage
* - No client-side encryption overhead
* - Automatic key rotation support
*
* @param bool $copy Whether this is for a copy operation (unused for KMS)
* @return array Parameters to merge into S3 API calls
*/
protected function getSSEKMSParameters(bool $copy = false): array {
if (!$this->isSSEKMSEnabled()) {
return [];
}

$params = [
'ServerSideEncryption' => 'aws:kms',
];

// Add specific KMS key if configured, otherwise use bucket default key
$keyId = $this->getSSEKMSKeyId();
if ($keyId !== null) {
$params['SSEKMSKeyId'] = $keyId;
}

// Note: For copy operations, S3 re-encrypts with the destination key
// No special source parameters needed (unlike SSE-C)

return $params;
}

/**
* Get unified server-side encryption parameters
*
* Supports both SSE-C (customer-provided keys) and SSE-KMS (AWS-managed keys).
* SSE-C takes precedence if both are configured (for backward compatibility
* during migration from SSE-C to SSE-KMS).
*
* @param bool $copy Whether this is for a copy operation
* @return array Encryption parameters to merge into S3 API calls
*/
protected function getServerSideEncryptionParameters(bool $copy = false): array {
// SSE-C takes precedence for backward compatibility during migration
$sseC = $this->getSSECParameters($copy);
if (!empty($sseC)) {
return $sseC;
}

// Fall back to SSE-KMS if enabled
return $this->getSSEKMSParameters($copy);
}

public function isUsePresignedUrl(): bool {
return $this->usePresignedUrl;
}
Expand Down
15 changes: 8 additions & 7 deletions lib/private/Files/ObjectStore/S3ObjectTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ abstract protected function getConnection();

abstract protected function getCertificateBundlePath(): ?string;
abstract protected function getSSECParameters(bool $copy = false): array;
abstract protected function getServerSideEncryptionParameters(bool $copy = false): array;

/**
* @param string $urn the unified resource name used to identify the object
Expand All @@ -45,7 +46,7 @@ public function readObject($urn) {
'Bucket' => $this->bucket,
'Key' => $urn,
'Range' => 'bytes=' . $range,
] + $this->getSSECParameters());
] + $this->getServerSideEncryptionParameters());
$request = \Aws\serialize($command);
$headers = [];
foreach ($request->getHeaders() as $key => $values) {
Expand Down Expand Up @@ -113,7 +114,7 @@ protected function writeSingle(string $urn, StreamInterface $stream, array $meta
'ContentType' => $mimetype,
'Metadata' => $this->buildS3Metadata($metaData),
'StorageClass' => $this->storageClass,
] + $this->getSSECParameters();
] + $this->getServerSideEncryptionParameters();

if ($size = $stream->getSize()) {
$args['ContentLength'] = $size;
Expand Down Expand Up @@ -156,7 +157,7 @@ protected function writeMultiPart(string $urn, StreamInterface $stream, array $m
'ContentType' => $mimetype,
'Metadata' => $this->buildS3Metadata($metaData),
'StorageClass' => $this->storageClass,
] + $this->getSSECParameters(),
] + $this->getServerSideEncryptionParameters(),
'before_upload' => function (Command $command) use (&$totalWritten) {
$totalWritten += $command['ContentLength'];
},
Expand Down Expand Up @@ -266,14 +267,14 @@ public function deleteObject($urn) {
}

public function objectExists($urn) {
return $this->getConnection()->doesObjectExist($this->bucket, $urn, $this->getSSECParameters());
return $this->getConnection()->doesObjectExist($this->bucket, $urn, $this->getServerSideEncryptionParameters());
}

public function copyObject($from, $to, array $options = []) {
$sourceMetadata = $this->getConnection()->headObject([
'Bucket' => $this->getBucket(),
'Key' => $from,
] + $this->getSSECParameters());
] + $this->getServerSideEncryptionParameters());

$size = (int)($sourceMetadata->get('Size') ?? $sourceMetadata->get('ContentLength'));

Expand All @@ -285,13 +286,13 @@ public function copyObject($from, $to, array $options = []) {
'bucket' => $this->getBucket(),
'key' => $to,
'acl' => 'private',
'params' => $this->getSSECParameters() + $this->getSSECParameters(true),
'params' => $this->getServerSideEncryptionParameters() + $this->getServerSideEncryptionParameters(true),
'source_metadata' => $sourceMetadata
], $options));
$copy->copy();
} else {
$this->getConnection()->copy($this->getBucket(), $from, $this->getBucket(), $to, 'private', array_merge([
'params' => $this->getSSECParameters() + $this->getSSECParameters(true),
'params' => $this->getServerSideEncryptionParameters() + $this->getServerSideEncryptionParameters(true),
'mup_threshold' => PHP_INT_MAX,
], $options));
}
Expand Down
Loading
Loading