Class: MCollective::Security::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/mcollective/security/base.rb

Overview

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.

Direct Known Subclasses

Aes_security, Psk, Ssl

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBase

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



38
39
40
41
42
# File 'lib/mcollective/security/base.rb', line 38

def initialize
  @config = Config.instance
  @log = Log
  @stats = PluginManager["global_stats"]
end

Instance Attribute Details

#initiated_byObject

Returns the value of attribute initiated_by.



29
30
31
# File 'lib/mcollective/security/base.rb', line 29

def initiated_by
  @initiated_by
end

#statsObject (readonly)

Returns the value of attribute stats.



28
29
30
# File 'lib/mcollective/security/base.rb', line 28

def stats
  @stats
end

Class Method Details

.inherited(klass) ⇒ Object

Register plugins that inherits base



32
33
34
# File 'lib/mcollective/security/base.rb', line 32

def self.inherited(klass)
  PluginManager << {:type => "security_plugin", :class => klass.to_s}
end

Instance Method Details

#calleridObject

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.



219
220
221
# File 'lib/mcollective/security/base.rb', line 219

def callerid
  "uid=#{Process.uid}"
end

#create_reply(reqid, agent, body) ⇒ Object



167
168
169
170
171
172
173
174
175
# File 'lib/mcollective/security/base.rb', line 167

def create_reply(reqid, agent, body)
  Log.debug("Encoded a message for request #{reqid}")

  {:senderid => @config.identity,
   :requestid => reqid,
   :senderagent => agent,
   :msgtime => Time.now.utc.to_i,
   :body => body}
end

#create_request(reqid, filter, msg, initiated_by, target_agent, target_collective, ttl = 60) ⇒ Object



177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/mcollective/security/base.rb', line 177

def create_request(reqid, filter, msg, initiated_by, target_agent, target_collective, ttl=60)
  Log.debug("Encoding a request for agent '#{target_agent}' in collective #{target_collective} with request id #{reqid}")

  {:body => msg,
   :senderid => @config.identity,
   :requestid => reqid,
   :filter => filter,
   :collective => target_collective,
   :agent => target_agent,
   :callerid => callerid,
   :ttl => ttl,
   :msgtime => Time.now.utc.to_i}
end

#decodemsg(msg) ⇒ Object

Security providers should provide this, see MCollective::Security::Psk



239
240
241
# File 'lib/mcollective/security/base.rb', line 239

def decodemsg(msg)
  Log.error("decodemsg is not implemented in #{self.class}")
end

#encodereply(sender, msg, requestcallerid = nil) ⇒ Object

Security providers should provide this, see MCollective::Security::Psk



234
235
236
# File 'lib/mcollective/security/base.rb', line 234

def encodereply(sender, msg, requestcallerid=nil)
  Log.error("encodereply is not implemented in #{self.class}")
end

#encoderequest(sender, msg, filter = {}) ⇒ Object

Security providers should provide this, see MCollective::Security::Psk



229
230
231
# File 'lib/mcollective/security/base.rb', line 229

def encoderequest(sender, msg, filter={})
  Log.error("encoderequest is not implemented in #{self.class}")
end

#should_process_msg?(msg, msgid) ⇒ Boolean

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

Returns:

  • (Boolean)


196
197
198
199
200
201
202
203
204
205
206
# File 'lib/mcollective/security/base.rb', line 196

def should_process_msg?(msg, msgid)
  if msg.expected_msgid
    unless msg.expected_msgid == msgid
      msgtext = "Got a message with id %s but was expecting %s, ignoring message" % [msgid, msg.expected_msgid]
      Log.debug msgtext
      raise MsgDoesNotMatchRequestID, msgtext
    end
  end

  true
end

#valid_callerid?(id) ⇒ Boolean

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

Returns:

  • (Boolean)


213
214
215
# File 'lib/mcollective/security/base.rb', line 213

def valid_callerid?(id)
  !!id.match(/^[\w]+=[\w\.\-]+$/)
end

#validate_filter?(filter) ⇒ Boolean

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

Returns:

  • (Boolean)


55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/mcollective/security/base.rb', line 55

def validate_filter?(filter)
  failed = 0
  passed = 0

  passed = 1 if Util.empty_filter?(filter)

  filter.keys.each do |key|
    case key
    when /puppet_class|cf_class/
      filter[key].each do |f|
        Log.debug("Checking for class #{f}")
        if Util.has_cf_class?(f) then
          Log.debug("Passing based on configuration management class #{f}")
          passed += 1
        else
          Log.debug("Failing based on configuration management class #{f}")
          failed += 1
        end
      end

    when "compound"
      filter[key].each do |compound|
        result = false
        truth_values = []

        begin
          compound.each do |expression|
            case expression.keys.first
              when "statement"
                truth_values << Matcher.eval_compound_statement(expression).to_s
              when "fstatement"
                truth_values << Matcher.eval_compound_fstatement(expression.values.first)
              when "and"
                truth_values << "&&"
              when "or"
                truth_values << "||"
              when "("
                truth_values << "("
              when ")"
                truth_values << ")"
              when "not"
                truth_values << "!"
            end
          end

          result = eval(truth_values.join(" "))
        rescue DDLValidationError
          result = false
        end

        if result
          Log.debug("Passing based on class and fact composition")
          passed +=1
        else
          Log.debug("Failing based on class and fact composition")
          failed +=1
        end
      end

    when "agent"
      filter[key].each do |f|
        if Util.has_agent?(f) || f == "mcollective"
          Log.debug("Passing based on agent #{f}")
          passed += 1
        else
          Log.debug("Failing based on agent #{f}")
          failed += 1
        end
      end

    when "fact"
      filter[key].each do |f|
        if Util.has_fact?(f[:fact], f[:value], f[:operator])
          Log.debug("Passing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}")
          passed += 1
        else
          Log.debug("Failing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}")
          failed += 1
        end
      end

    when "identity"
      unless filter[key].empty?
        # Identity filters should not be 'and' but 'or' as each node can only have one identity
        matched = filter[key].select{|f| Util.has_identity?(f)}.size

        if matched == 1
          Log.debug("Passing based on identity")
          passed += 1
        else
          Log.debug("Failed based on identity")
          failed += 1
        end
      end
    end
  end

  if failed == 0 && passed > 0
    Log.debug("Message passed the filter checks")

    @stats.passed

    return true
  else
    Log.debug("Message failed the filter checks")

    @stats.filtered

    return false
  end
end

#validrequest?(req) ⇒ Boolean

Security providers should provide this, see MCollective::Security::Psk

Returns:

  • (Boolean)


224
225
226
# File 'lib/mcollective/security/base.rb', line 224

def validrequest?(req)
  Log.error("validrequest? is not implemented in #{self.class}")
end