Class: MCollective::Security::Ssl
- Defined in:
- lib/mcollective/security/ssl.rb
Overview
Impliments a public/private key based message validation system using SSL public and private keys.
The design goal of the plugin is two fold:
-
give different security credentials to clients and servers to avoid a compromised server from sending new client requests.
-
create a token that uniquely identify the client - based on the filename of the public key
To setup you need to create a SSL key pair that is shared by all nodes.
openssl genrsa -out mcserver-private.pem 1024
openssl rsa -in mcserver-private.pem -out mcserver-public.pem -outform PEM -pubout
Distribute the private and public file to /etc/mcollective/ssl on all the nodes. Distribute the public file to /etc/mcollective/ssl everywhere the client code runs.
Now you should create a key pair for every one of your clients, here we create one for user john - you could also if you are less concerned with client id create one pair and share it with all clients:
openssl genrsa -out john-private.pem 1024
openssl rsa -in john-private.pem -out john-public.pem -outform PEM -pubout
Each user has a unique userid, this is based on the name of the public key. In this example case the userid would be ‘john-public’.
Store these somewhere like:
/home/john/.mc/john-private.pem
/home/john/.mc/john-public.pem
Every users public key needs to be distributed to all the nodes, save the john one in a file called:
/etc/mcollective/ssl/clients/john-public.pem
If you wish to use registration or auditing that sends connections over MC to a central host you will need also put the server-public.pem in the clients directory.
You should be aware if you do add the node public key to the clients dir you will in effect be weakening your overall security. You should consider doing this only if you also set up an Authorization method that limits the requests the nodes can make.
client.cfg:
securityprovider = ssl
plugin.ssl_server_public = /etc/mcollective/ssl/server-public.pem
plugin.ssl_client_private = /home/john/.mc/john-private.pem
plugin.ssl_client_public = /home/john/.mc/john-public.pem
If you have many clients per machine and dont want to configure the main config file with the public/private keys you can set the following environment variables:
export MCOLLECTIVE_SSL_PRIVATE=/home/john/.mc/john-private.pem
export MCOLLECTIVE_SSL_PUBLIC=/home/john/.mc/john-public.pem
server.cfg:
securityprovider = ssl
plugin.ssl_server_private = /etc/mcollective/ssl/server-private.pem
plugin.ssl_server_public = /etc/mcollective/ssl/server-public.pem
plugin.ssl_client_cert_dir = /etc/mcollective/etc/ssl/clients/
# Log but accept messages that may have been tampered with
plugin.ssl.enforce_ttl = 0
Serialization can be configured to use either Marshal or YAML, data types in and out of mcollective will be preserved from client to server and reverse
You can configure YAML serialization:
plugins.ssl_serializer = yaml
else the default is Marshal. Use YAML if you wish to write a client using a language other than Ruby that doesn’t support Marshal.
Validation is as default and is provided by MCollective::Security::Base
Initial code was contributed by Vladimir Vuksan and modified by R.I.Pienaar
Instance Attribute Summary
Attributes inherited from Base
Instance Method Summary collapse
-
#callerid ⇒ Object
sets the caller id to the md5 of the public key.
-
#decodemsg(msg) ⇒ Object
Decodes a message by unserializing all the bits etc, it also validates it as valid using the psk etc.
-
#encodereply(sender, msg, requestid, requestcallerid = nil) ⇒ Object
Encodes a reply.
-
#encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl = 60) ⇒ Object
Encodes a request msg.
-
#update_secure_property(msg, secure_property, property, description) ⇒ Object
To avoid tampering we turn the origin body into a hash and copy some of the protocol keys like :ttl and :msg_time into the hash before hashing it.
-
#validrequest?(req) ⇒ Boolean
Checks the SSL signature in the request body.
Methods inherited from Base
#create_reply, #create_request, inherited, #initialize, #should_process_msg?, #valid_callerid?, #validate_filter?
Constructor Details
This class inherits a constructor from MCollective::Security::Base
Instance Method Details
#callerid ⇒ Object
sets the caller id to the md5 of the public key
188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/mcollective/security/ssl.rb', line 188 def callerid if @initiated_by == :client id = "cert=#{File.basename(client_public_key).gsub(/\.pem$/, '')}" raise "Invalid callerid generated from client public key" unless valid_callerid?(id) else # servers need to set callerid as well, not usually needed but # would be if you're doing registration or auditing or generating # requests for some or other reason id = "cert=#{File.basename(server_public_key).gsub(/\.pem$/, '')}" raise "Invalid callerid generated from server public key" unless valid_callerid?(id) end return id end |
#decodemsg(msg) ⇒ Object
Decodes a message by unserializing all the bits etc, it also validates it as valid using the psk etc
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/mcollective/security/ssl.rb', line 90 def decodemsg(msg) body = deserialize(msg.payload) should_process_msg?(msg, body[:requestid]) if validrequest?(body) body[:body] = deserialize(body[:body]) unless @initiated_by == :client if body[:body].is_a?(Hash) update_secure_property(body, :ssl_ttl, :ttl, "TTL") update_secure_property(body, :ssl_msgtime, :msgtime, "Message Time") body[:body] = body[:body][:ssl_msg] if body[:body].include?(:ssl_msg) else unless @config.pluginconf["ssl.enforce_ttl"] == nil raise "Message %s is in an unknown or older security protocol, ignoring" % [request_description(body)] end end end return body else nil end end |
#encodereply(sender, msg, requestid, requestcallerid = nil) ⇒ Object
Encodes a reply
142 143 144 145 146 147 148 149 150 151 |
# File 'lib/mcollective/security/ssl.rb', line 142 def encodereply(sender, msg, requestid, requestcallerid=nil) serialized = serialize(msg) digest = makehash(serialized) req = create_reply(requestid, sender, serialized) req[:hash] = digest serialize(req) end |
#encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl = 60) ⇒ Object
Encodes a request msg
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/mcollective/security/ssl.rb', line 154 def encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl=60) req = create_request(requestid, filter, "", @initiated_by, target_agent, target_collective, ttl) ssl_msg = {:ssl_msg => msg, :ssl_ttl => ttl, :ssl_msgtime => req[:msgtime]} serialized = serialize(ssl_msg) digest = makehash(serialized) req[:hash] = digest req[:body] = serialized serialize(req) end |
#update_secure_property(msg, secure_property, property, description) ⇒ Object
To avoid tampering we turn the origin body into a hash and copy some of the protocol keys like :ttl and :msg_time into the hash before hashing it.
This function compares and updates the unhashed ones based on the hashed ones. By default it enforces matching and presense by raising exceptions, if ssl.enforce_ttl is set to 0 it will only log warnings about violations
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/mcollective/security/ssl.rb', line 123 def update_secure_property(msg, secure_property, property, description) req = request_description(msg) unless @config.pluginconf["ssl.enforce_ttl"] == "0" raise "Request #{req} does not have a secure #{description}" unless msg[:body].include?(secure_property) raise "Request #{req} #{description} does not match encrypted #{description} - possible tampering" unless msg[:body][secure_property] == msg[property] else if msg[:body].include?(secure_property) Log.warn("Request #{req} #{description} does not match encrypted #{description} - possible tampering") unless msg[:body][secure_property] == msg[property] else Log.warn("Request #{req} does not have a secure #{description}") unless msg[:body].include?(secure_property) end end msg[property] = msg[:body][secure_property] if msg[:body].include?(secure_property) msg[:body].delete(secure_property) end |
#validrequest?(req) ⇒ Boolean
Checks the SSL signature in the request body
171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/mcollective/security/ssl.rb', line 171 def validrequest?(req) = req[:body] signature = req[:hash] Log.debug("Validating request from #{req[:callerid]}") if verify(public_key_file(req[:callerid]), signature, .to_s) @stats.validated return true else @stats.unvalidated raise(SecurityValidationFailed, "Received an invalid signature in message") end end |