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

initiated_by[RW]
stats[R]

Public Class Methods

inherited(klass) click to toggle source

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
new() click to toggle source

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

callerid() click to toggle source

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
create_reply(reqid, agent, body) click to toggle source
    # 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
create_request(reqid, filter, msg, initiated_by, target_agent, target_collective, ttl=60) click to toggle source
    # 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
decodemsg(msg) click to toggle source

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
encodereply(sender, msg, requestcallerid=nil) click to toggle source

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
encoderequest(sender, msg, filter={}) click to toggle source

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
should_process_msg?(msg, msgid) click to toggle source

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
valid_callerid?(id) click to toggle source

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
validate_filter?(filter) click to toggle source

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
validrequest?(req) click to toggle source

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