smart_proxy_salt 3.1.2 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c5e6d0a2348b06d27789269578966ec1b2ae1413341ca1dc5bb318630653c44
4
- data.tar.gz: 8238c8469d73c893ead9a9cba3692652b9227a5aa6bdc400cc53e1c83e28e7db
3
+ metadata.gz: 63743a9b37c7860b60af93db9735f210608221d87675c7d11764eb9f08348caa
4
+ data.tar.gz: 38fa3a0d210534f63d1f925790d0a062b20a907499dad0ba2bae1125a0bb8190
5
5
  SHA512:
6
- metadata.gz: 2873b74a8f42b9e979c38da137674d92e3d0cccdfea62924d27b89ca3c76f4ffc3a5ed961c3e9935311ca1ca51868e44f4315d3cd4d86752d315de0434955b62
7
- data.tar.gz: '091bdeb081d90458404ad84baba32db5830b170a28678f554174030722563935186e7fd9abcb52fa743edf5148aae76b65177e7e13e5b2113f48fb0f8f1c8b75'
6
+ metadata.gz: '008b29492b0f227e04ba281423df738e9334e040492f2fcf62172e866cccd931a80de5a5eac14d8e5b4c81a17dd26697e11f89aa696ab28670471f55be585e26'
7
+ data.tar.gz: 05a4afde3202a35328d2b2daa4cc2266808a9c7af01b53dae3876a4a8b033ef898fc07e1d86bfc9a22c05b7ab3bcdb924884c34b1d5546e1a8c93a39ec256623
data/bin/foreman-node CHANGED
@@ -14,6 +14,7 @@ require 'net/http'
14
14
  require 'net/https'
15
15
  require 'etc'
16
16
  require 'timeout'
17
+ require 'msgpack' if SETTINGS[:filecache]
17
18
 
18
19
  begin
19
20
  require 'json'
@@ -57,19 +58,37 @@ rescue Exception => e
57
58
  exit 1
58
59
  end
59
60
 
61
+ def get_grains_from_filecache(minion)
62
+ # Use the grains from the salt master's filesystem based cache
63
+ # This requires the following settings in /etc/salt/foreman.yaml:
64
+ # :filecache: true
65
+ # :cachedir: "/path/to/master/cache" (default: "/var/cache/salt/master")
66
+ # Also, the msgpack rubygem needs to be present
67
+ cachedir = SETTINGS[:cachedir] || '/var/cache/salt/master'
68
+ content = File.read("#{cachedir}/minions/#{minion}/data.p")
69
+ data = MessagePack.unpack(content)
70
+ data['grains']
71
+ end
72
+
73
+ def get_grains_from_saltrun(minion)
74
+ result = IO.popen(['salt-run', '-l', 'quiet', '--output=json', 'cache.grains', minion], &:read)
75
+ data = JSON.parse(result)
76
+ data[minion]
77
+ end
78
+
60
79
  def plain_grains(minion)
61
80
  # We have to get the grains from the cache, because the client
62
81
  # is probably running 'state.highstate' right now.
63
82
 
64
- result = IO.popen(['salt-run', '-l', 'quiet', '--output=json', 'cache.grains', minion]) do |io|
65
- io.read
66
- end
67
-
68
- grains = JSON.parse(result)
83
+ grains = if SETTINGS[:filecache]
84
+ get_grains_from_filecache(minion)
85
+ else
86
+ get_grains_from_saltrun(minion)
87
+ end
69
88
 
70
89
  raise 'No grains received from Salt master' unless grains
71
90
 
72
- plainify(grains[minion]).flatten.inject(&:merge)
91
+ plainify(grains).flatten.inject(&:merge)
73
92
  end
74
93
 
75
94
  def plainify(hash, prefix = nil)
@@ -0,0 +1,16 @@
1
+ #!/bin/sh
2
+
3
+ set -u
4
+
5
+ for py in 'python3' 'python'; do
6
+ exe=$(type -p ${py})
7
+ if [ -n "${exe}" ]; then
8
+ if ${exe} -c 'import salt.config'; then
9
+ ${exe} "$@"
10
+ exit $?
11
+ fi
12
+ fi
13
+ done
14
+
15
+ echo "No usable python version found, check if python or python3 can import salt.config!" 1>&2
16
+ exit 1
@@ -14,44 +14,43 @@ module Proxy
14
14
  Proxy::Salt::Plugin.settings.autosign_file
15
15
  end
16
16
 
17
- def autosign_create(host)
18
- FileUtils.touch(autosign_file) unless File.exist?(autosign_file)
19
-
20
- autosign = open(autosign_file, File::RDWR)
17
+ def autosign_key_file
18
+ Proxy::Salt::Plugin.settings.autosign_key_file
19
+ end
21
20
 
22
- found = false
23
- autosign.each_line { |line| found = true if line.chomp == host }
24
- autosign.puts host unless found
25
- autosign.close
21
+ def autosign_create_hostname(hostname)
22
+ if append_value_to_file(autosign_file, hostname)
23
+ { message: 'Added hostname successfully.' }
24
+ else
25
+ { message: 'Failed to add hostname.' \
26
+ ' See smart proxy error log for more information.' }
27
+ end
28
+ end
26
29
 
27
- result = { :message => "Added #{host} to autosign" }
28
- logger.info result[:message]
29
- result
30
+ def autosign_remove_hostname(hostname)
31
+ if remove_value_from_file(autosign_file, hostname)
32
+ { message: 'Removed hostname successfully.' }
33
+ else
34
+ { message: 'Failed to remove hostname.' \
35
+ ' See smart proxy error log for more information.' }
36
+ end
30
37
  end
31
38
 
32
- def autosign_remove(host)
33
- raise "No such file #{autosign_file}" unless File.exist?(autosign_file)
39
+ def autosign_create_key(key)
40
+ if append_value_to_file(autosign_key_file, key)
41
+ { message: 'Added key successfully.' }
42
+ else
43
+ { message: 'Failed to add key.' \
44
+ ' See smart proxy error log for more information.' }
45
+ end
46
+ end
34
47
 
35
- found = false
36
- entries = open(autosign_file, File::RDONLY).readlines.collect do |l|
37
- if l.chomp != host
38
- l
39
- else
40
- found = true
41
- nil
42
- end
43
- end.uniq.compact
44
- if found
45
- autosign = open(autosign_file, File::TRUNC | File::RDWR)
46
- autosign.write entries.join("\n")
47
- autosign.write "\n"
48
- autosign.close
49
- result = { :message => "Removed #{host} from autosign" }
50
- logger.info result[:message]
51
- result
48
+ def autosign_remove_key(key)
49
+ if remove_value_from_file(autosign_key_file, key)
50
+ { message: 'Removed key successfully.' }
52
51
  else
53
- logger.info "Attempt to remove nonexistant client autosign for #{host}"
54
- raise Proxy::Salt::NotFound.new("Attempt to remove nonexistant client autosign for #{host}")
52
+ { message: 'Failed to remove key.' \
53
+ ' See smart proxy error log for more information.' }
55
54
  end
56
55
  end
57
56
 
@@ -62,6 +61,57 @@ module Proxy
62
61
  end.map(&:chomp)
63
62
  end
64
63
 
64
+ def append_value_to_file(filepath, value)
65
+ result = false
66
+ begin
67
+ raise "No such file: #{filepath}" unless File.exist?(filepath)
68
+
69
+ file = open(filepath, File::RDWR)
70
+ found = false
71
+ file.each_line { |line| found = true if line.chomp == value }
72
+ file.puts value unless found
73
+ file.close
74
+
75
+ logger.info "Added an entry to '#{filepath}' successfully."
76
+ result = true
77
+ rescue SystemCallError => e
78
+ logger.info "Attempted to add an entry to '#{filepath}', but an exception occurred: #{e}"
79
+ end
80
+ result
81
+ end
82
+
83
+ def remove_value_from_file(filepath, value)
84
+ result = false
85
+ begin
86
+ raise "No such file: #{filepath}" unless File.exist?(filepath)
87
+
88
+ found = false
89
+ entries = open(filepath, File::RDONLY).readlines.collect do |l|
90
+ entry = l.chomp
91
+ if entry == value
92
+ found = true
93
+ nil
94
+ elsif entry == ""
95
+ nil
96
+ else
97
+ l
98
+ end
99
+ end.uniq.compact
100
+ if found
101
+ file = open(filepath, File::TRUNC | File::RDWR)
102
+ file.write entries.join()
103
+ file.close
104
+ logger.info "Removed an entry from '#{filepath}' successfully."
105
+ result = true
106
+ else
107
+ raise Proxy::Salt::NotFound.new("Attempt to remove non-existent entry.")
108
+ end
109
+ rescue SystemCallError => e
110
+ logger.info "Attempted to remove an entry from '#{filepath}', but an exception occurred: #{e}"
111
+ end
112
+ result
113
+ end
114
+
65
115
  def highstate(host)
66
116
  find_salt_binaries
67
117
  cmd = [@sudo, '-u', Proxy::Salt::Plugin.settings.salt_command_user, @salt, '--async', escape_for_shell(host), 'state.highstate']
@@ -13,11 +13,24 @@ module Proxy
13
13
  plugin 'salt', Proxy::Salt::VERSION
14
14
 
15
15
  default_settings :autosign_file => '/etc/salt/autosign.conf',
16
+ :autosign_key_file => '/var/lib/foreman-proxy/salt/grains/autosign_key',
16
17
  :salt_command_user => 'root',
17
- :use_api => false
18
+ :use_api => false,
19
+ :saltfile => '/etc/foreman-proxy/settings.d/salt.saltfile'
18
20
 
19
- http_rackup_path File.expand_path('salt_http_config.ru', File.expand_path('../', __FILE__))
20
- https_rackup_path File.expand_path('salt_http_config.ru', File.expand_path('../', __FILE__))
21
+ requires :dynflow, '>= 0.5.0'
22
+
23
+ rackup_path File.expand_path('salt_http_config.ru', __dir__)
24
+
25
+ load_classes do
26
+ require 'smart_proxy_dynflow'
27
+ require 'smart_proxy_salt/salt_runner'
28
+ require 'smart_proxy_salt/salt_task_launcher'
29
+ end
30
+
31
+ load_dependency_injection_wirings do |_container_instance, _settings|
32
+ Proxy::Dynflow::TaskLauncherRegistry.register('salt', SaltTaskLauncher)
33
+ end
21
34
  end
22
35
 
23
36
  class << self
@@ -33,6 +46,10 @@ module Proxy
33
46
  super
34
47
  end
35
48
  end
49
+
50
+ def respond_to_missing?(method, include_private = false)
51
+ Proxy::Salt::Rest.respond_to?(method) || Proxy::Salt::CLI.respond_to?(method) || super
52
+ end
36
53
  end
37
54
  end
38
55
  end
@@ -9,12 +9,32 @@ module Proxy
9
9
  class Api < ::Sinatra::Base
10
10
  include ::Proxy::Log
11
11
  helpers ::Proxy::Helpers
12
- authorize_with_ssl_client
12
+ authorize_with_trusted_hosts
13
+
14
+ post '/autosign_key/:key' do
15
+ content_type :json
16
+ begin
17
+ Proxy::Salt.autosign_create_key(params[:key]).to_json
18
+ rescue Exception => e
19
+ log_halt 406, "Failed to create autosign key #{params[:key]}: #{e}"
20
+ end
21
+ end
22
+
23
+ delete '/autosign_key/:key' do
24
+ content_type :json
25
+ begin
26
+ Proxy::Salt.autosign_remove_key(params[:key]).to_json
27
+ rescue Proxy::Salt::NotFound => e
28
+ log_halt 404, e.to_s
29
+ rescue Exception => e
30
+ log_halt 406, "Failed to remove autosign key #{params[:key]}: #{e}"
31
+ end
32
+ end
13
33
 
14
34
  post '/autosign/:host' do
15
35
  content_type :json
16
36
  begin
17
- Proxy::Salt.autosign_create(params[:host]).to_json
37
+ Proxy::Salt.autosign_create_hostname(params[:host]).to_json
18
38
  rescue Exception => e
19
39
  log_halt 406, "Failed to create autosign for #{params[:host]}: #{e}"
20
40
  end
@@ -23,7 +43,7 @@ module Proxy
23
43
  delete '/autosign/:host' do
24
44
  content_type :json
25
45
  begin
26
- Proxy::Salt.autosign_remove(params[:host]).to_json
46
+ Proxy::Salt.autosign_remove_hostname(params[:host]).to_json
27
47
  rescue Proxy::Salt::NotFound => e
28
48
  log_halt 404, e.to_s
29
49
  rescue Exception => e
@@ -0,0 +1,55 @@
1
+ require 'smart_proxy_dynflow/runner/base'
2
+ require 'smart_proxy_dynflow/runner/command_runner'
3
+
4
+ module Proxy
5
+ module Salt
6
+ # Implements the SaltRunner to be used by foreman_remote_execution
7
+ class SaltRunner < Proxy::Dynflow::Runner::CommandRunner
8
+ DEFAULT_REFRESH_INTERVAL = 1
9
+
10
+ attr_reader :jid
11
+
12
+ def initialize(options, suspended_action)
13
+ super(options, :suspended_action => suspended_action)
14
+ @options = options
15
+ end
16
+
17
+ def start
18
+ command = generate_command
19
+ logger.debug("Running command '#{command.join(' ')}'")
20
+ initialize_command(*command)
21
+ end
22
+
23
+ def kill
24
+ publish_data('== TASK ABORTED BY USER ==', 'stdout')
25
+ publish_exit_status(1)
26
+ ::Process.kill('SIGTERM', @command_pid)
27
+ end
28
+
29
+ def publish_data(data, type)
30
+ if @jid.nil? && (match = data.match(/jid: ([0-9]+)/))
31
+ @jid = match[1]
32
+ end
33
+ super
34
+ end
35
+
36
+ def publish_exit_status(status)
37
+ # If there was no salt job associated with this run, mark the job as failed
38
+ status = 1 if @jid.nil?
39
+ super status
40
+ end
41
+
42
+ private
43
+
44
+ def generate_command
45
+ saltfile_path = ::Proxy::Salt::Plugin.settings[:saltfile]
46
+ command = %w[salt --show-jid]
47
+ command << "--saltfile=#{saltfile_path}" if File.file?(saltfile_path)
48
+ command << @options['name']
49
+ command << 'state.template_str'
50
+ command << @options['script']
51
+ command
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,27 @@
1
+ require 'smart_proxy_dynflow/task_launcher'
2
+
3
+ module Proxy
4
+ module Salt
5
+ # Implements the TaskLauncher::Batch for Salt
6
+ class SaltTaskLauncher < ::Proxy::Dynflow::TaskLauncher::Batch
7
+ # Implements the Runner::Action for Salt
8
+ class SaltRunnerAction < ::Proxy::Dynflow::Action::Runner
9
+ def initiate_runner
10
+ additional_options = {
11
+ :step_id => run_step_id,
12
+ :uuid => execution_plan_id
13
+ }
14
+ ::Proxy::Salt::SaltRunner.new(
15
+ input.merge(additional_options),
16
+ suspended_action
17
+ )
18
+ end
19
+ end
20
+
21
+ def child_launcher(parent)
22
+ ::Proxy::Dynflow::TaskLauncher::Single.new(world, callback, :parent => parent,
23
+ :action_class_override => SaltRunnerAction)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -3,6 +3,6 @@
3
3
  module Proxy
4
4
  # Salt module
5
5
  module Salt
6
- VERSION = '3.1.2'
6
+ VERSION = '4.0.0'.freeze
7
7
  end
8
8
  end
@@ -0,0 +1,48 @@
1
+ # Foreman Salt Minion Authentication
2
+
3
+ Currently, there are two possibilites to authenticate a newly deployed minion automatically:
4
+ 1. Use the _/etc/salt/autosign.conf_ file which stores the hostnames of acceptable hosts.
5
+ 2. Use _Salt Autosign Grains_ for a more secure way which relies on a shared secret key.
6
+
7
+ This README, handles the second option and how to configure it
8
+
9
+ ## Setup
10
+ Add the content of 'master.snippet' to '/etc/salt/master' which configures the grains key file on the master and a reactor. The grains file holds the acceptable keys and will be written by the Smart Proxy when a new minion is deployed. The reactor initiates an interaction with Foreman Salt if a new minion was authenticated successfully.
11
+ In case there is already a reactor configured, you need to adapt it using the options mentioned in 'master.snippet'. The directories given in 'master.snippet' are the default ones. In case you want your files in a different place, you have to change the paths accordingly.
12
+
13
+ If '/srv/salt' is configured as 'file_roots' in your '/etc/salt/master' config, setup the necessary Salt runners:
14
+
15
+ ```
16
+ /srv/salt/_runners/foreman_file.py
17
+ /srv/salt/_runners/foreman_https.py
18
+ ```
19
+
20
+ Check if the reactor ('foreman_minion_auth.sls') is at the appropriated location and set the correct smart proxy address in the reactor file:
21
+
22
+ ```
23
+ /var/lib/foreman-proxy/salt/reactors/foreman_minion_auth.sls
24
+ ```
25
+
26
+ After changing the Salt master, restart the salt-master service:
27
+
28
+ ```
29
+ systemctl restart salt-master
30
+ ```
31
+
32
+ After checking the reactor and runners, run the following command to make them available in the Salt environment:
33
+
34
+ ```
35
+ salt-run saltutil.sync_all
36
+ ```
37
+
38
+ ## Procedure
39
+
40
+ 1. A new host, configured as Salt minion, is deployed with Foreman Salt.
41
+ 2. Foreman Salt generates a unique key for that minion and distributes it via the Provisioning Template to the host and via an API call to the Smart Proxy.
42
+ 3. The Smart Proxy makes the key available for the Salt Autosign Grains procedure by adding it to the previously defined file (by default: /var/lib/foreman-proxy/salt/grains/autosign_key).
43
+ 4. The Salt minion is started and uses the configured Salt autosign grain for authentication to the Salt master.
44
+ 5. The Salt master accepts the minion depending on the key and the corresponding auth reactor is triggered on the Salt master.
45
+ 6. The Salt master initiates an API call to Foreman Salt which marks the corresponding host status as _authenticated_.
46
+ 7. Foreman Salt triggers an API call to Smart Proxy Salt which deletes the key from the acceptable keys list of the Salt master (since the minion was authenticated already and shall not be reused).
47
+
48
+ The minion was authenticated successfully.
@@ -0,0 +1,24 @@
1
+ {% if 'act' in data and 'id' in data %}
2
+ {% if data['act'] == 'accept' %}
3
+ {% if salt['saltutil.runner']('foreman_file.check_key', (data['id'], 100)) == True %}
4
+ {%- do salt.log.info('Minion authenticated successfully, starting HTTPS request to delete autosign key.') -%}
5
+ remove_autosign_key_custom_runner:
6
+ runner.foreman_https.query_cert:
7
+ - method: PUT
8
+ - host: example.proxy.com # set smart proxy address
9
+ - path: /salt/api/v2/salt_autosign_auth?name={{ data['id'] }}
10
+ - cert: /etc/pki/katello/puppet/puppet_client.crt # default cert location
11
+ - key: /etc/pki/katello/puppet/puppet_client.key # default key location
12
+ - port: 443
13
+ # Uncomment the following lines in case you want to use username + password authentication (not recommended)
14
+ # call_foreman_salt_custom_runner:
15
+ # runner.foreman_https.query_user:
16
+ # - method: PUT
17
+ # - host: example.proxy.com # set smart proxy address
18
+ # - path: /salt/api/v2/salt_autosign_auth?name={{ data['id'] }}
19
+ # - username: my_username # set username
20
+ # - password: my_password # set password
21
+ # - port: 443
22
+ {% endif %}
23
+ {% endif %}
24
+ {% endif %}
@@ -0,0 +1,5 @@
1
+ autosign_grains_dir: /var/lib/foreman-proxy/salt/grains
2
+
3
+ reactor:
4
+ - 'salt/auth':
5
+ - /var/lib/foreman-proxy/salt/reactors/foreman_minion_auth.sls
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python
2
+
3
+ import os
4
+ import time
5
+
6
+ SALT_KEY_PATH = "/etc/salt/pki/master/minions/"
7
+
8
+ def time_secs(path):
9
+ stat = os.stat(path)
10
+ return stat.st_mtime
11
+
12
+
13
+ def younger_than_secs(path, seconds):
14
+
15
+ now_time = time.time()
16
+ file_time = time_secs(path)
17
+ if now_time - file_time <= seconds:
18
+ return True
19
+ return False
20
+
21
+ def check_key(hostname, seconds):
22
+ return younger_than_secs(SALT_KEY_PATH + hostname, seconds)
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env python
2
+
3
+ from http.client import HTTPSConnection
4
+ import ssl
5
+ import base64
6
+ import json
7
+
8
+
9
+ def query_cert(host, path, port, method, cert, key,
10
+ payload=None, timeout=10):
11
+
12
+ headers = {"Accept": "application/json"}
13
+
14
+ if payload is not None or method.lower() in ['put', 'post']:
15
+ headers["Content-Type"] = "application/json"
16
+
17
+ ctx = ssl.create_default_context()
18
+ ctx.load_cert_chain(certfile=cert, keyfile=key)
19
+
20
+ connection = HTTPSConnection(host,
21
+ port=port,
22
+ context=ctx,
23
+ timeout=timeout)
24
+ if payload is None:
25
+ connection.request(method,
26
+ path,
27
+ headers=headers)
28
+ else:
29
+ payload_json = json.dumps(payload)
30
+ connection.request(method=method,
31
+ url=path,
32
+ body=payload_json,
33
+ headers=headers)
34
+ response = connection.getresponse()
35
+ response_str = response.read().decode('utf-8')
36
+ print(response_str)
37
+
38
+
39
+ def query_user(host, path, port, method, username, password,
40
+ payload=None, timeout=10):
41
+
42
+ auth = "{}:{}".format(username, password)
43
+ token = base64.b64encode(auth.encode('utf-8')).decode('ascii')
44
+ headers = {"Authorization": "Basic {}".format(token),
45
+ "Accept": "application/json"}
46
+ if payload is not None or method.lower() in ["put", "post"]:
47
+ headers["Content-Type"] = "application/json"
48
+
49
+ ctx = ssl._create_unverified_context()
50
+ connection = HTTPSConnection(host,
51
+ port=port,
52
+ context=ctx,
53
+ timeout=timeout)
54
+ if payload is None:
55
+ connection.request(method=method,
56
+ url=path,
57
+ headers=headers)
58
+ else:
59
+ payload_json = json.dumps(payload)
60
+ connection.request(method=method,
61
+ url=path,
62
+ body=payload_json,
63
+ headers=headers)
64
+ response = connection.getresponse()
65
+ response_str = response.read().decode('utf-8')
66
+ print(response_str)
@@ -99,7 +99,7 @@ def now(highstate):
99
99
  log.debug('Upload highstate to Foreman')
100
100
 
101
101
  try:
102
- report = create_report(base64.decodestring(highstate))
102
+ report = create_report(base64.b64decode(highstate))
103
103
  upload(report)
104
104
  except Exception as exc:
105
105
  log.error('Exception encountered: %s', exc)
@@ -6,5 +6,5 @@
6
6
  foreman_report_upload:
7
7
  runner.foreman_report_upload.now:
8
8
  - args:
9
- - highstate: '{{data|json|base64_encode}}'
9
+ - highstate: '{{ data|json|base64_encode }}'
10
10
  {% endif %}
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env python
1
+ #!/usr/bin/salt_python_wrapper
2
2
  # Uploads reports from the Salt job cache to Foreman
3
3
 
4
4
  from __future__ import print_function
@@ -34,17 +34,22 @@ def salt_config():
34
34
 
35
35
  def get_job(job_id):
36
36
  result = run('jobs.lookup_jid', [job_id])
37
-
38
37
  # If any minion's results are strings, they're exceptions
39
38
  # and should be wrapped in a list like other errors
39
+
40
40
  for minion, value in result.items():
41
- if type(value) == str:
42
- result[minion] = [value]
43
- else:
44
- for key, entry in value.items():
45
- if key.startswith('module_') and entry['__id__'] == 'state.highstate':
46
- result[minion] = entry['changes']['ret']
47
- break
41
+ try:
42
+ if isinstance(value,str):
43
+ result[minion] = [value]
44
+ elif isinstance(value,list):
45
+ result[minion] = value
46
+ else:
47
+ for key, entry in value.items():
48
+ if key.startswith('module_') and '__id__' in entry and entry['__id__'] == 'state.highstate':
49
+ result[minion] = entry['changes']['ret']
50
+ break
51
+ except KeyError:
52
+ traceback.print_exc()
48
53
 
49
54
  return {'job':
50
55
  {
@@ -117,8 +122,10 @@ def upload(jobs):
117
122
  connection = HTTPConnection(config[':host'],
118
123
  port=config[':port'])
119
124
  if ':username' in config and ':password' in config:
120
- token = base64.b64encode('{}:{}'.format(config[':username'],
121
- config[':password']))
125
+ auth = '{}:{}'.format(config[':username'], config[':password'])
126
+ if not isinstance(auth, bytes):
127
+ auth = auth.encode('UTF-8')
128
+ token = base64.b64encode(auth)
122
129
  headers['Authorization'] = 'Basic {}'.format(token)
123
130
 
124
131
  for job_id, job in jobs:
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  :enabled: true
3
3
  :autosign_file: /etc/salt/autosign.conf
4
+ :autosign_key_file: /var/lib/foreman-proxy/salt/grains/autosign_key
4
5
  :salt_command_user: root
5
6
  # Some features require using the Salt API - such as listing
6
7
  # environments and retrieving state info
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_proxy_salt
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.2
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Moll
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-06-09 00:00:00.000000000 Z
12
+ date: 2021-08-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: test-unit
@@ -59,28 +59,28 @@ dependencies:
59
59
  requirements:
60
60
  - - "~>"
61
61
  - !ruby/object:Gem::Version
62
- version: '10'
62
+ version: '13'
63
63
  type: :development
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
- version: '10'
69
+ version: '13'
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: rubocop
72
72
  requirement: !ruby/object:Gem::Requirement
73
73
  requirements:
74
74
  - - '='
75
75
  - !ruby/object:Gem::Version
76
- version: 0.32.1
76
+ version: 0.50.0
77
77
  type: :development
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - '='
82
82
  - !ruby/object:Gem::Version
83
- version: 0.32.1
83
+ version: 0.50.0
84
84
  - !ruby/object:Gem::Dependency
85
85
  name: rack-test
86
86
  requirement: !ruby/object:Gem::Requirement
@@ -95,10 +95,25 @@ dependencies:
95
95
  - - "~>"
96
96
  - !ruby/object:Gem::Version
97
97
  version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: smart_proxy_dynflow
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: 0.5.0
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 0.5.0
98
112
  description: SaltStack Plug-In for Foreman's Smart Proxy
99
113
  email: foreman-dev@googlegroups.com
100
114
  executables:
101
115
  - foreman-node
116
+ - salt_python_wrapper
102
117
  extensions: []
103
118
  extra_rdoc_files:
104
119
  - README.md
@@ -107,6 +122,7 @@ files:
107
122
  - LICENSE
108
123
  - README.md
109
124
  - bin/foreman-node
125
+ - bin/salt_python_wrapper
110
126
  - bundler.d/salt.rb
111
127
  - cron/smart_proxy_salt
112
128
  - etc/foreman.yaml.example
@@ -117,7 +133,14 @@ files:
117
133
  - lib/smart_proxy_salt/salt.rb
118
134
  - lib/smart_proxy_salt/salt_api.rb
119
135
  - lib/smart_proxy_salt/salt_http_config.ru
136
+ - lib/smart_proxy_salt/salt_runner.rb
137
+ - lib/smart_proxy_salt/salt_task_launcher.rb
120
138
  - lib/smart_proxy_salt/version.rb
139
+ - salt/minion_auth/README.md
140
+ - salt/minion_auth/foreman_minion_auth.sls
141
+ - salt/minion_auth/master.snippet
142
+ - salt/minion_auth/srv/salt/_runners/foreman_file.py
143
+ - salt/minion_auth/srv/salt/_runners/foreman_https.py
121
144
  - salt/report_upload/README.md
122
145
  - salt/report_upload/master.snippet
123
146
  - salt/report_upload/srv/salt/_runners/foreman_report_upload.py