choria-mcorpc-support 2.23.0 → 2.23.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -86,14 +86,6 @@ module MCollective
86
86
  end
87
87
  end
88
88
 
89
- # Which port to provide stats over HTTP on
90
- #
91
- # @return [Integer,nil]
92
- # @raise [StandardError] when not numeric
93
- def stats_port
94
- Integer(get_option("choria.stats_port", "")) if has_option?("choria.stats_port")
95
- end
96
-
97
89
  # Determines if there are any federations configured
98
90
  #
99
91
  # @return [Boolean]
@@ -888,15 +880,6 @@ module MCollective
888
880
  File.exist?(csr_path)
889
881
  end
890
882
 
891
- # The formatted string representation of the CSR fingerprint
892
- #
893
- # @return [String]
894
- def csr_fingerprint
895
- require "puppet"
896
- csr = OpenSSL::X509::Request.new(File.read(csr_path))
897
- Puppet::SSL::Digest.new(nil, csr.to_der)
898
- end
899
-
900
883
  # Searches the PATH for an executable command
901
884
  #
902
885
  # @param command [String] a command to search for
@@ -927,146 +910,6 @@ module MCollective
927
910
  which("facter")
928
911
  end
929
912
 
930
- # Creates any missing SSL directories
931
- #
932
- # This prepares a Puppet like SSL tree in case Puppet
933
- # has not been initialized yet
934
- #
935
- # @return [void]
936
- def make_ssl_dirs
937
- return if file_security?
938
-
939
- FileUtils.mkdir_p(ssl_dir, :mode => 0o0771)
940
-
941
- ["certificate_requests", "certs", "public_keys"].each do |dir|
942
- FileUtils.mkdir_p(File.join(ssl_dir, dir), :mode => 0o0755)
943
- end
944
-
945
- ["private_keys", "private"].each do |dir|
946
- FileUtils.mkdir_p(File.join(ssl_dir, dir), :mode => 0o0750)
947
- end
948
- end
949
-
950
- # Creates a RSA key of a certain strenth
951
- #
952
- # @return [OpenSSL::PKey::RSA]
953
- def create_rsa_key(bits)
954
- OpenSSL::PKey::RSA.new(bits)
955
- end
956
-
957
- # Writes a new 4096 bit key in the puppet default locatioj
958
- #
959
- # @return [OpenSSL::PKey::RSA]
960
- # @raise [StandardError] when the key already exist
961
- def write_key
962
- raise("Refusing to overwrite existing key in %s" % client_private_key) if has_client_private_key?
963
-
964
- key = create_rsa_key(4096)
965
- File.open(client_private_key, "w", 0o0640) {|f| f.write(key.to_pem)}
966
-
967
- key
968
- end
969
-
970
- # Creates a basic CSR
971
- #
972
- # @return [OpenSSL::X509::Request] signed CSR
973
- def create_csr(comonname, orgunit, key)
974
- csr = OpenSSL::X509::Request.new
975
- csr.version = 0
976
- csr.public_key = key.public_key
977
- csr.subject = OpenSSL::X509::Name.new(
978
- [
979
- ["CN", comonname, OpenSSL::ASN1::UTF8STRING],
980
- ["OU", orgunit, OpenSSL::ASN1::UTF8STRING]
981
- ]
982
- )
983
- csr.sign(key, OpenSSL::Digest.new("SHA1"))
984
-
985
- csr
986
- end
987
-
988
- # Creates a new CSR signed by the given key
989
- #
990
- # @param key [OpenSSL::PKey::RSA]
991
- # @return [String] PEM encoded CSR
992
- def write_csr(key)
993
- raise("Refusing to overwrite existing CSR in %s" % csr_path) if has_csr?
994
-
995
- csr = create_csr(certname, "mcollective", key)
996
-
997
- File.open(csr_path, "w", 0o0644) {|f| f.write(csr.to_pem)}
998
-
999
- csr.to_pem
1000
- end
1001
-
1002
- # Fetch and save the CA from Puppet
1003
- #
1004
- # @return [Boolean]
1005
- def fetch_ca
1006
- return true if has_ca?
1007
-
1008
- server = puppetca_server
1009
-
1010
- req = http_get("/puppet-ca/v1/certificate/ca?environment=production", "Accept" => "text/plain")
1011
- resp, _ = https(server).request(req)
1012
-
1013
- if resp.code == "200"
1014
- File.open(ca_path, "w", 0o0644) {|f| f.write(resp.body)}
1015
- else
1016
- raise(UserError, "Failed to fetch CA from %s:%s: %s: %s" % [server[:target], server[:port], resp.code, resp.message])
1017
- end
1018
-
1019
- has_ca?
1020
- end
1021
-
1022
- # Requests a certificate from the Puppet CA
1023
- #
1024
- # This will attempt to create a new key, write a CSR and
1025
- # then sends it to the CA for signing
1026
- #
1027
- # @return [Boolean]
1028
- # @raise [UserError] when requesting the cert fails
1029
- def request_cert
1030
- key = write_key
1031
- csr = write_csr(key)
1032
-
1033
- server = puppetca_server
1034
-
1035
- req = Net::HTTP::Put.new("/puppet-ca/v1/certificate_request/%s?environment=production" % certname, "Content-Type" => "text/plain")
1036
- req.body = csr
1037
- resp, _ = https(server).request(req)
1038
-
1039
- if resp.code == "200"
1040
- true
1041
- else
1042
- raise(UserError, "Failed to request certificate from %s:%s: %s: %s: %s" % [server[:target], server[:port], resp.code, resp.message, resp.body])
1043
- end
1044
- end
1045
-
1046
- # Attempts to fetch a cert from the CA
1047
- #
1048
- # @return [Boolean]
1049
- def attempt_fetch_cert
1050
- return true if has_client_public_cert?
1051
-
1052
- req = http_get("/puppet-ca/v1/certificate/%s?environment=production" % certname, "Accept" => "text/plain")
1053
- resp, _ = https(puppetca_server).request(req)
1054
-
1055
- if resp.code == "200"
1056
- File.open(client_public_cert, "w", 0o0644) {|f| f.write(resp.body)}
1057
- true
1058
- else
1059
- false
1060
- end
1061
- end
1062
-
1063
- # Determines if a CSR has been sent but not yet retrieved
1064
- #
1065
- # @return [Boolean]
1066
- def waiting_for_cert?
1067
- !has_client_public_cert? && has_client_private_key?
1068
- end
1069
-
1070
913
  # Gets a config option
1071
914
  #
1072
915
  # @param opt [String] config option to look up
@@ -250,8 +250,9 @@ module MCollective
250
250
  # @param environment [Hash] environment to run with
251
251
  # @param stdin [String] stdin to send to the command
252
252
  # @param spooldir [String] path to the spool for this specific request
253
+ # @param run_as [String] name of the user who will run the command
253
254
  # @return [Integer] the pid that was spawned
254
- def spawn_command(command, environment, stdin, spooldir)
255
+ def spawn_command(command, environment, stdin, spooldir, run_as)
255
256
  wrapper_input = File.join(spooldir, "wrapper_stdin")
256
257
  wrapper_stdout = File.join(spooldir, "wrapper_stdout")
257
258
  wrapper_stderr = File.join(spooldir, "wrapper_stderr")
@@ -269,7 +270,24 @@ module MCollective
269
270
  options[:in] = wrapper_input
270
271
  end
271
272
 
272
- pid = Process.spawn(environment, command, options)
273
+ if run_as
274
+ raise("System does not allow forking. run_as not usable.") unless Process.respond_to?(:fork)
275
+
276
+ require "etc"
277
+
278
+ u = Etc.getpwnam(run_as)
279
+
280
+ FileUtils.chown_R(u.uid, u.gid, spooldir)
281
+
282
+ pid = Process.fork
283
+ if pid.nil?
284
+ Process.gid = Process.egid = u.gid
285
+ Process.uid = Process.euid = u.uid
286
+ Process.exec(environment, command, options)
287
+ end
288
+ else
289
+ pid = Process.spawn(environment, command, options)
290
+ end
273
291
 
274
292
  sleep 0.1 until File.exist?(wrapper_stdout)
275
293
 
@@ -353,7 +371,7 @@ module MCollective
353
371
  meta.print(data.to_json)
354
372
  end
355
373
 
356
- pid = spawn_command(wrapper_path, task_environment(task, requestid, callerid), wrapper_input.to_json, spool)
374
+ pid = spawn_command(wrapper_path, task_environment(task, requestid, callerid), wrapper_input.to_json, spool, task["run_as"])
357
375
 
358
376
  Log.info("Spawned task %s in spool %s with pid %s" % [task["task"], spool, pid])
359
377
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: choria-mcorpc-support
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.23.0
4
+ version: 2.23.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - R.I.Pienaar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-29 00:00:00.000000000 Z
11
+ date: 2021-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: systemu
@@ -79,7 +79,6 @@ files:
79
79
  - lib/mcollective/application.rb
80
80
  - lib/mcollective/application/choria.rb
81
81
  - lib/mcollective/application/completion.rb
82
- - lib/mcollective/application/describe_filter.rb
83
82
  - lib/mcollective/application/facts.rb
84
83
  - lib/mcollective/application/federation.rb
85
84
  - lib/mcollective/application/find.rb
@@ -145,9 +144,6 @@ files:
145
144
  - lib/mcollective/logger/console_logger.rb
146
145
  - lib/mcollective/logger/file_logger.rb
147
146
  - lib/mcollective/logger/syslog_logger.rb
148
- - lib/mcollective/matcher.rb
149
- - lib/mcollective/matcher/parser.rb
150
- - lib/mcollective/matcher/scanner.rb
151
147
  - lib/mcollective/message.rb
152
148
  - lib/mcollective/monkey_patches.rb
153
149
  - lib/mcollective/optionparser.rb
@@ -1,87 +0,0 @@
1
- module MCollective
2
- class Application::Describe_filter < Application # rubocop:disable Style/ClassAndModuleChildren
3
- exclude_argument_sections "common", "rpc"
4
-
5
- description "Display human readable interpretation of filters"
6
-
7
- usage "mco describe_filter -S <filter> -F <filter> -C <filter>"
8
-
9
- def describe_s_filter(stack)
10
- indent = " "
11
- old_indent = " "
12
- puts "-S Query expands to the following instructions:"
13
- puts
14
- stack.each do |token|
15
- case token.keys[0]
16
- when "statement"
17
- if token.values[0] =~ /(<=|>=|=|=~|=)/
18
- op = $1
19
- k, v = token.values[0].split(op)
20
- puts indent + get_fact_string(k, v, op)
21
- else
22
- puts indent + get_class_string(token.values[0])
23
- end
24
- when "fstatement"
25
- v = token.values[0]
26
- result_string = indent + "Execute the Data Query '#{v['name']}'"
27
- result_string += " with parameters (#{v['params']})" if v["params"]
28
- result_string += ". "
29
- result_string += "Check if the query's '#{v['value']}' value #{v['operator']} '#{v['r_compare']}' "
30
- puts result_string
31
- when "("
32
- puts "#{indent}("
33
- old_indent = indent
34
- indent *= 2
35
- when ")"
36
- indent = old_indent
37
- puts "#{indent})"
38
- else
39
- puts indent + token.keys[0].upcase
40
- end
41
- end
42
- end
43
-
44
- def describe_f_filter(facts)
45
- puts "-F filter expands to the following fact comparisons:"
46
- puts
47
- facts.each do |f|
48
- puts " #{get_fact_string(f[:fact], f[:value], f[:operator])}"
49
- end
50
- end
51
-
52
- def describe_c_filter(classes)
53
- puts "-C filter expands to the following class checks:"
54
- puts
55
- classes.each do |c|
56
- puts " #{get_class_string(c)}"
57
- end
58
- end
59
-
60
- def main
61
- unless @options[:filter]["fact"].empty?
62
- describe_f_filter(@options[:filter]["fact"])
63
- puts
64
- end
65
-
66
- unless @options[:filter]["cf_class"].empty?
67
- describe_c_filter(@options[:filter]["cf_class"])
68
- puts
69
- end
70
-
71
- unless @options[:filter]["compound"].empty?
72
- describe_s_filter(@options[:filter]["compound"][0])
73
- puts
74
- end
75
- end
76
-
77
- private
78
-
79
- def get_fact_string(fact, value, oper)
80
- "Check if fact '#{fact}' #{oper} '#{value}'"
81
- end
82
-
83
- def get_class_string(classname)
84
- "Check if class '#{classname}' is present on the host"
85
- end
86
- end
87
- end
@@ -1,220 +0,0 @@
1
- module MCollective
2
- # A parser and scanner that creates a stack machine for a simple
3
- # fact and class matching language used on the CLI to facilitate
4
- # a rich discovery language
5
- #
6
- # Language EBNF
7
- #
8
- # compound = ["("] expression [")"] {["("] expression [")"]}
9
- # expression = [!|not]statement ["and"|"or"] [!|not] statement
10
- # char = A-Z | a-z | < | > | => | =< | _ | - |* | / { A-Z | a-z | < | > | => | =< | _ | - | * | / | }
11
- # int = 0|1|2|3|4|5|6|7|8|9{|0|1|2|3|4|5|6|7|8|9|0}
12
- module Matcher
13
- require "mcollective/matcher/parser"
14
- require "mcollective/matcher/scanner"
15
-
16
- # Helper creates a hash from a function call string
17
- def self.create_function_hash(function_call)
18
- func_hash = {}
19
- f = ""
20
- func_parts = function_call.split(/(!=|>=|<=|<|>|=)/)
21
- func_hash["r_compare"] = func_parts.pop
22
- func_hash["operator"] = func_parts.pop
23
- func = func_parts.join
24
-
25
- # Deal with dots in function parameters and functions without dot values
26
- if func =~ /^.+\(.*\)$/
27
- f = func
28
- else
29
- func_parts = func.split(".")
30
- func_hash["value"] = func_parts.pop
31
- f = func_parts.join(".")
32
- end
33
-
34
- # Deal with regular expression matches
35
- if func_hash["r_compare"] =~ /^\/.*\/$/
36
- func_hash["operator"] = "=~" if func_hash["operator"] == "="
37
- func_hash["operator"] = "!=~" if func_hash["operator"] == "!="
38
- func_hash["r_compare"] = Regexp.new(func_hash["r_compare"].gsub(/^\/|\/$/, ""))
39
- # Convert = operators to == so they can be propperly evaluated
40
- elsif func_hash["operator"] == "="
41
- func_hash["operator"] = "=="
42
- end
43
-
44
- # Grab function name and parameters from left compare string
45
- func_hash["name"], func_hash["params"] = f.split("(")
46
- if func_hash["params"] == ")"
47
- func_hash["params"] = nil
48
- else
49
-
50
- # Walk the function parameters from the front and from the
51
- # back removing the first and last instances of single of
52
- # double qoutes. We do this to handle the case where params
53
- # contain escaped qoutes.
54
- func_hash["params"] = func_hash["params"].gsub(")", "")
55
- func_quotes = func_hash["params"].split(/('|")/)
56
-
57
- func_quotes.each_with_index do |item, i|
58
- if item =~ /'|"/
59
- func_quotes.delete_at(i)
60
- break
61
- end
62
- end
63
-
64
- func_quotes.reverse.each_with_index do |item, i|
65
- if item =~ /'|"/
66
- func_quotes.delete_at(func_quotes.size - i - 1)
67
- break
68
- end
69
- end
70
-
71
- func_hash["params"] = func_quotes.join
72
- end
73
-
74
- func_hash
75
- end
76
-
77
- # Returns the result of an executed function
78
- def self.execute_function(function_hash)
79
- # In the case where a data plugin isn't present there are two ways we can handle
80
- # the raised exception. The function result can either be false or the entire
81
- # expression can fail.
82
- #
83
- # In the case where we return the result as false it opens us op to unexpected
84
- # negation behavior.
85
- #
86
- # !foo('bar').name = bar
87
- #
88
- # In this case the user would expect discovery to match on all machines where
89
- # the name value of the foo function does not equal bar. If a non existent function
90
- # returns false then it is posible to match machines where the name value of the
91
- # foo function is bar.
92
- #
93
- # Instead we raise a DDLValidationError to prevent this unexpected behavior from
94
- # happening.
95
-
96
- result = Data.send(function_hash["name"], function_hash["params"])
97
-
98
- if function_hash["value"]
99
- begin
100
- eval_result = result.send(function_hash["value"])
101
- rescue
102
- # If data field has not been set we set the comparison result to nil
103
- eval_result = nil
104
- end
105
- eval_result
106
- else
107
- result
108
- end
109
- rescue NoMethodError
110
- Log.debug("cannot execute discovery function '#{function_hash['name']}'. data plugin not found")
111
- raise DDLValidationError
112
- end
113
-
114
- # Evaluates a compound statement
115
- def self.eval_compound_statement(expression)
116
- case expression.values.first
117
- when /^\//
118
- Util.has_cf_class?(expression.values.first)
119
- when />=|<=|=|<|>/
120
- optype = expression.values.first.match(/>=|<=|=|<|>/)
121
- name, value = expression.values.first.split(optype[0])
122
- if value.split("")[0] == "/"
123
- optype = "=~"
124
- else
125
- optype[0] == "=" ? optype = "==" : optype = optype[0]
126
- end
127
-
128
- Util.has_fact?(name, value, optype).to_s
129
- else
130
- Util.has_cf_class?(expression.values.first)
131
- end
132
- end
133
-
134
- # Returns the result of an evaluated compound statement that
135
- # includes a function
136
- def self.eval_compound_fstatement(function_hash) # rubocop:disable Metrics/MethodLength
137
- l_compare = execute_function(function_hash)
138
- r_compare = function_hash["r_compare"]
139
- operator = function_hash["operator"]
140
-
141
- # Break out early and return false if the function returns nil
142
- return false if l_compare.nil?
143
-
144
- # Prevent unwanted discovery by limiting comparison operators
145
- # on Strings and Booleans
146
- if (l_compare.is_a?(String) || l_compare.is_a?(TrueClass) || l_compare.is_a?(FalseClass)) && function_hash["operator"].match(/<|>/)
147
- Log.debug("Cannot do > and < comparison on Booleans and Strings '#{l_compare} #{function_hash['operator']} #{function_hash['r_compare']}'")
148
- return false
149
- end
150
-
151
- # Prevent backticks in function parameters
152
- if function_hash["params"] =~ /`/
153
- Log.debug("Cannot use backticks in function parameters")
154
- return false
155
- end
156
-
157
- # Do a regex comparison if right compare string is a regex
158
- if operator =~ /(=~|!=~)/
159
- # Fail if left compare value isn't a string
160
- if l_compare.is_a?(String)
161
- result = l_compare.match(r_compare)
162
- # Flip return value for != operator
163
- if function_hash["operator"] == "!=~"
164
- !result
165
- else
166
- !!result
167
- end
168
- else
169
- Log.debug("Cannot do a regex check on a non string value.")
170
- false
171
- end
172
- # Otherwise do a normal comparison while taking the type into account
173
- else
174
- if l_compare.is_a? String
175
- r_compare = r_compare.to_s
176
- elsif r_compare.is_a? String
177
- case l_compare
178
- when Numeric
179
- r_compare = r_compare.strip
180
- begin
181
- r_compare = Integer(r_compare)
182
- rescue ArgumentError # rubocop:disable Metrics/BlockNesting
183
- begin
184
- r_compare = Float(r_compare)
185
- rescue ArgumentError # rubocop:disable Metrics/BlockNesting
186
- raise(ArgumentError, "invalid numeric value: #{r_compare}")
187
- end
188
- end
189
- when TrueClass, FalseClass
190
- r_compare = r_compare.strip
191
- if r_compare == true.to_s # rubocop:disable Metrics/BlockNesting
192
- r_compare = true
193
- elsif r_compare == false.to_s # rubocop:disable Metrics/BlockNesting
194
- r_compare = false
195
- else
196
- raise(ArgumentError, "invalid boolean value: #{r_compare}")
197
- end
198
- end
199
- end
200
- operator = operator.strip
201
- if operator =~ /(?:(!=|<=|>=|<|>)|==?)/
202
- operator = $1 ? $1.to_sym : :==
203
- else
204
- raise(ArgumentError, "invalid operator: #{operator}")
205
- end
206
- l_compare.send(operator, r_compare)
207
-
208
- end
209
- end
210
-
211
- # Creates a callstack to be evaluated from a compound evaluation string
212
- def self.create_compound_callstack(call_string)
213
- callstack = Matcher::Parser.new(call_string).execution_stack
214
- callstack.each_with_index do |statement, i|
215
- callstack[i]["fstatement"] = create_function_hash(statement.values.first) if statement.keys.first == "fstatement"
216
- end
217
- callstack
218
- end
219
- end
220
- end