class MCollective::Security::Base
This is a base class the other security modules should inherit from it handles statistics and validation of messages that should in most cases apply to all security models.
To create your own security plugin you should provide a plugin that inherits from this and provides the following methods:
decodemsg - Decodes a message that was received from the middleware encodereply - Encodes a reply message to a previous request message encoderequest - Encodes a new request message validrequest? - Validates a request received from the middleware
Optionally if you are identifying users by some other means like certificate name you can provide your own callerid method that can provide the rest of the system with an id, and you would see this id being usable in SimpleRPC authorization methods
The @initiated_by variable will be set to either :client or :node depending on who is using this plugin. This is to help security providers that operate in an asymetric mode like public/private key based systems.
Specifics of each of these are a bit fluid and the interfaces for this is not set in stone yet, specifically the encode methods will be provided with a helper that takes care of encoding the core requirements. The best place to see how security works is by looking at the provided MCollective::Security::PSK plugin.
Attributes
Public Class Methods
Register plugins that inherits base
# File lib/mcollective/security/base.rb 32 def self.inherited(klass) 33 PluginManager << {:type => "security_plugin", :class => klass.to_s} 34 end
Initializes configuration and logging as well as prepare a zero'd hash of stats various security methods and filter validators should increment stats, see MCollective::Security::Psk
for a sample
# File lib/mcollective/security/base.rb 38 def initialize 39 @config = Config.instance 40 @log = Log 41 @stats = PluginManager["global_stats"] 42 end
Public Instance Methods
Returns a unique id for the caller, by default we just use the unix user id, security plugins can provide their own means of doing ids.
# File lib/mcollective/security/base.rb 219 def callerid 220 "uid=#{Process.uid}" 221 end
# File lib/mcollective/security/base.rb 167 def create_reply(reqid, agent, body) 168 Log.debug("Encoded a message for request #{reqid}") 169 170 {:senderid => @config.identity, 171 :requestid => reqid, 172 :senderagent => agent, 173 :msgtime => Time.now.utc.to_i, 174 :body => body} 175 end
# File lib/mcollective/security/base.rb 177 def create_request(reqid, filter, msg, initiated_by, target_agent, target_collective, ttl=60) 178 Log.debug("Encoding a request for agent '#{target_agent}' in collective #{target_collective} with request id #{reqid}") 179 180 {:body => msg, 181 :senderid => @config.identity, 182 :requestid => reqid, 183 :filter => filter, 184 :collective => target_collective, 185 :agent => target_agent, 186 :callerid => callerid, 187 :ttl => ttl, 188 :msgtime => Time.now.utc.to_i} 189 end
Security
providers should provide this, see MCollective::Security::Psk
# File lib/mcollective/security/base.rb 239 def decodemsg(msg) 240 Log.error("decodemsg is not implemented in #{self.class}") 241 end
Security
providers should provide this, see MCollective::Security::Psk
# File lib/mcollective/security/base.rb 234 def encodereply(sender, msg, requestcallerid=nil) 235 Log.error("encodereply is not implemented in #{self.class}") 236 end
Security
providers should provide this, see MCollective::Security::Psk
# File lib/mcollective/security/base.rb 229 def encoderequest(sender, msg, filter={}) 230 Log.error("encoderequest is not implemented in #{self.class}") 231 end
Give a MC::Message instance and a message id this will figure out if you the incoming message id matches the one the Message
object is expecting and raise if its not
Mostly used by security plugins to figure out if they should do the hard work of decrypting etc messages that would only later on be ignored
# File lib/mcollective/security/base.rb 196 def should_process_msg?(msg, msgid) 197 if msg.expected_msgid 198 unless msg.expected_msgid == msgid 199 msgtext = "Got a message with id %s but was expecting %s, ignoring message" % [msgid, msg.expected_msgid] 200 Log.debug msgtext 201 raise MsgDoesNotMatchRequestID, msgtext 202 end 203 end 204 205 true 206 end
Validates a callerid. We do not want to allow things like \ and / in callerids since other plugins make assumptions that these are safe strings.
callerids are generally in the form uid=123 or cert=foo etc so we do that here but security plugins could override this for some complex uses
# File lib/mcollective/security/base.rb 213 def valid_callerid?(id) 214 !!id.match(/^[\w]+=[\w\.\-]+$/) 215 end
Takes a Hash with a filter in it and validates it against host information.
At present this supports filter matches against the following criteria:
-
puppet_class|cf_class - Presence of a configuration management class in
the file configured with classesfile
-
agent - Presence of a
MCollective
agent with a supplied name -
fact - The value of a fact avout this system
-
identity - the configured identity of the system
TODO: Support REGEX and/or multiple filter keys to be AND'd
# File lib/mcollective/security/base.rb 55 def validate_filter?(filter) 56 failed = 0 57 passed = 0 58 59 passed = 1 if Util.empty_filter?(filter) 60 61 filter.keys.each do |key| 62 case key 63 when /puppet_class|cf_class/ 64 filter[key].each do |f| 65 Log.debug("Checking for class #{f}") 66 if Util.has_cf_class?(f) then 67 Log.debug("Passing based on configuration management class #{f}") 68 passed += 1 69 else 70 Log.debug("Failing based on configuration management class #{f}") 71 failed += 1 72 end 73 end 74 75 when "compound" 76 filter[key].each do |compound| 77 result = false 78 truth_values = [] 79 80 begin 81 compound.each do |expression| 82 case expression.keys.first 83 when "statement" 84 truth_values << Matcher.eval_compound_statement(expression).to_s 85 when "fstatement" 86 truth_values << Matcher.eval_compound_fstatement(expression.values.first) 87 when "and" 88 truth_values << "&&" 89 when "or" 90 truth_values << "||" 91 when "(" 92 truth_values << "(" 93 when ")" 94 truth_values << ")" 95 when "not" 96 truth_values << "!" 97 end 98 end 99 100 result = eval(truth_values.join(" ")) 101 rescue DDLValidationError 102 result = false 103 end 104 105 if result 106 Log.debug("Passing based on class and fact composition") 107 passed +=1 108 else 109 Log.debug("Failing based on class and fact composition") 110 failed +=1 111 end 112 end 113 114 when "agent" 115 filter[key].each do |f| 116 if Util.has_agent?(f) || f == "mcollective" 117 Log.debug("Passing based on agent #{f}") 118 passed += 1 119 else 120 Log.debug("Failing based on agent #{f}") 121 failed += 1 122 end 123 end 124 125 when "fact" 126 filter[key].each do |f| 127 if Util.has_fact?(f[:fact], f[:value], f[:operator]) 128 Log.debug("Passing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}") 129 passed += 1 130 else 131 Log.debug("Failing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}") 132 failed += 1 133 end 134 end 135 136 when "identity" 137 unless filter[key].empty? 138 # Identity filters should not be 'and' but 'or' as each node can only have one identity 139 matched = filter[key].select{|f| Util.has_identity?(f)}.size 140 141 if matched == 1 142 Log.debug("Passing based on identity") 143 passed += 1 144 else 145 Log.debug("Failed based on identity") 146 failed += 1 147 end 148 end 149 end 150 end 151 152 if failed == 0 && passed > 0 153 Log.debug("Message passed the filter checks") 154 155 @stats.passed 156 157 return true 158 else 159 Log.debug("Message failed the filter checks") 160 161 @stats.filtered 162 163 return false 164 end 165 end
Security
providers should provide this, see MCollective::Security::Psk
# File lib/mcollective/security/base.rb 224 def validrequest?(req) 225 Log.error("validrequest? is not implemented in #{self.class}") 226 end