Using AWS credentials in a public GitHub repo (in a node.js project, built with Grunt.js) - security

We have a MEAN (nodes.js back-end + angular front-end), where we use the Amazon S3 sdk to upload files to the storage service. We have our AWS credentials and we need to use them in the code.
We would like to publish our project in a public repository, but we don't want to share our credentials (by the way, Amazon monitors GitHub and notifies developers who disclose their credentials). How should we adapt our development workflow to have something secure (no disclosure) and convenient?

The solution consists in tuning the Gruntfile.js file, which defines what needs to be done during the build process. Depending on the one that you use as a starting point, things will be a bit different (and various use cases might need to be considered).
In our case, we use the angular fullstack yeoman generator (https://github.com/DaftMonk/generator-angular-fullstack) to generate the project skeleton. The Gruntfile.js that is produced by the generator is fairly sophisticated, so we had to edit a couple of sections. Here are the key points:
1. Passing arguments to Grunt
The first thing that we need, is to be able to invoke the build process and to pass arguments. The arguments should not end up in the repository, so developers should create a script outside the codebase to launch the build process (or type the arguments manually each time...). Grunt gives the ability to access command line arguments with the grunt.option() function. So, the first modification to the Gruntfile.js file is to add the following two lines right at the beginning:
module.exports = function (grunt) {
var amznUserId = grunt.option('AMZN_USER_ID');
var amznUserPassword = grunt.option('AMZN_USER_PASSWORD');
...
}
If you do that and then invoke grunt by typing:
grunt build --AMZN_USER_ID=myUserId --AMZN_USER_PASSWORD=aSecret
grunt serve --AMZN_USER_ID=myUserId --AMZN_USER_PASSWORD=aSecret
Then you will have the credential values available in the script variables.
2. Adding placeholders in the source files
The AMZN credentials are used in an Angular.JS component, typically in a controller. What you need to do is to replace the hard-coded values with variable placeholders, like this:
angular.module('demoApp')
.controller('MainCtrl', function ($scope, $http, socket) {
$scope.amzn_user_id = '##AMZN_USER_ID';
$scope.amzn_user_password = '##AMZN_USER_PASSWORD';
3. Injecting command line arguments into the placeholders
The last (and most tricky) part of the solution consists of injecting the 2 values passed to grunt into the placeholders. There are different Grunt plugins that can be used for this purpose. We have used the grunt-replace plugin with success. Here is what we had to do:
3.1 Install the plugin
npm install grunt-replace --save
3.2 Configure the plugin (specify how the injection should happen, more on this at the end)
grunt.initConfig({
replace: {
dist: {
options: {
patterns: [
{
match: 'AMZN_USER_ID',
replacement: amznUserId
},
{
match: 'AMZN_USER_PASSWORD',
replacement: amznUserPassword
}
]
},
files: [
{
expand: true,
flatten: false,
src: ['.tmp/concat/app/app.js'],
dest: '.'
},
{
expand: false,
flatten: false,
src: ['client/app/main/main.controller.js'],
dest: '.tmp/app/main/main.controller.js'
}
]
}
},
3.3. Invoke the plugin in the grunt build and grunt serve workflows
Please note that the order is important and that the replace lines must be exactly in that position.
grunt.registerTask('serve', function (target) {
if (target === 'dist') {
return grunt.task.run(['build', 'env:all', 'env:prod', 'express:prod', 'wait', 'open', 'express-keepalive']);
}
if (target === 'debug') {
return grunt.task.run([
'clean:server',
'env:all',
'injector:stylus',
'concurrent:server',
'injector',
'wiredep',
'autoprefixer',
'concurrent:debug'
]);
}
grunt.task.run([
'clean:server',
'env:all',
'injector:stylus',
'concurrent:server',
'injector',
'wiredep',
'replace',
'autoprefixer',
'express:dev',
'wait',
'open',
'watch'
]);
});
and
grunt.registerTask('build', [
'clean:dist',
'injector:stylus',
'concurrent:dist',
'injector',
'wiredep',
'useminPrepare',
'autoprefixer',
'ngtemplates',
'concat',
'replace',
'ngAnnotate',
'copy:dist',
'cdnify',
'cssmin',
'uglify',
'rev',
'usemin'
]);
What happens behind the scenes
The key point is to realize that the angular fullstack generator defines a build workflow, where a special directory named .tmp is used. The content of this directory is not always the same (grunt build and grunt serve use it in slightly different ways). That is the reason why we had to define two replacement rules.

Related

How to setup gulp-watch with gulp-connect livereload?

Most questions and answers on this site do not contain an easy-to follow general approach to using these two libraries together.
So, being that we use the gulp-connect npm package, and we want to make use of the gulp-watch npm package, how do we set it up so that we can:
watch changes in some files
perform some operation, like building / compiling those files
live-reload the server once the building is done
First, you will define your build task. This can have pre-required tasks, can be a task of some sort, it doesn't matter.
gulp.task('build', ['your', 'tasks', 'here']);
Then, you will need to activate the connect server. It is important that you are serving the result of the compilation (in this example, the dist directory) and you're enabling livereload with the livereload: true parameter.
const connect = require('gulp-connect');
gulp.task('server', function() {
return connect.server({
root: 'dist',
livereload: true
});
});
Finally, you will setup your watch logic. Note that we're using watch and not gulp.watch. If you decide to change it, notice that their APIs are different and they have different capabilities. This example uses gulp-watch.
const watch = require('gulp-watch');
gulp.task('watch-and-reload', ['build'], function() {
watch(['src/**'], function() {
gulp.start('build');
}).pipe(connect.reload());
});
gulp.task('watch', ['build', 'watch-and-reload', 'server']);
The watch-and-reload task will depend on the build task, so that it ensures to run at least one build.
Then, it will watch for your source files, and in the callback, it will start the build task. This callback gets executed every time that a file is changed in the directory. You could pass an options object to the watch method to be more specific. Check the usage API in their repository.
Also, you will need to start the build action, for which we're using gulp.start. This is not the recommended approach, and will be deprecated eventually, but so far it works. Most questions with these issues in StackOverflow will look for an alternative workaround that changes the approach. (See related questions.)
Notice that gulp.start is called synchronously. This is what you want, since you want to allow the build task to finish before you proceed with the event stream.
And finally, you can use the event stream to reload the page. The event stream will correctly capture what files changed and will reload those.
Bringing up to speed, as per current stable gulp release
gulp.task API isn't the recommended pattern anymore. Use exports object to make public tasks
From official documentation: https://gulpjs.com/docs/en/api/task#task
To Configure watch and livereload you need following
gulp.watch
gulp-connect
watch function is available in gulp module itself
install gulp-connect using npm install --save-dev gulp-connect
To configure gulp-connect server for livereload we need to set property livereload to true
Run all tasks followed by task that calls watch function in which globs and task are given. Any changes to files that match globs trigger task passed to watch().
task passed to watch() should signal async complection else task will not be run a second time. Simple works: should call callback or return stream or promise
Once watch() is configured, append .pipe(connect.reload()) followed by pipe(dest(..)) where ever you think created files by dest are required to reload
Here is simple working gulpfile.js with connect lifereload
const {src, dest, watch, series, parallel } = require("gulp");
const htmlmin = require("gulp-htmlmin");
const gulpif = require("gulp-if");
const rename = require('gulp-rename');
const connect = require("gulp-connect");
//environment variable NODE_ENV --> set NODE_ENV=production for prouduction to minify html and perform anything related to prod
mode = process.env.NODE_ENV || 'dev';
var outDir = (mode != 'dev') ? 'dist/prod': 'dist/';
const htmlSources = ['src/*.html'];
function html() {
return src(htmlSources)
.pipe(gulpif(
mode.toLowerCase() != 'dev',
htmlmin({
removeComments: true,
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true
})
)
)
.pipe(dest(outDir))
.pipe(connect.reload());
}
function js(){
return src('src/*.js')
.pipe(uglify())
.pipe(rename({ extname: '.min.js' }))
.pipe(dest(outDir))
.pipe(connect.reload());
}
function server() {
return connect.server({
port: 8000,
root: outDir,
livereload: true
})
}
function watchReload() {
let tasks = series(html, js);
watch(["src/**"], tasks);
}
exports.html = html;
exports.js = js;
exports.dev = parallel(html, js, server, watchReload);
Configure connect server with livereload property
function server() {
return connect.server({
port: 8000,
root: outDir,
livereload: true //essential for live reload
})
}
Notice .pipe(connect.reload()) in the above code. It is essential that stream of required files to be piped to connect.reload() else it may not work if you call connect.reload() arbitrarily
function html() {
return src(htmlSources)
.pipe(gulpif(
mode.toLowerCase() != 'dev',
htmlmin({
removeComments: true,
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true
})
)
)
.pipe(dest(outDir))
.pipe(connect.reload()); //Keep it if you want livereload else discard
}
Since we configure public task dev following command will execute all tasks followed by connect and watchReload
gulp dev

I don't understand how to work Bower properly

I'm building a site and I've decided to use a bootstrap template for the back-end (admin tools and whatnot).
I like the look of sb-admin-2 (http://startbootstrap.com/template-overviews/sb-admin-2/) but I'ma bit confused how to practically employ this in my site.
I installed Bower and installed sb-admin using bower install startbootstrap-sb-admin-2
Now I have a folder called bower_components, and it's filled with all the relevant packages... However, these packages include the development files as well.
If I upload this to my site as is, 80% of it will be unnecessary source data.
I'm currently using Gulp with my project, but I don't yet see how the 2 are supposed to interact. Is there a gulp package for compiling the bower_components into 1 concise thing?
I'm new to this kind of workflow and I can't find the answers to the questions despite my efforts. Apologies if I sound like a total noob.
There's no pre-built gulp package that will pull in all your bower source files. You should write a task that pulls in just the files you need. Here's an example from a project I'm working on (simplified):
var scripts = [
'bower_components/timezone-js/src/date.js', // https://github.com/mde/timezone-js
'bower_components/jquery/jquery.min.js', // http://api.jquery.com/
'bower_components/jquery-migrate/jquery-migrate.js', // https://github.com/appleboy/jquery-migrate
'bower_components/jquery-ui/ui/minified/jquery-ui.min.js', // todo: include just the bits we need
'bower_components/jqueryui-touch-punch/jquery.ui.touch-punch.min.js', // https://github.com/furf/jquery-ui-touch-punch
'bower_components/jquery-cookie/jquery.cookie.js', // https://github.com/carhartl/jquery-cookie
'bower_components/jquery.expander/jquery.expander.min.js', // https://github.com/kswedberg/jquery-expander
'bower_components/jquery.transit/jquery.transit.js', // http://ricostacruz.com/jquery.transit/
'bower_components/select2/select2.min.js', // http://ivaynberg.github.io/select2/
'bower_components/fancybox/source/jquery.fancybox.pack.js', // http://fancyapps.com/fancybox/
'bower_components/lodash/dist/lodash.compat.min.js', // https://lodash.com/docs
'bower_components/underscore.string/dist/underscore.string.min.js', // https://github.com/epeli/underscore.string#string-functions
'bower_components/json2/json2.js', // https://github.com/douglascrockford/JSON-js
'bower_components/jquery-validation/dist/jquery.validate.min.js', // http://jqueryvalidation.org/documentation/
'bower_components/jquery-file-upload/js/jquery.iframe-transport.js',
'bower_components/jquery-file-upload/js/jquery.fileupload.js', // https://blueimp.github.io/jQuery-File-Upload/
'bower_components/DataTables/media/js/jquery.dataTables.js', // https://datatables.net/
];
gulp.task('scripts', function () {
return gulp.src(scripts, {base: '.'})
.pipe(plumber())
.pipe(sourcemaps.init({
loadMaps: false,
debug: debug,
}))
.pipe(concat('all_the_things.js', {
newLine:'\n;' // the newline is needed in case the file ends with a line comment, the semi-colon is needed if the last statement wasn't terminated
}))
.pipe(uglify({
output: { // http://lisperator.net/uglifyjs/codegen
beautify: debug,
comments: debug ? true : /^!|\b(copyright|license)\b|#(preserve|license|cc_on)\b/i,
},
compress: { // http://lisperator.net/uglifyjs/compress, http://davidwalsh.name/compress-uglify
sequences: !debug,
booleans: !debug,
conditionals: !debug,
hoist_funs: false,
hoist_vars: debug,
warnings: debug,
},
mangle: !debug,
outSourceMap: true,
basePath: 'www',
sourceRoot: '/'
}))
.pipe(sourcemaps.write('.', {
includeContent: true,
sourceRoot: '/',
}))
.pipe(plumber.stop())
.pipe(gulp.dest('www/js'))
});
I'm cherry-picking the source files I want, combining and minifying them, and dumping them into my public directory so that can be served to the client. You don't need to upload the bower_components folder to your production server; but it probably wouldn't hurt much either (it's not THAT big!).

How task names are mapped in grunt?

I cannot understand how grunt matches tasks with Gruntfile.js:
module.exports = function (grunt) {
grunt.initConfig({
concat: {
dist: {
src: ['src/*.js'],
dest: 'dest/all.js'
}
}
});
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('default', ['concat']);
};
It's a valid config. But I don't know how grunt match 'concat' to 'grunt-contrib-concat'.
Does grunt trim the 'grunt-contrib-' prefix to match 'concat' to 'grunt-contrib-concat'?
First, we look inside grunt-contrib-concat source code:
grunt.registerMultiTask('concat', 'Concatenate files.', function() {
Looking inside grunt creating tasks docs, the first argument passed into a task registration function is the name of the task:
grunt.registerMultiTask(taskName, [description, ] taskFunction)
grunt.registerTask(taskName, [description, ] taskFunction)
Consclusion
There is no "magic" names nor "grunt keywords"
There is no difference between your custom tasks and task plugins ( even grunt-contrib.. )
The API for creating tasks is simple as that.
contrib-less, contrib-jade, contrib-concat .... so contrib is just to signifies that these plugins are contributed by Grunt community developers and less, jade, concat indicate the modules which you want to work on your project as mentioned in gruntfile.js.
So when you say:
grunt.loadNpmTasks('grunt-contrib-concat')
It loads the mentioned module.
But in order to make it work when you fire grunt, you actually have to register it.
grunt.registerTask('default', ['concat','jade','less']);
grunt.registerTask('test', ['concat','jade','less']);
grunt.registerTask('dist', ['concat','jade','less','uglify']);
So as you can see in production we may want to uglify, so we can register our task in by 'dist'.

How set/get environment specific variable in a Yeoman Ember app

Considering an Yeoman Ember app.
I've looked different tools like:
https://github.com/logankoester/grunt-environment
https://github.com/jsoverson/grunt-env
https://github.com/outaTiME/grunt-config
But I don't quite see how can you set/get for instance different adapter url in your router.js depending on some grunt/node environment.
One approach would be having for example two files: development.properties and production.properties, and using grunt you can copy them to a current.properties file and then you can read the current.properties file always, using for example nconf from any place of your application.
You just need to use the grunt-contrib-copy tasks in your grunt file.
Summary: create a gruntfile that read the environment from the command line and copy the corresponding file property to current.properties, and then in your route.js you just include nconf and you just use it.
Also as an alternative nconf can read parameters from the command line given to node when you run it, so you can avoid the grunt file and run it like node app.js --property=value and take the value of the property with nconf from your route.js
So I ended up figuring out a way that works well but only with two env:
I made a pull-request for the ember-generator to start using that pattern:
https://github.com/borisrorsvort/generator-ember/commit/66f26e9e5a7599da53cb99b85e8ef5864648349b
Here is an implementation that works with yeoman ember generator:
Your replace task should look something like this:
replace: {
app: {
options: {
variables: {
ember: 'bower_components/ember/ember.js',
ember_data: 'bower_components/ember-data-shim/ember-data.prod.js',
app_config: 'scripts/app_config/development.js'
}
},
files: [{
src: '<%= yeoman.app %>/index.html',
dest: '.tmp/index.html'
}]
},
dist: {
options: {
variables: {
ember: 'bower_components/ember/ember.prod.js',
ember_data: 'bower_components/ember-data-shim/ember-data.prod.js',
app_config: 'scripts/app_config/production.js'
}
},
files: [{
src: '<%= yeoman.app %>/index.html',
dest: '.tmp/index.html'
}]
}
}
======
Then add '##app_config' the index.html under ##ember and ##ember_data so that it get replaced by the correct string
======
Then create two files:
app/templates/scripts/app_config/development.js
app/templates/scripts/app_config/production.js
These contain an AppConfig onject that you can use in your Ember App
var AppConfig = {};
======
Now Imagine you want to change your adapter when you're building the app:
Just use:
MyApp.ApplicationAdapter = DS.RESTAdapter.extend({
host: AppConfig.apiAdapterUrl
});

Nodemon-like task in Grunt : execute node process and watch

I feel like I'm missing something.
Here is what I want to achieve :
Having a grunt task that executes my server.js and runs watch task in parallel. It feels to me that this is precisely one of the tasks grunt was designed for but I can't achieve this configuration.
Among others, I have read this :
Running Node app through Grunt
but I still can't make it.
Here is my Gruntfile.js :
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
watch: {
scripts: {
files: ['*.js'],
tasks: ['start'],
options: {
nospawn: true
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('start', function() {
grunt.util.spawn({
cmd: 'node',
args: ['server.js']
});
grunt.task.run('watch');
});
grunt.registerTask('default', 'start');
};
I have "grunt-contrib-watch": "~0.3.1" which should be higher version than grunt-contrib-watch#0.3.0 as in the previously mentioned post.
If you could help me achieve the proper configuration, I would be extremely grateful. But more in general, I don't understand why there is no official grunt-contrib-nodemon-like package and task since I have the feeling it would be another great reason to use grunt (which I really like as a tool !)
Thanks
Edit: grunt-nodemon
since writing this, a nice person developed that.
I was having a lot of trouble using grunt.util.spawn to fire off new processes. They would run, but they wouldn't give me any output back. Perhaps you can figure out what I could not in these docs. http://gruntjs.com/api/grunt.util#grunt.util.spawn
Two problems I see with what you have:
I think grunt.registerTask() has to take three arguments when you use a callback function to run your task.
I don't think you can just call node server.js over and over again everytime a file changes. It will work on the first time, for it to really work you'd have to manage the server as a child process, killing and restarting it on subsequent file changes.
For the registerTask arguments try this, just to see if you can get something to work in your current implementation.
http://gruntjs.com/api/grunt.task#grunt.task.registertask
It takes (taskName, description, taskFunction) like so:
grunt.registerTask('start', 'My start task description', function() {
grunt.util.spawn({
cmd: 'node',
args: ['server.js']
});
grunt.task.run('watch');
});
That might at least get your watch to run node server.js the first time a file changes.
Here's what I would do instead.
Either just use nodemon $ nodemon server.js as is
or...
Read the source and use grunt-develop
He is managing the server as a child process, might be what you're looking for.
or...
Get grunt-shell
npm install grunt-shell --save-dev
And use it to run nodemon for you:
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
serverFile: 'server.js',
shell: {
nodemon: {
command: 'nodemon <%= serverFile %>',
options: {
stdout: true,
stderr: true
}
}
},
watch: { /* nothing to do in watch anymore */ }
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-shell');
grunt.registerTask('default', 'shell:nodemon');
};
$ grunt shell:nodemon
I sincerely hope that helps. Good luck!
Hi I also came across this problem and here is my solution (based on nackjicholson's answer). This uses grunt-nodemon in a spawned process. so I can:
Reload nodejs
Watch for changes to e.g. .less files
Get output of both tasks
grunt.loadNpmTasks('grunt-nodemon');
grunt.initConfig({
nodemon: {
dev: {
options: {
file: 'server.js',
nodeArgs: ['--debug'],
env: {
PORT: '8282'
}
}
}
},
});
grunt.registerTask('server', function (target) {
// Running nodejs in a different process and displaying output on the main console
var nodemon = grunt.util.spawn({
cmd: 'grunt',
grunt: true,
args: 'nodemon'
});
nodemon.stdout.pipe(process.stdout);
nodemon.stderr.pipe(process.stderr);
// here you can run other tasks e.g.
// grunt.task.run([ 'watch' ]);
});
Use grunt-concurrent
The issue is that tasks like watch and nodemon will never terminate, so grunt will never reach them. You need to spawn a new process.
You can do this easily using grunt-concurrent:
https://github.com/sindresorhus/grunt-concurrent
For example:
module.exports = function(grunt) {
grunt.initConfig({
...
concurrent: {
dev: {
tasks: ['nodemon', 'watch'],
options: {
logConcurrentOutput: true
}
}
}
});
};
The two will now run happily side by side.

Resources