module InsxSync
Constants
- VERSION
Public Instance Methods
Configures the application based on user input. Stores encrpyted config in a file as a delimited base 64 string
# File lib/insxsync.rb, line 226 def configure # verify the config directory exists Dir.mkdir(File.join(Dir.home, '.insxsync')) if not File.exists?(File.join(Dir.home, '.insxsync')) # check if the configuration file exists and ask the user if it should be overwritten if File.exists?(File.join(Dir.home, '.insxsync', 'config.ini')) require_condition yesno("Configuration exists. Overwrite?", false), "Already configured. No changes made..." end # open the config file for writing File.open(File.join(Dir.home, '.insxsync', 'config.ini'), 'w') do |file| # Get the password for the new config and verify it was entered correctly twice pass = get_prompt("Please enter a credential storage password: ") pass2 = get_prompt("Please verify the credential storage password: ") require_condition pass == pass2, "Password mismatch. Exiting..." # Get the access key id and secret access key accessKey = get_prompt("Please enter your AWS access key ID for insxsync: ", true) accessSecret = get_prompt("Please enter your AWS secret access key for insxsync: ", true) # check the credentials work if the user wants if yesno("Would you like to test your credentials?", true) passed, message = testAWS(accessKey, accessSecret, false) require_condition passed, "AWS test failed! Invalid AWS credentials specified. No changes made..." end # generate the data string to be encrypted data = "AccessKeyId = '#{accessKey}'\nSecretAccessKey = '#{accessSecret}'" # generate the key and IV key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(pass, 'P@ssw0rd00!', 5813, 32) iv = gen_iv(pass) # setup the encryption cipher cipher = OpenSSL::Cipher::AES256.new(:CFB) cipher.encrypt cipher.key = key cipher.iv = iv # encrypt the data encrypted = cipher.update(data) + cipher.final # convert the encrypted data to a delimited base64 string contents = "===BEGIN AWS CREDENTIALS===\n" + Base64.encode64(encrypted) + "===END AWS CREDENTIALS===" # write the contents out to the file file.write(contents) end puts "Configuration successful!" end
Returns the truth value of whether the application is running$command an export operation @return [true, false] whether the application in running an export
# File lib/insxsync.rb, line 302 def exporting? $OLD_ARGV0 == "export" end
Generates the initialization vector for the applications cryptography based on a passed string Uses a character representation of a truncated 512-bit SHA2 hash @param [String] data a string of data to generate the IV from @return [String] the initialization vector
# File lib/insxsync.rb, line 200 def gen_iv (data) # Generate an 512-bit SHA2 hash of the data and map it to an array of bytes temp = Digest::SHA2.new(512).update(data).to_s.scan(/../).map(&:hex) # set the array equal to an array of each of its halves temp = temp.each_slice(32).to_a # Add the second half of the array to the first temp[1].each_index do |index| temp[0][index] += temp[1][index] end # set the array equal to an array of each half of the zeroth array element temp = temp[0].each_slice(16).to_a # Add the second half of the array to the first temp[1].each_index do |index| temp[0][index] += temp[1][index] end # set the IV to a character representation of the first half of the array of bytes iv = temp[0].pack('c*') end
Displays a prompt for user input. Allows the echo of the input to be disabled @param [String] prompt The prompt to display to the user @param [true, false] whether to echo the input or not @return [String] the users input
# File lib/insxsync.rb, line 296 def get_prompt(prompt="Enter Password: ", echo=false) ask(prompt) {|q| q.echo = echo} end
Loads AWS credentials from config file into global variables
# File lib/insxsync.rb, line 34 def loadCredentials # get config file path configFile = File.join(Dir.home, '.insxsync', 'config.ini') # Ensure config exists require_condition File.exists?(configFile), "Application not configured. Exiting..." # get the password for credential storage p = get_prompt("Please enter credential storage password: ") # get config file contents content = File.open(configFile, 'r').read # get the encrypted file contents encrypted = content.scan(/===BEGIN AWS CREDENTIALS===.(.*).===END AWS CREDENTIALS===/m).flatten[0] # Generate IV and key from password iv = gen_iv(p) key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, 'P@ssw0rd00!', 5813, 32) # create and configure decipher decipher = OpenSSL::Cipher::AES256.new(:CFB) decipher.decrypt decipher.key = key decipher.iv = iv # decrypt contents plain = decipher.update(Base64.decode64(encrypted)) + decipher.final # retrieve access key id and secret access key accessKeyId = plain.scan(/AccessKeyId\s*=\s*'([^']*)'/).flatten[0] secretAccessKey = plain.scan(/SecretAccessKey\s*=\s*'([^']*)'/).flatten[0] require_condition accessKeyId.not_nil?, "AWS credentials could not be retrieved. Please check your password and try again..." require_condition secretAccessKey.not_nil?, "AWS credentials could not be retrieved. Please check your password and try again..." $AWS_ID = accessKeyId $AWS_SECRET = secretAccessKey end
Exits the application and displays the message specified @param [true, false] condition the truth value of the condition to be tested @param [String] message the message to be displayed in the event of a false condition
# File lib/insxsync.rb, line 342 def require_condition(condition, message) if not condition $stderr.puts message exit -1 end end
Returns the truth value of whether the application is running an sync operation @return [true, false] whether the application in running an sync
# File lib/insxsync.rb, line 308 def syncing? $OLD_ARGV0 =~ /sync(-uat)?/ end
Tests all necessary connections and acces to AWS @param [String] key AWS Access Key ID. Default: nil @param [String] secret AWS Secret Access Key Default: nil @param [true, false] echo whether to echo test progress or not @return [Boolean] truth value of test success @return [String] explanatory message if failed, nil otherwise
# File lib/insxsync.rb, line 80 def testAWS(key=nil, secret=nil, echo = true) # if both key and secret are not specified if ((key.not_nil? and secret.nil?) or (key.nil? and secret.not_nil?)) # return false for failure. Message not needed since the presence of # key or secret implies a programmatic test, not interactive return false end # if no key was specified if key.nil? # get the password for credential storage p = get_prompt("Please enter credential storage password: ") print "Locating configuration...\t\t" if echo # get config file path configFile = File.join(Dir.home, '.insxsync', 'config.ini') # Fail if it does not exist if not File.exists?(configFile) return false, "\nApplication not configured. Please run 'insxsync configure' to fix. Exiting..." end print "OK\n" if echo print "Decrypting credentials...\t\t" if echo # get config file contents content = File.open(configFile, 'r').read # get the encrypted file contents encrypted = content.scan(/===BEGIN AWS CREDENTIALS===.(.*).===END AWS CREDENTIALS===/m).flatten[0] # Generate IV and key from password iv = gen_iv(p) key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, 'P@ssw0rd00!', 5813, 32) # create and configure decipher decipher = OpenSSL::Cipher::AES256.new(:CFB) decipher.decrypt decipher.key = key decipher.iv = iv # decrypt contents plain = decipher.update(Base64.decode64(encrypted)) + decipher.final # retrieve access key id and secret access key accessKeyId = plain.scan(/AccessKeyId\s*=\s*'([^']*)'/).flatten[0] secretAccessKey = plain.scan(/SecretAccessKey\s*=\s*'([^']*)'/).flatten[0] # if either are nil, fail if accessKeyId.nil? or secretAccessKey.nil? return false, "\nAWS credentials could not be retrieved. Please check your password and try again..." end print "OK\n" if echo else # key and secret provided as arguments accessKeyId = key secretAccessKey = secret end print "Accessing S3 Buckets...\t\t" if echo # create AWS SDK objects for test begin # This is the bucket object for sync data syncBucket = AWS::S3.new(:access_key_id=>accessKeyId, :secret_access_key=>secretAccessKey).buckets[$SYNC_BUCKET] # this assumes the role on the production account to access CGE backups sts = AWS::STS.new(:access_key_id=>accessKeyId, :secret_access_key=>secretAccessKey) role = sts.assume_role(:role_arn => $ROLE_ARN, :role_session_name => SecureRandom.uuid.gsub(/-/,''), :duration_seconds => 60 * 15) # store role credentials creds = role[:credentials] # open the CGE backup bucket cgeBucket = AWS::S3.new(creds).buckets[$CGE_BUCKET] rescue AWS::STS::Errors::InvalidClientTokenId # in event key does not have permission for role return false, "\nAWS credentials invalid. Please check them and try again..." rescue AWS::STS::Errors::SignatureDoesNotMatch # in event key and secret do not match return false, "\nAWS credentials invalid. Please check them and try again..." end print "OK\n" if echo print "Enumerating objects...\t\t" if echo cgeBucket.objects.each syncBucket.objects.each print "OK\n" if echo print "Testing upload...\t\t" if echo syncBucket.objects['insxsync.upload'].write("This file was uploaded successfully!") print "OK\n" if echo print "Testing deletion...\t\t" if echo syncBucket.objects['insxsync.upload'].delete print "OK\n" if echo print "Testing download...\t\t" if echo syncTest = syncBucket.objects['insxsync.test'].read cgeTest = cgeBucket.objects['insxsync.test'].read if syncTest != "This file was downloaded successfully!" return false, "Download from sync bucket failed!" end if cgeTest != "This file was downloaded successfully!" return false, "Download from CGE backups bucket failed!" end print "OK\n" if echo puts "Test successful!" if echo return true end
Acts like the *nix which command. Given an array of commands ordered from most prefered to lease, it returns the full path to the first command it finds. Operating system agnostic. @overload which(cmd)
Finds the path to the specified command. @param [String] cmd command name @return [String] full path to specified command. Nil if the command was not found.
@overload which(cmd)
Finds the path to the specified command. @param [Array<String>] cmds command names in order of preference @return [String] full path to first command found. Nil if none were found.
# File lib/insxsync.rb, line 322 def which(*cmds) # For each command cmds.flatten.each do |cmd| # Get the list of executable extensions (if any) exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : [''] exts.push('') if not exts.include?('') # Needs to inlcude a blank extension for *nix systems ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| # For each dir in the PATH variable exts.each { |ext| # Check to see if the command exists in the directory with one of the possible extensions exe = File.join(path, "#{cmd}#{ext}") return exe if File.executable? exe } end end return nil # Oops, guess the command isn't there end
Asks the user for a single character of input for yes or no @param [String] prmpts the prompt to ask @param [true, false] default default choice @return [true, false] the user's choice
# File lib/insxsync.rb, line 281 def yesno(prompt = 'Continue?', default = true) a = '' s = default ? '[Y/n]' : '[y/N]' d = default ? 'y' : 'n' until %w[y n].include? a a = ask("#{prompt} #{s} ") { |q| q.limit = 1; q.case = :downcase } a = d if a.length == 0 end a == 'y' end