require'gpgme'classNumericdefmegabytes;(self*1024**2).round;endend# When the file is encrypteddefload_encryptedGPGME::Crypto.new.instance_evaldo|crypto|config_io=Pathname'config.rb.gpg'evalcrypto.decrypt(config_io).to_s,binding,config_ioendend# For signing only, with plain text config file and signaturedefload_signed(text_path='config.rb',sig_path=text_path+'.sig')text_io=Pathnametext_pathsignature_io=Pathnamesig_pathGPGME::Crypto.new.instance_evaldo|crypto|crypto.verify(signature_io,signed_text:text_io)do|signature|signature.valid?||raise("invalid signature for #{text_io}")endend# raise will skip thisevaltext_io.read,binding,text_pathend
$SAFE = 1
How to load from untainted file names
123456789101112131415161718
defconfig_path@config_path||=Pathname'config.rb'enddefrestricted_config_path@restricted_config_path||=Pathname'restricted_config.rb'end# total trustloadconfig_pathdefload_mostly_trustedlambdado# config file can define methods and do metaprogramming, but can't do some other stuff$SAFE=1requireconfig_path.to_send.callend
# This fails because of "SecurityError: Insecure operation - safe_load"# Maybe a Ruby bug?defsafe_load# call before $SAFE change to set instance variableconfig_pathlambdado$SAFE=4loadrestricted_config_path,trueend.callend# Very restricteddefsafe_eval# call before $SAFE change to set instance variableconfig_pathlambdadounsafe_string=restricted_config_path.read$SAFE=4evalunsafe_string,binding,restricted_config_pathend.callend# could use ThreadGroup and enclose to prevent spawning of threadsdefno_dos_evalconfig_paththread=Thread.newdounsafe_string=restricted_config_path.read$SAFE=4evalunsafe_string,binding,restricted_config_pathend# get thread value if it hasn't run for too long, otherwise kill itifthread.join(1)thread.valueelsethread.killendend
And some of the things you can and cannot do while running under $SAFE = 4
# Can't set constants# class NewClass# def initialize# File.read '/etc/passwd'# end# endmyclass=Class.newdodefhello'world'endendmy_instance=myclass.new# can't even call STDOUT.write# puts my_instance.hellovalues={sequel_connection:"mysql2://root:pass@localhost:/stuff",redis:{"host"=>"securitas"},redis_max_memory:3.gigabytes,worker:{"max_memory"=>75.megabytes},output_dir:"/var/data/lenep/output",charset:"utf8mb4",collation:"utf8mb4_unicode_ci",}values[:friendly_name]=caseSettings.host_namewhen'production''Live'when'uat''UAT'when'staging''Staging'endvalues# reopen fails# class Settings# def sequel_connection# `rm -rf /`# end# end
dot-notation for a Hash
This is a nice way to see the distinction between class-oriented
languages (eg c++,Java) and object oriented languages (eg Ruby)
where any instance can have behaviour different to that of the
other instances of the class it belongs to.
moduleHashDotdefmethod_missing(meth,*args,&blk)property=meth.id2nameassign=property.chomp!('=')superunlesskeys.include?(property.to_sym)ifassignself[property.to_sym]=args.firstelserv=self[property.to_sym]rv.extendHashDotifrv.kind_of?Hashrvendenddefself.extended(some_hash)some_hash.each_pairdo|k,v|raise"key #{k} is not a symbol"unlessk.is_a?SymbolendendendclassNumericdefkilobytes;(self*1024**1).round;enddefmegabytes;(self*1024**2).round;enddefgigabytes;(self*1024**3).round;endendsome_hash={sequel_connection:"mysql2://root:pass@localhost:/stuff",redis:{host:"fast_box",max_memory:55.megabytes},worker:{max_memory:75.megabytes},output_dir:"/var/data/output",charset:"utf8mb4",collation:"utf8mb4_unicode_ci",# 'oops' => 'value',}.extend(HashDot)some_hash.worker.max_memory
Read from environment variables, similar to HashDot
require'rb-inotify'require'pathname'require'yaml'# Extending a hash is not the best way to do this. Delegator?moduleReloaderdef[](*args);maybe_reload;super;enddefinspect;maybe_reload;super;end# ... etcdefinotify_reload(config_path)config_path=Pathname(config_path).realpath@notifier.closeif@notifier@notifier=INotify::Notifier.new# have to watch the directory, mainly because vim will rename the .swp file on save...@notifier.watchconfig_path.parent.to_s,:close_write,:moved_todo|event|# ... so then we have to filter by event filenameifPathname(event.absolute_name)==config_pathmerge!YAML.load_file(config_path)endendendprotecteddefmaybe_reloadreturnunless@notifier# reload file (via notifier.watch block, above) if there is a pending change# Return immediately if there isn't one.# JRuby WARNING rb-inotify 0.8.8 docs say this does not work@notifier.processwhileIO.select([@notifier.to_io],[],[],0)endendconfig_path=Pathname(__FILE__).parent+'config.yml'settings=YAML.load_fileconfig_pathsettings.extend(Reloader).inotify_reloadconfig_path
Global functions in Ruby
Every time I do a talk, I learn something. In this case it was these
global functions. String, Integer, Float, Array are implemented like
this. Also Pathname. Obviously not a good thing to do unless you have
objects working at that level.
The private is there because otherwise you could do something
like "banjo".OpenStruct( key: 'value' ) which would work, but
which doesn’t make much sense.