electron-builder: [ERR] Auto-update raises access denied when acl is set to private for S3

  • 19.47.1:
  • 2.16.3
  • Linux, AppImage, x64, Fedora 27:

Hi! I’m working on getting publishing for linux + auto update for AppImage to work with private s3 bucket and private AppImage. I have a very simple app that print its version… and that’s about it. When I use S3Options and I set acl : public-read everything works perfect. I’m also able to publish to the private s3 bucket and I’m able to set acl: private. However, I get an error access denied when I run the app and the app wants to check for updates. I have configured ~/.aws/credentials, I even tried setting env vars, nothing helps. Here is my log

[fedora@localhost electron-update-example]$ dist/simple-electron-0.0.1-x86_64.AppImage 
installed: X-AppImage-BuildId=eb49d8b0-d51e-11a7-213a-0b6d859a326c image: X-AppImage-BuildId=bda05140-d8da-11a7-1864-9feaf57d15d2
[10:22:32.809] [info] App starting...
[10:22:33.480] [info] Checking for update
[10:22:33.481] [info] Checking for update...
[10:22:34.684] [error] Error: HttpError: 403 Forbidden
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>CBC7496C38F832C2</RequestId><HostId>ss8I01KR+SqYTft6Rcq78XrSdLPwAqPrW4T9FU7UykqGk6a0Dvdj49nzv2a/zwcRsD8tyTNfM7A=</HostId></Error>"
Headers: {
  "content-type": [
    "application/xml"
  ],
  "date": [
    "Mon, 04 Dec 2017 10:34:29 GMT"
  ],
  "server": [
    "AmazonS3"
  ],
  "transfer-encoding": [
    "chunked"
  ],
  "x-amz-id-2": [
    "ss8I01KR+SqYTft6Rcq78XrSdLPwAqPrW4T9FU7UykqGk6a0Dvdj49nzv2a/zwcRsD8tyTNfM7A="
  ],
  "x-amz-request-id": [
    "CBC7496C38F832C2"
  ]
}
    at createHttpError (/tmp/.mount_simplejtlBEG/app/resources/app.asar/node_modules/builder-util-runtime/src/httpExecutor.ts:29:10)
    at IncomingMessage.response.on (/tmp/.mount_simplejtlBEG/app/resources/app.asar/node_modules/builder-util-runtime/src/httpExecutor.ts:146:18)
    at emitNone (events.js:86:13)
    at IncomingMessage.emit (events.js:188:7)
    at endReadableNT (_stream_readable.js:975:12)
    at _combinedTickCallback (internal/process/next_tick.js:80:11)
    at process._tickCallback (internal/process/next_tick.js:104:9)
From previous event:
    at CancellationToken.createPromise (/tmp/.mount_simplejtlBEG/app/resources/app.asar/node_modules/builder-util-runtime/src/CancellationToken.ts:51:5)
    at ElectronHttpExecutor.doApiRequest (/tmp/.mount_simplejtlBEG/app/resources/app.asar/node_modules/builder-util-runtime/src/httpExecutor.ts:79:30)
    at ElectronHttpExecutor.request (/tmp/.mount_simplejtlBEG/app/resources/app.asar/node_modules/builder-util-runtime/src/httpExecutor.ts:71:17)
    at /tmp/.mount_simplejtlBEG/app/resources/app.asar/node_modules/electron-updater/src/GenericProvider.ts:19:55
    at Generator.next (<anonymous>)
From previous event:
    at GenericProvider.getLatestVersion (/tmp/.mount_simplejtlBEG/app/resources/app.asar/node_modules/electron-updater/out/GenericProvider.js:72:11)
    at /tmp/.mount_simplejtlBEG/app/resources/app.asar/node_modules/electron-updater/src/AppUpdater.ts:251:37
From previous event:
    at AppImageUpdater.doCheckForUpdates (/tmp/.mount_simplejtlBEG/app/resources/app.asar/node_modules/electron-updater/out/AppUpdater.js:326:11)
    at /tmp/.mount_simplejtlBEG/app/resources/app.asar/node_modules/electron-updater/src/AppUpdater.ts:228:25
    at Generator.next (<anonymous>)
    at runCallback (timers.js:672:20)
    at tryOnImmediate (timers.js:645:5)
    at processImmediate [as _immediateCallback] (timers.js:617:5)
From previous event:
    at AppImageUpdater._checkForUpdates (/tmp/.mount_simplejtlBEG/app/resources/app.asar/node_modules/electron-updater/out/AppUpdater.js:280:11)
    at AppImageUpdater.checkForUpdates (/tmp/.mount_simplejtlBEG/app/resources/app.asar/node_modules/electron-updater/src/AppUpdater.ts:177:35)
    at App.<anonymous> (/tmp/.mount_simplejtlBEG/app/resources/app.asar/main.js:139:15)
    at emitTwo (events.js:111:20)
    at App.emit (events.js:194:7)
[10:22:34.698] [info] Error in auto-updater. HttpError: 403 Forbidden
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>CBC7496C38F832C2</RequestId><HostId>ss8I01KR+SqYTft6Rcq78XrSdLPwAqPrW4T9FU7UykqGk6a0Dvdj49nzv2a/zwcRsD8tyTNfM7A=</HostId></Error>"
Headers: {
  "content-type": [
    "application/xml"
  ],
  "date": [
    "Mon, 04 Dec 2017 10:34:29 GMT"
  ],
  "server": [
    "AmazonS3"
  ],
  "transfer-encoding": [
    "chunked"
  ],
  "x-amz-id-2": [
    "ss8I01KR+SqYTft6Rcq78XrSdLPwAqPrW4T9FU7UykqGk6a0Dvdj49nzv2a/zwcRsD8tyTNfM7A="
  ],
  "x-amz-request-id": [
    "CBC7496C38F832C2"
  ]
}

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 19 (2 by maintainers)

Most upvoted comments

OK here it is - the ultimate guide to having private s3 bucket, for anyone else who is interested in this.

HOWTO:

Dependencies aws-sign aws-sdk

Main.js All modifications are done in the main.js electron file - the file where you implement auto-update.

Step 1 - Import libraries

const aws = require('aws-sdk');
const AwsSign = require('aws-sign');

Step 2 - Set constants You’ll need a copy of your package.json build section, the one that looks like this

{
  "build": {
    "publish": {
      "bucket": "BUCKET_NAME",
      "path": "PATH/TO/FILES"
    }
  }
}

Using it we define the following constants

const release_path = config.get('build').publish.path;
const latest_yml_path = `/${release_path}/latest-${process.platform}.yml`;
const s3_bucket = config.get('build').publish.bucket;

Step 3 - Create signer

const credentials = new aws.SharedIniFileCredentials();
const signer = new AwsSign({
  accessKeyId: credentials.accessKeyId,
  secretAccessKey: credentials.secretAccessKey
});

Step 4 - Update checking-for-update

autoUpdater.on('checking-for-update', () => {
  var opts = {
    method: 'GET',
    host: `${s3_bucket}.s3.amazonaws.com`,
    path: latest_yml_path
  };
  signer.sign(opts);
  autoUpdater.requestHeaders = opts.headers
})

Step 5 - Update update-available

autoUpdater.on('update-available', (info) => {
  let update_path = `/${release_path}/${info.path}`;
  let opts = {
    method: 'GET',
    host: `${s3_bucket}.s3.amazonaws.com`,
    path: update_path
  };
  signer.sign(opts);
  autoUpdater.requestHeaders = opts.headers
  autoUpdater.downloadUpdate()
  }
})

Hope this helps!

For us, hosting our tools (electron apps) on public accessible buckets is not an option. The given example didn’t work with our region eu-central-1 which only allows V4 signing. Therefore we had to change the signing part a bit:

Solution:

We use aws4 for request signing.

and

autoUpdater.on('checking-for-update', () => {
  var opts = {
    method: 'GET',
    host: `${s3_bucket}.s3.amazonaws.com`,
    path: latest_yml_path
  };
  signer.sign(opts);
  autoUpdater.requestHeaders = opts.headers
})

becomes

import * as aws4 from 'aws4';
import * as path from 'path'
const pkg = require('../../package');

autoUpdater.on('checking-for-update', () => {
  const opts = {
    service: 's3',
      region: pkg.build.publish.region,
      method: 'GET',
      host: `s3-${pkg.build.publish.region}.amazonaws.com`,
      path: path.join('/', pkg.build.publish.bucket, latest_yml_path)
  };
  
  aws4.sign(opts, {
    accessKeyId: <AWS_ACCESS_KEY>,
    secretAccessKey: <AWS_SECRET_ACCESS_KEY>
  });
  signer.sign(opts);
  autoUpdater.requestHeaders = opts.headers
})

The same also works for the actual update.

Question:

Is there a way to write a custom Provider, or extends the existing GenericProvider to apply signing where it should actually happen

As far I see, you need to set Authorization header. You can simply set it using autoUpdater.requestHeaders property.

See http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#ConstructingTheAuthenticationHeader

https://www.npmjs.com/package/aws4 can be used to generate such header. Please try 😃

autoUpdater.requestHeaders = {Authorization: "secret"}

Referencing my above code - Error: net::ERR_TOO_MANY_REDIRECTS is the error in the development environment.

When I build the app I get the following:

"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>

This leads me to believe I am constructing the opts object wrong. Super annoying!

@stuartcusackie

sorry, my code had a bug, it’s actually:

import * as aws4 from 'aws4';
import * as path from 'path'
const pkg = require('../../package');

autoUpdater.on('checking-for-update', () => {
  const opts = {
    service: 's3',
      region: pkg.build.publish.region,
      method: 'GET',
      host: `s3-${pkg.build.publish.region}.amazonaws.com`,
      path: path.join('/', pkg.build.publish.bucket, latest_yml_path)
  };
  
  aws4.sign(opts, {
    accessKeyId: <AWS_ACCESS_KEY>,
    secretAccessKey: <AWS_SECRET_ACCESS_KEY>
  });
  // signer.sign(opts); --remove this line --
  autoUpdater.requestHeaders = opts.headers
})