| 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