Skip to content

Commit 82ec643

Browse files
authored
Support private S3 buckets (#923)
1 parent c842373 commit 82ec643

File tree

12 files changed

+352
-22
lines changed

12 files changed

+352
-22
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## master
44

5+
## 2.0.2
6+
- Support private ACL for S3 buckets [#923](https://github.com/mapbox/node-pre-gyp/pull/923)
7+
58
## 2.0.1
69
- Update abi_crosswalk.json for abi 137 / node 24 (https://github.com/mapbox/node-pre-gyp/pull/904)
710

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Options include:
8585
- `--target=0.4.0`: Pass the target node or node-webkit version to compile against
8686
- `--target_arch=ia32`: Pass the target arch and override the host `arch`. Any value that is [supported by Node.js](https://nodejs.org/api/os.html#osarch) is valid.
8787
- `--target_platform=win32`: Pass the target platform and override the host `platform`. Valid values are `linux`, `darwin`, `win32`, `sunos`, `freebsd`, `openbsd`, and `aix`.
88+
- `--acl=<acl>`: Set the S3 ACL when publishing binaries (e.g., `public-read`, `private`). Overrides the `binary.acl` setting in package.json.
8889

8990
Both `--build-from-source` and `--fallback-to-build` can be passed alone or they can provide values. You can pass `--fallback-to-build=false` to override the option as declared in package.json. In addition to being able to pass `--build-from-source` you can also pass `--build-from-source=myapp` where `myapp` is the name of your module.
9091

@@ -185,6 +186,33 @@ Your S3 server region.
185186

186187
Set `s3ForcePathStyle` to true if the endpoint url should not be prefixed with the bucket name. If false (default), the server endpoint would be constructed as `bucket_name.your_server.com`.
187188

189+
###### acl
190+
191+
The S3 Access Control List (ACL) to apply when publishing binaries. Defaults to `'public-read'` for backward compatibility. Common values include:
192+
193+
- `public-read` - (default) Binary is publicly accessible by anyone
194+
- `private` - Binary requires AWS credentials to download
195+
- `authenticated-read` - Any authenticated AWS user can download
196+
- `bucket-owner-read` - Bucket owner gets READ access
197+
- `bucket-owner-full-control` - Bucket owner gets FULL_CONTROL access
198+
199+
**For private binaries:**
200+
- Users installing your package will need AWS credentials configured (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables)
201+
- The `aws-sdk` package must be available at install time
202+
- If authentication fails, node-pre-gyp will fall back to building from source (if `--fallback-to-build` is specified)
203+
204+
You can also specify the ACL via command-line flag: `node-pre-gyp publish --acl=private`
205+
206+
Example for private binaries:
207+
```json
208+
"binary": {
209+
"module_name": "your_module",
210+
"module_path": "./lib/binding/",
211+
"host": "https://your-bucket.s3.us-east-1.amazonaws.com",
212+
"acl": "private"
213+
}
214+
```
215+
188216
##### The `binary` object has optional properties
189217

190218
###### remote_path

lib/install.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const log = require('./util/log.js');
1010
const existsAsync = fs.exists || path.exists;
1111
const versioning = require('./util/versioning.js');
1212
const napi = require('./util/napi.js');
13+
const s3_setup = require('./util/s3_setup.js');
14+
const url = require('url');
1315
// for fetching binaries
1416
const fetch = require('node-fetch');
1517
const tar = require('tar');
@@ -23,6 +25,65 @@ try {
2325
// do nothing
2426
}
2527

28+
function place_binary_authenticated(opts, targetDir, callback) {
29+
log.info('install', 'Attempting authenticated S3 download');
30+
31+
// Check if AWS credentials are available
32+
if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) {
33+
const err = new Error('Binary is private but AWS credentials not found. Please configure AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables, or use --fallback-to-build to compile from source.');
34+
err.statusCode = 403;
35+
return callback(err);
36+
}
37+
38+
try {
39+
const config = s3_setup.detect(opts);
40+
const s3 = s3_setup.get_s3(config);
41+
const key_name = url.resolve(config.prefix, opts.package_name);
42+
43+
log.info('install', 'Downloading from S3:', config.bucket, key_name);
44+
45+
const s3_opts = {
46+
Bucket: config.bucket,
47+
Key: key_name
48+
};
49+
50+
s3.getObject(s3_opts, (err, data) => {
51+
if (err) {
52+
log.error('install', 'Authenticated S3 download failed:', err.message);
53+
return callback(err);
54+
}
55+
56+
log.info('install', 'Authenticated download successful, extracting...');
57+
58+
const { Readable } = require('stream');
59+
const dataStream = Readable.from(data.Body);
60+
61+
let extractions = 0;
62+
const countExtractions = (entry) => {
63+
extractions += 1;
64+
log.info('install', `unpacking ${entry.path}`);
65+
};
66+
67+
dataStream.pipe(extract(targetDir, countExtractions))
68+
.on('error', (e) => {
69+
callback(e);
70+
})
71+
.on('close', () => {
72+
log.info('install', `extracted file count: ${extractions}`);
73+
callback();
74+
});
75+
});
76+
} catch (e) {
77+
if (e.code === 'MODULE_NOT_FOUND' && e.message.includes('aws-sdk')) {
78+
const err = new Error('Binary is private and requires aws-sdk for authenticated download. Please run: npm install aws-sdk');
79+
err.statusCode = 403;
80+
return callback(err);
81+
}
82+
log.error('install', 'Error setting up authenticated download:', e.message);
83+
callback(e);
84+
}
85+
}
86+
2687
function place_binary(uri, targetDir, opts, callback) {
2788
log.log('GET', uri);
2889

@@ -63,6 +124,14 @@ function place_binary(uri, targetDir, opts, callback) {
63124
fetch(sanitized, { agent })
64125
.then((res) => {
65126
if (!res.ok) {
127+
// If we get 403 Forbidden, the binary might be private - try authenticated download
128+
if (res.status === 403) {
129+
log.info('install', 'Received 403 Forbidden - attempting authenticated download');
130+
// Call place_binary_authenticated and return a special marker
131+
// to prevent the promise chain from calling callback again
132+
place_binary_authenticated(opts, targetDir, callback);
133+
return { authenticated: true };
134+
}
66135
throw new Error(`response status ${res.status} ${res.statusText} on ${sanitized}`);
67136
}
68137
const dataStream = res.body;
@@ -87,6 +156,9 @@ function place_binary(uri, targetDir, opts, callback) {
87156
});
88157
})
89158
.then((text) => {
159+
if (text && text.authenticated) {
160+
return; // Don't call callback - place_binary_authenticated will handle it
161+
}
90162
log.info(text);
91163
callback();
92164
})

lib/mock/s3.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ function s3_mock() {
3636
},
3737
putObject(params, callback) {
3838
return s3.putObject(params, wcb(callback));
39+
},
40+
getObject(params, callback) {
41+
return s3.getObject(params, wcb(callback));
3942
}
4043
};
4144
}

lib/node-pre-gyp.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ proto.configDefs = {
9898
debug: Boolean, // 'build'
9999
directory: String, // bin
100100
proxy: String, // 'install'
101-
loglevel: String // everywhere
101+
loglevel: String, // everywhere
102+
acl: String // 'publish' - S3 ACL for published binaries
102103
};
103104

104105
/**

lib/publish.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,13 @@ function publish(gyp, argv, callback) {
4343
// the object does not already exist
4444
log.info('publish', 'Preparing to put object');
4545
const s3_put_opts = {
46-
ACL: 'public-read',
46+
ACL: opts.acl,
4747
Body: fs.createReadStream(tarball),
4848
Key: key_name,
4949
Bucket: config.bucket
5050
};
51-
log.info('publish', 'Putting object', s3_put_opts.ACL, s3_put_opts.Bucket, s3_put_opts.Key);
51+
log.info('publish', 'Putting object with ACL:', s3_put_opts.ACL);
52+
log.info('publish', 'Bucket:', s3_put_opts.Bucket, 'Key:', s3_put_opts.Key);
5253
try {
5354
s3.putObject(s3_put_opts, (err2, resp) => {
5455
log.info('publish', 'returned from putting object');

lib/util/s3_setup.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ module.exports.get_s3 = function(config) {
8484
},
8585
putObject(params, callback) {
8686
return s3.putObject(params, callback);
87+
},
88+
getObject(params, callback) {
89+
return s3.getObject(params, callback);
8790
}
8891
};
8992
};

lib/util/versioning.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,8 @@ module.exports.evaluate = function(package_json, options, napi_build_version) {
307307
toolset: options.toolset || '', // address https://github.com/mapbox/node-pre-gyp/issues/119
308308
bucket: package_json.binary.bucket,
309309
region: package_json.binary.region,
310-
s3ForcePathStyle: package_json.binary.s3ForcePathStyle || false
310+
s3ForcePathStyle: package_json.binary.s3ForcePathStyle || false,
311+
acl: options.acl || package_json.binary.acl || 'public-read'
311312
};
312313
// support host mirror with npm config `--{module_name}_binary_host_mirror`
313314
// e.g.: https://github.com/node-inspector/v8-profiler/blob/master/package.json#L25

package-lock.json

Lines changed: 14 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@mapbox/node-pre-gyp",
33
"description": "Node.js native addon binary install tool",
4-
"version": "2.0.1",
4+
"version": "2.0.2",
55
"keywords": [
66
"native",
77
"addon",
@@ -61,5 +61,8 @@
6161
"test": "tape test/*test.js",
6262
"test:s3": "tape test/s3.test.js",
6363
"bucket": "node scripts/set-bucket.js"
64+
},
65+
"overrides": {
66+
"js-yaml": "^3.14.2"
6467
}
6568
}

0 commit comments

Comments
 (0)