Repository navigation
CLDSRV-1012: Backport KMS error and socket leak on DataWrapper #6322
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b004c1b
dc96edd
5035303
a5b4c80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -1,5 +1,6 @@ | ||||
| const { errors, s3middleware } = require('arsenal'); | ||||
| const { parseRange } = require('arsenal').network.http.utils; | ||||
| const async = require('async'); | ||||
|
|
||||
| const { data } = require('../data/wrapper'); | ||||
|
|
||||
|
|
@@ -12,6 +13,7 @@ const setPartRanges = require('./apiUtils/object/setPartRanges'); | |||
| const { standardMetadataValidateBucketAndObj } = require('../metadata/metadataUtils'); | ||||
| const { getPartCountFromMd5 } = require('./apiUtils/object/partInfo'); | ||||
| const { setExpirationHeaders } = require('./apiUtils/object/expirationHeaders'); | ||||
| const kms = require('../kms/wrapper'); | ||||
|
|
||||
| const validateHeaders = s3middleware.validateConditionalHeaders; | ||||
|
|
||||
|
|
@@ -211,24 +213,57 @@ function objectGet(authInfo, request, returnTagCount, log, callback) { | |||
| dataLocator = setPartRanges(dataLocator, byteRange); | ||||
| } | ||||
| } | ||||
| return data.head(dataLocator, log, err => { | ||||
| if (err) { | ||||
| log.error('error from external backend checking for ' + | ||||
| 'object existence', { error: err }); | ||||
| return callback(err); | ||||
| // Check KMS Key access and usability before checking data | ||||
| // diff with AWS: for empty object (no dataLocator) KMS not checked | ||||
| return async.each(dataLocator || [], | ||||
| (objectGetInfo, next) => { | ||||
| if (!objectGetInfo.cipheredDataKey) { | ||||
| return next(); | ||||
| } | ||||
| const serverSideEncryption = { | ||||
| cryptoScheme: objectGetInfo.cryptoScheme, | ||||
| masterKeyId: objectGetInfo.masterKeyId, | ||||
| cipheredDataKey: Buffer.from( | ||||
| objectGetInfo.cipheredDataKey, 'base64'), | ||||
| }; | ||||
| const offset = objectGetInfo.range ? objectGetInfo.range[0] : 0; | ||||
| return kms.createDecipherBundle(serverSideEncryption, | ||||
| offset, log, (err, decipherBundle) => { | ||||
| if (err) { | ||||
| log.error('cannot get decipher bundle from kms', | ||||
| { method: 'objectGet' }); | ||||
| return next(err); | ||||
| } | ||||
| // eslint-disable-next-line no-param-reassign | ||||
| objectGetInfo.decipherStream = decipherBundle.decipher; | ||||
| return next(); | ||||
| }); | ||||
| }, | ||||
| err => { | ||||
| if (err) { | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question: if one part's Also, would it make sense to add a cloudserver unit test for "KMS error is returned before any data is read", for example with a stubbed
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current fix is related to an HTTP request keeping a socket to sproxyd. The decipher stream has no socket to external service, it's a Line 124 in 17977b1
https://nodejs.org/api/crypto.html#cryptocreatedecipherivalgorithm-key-iv-options It's in memory Transform with no socket involved. So it gets garbage collected. JS memory is very light on Transform but that crypto does hold for OpenSSL some C++ native memory. So it would be better to clean them ASAP instead of waiting for GC or it might create native memory OOM in some conditions. I'll likely open another PR / ticket to fix those 2 issues present in all versions of cloudserver out of this backport. And I'll add some tests as well if they don't exist |
||||
| return callback(err); | ||||
| } | ||||
|
|
||||
| return data.head(dataLocator, log, err => { | ||||
| if (err) { | ||||
| log.error('error from external backend checking for ' + | ||||
| 'object existence', { error: err }); | ||||
| return callback(err); | ||||
| } | ||||
| pushMetric('getObject', log, { | ||||
| authInfo, | ||||
| bucket: bucketName, | ||||
| keys: [objectKey], | ||||
| newByteLength: | ||||
| Number.parseInt(responseMetaHeaders['Content-Length'], 10), | ||||
| versionId: objMD.versionId, | ||||
| location: objMD.dataStoreName, | ||||
| }); | ||||
| return callback(null, dataLocator, responseMetaHeaders, | ||||
| byteRange); | ||||
| }); | ||||
| } | ||||
| pushMetric('getObject', log, { | ||||
| authInfo, | ||||
| bucket: bucketName, | ||||
| keys: [objectKey], | ||||
| newByteLength: | ||||
| Number.parseInt(responseMetaHeaders['Content-Length'], 10), | ||||
| versionId: objMD.versionId, | ||||
| location: objMD.dataStoreName, | ||||
| }); | ||||
| return callback(null, dataLocator, responseMetaHeaders, | ||||
| byteRange); | ||||
| }); | ||||
| ); | ||||
| }); | ||||
| } | ||||
|
|
||||
|
|
||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit/question:
async.eachfires one KMS call per location entry, all in parallel. For an MPU object with thousands of parts, that is thousands of concurrent requests to the KMS on a single GET. Could we useasync.eachLimitwith a small cap (5 or 10) so one big object cannot flood the KMS?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, and this has been shipped like that since more than 1 year in 9.5.0. This needs to be fixed in all versions.
I could even dedup the keys to avoid querying the same key multiple times as for MPU it's likely all parts use the same master key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created ticket https://scality.atlassian.net/browse/CLDSRV-1020 for that.