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:

  • Each actor - clients and servers - can have their own set of public and private keys
  • All actors are uniquely and cryptographically identified
  • Requests are encrypted using the clients private key and anyone that has the public key can see the request. Thus an atacker may see the requests given access to network or machine due to the broadcast nature of mcollective
  • Replies are encrypted using the calling clients public key. Thus no-one but the caller can view the contents of replies.
  • Servers can all have their own RSA keys, or share one, or reuse keys created by other PKI using software like Puppet
  • Requests from servers - like registration data - can be secured even to external eaves droppers depending on the level of configuration you are prepared to do
  • Given a network where you can ensure third parties are not able to access the middleware public key distribution can happen automatically

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

Methods

Public Instance methods

sets the caller id to the md5 of the public key

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

    # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Validate]