chook 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -80,9 +80,12 @@ logs_to_keep: 10
80
80
  log_max_megs: 10
81
81
 
82
82
  #################
83
- # Any value here will turn on 'HTTP Basic Authentication' and this will be the username
84
- # required to make any connection to the Chook server.
85
- # Leaving this empty will allow any connection without authentication
83
+ # Any value here will turn on 'HTTP Basic Authentication' for webhooks,
84
+ # and this will be the username required for a Jamf Pro server to send
85
+ # webhooks to the Chook server.
86
+ #
87
+ # Leaving this empty will allow receiving of webooks with no authentication
88
+ #
86
89
  # Default: none
87
90
  #
88
91
  webhooks_user:
@@ -102,3 +105,79 @@ webhooks_user:
102
105
  # Default: none
103
106
  #
104
107
  webhooks_user_pw:
108
+
109
+ #################
110
+ # Any value here will require authentication to view the admin web page.
111
+ #
112
+ # Leaving this empty will allow anyone to see the admin web page.
113
+ #
114
+ # If the value here is 'use_jamf' then anyone can log in with their
115
+ # Jamf Pro credentials, and the admin_pw setting is ignored
116
+ # (see the jamf_* settings below)
117
+ #
118
+ # If the value is anything else, it is the username required to
119
+ # log in to the admin web page.
120
+ #
121
+ # Default: none
122
+ #
123
+ admin_user:
124
+
125
+ #################
126
+ # When admin page authentication is enabled by setting admin_user, this
127
+ # tells chook how to learn the password for that user:
128
+ #
129
+ # - if the admin_user is 'use_jamf' then this is ignored, see the jamf_* settings below
130
+ #
131
+ # - If its a path to a file, the file contains the password and nothing else.
132
+ # The file must be owned by the user running the chook server, and must have
133
+ # mode 0600.
134
+ #
135
+ # - If it ends with a pipe character (|), everything execpt the pipe is considered
136
+ # to be a shell command, executable by the user running the chook server.
137
+ # The standard-output of the command will be the password.
138
+ #
139
+ # Default: none
140
+ #
141
+ admin_pw:
142
+
143
+ #################
144
+ # When admin page authentication is enabled by setting admin_user,
145
+ # this is how many seconds before the browser session expires and the
146
+ # admin must log in again.
147
+ #
148
+ # Default: 86400 (24 hours)
149
+ #
150
+ admin_session_expires: 86400
151
+
152
+ #################
153
+ # When the admin_user is 'use_jamf', this is the hostname of the
154
+ # Jamf Pro server to use for authentication
155
+ #
156
+ # Default: none, but /etc/ruby-jss.conf will be honored
157
+ #
158
+ jamf_server:
159
+
160
+ #################
161
+ # When the admin_user is 'use_jamf', this is the port of the
162
+ # Jamf Pro server to use for Authentication
163
+ #
164
+ # Default: none, but /etc/ruby-jss.conf will be honored
165
+ #
166
+ jamf_port:
167
+
168
+ #################
169
+ # When the admin_user is 'use_jamf', should SSL be used when
170
+ # connection to the jamf_server? true or false
171
+ #
172
+ # Default: none, but /etc/ruby-jss.conf will be honored
173
+ #
174
+ jamf_use_ssl:
175
+
176
+ #################
177
+ # When the admin_user is 'use_jamf', and jamf_use_ssl is true,
178
+ # should SSL certificates from the jamf server be
179
+ # validated?
180
+ #
181
+ # Default: none, but /etc/ruby-jss.conf will be honored
182
+ #
183
+ jamf_verify_cert:
@@ -54,7 +54,14 @@ module Chook
54
54
  log_max_megs: :to_i,
55
55
  logs_to_keep: :to_i,
56
56
  webhooks_user: nil,
57
- webhooks_user_pw: nil
57
+ webhooks_user_pw: nil,
58
+ admin_user: nil,
59
+ admin_pw: nil,
60
+ admin_session_expires: :to_i,
61
+ jamf_server: nil,
62
+ jamf_port: :to_i,
63
+ jamf_use_ssl: Chook::Procs::STRING_TO_BOOLEAN,
64
+ jamf_verify_cert: Chook::Procs::STRING_TO_BOOLEAN
58
65
  }.freeze
59
66
 
60
67
  # Class Variables
@@ -139,12 +139,12 @@ module Chook
139
139
  end # def handle
140
140
 
141
141
  def pipe_to_executable(handler)
142
- logger.debug "Sending JSON to stdin of '#{handler}'"
142
+ logger.debug "EXTERNAL: Sending JSON to stdin of '#{handler.basename}'"
143
143
  IO.popen([handler.to_s], 'w') { |h| h.puts @raw_json }
144
144
  end
145
145
 
146
146
  def handle_with_proc(handler)
147
- logger.debug "Running Handler defined in #{handler.handler_file}"
147
+ logger.debug "INTERNAL: Running Handler defined in #{handler.handler_file}"
148
148
  handler.handle self
149
149
  end
150
150
 
@@ -25,10 +25,11 @@
25
25
 
26
26
  require 'sinatra/base'
27
27
  require 'sinatra/custom_logger'
28
- # require 'haml'
28
+ require 'haml'
29
29
  require 'openssl'
30
30
  require 'chook/event_handling'
31
31
  require 'chook/server/log'
32
+ require 'chook/server/auth'
32
33
  require 'chook/server/routes'
33
34
 
34
35
  module Chook
@@ -40,9 +41,11 @@ module Chook
40
41
  DEFAULT_PORT = 80
41
42
  DEFAULT_SSL_PORT = 443
42
43
  DEFAULT_CONCURRENCY = true
44
+ DEFAULT_SESSION_EXPIRE = 24 * 60 * 60 # one day
43
45
 
44
46
  # set defaults in config
45
47
  Chook.config.port ||= Chook.config.use_ssl ? DEFAULT_SSL_PORT : DEFAULT_PORT
48
+ Chook.config.admin_session_expires ||= DEFAULT_SESSION_EXPIRE
46
49
 
47
50
  # can't use ||= here cuz nil and false have different meanings
48
51
  Chook.config.concurrency = DEFAULT_CONCURRENCY if Chook.config.concurrency.nil?
@@ -50,21 +53,7 @@ module Chook
50
53
  # Run the server
51
54
  ###################################
52
55
  def self.run!(log_level: nil)
53
- log_level ||= Chook.config.log_level
54
- @log_level = Chook::Procs::STRING_TO_LOG_LEVEL.call log_level
55
-
56
- configure do
57
- set :logger, Log.startup(@log_level)
58
- set :server, :thin
59
- set :bind, '0.0.0.0'
60
- set :port, Chook.config.port
61
- set :show_exceptions, :after_handler if development?
62
- set :root, "#{File.dirname __FILE__}/server"
63
- enable :static
64
- enable :lock unless Chook.config.concurrency
65
- end # configure
66
-
67
- Chook::HandledEvent::Handlers.load_handlers
56
+ prep_to_run
68
57
 
69
58
  if Chook.config.use_ssl
70
59
  super do |server|
@@ -75,43 +64,31 @@ module Chook
75
64
  verify_peer: false
76
65
  }
77
66
  end # super do
78
- else
67
+
68
+ else # no ssl
79
69
  super
80
70
  end # if use ssl
81
71
  end # self.run
82
72
 
83
- # Learn the client password, if we're using basic auth
84
- ###################################
85
- def self.webhooks_user_pw
86
- return @webhooks_user_pw if @webhooks_user_pw
87
- return nil unless Chook.config.webhooks_user_pw
88
-
89
- setting = Chook.config.webhooks_user_pw
90
-
91
- @webhooks_user_pw =
92
- if setting.end_with? '|'
93
- # if the path ends with a pipe, its a command that will
94
- # return the desired password, so remove the pipe,
95
- # execute it, and return stdout from it.
96
- cmd = setting.chomp '|'
97
- output = `#{cmd} 2>&1`.chomp
98
- raise "Can't get webhooks user password: #{output}" unless $CHILD_STATUS.exitstatus.zero?
99
- output
100
-
101
- else
102
- # otherwise its a file path, and read the pw from the contents
103
- file = Pathname.new setting
104
- return nil unless file.file?
105
- stat = file.stat
106
- mode = format('%o', stat.mode)
107
- raise 'Password file for webhooks user has insecure mode, must be 0600.' unless mode.end_with?('0600')
108
- raise "Password file for webhooks user has insecure owner, must be owned by UID #{Process.euid}." unless stat.owned?
73
+ def self.prep_to_run
74
+ log_level ||= Chook.config.log_level
75
+ @log_level = Chook::Procs::STRING_TO_LOG_LEVEL.call log_level
109
76
 
110
- # chomping an empty string removes all trailing \n's and \r\n's
111
- file.read.chomp('')
77
+ configure do
78
+ set :logger, Log.startup(@log_level)
79
+ set :server, :thin
80
+ set :bind, '0.0.0.0'
81
+ set :port, Chook.config.port
82
+ set :show_exceptions, :after_handler if development?
83
+ set :root, "#{File.dirname __FILE__}/server"
84
+ enable :static
85
+ enable :sessions
86
+ set :sessions, expire_after: Chook.config.admin_session_expires if Chook.config.admin_user
87
+ enable :lock unless Chook.config.concurrency
88
+ end # configure
112
89
 
113
- end # if else
114
- end # self.webhooks_user_pw
90
+ Chook::HandledEvent::Handlers.load_handlers
91
+ end # prep to run
115
92
 
116
93
  end # class server
117
94
 
@@ -0,0 +1,164 @@
1
+ ### Copyright 2017 Pixar
2
+
3
+ ###
4
+ ### Licensed under the Apache License, Version 2.0 (the "Apache License")
5
+ ### with the following modification; you may not use this file except in
6
+ ### compliance with the Apache License and the following modification to it:
7
+ ### Section 6. Trademarks. is deleted and replaced with:
8
+ ###
9
+ ### 6. Trademarks. This License does not grant permission to use the trade
10
+ ### names, trademarks, service marks, or product names of the Licensor
11
+ ### and its affiliates, except as required to comply with Section 4(c) of
12
+ ### the License and to reproduce the content of the NOTICE file.
13
+ ###
14
+ ### You may obtain a copy of the Apache License at
15
+ ###
16
+ ### http://www.apache.org/licenses/LICENSE-2.0
17
+ ###
18
+ ### Unless required by applicable law or agreed to in writing, software
19
+ ### distributed under the Apache License with the above modification is
20
+ ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21
+ ### KIND, either express or implied. See the Apache License for the specific
22
+ ### language governing permissions and limitations under the Apache License.
23
+ ###
24
+ ###
25
+
26
+ module Chook
27
+
28
+ # the server
29
+ class Server < Sinatra::Base
30
+
31
+ # helper module for authentication
32
+ module Auth
33
+
34
+ USE_JAMF_ADMIN_USER = 'use_jamf'.freeze
35
+
36
+ def protect_via_basic_auth!
37
+ # don't protect if user isn't defined
38
+ return unless Chook.config.webhooks_user
39
+ return if webhook_user_authorized?
40
+ headers['WWW-Authenticate'] = 'Basic realm="Restricted Area"'
41
+ halt 401, "Not authorized\n"
42
+ end
43
+
44
+ def webhook_user_authorized?
45
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
46
+
47
+ # gotta have basic auth presented to us
48
+ unless @auth.provided? && @auth.basic? && @auth.credentials
49
+ Chook.logger.debug "No basic auth provided on protected route: #{request.path_info} from: #{request.ip}"
50
+ return false
51
+ end
52
+
53
+ authenticate_webhooks_user @auth.credentials
54
+ end # authorized?
55
+
56
+ # webhook user auth always comes from config
57
+ def authenticate_webhooks_user(creds)
58
+ if creds.first == Chook.config.webhooks_user && creds.last == Chook::Server.webhooks_user_pw
59
+ Chook.logger.debug "Got HTTP Basic auth for webhooks user: #{Chook.config.webhooks_user}@#{request.ip}"
60
+ true
61
+ else
62
+ Chook.logger.error "FAILED auth for webhooks user: #{Chook.config.webhooks_user}@#{request.ip}"
63
+ false
64
+ end
65
+ end # authenticate_webhooks_user
66
+
67
+ # admin user auth might come from config, might come from Jamf Pro
68
+ def authenticate_admin(user, pw)
69
+ return authenticate_jamf_admin(user, pw) if Chook.config.admin_user == USE_JAMF_ADMIN_USER
70
+ authenticate_admin_user(user, pw)
71
+ end
72
+
73
+ # admin auth from config
74
+ def authenticate_admin_user(user, pw)
75
+ if user == Chook.config.admin_user && pw == Chook::Server.admin_user_pw
76
+ Chook.logger.debug "Got auth for admin user: #{user}@#{request.ip}"
77
+ session[:authed_admin] = user
78
+ true
79
+ else
80
+ Chook.logger.warn "FAILED auth for admin user: #{user}@#{request.ip}"
81
+ session[:authed_admin] = nil
82
+ false
83
+ end
84
+ end
85
+
86
+ # admin auth from jamf pro
87
+ def authenticate_jamf_admin(user, pw)
88
+ require 'ruby-jss'
89
+ JSS::APIConnection.new(
90
+ user: user,
91
+ pw: pw,
92
+ server: Chook.config.jamf_server,
93
+ port: Chook.config.jamf_port,
94
+ use_ssl: Chook.config.jamf_use_ssl,
95
+ verify_cert: Chook.config.jamf_verify_cert
96
+ )
97
+ Chook.logger.debug "Jamf Admin login for: #{user}@#{request.ip}"
98
+
99
+ session[:authed_admin] = user
100
+ true
101
+ rescue JSS::AuthenticationError
102
+ Chook.logger.warn "Jamf Admin login FAILED for: #{user}@#{request.ip}"
103
+ session[:authed_admin] = nil
104
+ false
105
+ end # authenticate_jamf_admin
106
+
107
+ end # module auth
108
+
109
+ helpers Chook::Server::Auth
110
+
111
+ # Learn the webhook or admin passwords from config.
112
+ # so we can authenticate them from the browser and the JSS
113
+ #
114
+ # This is at the Server level, since we only need read it
115
+ # once per server startup, so we store it in a server
116
+ # instance var.
117
+ ###################################
118
+ def self.webhooks_user_pw
119
+ @webhooks_user_pw ||= pw_from_conf Chook.config.webhooks_user_pw
120
+ end # self.webhooks_user_pw
121
+
122
+ def self.admin_user_pw
123
+ @admin_user_pw ||= pw_from_conf Chook.config.admin_pw
124
+ end
125
+
126
+ def self.pw_from_conf(setting)
127
+ return '' unless setting
128
+
129
+ # if the path ends with a pipe, its a command that will
130
+ # return the desired password, so remove the pipe,
131
+ # execute it, and return stdout from it.
132
+ return pw_from_command(setting) if setting.end_with? '|'
133
+
134
+ # otherwise its a file path, and read the pw from the contents
135
+ pw_from_file(setting)
136
+ end # def pw_from_conf(setting)
137
+
138
+ def self.pw_from_command(cmd)
139
+ cmd = cmd.chomp '|'
140
+ output = `#{cmd} 2>&1`.chomp
141
+ raise "Can't get password from #{setting}: #{output}" unless $CHILD_STATUS.exitstatus.zero?
142
+ output
143
+ end
144
+
145
+ def self.pw_from_file(file)
146
+ file = Pathname.new file
147
+ return nil unless file.file?
148
+ stat = file.stat
149
+ mode = format('%o', stat.mode)
150
+ raise "Password file #{setting} has insecure mode, must be 0600." unless mode.end_with?('0600')
151
+ raise "Password file #{setting} has insecure owner, must be owned by UID #{Process.euid}." unless stat.owned?
152
+ # chomping an empty string removes all trailing \n's and \r\n's
153
+ file.read.chomp('')
154
+ end
155
+
156
+
157
+ end # server
158
+
159
+ end # Chook
160
+
161
+ require 'chook/server/routes/home'
162
+ require 'chook/server/routes/handle_webhook_event'
163
+ require 'chook/server/routes/handlers'
164
+ require 'chook/server/routes/log'
@@ -39,6 +39,10 @@
39
39
  font-size: 1.2em;
40
40
  }
41
41
 
42
+ #login_incorrect {
43
+ color: red;
44
+ }
45
+
42
46
  /* ******* Section Label Areas ******* */
43
47
 
44
48
  .section_label {
@@ -1,3 +1,23 @@
1
+ // log out of basic auth by sending an incorrect name /pw
2
+ // may not work on all browsers
3
+ // see second comment at
4
+ // https://stackoverflow.com/questions/233507/how-to-log-out-user-from-web-site-using-basic-authentication#492926
5
+
6
+ function logout() {
7
+ //var logged_out_page = window.location.protocol + '//xxxx:xxxx@' + window.location.host + '/login';
8
+
9
+ var xhttp = new XMLHttpRequest();
10
+
11
+ xhttp.onreadystatechange = function() {
12
+ if (this.readyState == 4) {
13
+ window.location = "/login";
14
+ }
15
+ };
16
+
17
+ xhttp.open("GET", "/LOG_OUT:no_such_pw@logout", true);
18
+ xhttp.setRequestHeader("Authorization", "Basic " + btoa("LOG_OUT:no_such_pw"));
19
+ xhttp.send();
20
+ }
1
21
 
2
22
  // show the logbox
3
23
  function view_log() {
@@ -28,32 +28,13 @@ module Chook
28
28
  # the server
29
29
  class Server < Sinatra::Base
30
30
 
31
- # These two helpers let us decude which routes need
32
- # http basic auth and which don't
33
- #
34
- # To protect a route, put `protected!` as the
35
- # first line of code in the route.
36
- #
37
- # See http://sinatrarb.com/faq.html#auth
38
- #
39
- helpers do
40
- def protected!
41
- # don't protect if user isn't defined
42
- return unless Chook.config.webhooks_user
31
+ HANDLE_EVENT_ROUTE = '/handle_webhook_event'.freeze
43
32
 
44
- return if authorized?
45
- headers['WWW-Authenticate'] = 'Basic realm="Restricted Area"'
46
- halt 401, "Not authorized\n"
47
- end
48
-
49
- def authorized?
50
- @auth ||= Rack::Auth::Basic::Request.new(request.env)
51
- @auth.provided? && \
52
- @auth.basic? && \
53
- @auth.credentials && \
54
- @auth.credentials == [Chook.config.webhooks_user, Chook::Server.webhooks_user_pw]
55
- end
56
- end
33
+ # before do
34
+ # break if request.path_info == Chook::Server::HANDLE_EVENT_ROUTE
35
+ # # break if request.path_info == '/' && session[:authed_admin]
36
+ # # redirect '/' unless session[:authed_admin]
37
+ # end
57
38
 
58
39
  # log errors in production (in dev, they go to stdout and the browser)
59
40
  error do
@@ -69,4 +50,5 @@ end # Chook
69
50
  require 'chook/server/routes/home'
70
51
  require 'chook/server/routes/handle_webhook_event'
71
52
  require 'chook/server/routes/handlers'
53
+ require 'chook/server/routes/login_logout'
72
54
  require 'chook/server/routes/log'