Class MCollective::Security::Sshkey
In: plugins/mcollective/security/sshkey.rb
Parent: Base

A security plugin for MCollective that uses ssh keys for message signing and verification

For clients (things initiating RPC calls):

  • Message signing will use your ssh-agent.
  • Message verification is done using the public key of the host that sent the message. This means you have to have the public key known before you can verify a message. Generally, this is your ~/.ssh/known_hosts file, but we invoke ‘ssh-keygen -F <host>’ The ‘host’ comes from the SimpleRPC senderid (defaults to the hostname)

    Clients identify themselves with the RPCcallerid’ as your current user (via Etc::getlogin)

For nodes/agents:

  • Message signing uses the value of ‘plugin.sshkey’ in server.cfg. I recommend you use the path of your host‘s ssh rsa key, for example: /etc/ssh/ssh_host_rsa_key
  • Message verification uses your user‘s authorized_keys file. The ‘user’ comes from the RPCcallerid’ field. This user must exist on your node host

In cases of configurable paths, like the location of your authorized_keys file, the ‘sshkeyauth’ library will try to parse it from the sshd_config file (defaults to /etc/ssh/sshd_config)

Since there is no challenge-reponse in MCollective RPC, we can‘t emulate ssh‘s "try each key until one is accepted" method. Instead, we will sign each method with all keys in your agent and the receiver will try to verify against any of them.

Serialization uses Marshal.

NOTE: This plugin should be considered experimental at this point as it has

      a few gotchas and drawbacks.

      * Nodes cannot easily send messages now, this means registration is
        not supported
      * Automated systems that wish to manage a collective with this plugin
        will somehow need access to ssh agents, this can insecure and
        problematic in general.

We‘re including this plugin as an early preview of what is being worked on in order to solicit feedback.

Configuration:

For clients:

  securityprovider = sshkey

For nodes:

  securityprovider = sshkey
  plugin.sshkey = /etc/ssh/ssh_host_rsa_key

Methods

Public Instance methods

[Source]

     # File plugins/mcollective/security/sshkey.rb, line 114
114:             def callerid
115:                 return Etc.getlogin
116:             end

Decodes a message by unserializing all the bits etc TODO(sissel): refactor this into Base?

[Source]

    # File plugins/mcollective/security/sshkey.rb, line 67
67:             def decodemsg(msg)
68:                 body = Marshal.load(msg.payload)
69: 
70:                 if validrequest?(body)
71:                     body[:body] = Marshal.load(body[:body])
72:                     return body
73:                 else
74:                     nil
75:                 end
76:             end

Encodes a reply

[Source]

    # File plugins/mcollective/security/sshkey.rb, line 79
79:             def encodereply(sender, target, msg, requestid, requestcallerid=nil)
80:                 serialized  = Marshal.dump(msg)
81:                 digest = makehash(serialized)
82: 
83:                 Log.debug("Encoded a message with hash #{digest} for request #{requestid}")
84: 
85:                 Marshal.dump({:senderid => @config.identity,
86:                               :requestid => requestid,
87:                               :senderagent => sender,
88:                               :msgtarget => target,
89:                               :msgtime => Time.now.to_i,
90:                               :hash => digest,
91:                               :body => serialized})
92:             end

Encodes a request msg

[Source]

     # File plugins/mcollective/security/sshkey.rb, line 95
 95:             def encoderequest(sender, target, msg, requestid, filter={})
 96:                 serialized = Marshal.dump(msg)
 97:                 digest = makehash(serialized)
 98: 
 99:                 Log.debug("Encoding a request for '#{target}' with request id #{requestid}")
100:                 request = {:body => serialized,
101:                            :hash => digest,
102:                            :senderid => @config.identity,
103:                            :requestid => requestid,
104:                            :msgtarget => target,
105:                            :filter => filter,
106:                            :msgtime => Time.now.to_i}
107: 
108:                 # if we're in use by a client add the callerid to the main client hashes
109:                 request[:callerid] = callerid if @initiated_by == :client
110: 
111:                 Marshal.dump(request)
112:             end

Checks the md5 hash in the request body against our psk, the request sent for validation should not have been deserialized already

[Source]

     # File plugins/mcollective/security/sshkey.rb, line 121
121:             def validrequest?(req)
122:                 Log.info "Caller id: #{req[:callerid]}"
123:                 Log.info "Sender id: #{req[:senderid]}"
124:                 message = req[:body]
125: 
126:                 #@log.info req.awesome_inspect
127:                 identity = (req[:callerid] or req[:senderid])
128:                 verifier = SSH::Key::Verifier.new(identity)
129: 
130:                 Log.info "Using name '#{identity}'"
131: 
132:                 # If no callerid, this is a 'response' message and we should
133:                 # attempt to authenticate using the senderid (hostname, usually)
134:                 # and that ssh key in known_hosts.
135:                 if !req[:callerid]
136:                   # Search known_hosts for the senderid hostname
137:                   verifier.add_key_from_host(identity)
138:                   verifier.use_agent = false
139:                   verifier.use_authorized_keys = false
140:                 end
141: 
142:                 signatures = Marshal.load(req[:hash])
143:                 if verifier.verify?(signatures, req[:body])
144:                     @stats.validated
145:                     return true
146:                 else
147:                     @stats.unvalidated
148:                     raise(SecurityValidationFailed, "Received an invalid signature in message")
149:                 end
150:             end

[Validate]