Class | MCollective::Security::Aes_security |
In: |
plugins/mcollective/security/aes_security.rb
|
Parent: | Base |
Impliments a security system that encrypts payloads using AES and secures the AES encrypted data using RSA public/private key encryption.
The design goals of this plugin are:
Configuration Options:
Common Options:
# Enable this plugin securityprovider = aes_security # Use YAML as serializer plugin.aes.serializer = yaml # Send our public key with every request so servers can learn it plugin.aes.send_pubkey = 1
Clients:
# The clients public and private keys plugin.aes.client_private = /home/user/.mcollective.d/user-private.pem plugin.aes.client_public = /home/user/.mcollective.d/user.pem
Servers:
# Where to cache client keys or find manually distributed ones plugin.aes.client_cert_dir = /etc/mcollective/ssl/clients # Cache public keys promiscuously from the network plugin.aes.learn_pubkeys = 1 # The servers public and private keys plugin.aes.server_private = /etc/mcollective/ssl/server-private.pem plugin.aes.server_public = /etc/mcollective/ssl/server-public.pem
sets the caller id to the md5 of the public key
# File plugins/mcollective/security/aes_security.rb, line 167 167: def callerid 168: if @initiated_by == :client 169: return "cert=#{File.basename(client_public_key).gsub(/\.pem$/, '')}" 170: else 171: # servers need to set callerid as well, not usually needed but 172: # would be if you're doing registration or auditing or generating 173: # requests for some or other reason 174: return "cert=#{File.basename(server_public_key).gsub(/\.pem$/, '')}" 175: end 176: end
Takes our cert=foo callerids and return the foo bit else nil
# File plugins/mcollective/security/aes_security.rb, line 265 265: def certname_from_callerid(id) 266: if id =~ /^cert=(.+)/ 267: return $1 268: else 269: return nil 270: end 271: end
Figures out where to get client public certs from the plugin.aes.client_cert_dir config option
# File plugins/mcollective/security/aes_security.rb, line 259 259: def client_cert_dir 260: raise("No plugin.aes.client_cert_dir configuration option specified") unless @config.pluginconf.include?("aes.client_cert_dir") 261: @config.pluginconf["aes.client_cert_dir"] 262: end
Figures out the client private key either from MCOLLECTIVE_AES_PRIVATE or the plugin.aes.client_private config option
# File plugins/mcollective/security/aes_security.rb, line 228 228: def client_private_key 229: return ENV["MCOLLECTIVE_AES_PRIVATE"] if ENV.include?("MCOLLECTIVE_AES_PRIVATE") 230: 231: raise("No plugin.aes.client_private configuration option specified") unless @config.pluginconf.include?("aes.client_private") 232: 233: return @config.pluginconf["aes.client_private"] 234: end
Figures out the client public key either from MCOLLECTIVE_AES_PUBLIC or the plugin.aes.client_public config option
# File plugins/mcollective/security/aes_security.rb, line 238 238: def client_public_key 239: return ENV["MCOLLECTIVE_AES_PUBLIC"] if ENV.include?("MCOLLECTIVE_AES_PUBLIC") 240: 241: raise("No plugin.aes.client_public configuration option specified") unless @config.pluginconf.include?("aes.client_public") 242: 243: return @config.pluginconf["aes.client_public"] 244: end
# File plugins/mcollective/security/aes_security.rb, line 56 56: def decodemsg(msg) 57: body = deserialize(msg.payload) 58: 59: # if we get a message that has a pubkey attached and we're set to learn 60: # then add it to the client_cert_dir this should only happen on servers 61: # since clients will get replies using their own pubkeys 62: if @config.pluginconf.include?("aes.learn_pubkeys") && @config.pluginconf["aes.learn_pubkeys"] == "1" 63: if body.include?(:sslpubkey) 64: if client_cert_dir 65: certname = certname_from_callerid(body[:callerid]) 66: if certname 67: certfile = "#{client_cert_dir}/#{certname}.pem" 68: unless File.exist?(certfile) 69: Log.debug("Caching client cert in #{certfile}") 70: File.open(certfile, "w") {|f| f.print body[:sslpubkey]} 71: end 72: end 73: end 74: end 75: end 76: 77: cryptdata = {:key => body[:sslkey], :data => body[:body]} 78: 79: if @initiated_by == :client 80: body[:body] = deserialize(decrypt(cryptdata, nil)) 81: else 82: body[:body] = deserialize(decrypt(cryptdata, body[:callerid])) 83: end 84: 85: return body 86: rescue OpenSSL::PKey::RSAError 87: raise MsgDoesNotMatchRequestID, "Could not decrypt message using our key, possibly directed at another client" 88: 89: rescue Exception => e 90: Log.warn("Could not decrypt message from client: #{e.class}: #{e}") 91: raise SecurityValidationFailed, "Could not decrypt message" 92: end
# File plugins/mcollective/security/aes_security.rb, line 201 201: def decrypt(string, certid) 202: if @initiated_by == :client 203: @ssl ||= SSL.new(client_public_key, client_private_key) 204: 205: Log.debug("Decrypting message using private key") 206: return @ssl.decrypt_with_private(string) 207: else 208: Log.debug("Decrypting message using public key for #{certid}") 209: 210: ssl = SSL.new(public_key_path_for_client(certid)) 211: return ssl.decrypt_with_public(string) 212: end 213: end
De-Serializes a message using the configured encoder
# File plugins/mcollective/security/aes_security.rb, line 153 153: def deserialize(msg) 154: serializer = @config.pluginconf["aes.serializer"] || "marshal" 155: 156: Log.debug("De-Serializing using #{serializer}") 157: 158: case serializer 159: when "yaml" 160: return YAML.load(msg) 161: else 162: return Marshal.load(msg) 163: end 164: end
Encodes a reply
# File plugins/mcollective/security/aes_security.rb, line 95 95: def encodereply(sender, target, msg, requestid, requestcallerid) 96: crypted = encrypt(serialize(msg), requestcallerid) 97: 98: Log.debug("Encoded a reply for request #{requestid} for #{requestcallerid}") 99: 100: req = {:senderid => @config.identity, 101: :requestid => requestid, 102: :senderagent => sender, 103: :msgtarget => target, 104: :msgtime => Time.now.to_i, 105: :sslkey => crypted[:key], 106: :body => crypted[:data]} 107: 108: serialize(req) 109: end
Encodes a request msg
# File plugins/mcollective/security/aes_security.rb, line 112 112: def encoderequest(sender, target, msg, requestid, filter={}) 113: crypted = encrypt(serialize(msg), callerid) 114: 115: Log.debug("Encoding a request for '#{target}' with request id #{requestid}") 116: 117: req = {:senderid => @config.identity, 118: :requestid => requestid, 119: :msgtarget => target, 120: :msgtime => Time.now.to_i, 121: :body => crypted, 122: :filter => filter, 123: :callerid => callerid, 124: :sslkey => crypted[:key], 125: :body => crypted[:data]} 126: 127: if @config.pluginconf.include?("aes.send_pubkey") && @config.pluginconf["aes.send_pubkey"] == "1" 128: if @initiated_by == :client 129: req[:sslpubkey] = File.read(client_public_key) 130: else 131: req[:sslpubkey] = File.read(server_public_key) 132: end 133: end 134: 135: serialize(req) 136: end
# File plugins/mcollective/security/aes_security.rb, line 178 178: def encrypt(string, certid) 179: if @initiated_by == :client 180: @ssl ||= SSL.new(client_public_key, client_private_key) 181: 182: Log.debug("Encrypting message using private key") 183: return @ssl.encrypt_with_private(string) 184: else 185: # when the server is initating requests like for registration 186: # then the certid will be our callerid 187: if certid == callerid 188: Log.debug("Encrypting message using private key #{server_private_key}") 189: 190: ssl = SSL.new(server_public_key, server_private_key) 191: return ssl.encrypt_with_private(string) 192: else 193: Log.debug("Encrypting message using public key for #{certid}") 194: 195: ssl = SSL.new(public_key_path_for_client(certid)) 196: return ssl.encrypt_with_public(string) 197: end 198: end 199: end
On servers this will look in the aes.client_cert_dir for public keys matching the clientid, clientid is expected to be in the format set by callerid
# File plugins/mcollective/security/aes_security.rb, line 218 218: def public_key_path_for_client(clientid) 219: raise "Unknown callerid format in '#{clientid}'" unless clientid.match(/^cert=(.+)$/) 220: 221: clientid = $1 222: 223: client_cert_dir + "/#{clientid}.pem" 224: end
Serializes a message using the configured encoder
# File plugins/mcollective/security/aes_security.rb, line 139 139: def serialize(msg) 140: serializer = @config.pluginconf["aes.serializer"] || "marshal" 141: 142: Log.debug("Serializing using #{serializer}") 143: 144: case serializer 145: when "yaml" 146: return YAML.dump(msg) 147: else 148: return Marshal.dump(msg) 149: end 150: end
Figures out the server private key from the plugin.aes.server_private config option
# File plugins/mcollective/security/aes_security.rb, line 253 253: def server_private_key 254: raise("No plugin.aes.server_private configuration option specified") unless @config.pluginconf.include?("aes.server_private") 255: @config.pluginconf["aes.server_private"] 256: end
Figures out the server public key from the plugin.aes.server_public config option
# File plugins/mcollective/security/aes_security.rb, line 247 247: def server_public_key 248: raise("No aes.server_public configuration option specified") unless @config.pluginconf.include?("aes.server_public") 249: return @config.pluginconf["aes.server_public"] 250: end