Skip to main content

Distroless & obfuscation

· 3 min read
Creator of Spider

Spider Docker images have been upgraded to distroless and obfuscated images!

Origin

I recently received the request from developers to be able to use Spider locally on their own computer.

That meant deploying Whisperer image potentially anywhere.
For this, I needed to protect Spider deliverables.

Whisperers are delivered as Dockers, with the code bundled with Webpack.
Thus the whole code of services and clients are delivered as a single javascript file, aside the (external, for licences) node modules.

Moving to distroless

After much reading, I came to the conclusion that Google distroless images require much customisation to adapt, and are in fact not much secured than Alpine (which triggers less red flags in security scanners).

One way to secure even more Alpine is possible by removing busybox.
Then:

  • Packages installation are not possible
  • Connection inside the container is not possible
  • Shell operations are not possible

Thus reducing many security attacks vectors. This can be easily done by adding these lines in Dockerfile:

find /sbin /bin /usr/bin /usr/local/bin/ -type l  -exec busybox rm -rf {} \;; \
busybox rm /sbin/apk /bin/busybox

I thus added this to last stage of all Spider dockerfiles, rebuilt & redeployed.

tip

Apko looks like a good solution for later (WIP):
GitHub - chainguard-dev/apko: Build OCI images using APK directly without Dockerfile

But it is not advised for production yet.

Obfuscation

Since I was already using Webpack to bundle the javascript code, I only added a nice webpack plugin: webpack-obfuscator.

This plugin uses javascript-obfuscator package to change source files so that they perform the same but cannot be read intelligibly or even navigated with debug tools.

function hi() {
console.log("Hello World!");
}
hi();

becomes

(function(_0x2aa05e,_0xf3be43){var _0x2f8c30=_0x5027,_0x576114=_0x2aa05e();while(!![]){try{var _0x13bd9e=parseInt(_0x2f8c30(0x159))/0x1+parseInt(_0x2f8c30(0x163))/0x2*(-parseInt(_0x2f8c30(0x166))/0x3)+-parseInt(_0x2f8c30(0x164))/0x4*(parseInt(_0x2f8c30(0x16a))/0x5)+-parseInt(_0x2f8c30(0x152))/0x6*(-parseInt(_0x2f8c30(0x155))/0x7)+-parseInt(_0x2f8c30(0x158))/0x8+parseInt(_0x2f8c30(0x151))/0x9*(parseInt(_0x2f8c30(0x157))/0xa)+parseInt(_0x2f8c30(0x14f))/0xb;if(_0x13bd9e===_0xf3be43)break;else _0x576114['push'](_0x576114['shift']());}catch(_0x303073){_0x576114['push'](_0x576114['shift']());}}}(_0x2b54,0x770c6));function _0x2b54(){var _0x3c15dd=['2813802ITmBxS','error','log','7nSuurB','(((.+)+)+)+$','7565270MdEONc','7769080kvzVxH','710491dIRrGj','console','apply','info','trace','__proto__','bind','warn','exception','return\x20(function()\x20','14OOcQBt','260xIzcsV','constructor','356295JgVJbk','search','Hello\x20World!','length','25615YPwmYx','toString','table','7558342ucTjJn','{}.constructor(\x22return\x20this\x22)(\x20)','9otLKMz'];_0x2b54=function(){return _0x3c15dd;};return _0x2b54();}function hi(){var _0x2d8b21=_0x5027,_0x34570d=(function(){var _0x2b1465=!![];return function(_0x22790a,_0x445d13){var _0x128fe2=_0x2b1465?function(){if(_0x445d13){var _0x2ef43e=_0x445d13['apply'](_0x22790a,arguments);return _0x445d13=null,_0x2ef43e;}}:function(){};return _0x2b1465=![],_0x128fe2;};}()),_0x4ee4d1=_0x34570d(this,function(){var _0x4caeee=_0x5027;return _0x4ee4d1['toString']()['search'](_0x4caeee(0x156))[_0x4caeee(0x16b)]()[_0x4caeee(0x165)](_0x4ee4d1)[_0x4caeee(0x167)](_0x4caeee(0x156));});_0x4ee4d1();var _0x225cf7=(function(){var _0x13ee3f=!![];return function(_0xa7923a,_0x4ca223){var _0x2e14e2=_0x13ee3f?function(){var _0x16f09d=_0x5027;if(_0x4ca223){var _0x66455d=_0x4ca223[_0x16f09d(0x15b)](_0xa7923a,arguments);return _0x4ca223=null,_0x66455d;}}:function(){};return _0x13ee3f=![],_0x2e14e2;};}()),_0x30fb56=_0x225cf7(this,function(){var _0xf5d605=_0x5027,_0x2dc88d;try{var _0x347cbe=Function(_0xf5d605(0x162)+_0xf5d605(0x150)+');');_0x2dc88d=_0x347cbe();}catch(_0x975ec1){_0x2dc88d=window;}var _0x3e838d=_0x2dc88d[_0xf5d605(0x15a)]=_0x2dc88d[_0xf5d605(0x15a)]||{},_0xbeda5b=['log',_0xf5d605(0x160),_0xf5d605(0x15c),_0xf5d605(0x153),_0xf5d605(0x161),_0xf5d605(0x14e),_0xf5d605(0x15d)];for(var _0x2b96ad=0x0;_0x2b96ad<_0xbeda5b[_0xf5d605(0x169)];_0x2b96ad++){var _0xe0c6e1=_0x225cf7['constructor']['prototype']['bind'](_0x225cf7),_0x23d4f0=_0xbeda5b[_0x2b96ad],_0x54eb75=_0x3e838d[_0x23d4f0]||_0xe0c6e1;_0xe0c6e1[_0xf5d605(0x15e)]=_0x225cf7[_0xf5d605(0x15f)](_0x225cf7),_0xe0c6e1[_0xf5d605(0x16b)]=_0x54eb75[_0xf5d605(0x16b)][_0xf5d605(0x15f)](_0x54eb75),_0x3e838d[_0x23d4f0]=_0xe0c6e1;}});_0x30fb56(),console[_0x2d8b21(0x154)](_0x2d8b21(0x168));}function _0x5027(_0x84155e,_0x42b1b7){var _0x130451=_0x2b54();return _0x5027=function(_0x4d97f1,_0x8e8c38){_0x4d97f1=_0x4d97f1-0x14e;var _0xe6422b=_0x130451[_0x4d97f1];return _0xe6422b;},_0x5027(_0x84155e,_0x42b1b7);}hi();

See obfuscator.io for demo.

Performance check

I used the default profile from javascript-obfuscator, the one oriented to best performance. I then compare performance for 5 hours of Spider processing at night.

API speed

The following tables show performance comparison between before and after obfuscation for API and services calls:

Performance check

Performance is globally the same, some APIs are faster (?!), some are slower.

Resource usage

Regarding resource usage over the both periods (over 5 nodes and 100 services):

MetricNon obfuscatedObfuscated
Total CPU used343%356%
Total Memory allocated4 893 MB5 129 MB

Obfuscated code uses a bit more CPU and RAM.

Store access

Strangely, however there is no change in Redis access response time, Elasticsearch response times are faster :-/

ES calls

Conclusion

Overall Spider deployment gets safer, more secure, with small changes and at little performance overhead!

All good :)