3 # This is a base class the other security modules should inherit from
4 # it handles statistics and validation of messages that should in most
5 # cases apply to all security models.
7 # To create your own security plugin you should provide a plugin that inherits
8 # from this and provides the following methods:
10 # decodemsg - Decodes a message that was received from the middleware
11 # encodereply - Encodes a reply message to a previous request message
12 # encoderequest - Encodes a new request message
13 # validrequest? - Validates a request received from the middleware
15 # Optionally if you are identifying users by some other means like certificate name
16 # you can provide your own callerid method that can provide the rest of the system
17 # with an id, and you would see this id being usable in SimpleRPC authorization methods
19 # The @initiated_by variable will be set to either :client or :node depending on
20 # who is using this plugin. This is to help security providers that operate in an
21 # asymetric mode like public/private key based systems.
23 # Specifics of each of these are a bit fluid and the interfaces for this is not
24 # set in stone yet, specifically the encode methods will be provided with a helper
25 # that takes care of encoding the core requirements. The best place to see how security
26 # works is by looking at the provided MCollective::Security::PSK plugin.
29 attr_accessor :initiated_by
31 # Register plugins that inherits base
32 def self.inherited(klass)
33 PluginManager << {:type => "security_plugin", :class => klass.to_s}
36 # Initializes configuration and logging as well as prepare a zero'd hash of stats
37 # various security methods and filter validators should increment stats, see MCollective::Security::Psk for a sample
39 @config = Config.instance
41 @stats = PluginManager["global_stats"]
44 # Takes a Hash with a filter in it and validates it against host information.
46 # At present this supports filter matches against the following criteria:
48 # - puppet_class|cf_class - Presence of a configuration management class in
49 # the file configured with classesfile
50 # - agent - Presence of a MCollective agent with a supplied name
51 # - fact - The value of a fact avout this system
52 # - identity - the configured identity of the system
54 # TODO: Support REGEX and/or multiple filter keys to be AND'd
55 def validate_filter?(filter)
59 passed = 1 if Util.empty_filter?(filter)
61 filter.keys.each do |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}")
70 Log.debug("Failing based on configuration management class #{f}")
76 filter[key].each do |compound|
81 compound.each do |expression|
82 case expression.keys.first
84 truth_values << Matcher.eval_compound_statement(expression).to_s
86 truth_values << Matcher.eval_compound_fstatement(expression.values.first)
100 result = eval(truth_values.join(" "))
101 rescue DDLValidationError
106 Log.debug("Passing based on class and fact composition")
109 Log.debug("Failing based on class and fact composition")
115 filter[key].each do |f|
116 if Util.has_agent?(f) || f == "mcollective"
117 Log.debug("Passing based on agent #{f}")
120 Log.debug("Failing based on agent #{f}")
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]}")
131 Log.debug("Failing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}")
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
142 Log.debug("Passing based on identity")
145 Log.debug("Failed based on identity")
152 if failed == 0 && passed > 0
153 Log.debug("Message passed the filter checks")
159 Log.debug("Message failed the filter checks")
167 def create_reply(reqid, agent, body)
168 Log.debug("Encoded a message for request #{reqid}")
170 {:senderid => @config.identity,
172 :senderagent => agent,
173 :msgtime => Time.now.utc.to_i,
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}")
181 :senderid => @config.identity,
184 :collective => target_collective,
185 :agent => target_agent,
186 :callerid => callerid,
188 :msgtime => Time.now.utc.to_i}
191 # Give a MC::Message instance and a message id this will figure out if you the incoming
192 # message id matches the one the Message object is expecting and raise if its not
194 # Mostly used by security plugins to figure out if they should do the hard work of decrypting
195 # etc messages that would only later on be ignored
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]
201 raise MsgDoesNotMatchRequestID, msgtext
208 # Validates a callerid. We do not want to allow things like \ and / in
209 # callerids since other plugins make assumptions that these are safe strings.
211 # callerids are generally in the form uid=123 or cert=foo etc so we do that
212 # here but security plugins could override this for some complex uses
213 def valid_callerid?(id)
214 !!id.match(/^[\w]+=[\w\.\-]+$/)
217 # Returns a unique id for the caller, by default we just use the unix
218 # user id, security plugins can provide their own means of doing ids.
223 # Security providers should provide this, see MCollective::Security::Psk
224 def validrequest?(req)
225 Log.error("validrequest? is not implemented in #{self.class}")
228 # Security providers should provide this, see MCollective::Security::Psk
229 def encoderequest(sender, msg, filter={})
230 Log.error("encoderequest is not implemented in #{self.class}")
233 # Security providers should provide this, see MCollective::Security::Psk
234 def encodereply(sender, msg, requestcallerid=nil)
235 Log.error("encodereply is not implemented in #{self.class}")
238 # Security providers should provide this, see MCollective::Security::Psk
240 Log.error("decodemsg is not implemented in #{self.class}")