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 151 151: def callerid 152: if @initiated_by == :client 153: return "cert=#{File.basename(client_public_key).gsub(/\.pem$/, '')}" 154: else 155: # servers need to set callerid as well, not usually needed but 156: # would be if you're doing registration or auditing or generating 157: # requests for some or other reason 158: return "cert=#{File.basename(server_public_key).gsub(/\.pem$/, '')}" 159: end 160: end
Takes our cert=foo callerids and return the foo bit else nil
# File plugins/mcollective/security/aes_security.rb, line 249 249: def certname_from_callerid(id) 250: if id =~ /^cert=(.+)/ 251: return $1 252: else 253: return nil 254: end 255: 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 243 243: def client_cert_dir 244: raise("No plugin.aes.client_cert_dir configuration option specified") unless @config.pluginconf.include?("aes.client_cert_dir") 245: @config.pluginconf["aes.client_cert_dir"] 246: 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 212 212: def client_private_key 213: return ENV["MCOLLECTIVE_AES_PRIVATE"] if ENV.include?("MCOLLECTIVE_AES_PRIVATE") 214: 215: raise("No plugin.aes.client_private configuration option specified") unless @config.pluginconf.include?("aes.client_private") 216: 217: return @config.pluginconf["aes.client_private"] 218: 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 222 222: def client_public_key 223: return ENV["MCOLLECTIVE_AES_PUBLIC"] if ENV.include?("MCOLLECTIVE_AES_PUBLIC") 224: 225: raise("No plugin.aes.client_public configuration option specified") unless @config.pluginconf.include?("aes.client_public") 226: 227: return @config.pluginconf["aes.client_public"] 228: 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 185 185: def decrypt(string, certid) 186: if @initiated_by == :client 187: @ssl ||= SSL.new(client_public_key, client_private_key) 188: 189: Log.debug("Decrypting message using private key") 190: return @ssl.decrypt_with_private(string) 191: else 192: Log.debug("Decrypting message using public key for #{certid}") 193: 194: ssl = SSL.new(public_key_path_for_client(certid)) 195: return ssl.decrypt_with_public(string) 196: end 197: end
De-Serializes a message using the configured encoder
# File plugins/mcollective/security/aes_security.rb, line 137 137: def deserialize(msg) 138: serializer = @config.pluginconf["aes.serializer"] || "marshal" 139: 140: Log.debug("De-Serializing using #{serializer}") 141: 142: case serializer 143: when "yaml" 144: return YAML.load(msg) 145: else 146: return Marshal.load(msg) 147: end 148: 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: req = create_reply(requestid, sender, target, crypted[:data]) 99: req[:sslkey] = crypted[:key] 100: 101: serialize(req) 102: end
Encodes a request msg
# File plugins/mcollective/security/aes_security.rb, line 105 105: def encoderequest(sender, target, msg, requestid, filter={}, target_agent=nil, target_collective=nil) 106: crypted = encrypt(serialize(msg), callerid) 107: 108: req = create_request(requestid, target, filter, crypted[:data], @initiated_by, target_agent, target_collective) 109: req[:sslkey] = crypted[:key] 110: 111: if @config.pluginconf.include?("aes.send_pubkey") && @config.pluginconf["aes.send_pubkey"] == "1" 112: if @initiated_by == :client 113: req[:sslpubkey] = File.read(client_public_key) 114: else 115: req[:sslpubkey] = File.read(server_public_key) 116: end 117: end 118: 119: serialize(req) 120: end
# File plugins/mcollective/security/aes_security.rb, line 162 162: def encrypt(string, certid) 163: if @initiated_by == :client 164: @ssl ||= SSL.new(client_public_key, client_private_key) 165: 166: Log.debug("Encrypting message using private key") 167: return @ssl.encrypt_with_private(string) 168: else 169: # when the server is initating requests like for registration 170: # then the certid will be our callerid 171: if certid == callerid 172: Log.debug("Encrypting message using private key #{server_private_key}") 173: 174: ssl = SSL.new(server_public_key, server_private_key) 175: return ssl.encrypt_with_private(string) 176: else 177: Log.debug("Encrypting message using public key for #{certid}") 178: 179: ssl = SSL.new(public_key_path_for_client(certid)) 180: return ssl.encrypt_with_public(string) 181: end 182: end 183: 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 202 202: def public_key_path_for_client(clientid) 203: raise "Unknown callerid format in '#{clientid}'" unless clientid.match(/^cert=(.+)$/) 204: 205: clientid = $1 206: 207: client_cert_dir + "/#{clientid}.pem" 208: end
Serializes a message using the configured encoder
# File plugins/mcollective/security/aes_security.rb, line 123 123: def serialize(msg) 124: serializer = @config.pluginconf["aes.serializer"] || "marshal" 125: 126: Log.debug("Serializing using #{serializer}") 127: 128: case serializer 129: when "yaml" 130: return YAML.dump(msg) 131: else 132: return Marshal.dump(msg) 133: end 134: end
Figures out the server private key from the plugin.aes.server_private config option
# File plugins/mcollective/security/aes_security.rb, line 237 237: def server_private_key 238: raise("No plugin.aes.server_private configuration option specified") unless @config.pluginconf.include?("aes.server_private") 239: @config.pluginconf["aes.server_private"] 240: end
Figures out the server public key from the plugin.aes.server_public config option
# File plugins/mcollective/security/aes_security.rb, line 231 231: def server_public_key 232: raise("No aes.server_public configuration option specified") unless @config.pluginconf.include?("aes.server_public") 233: return @config.pluginconf["aes.server_public"] 234: end