chook 1.0.1.b1 → 1.1.5b1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES.md +56 -0
  3. data/README.md +397 -145
  4. data/bin/chook-server +31 -1
  5. data/data/chook.conf.example +183 -0
  6. data/data/com.pixar.chook-server.plist +20 -0
  7. data/data/sample_handlers/RestAPIOperation.rb +11 -11
  8. data/data/sample_handlers/SmartGroupComputerMembershipChange.rb +3 -6
  9. data/data/sample_jsons/SmartGroupComputerMembershipChange.json +3 -1
  10. data/data/sample_jsons/SmartGroupMobileDeviceMembershipChange.json +3 -1
  11. data/lib/chook/configuration.rb +27 -8
  12. data/lib/chook/event.rb +6 -1
  13. data/lib/chook/event/handled_event.rb +36 -9
  14. data/lib/chook/event/handled_event/handlers.rb +252 -99
  15. data/lib/chook/event/handled_event_logger.rb +86 -0
  16. data/lib/chook/event_handling.rb +1 -0
  17. data/lib/chook/foundation.rb +3 -0
  18. data/lib/chook/procs.rb +17 -1
  19. data/lib/chook/server.rb +73 -72
  20. data/lib/chook/server/auth.rb +164 -0
  21. data/lib/chook/server/log.rb +215 -0
  22. data/lib/chook/server/public/css/chook.css +133 -0
  23. data/lib/chook/server/public/imgs/ChookLogoAlMcWhiggin.png +0 -0
  24. data/lib/chook/server/public/js/chook.js +126 -0
  25. data/lib/chook/server/public/js/logstream.js +101 -0
  26. data/lib/chook/server/routes.rb +28 -0
  27. data/lib/chook/server/routes/handle_by_name.rb +65 -0
  28. data/lib/chook/server/routes/handle_webhook_event.rb +27 -3
  29. data/lib/chook/server/routes/handlers.rb +52 -0
  30. data/lib/chook/server/routes/home.rb +48 -1
  31. data/lib/chook/server/routes/log.rb +105 -0
  32. data/lib/chook/server/routes/login_logout.rb +48 -0
  33. data/lib/chook/server/views/admin.haml +11 -0
  34. data/lib/chook/server/views/bak.haml +48 -0
  35. data/lib/chook/server/views/config.haml +15 -0
  36. data/lib/chook/server/views/handlers.haml +63 -0
  37. data/lib/chook/server/views/layout.haml +64 -0
  38. data/lib/chook/server/views/logstream.haml +33 -0
  39. data/lib/chook/server/views/sketch_admin +44 -0
  40. data/lib/chook/subject.rb +13 -2
  41. data/lib/chook/subject/dep_device.rb +81 -0
  42. data/lib/chook/subject/policy_finished.rb +43 -0
  43. data/lib/chook/subject/smart_group.rb +6 -0
  44. data/lib/chook/subject/test_subject.rb +2 -2
  45. data/lib/chook/version.rb +1 -1
  46. metadata +78 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9c06a719ccd3792fc0072afe43c498ffb5a01eed
4
- data.tar.gz: 2b9990fa45441cf0c4c82133deb0cab92b9a86b4
2
+ SHA256:
3
+ metadata.gz: a2a39df5350d3ae528addf0baaebe5a122b1701b08e2e503afd9b0500910120c
4
+ data.tar.gz: a8a277843eb2606251f8c6000cd25b2e11c60a94c69998046e59f5c0010c4489
5
5
  SHA512:
6
- metadata.gz: e4142bc3f93a40641c20bcbfe9acbd3907e12adc006e4cb5d296f4fb5623b076093eed789ef9b514e9077a35c7454bdfbaab24b7f54374a2feb56eddd0e1c18c
7
- data.tar.gz: 2f688ccff5b885fa3d482826f51e6589e1652391b427cdf5d66e229496c83709e6b41f5b042c455760d29ca280d3ca684856045e5969ddc0daaa6a67aeac2d47
6
+ metadata.gz: 519c4bf9ab0662a599eeef50e6e6bb637203f22c24a4cdc121bc94f28f239f856d9ff1380422259b67b64cb19554d18e76abb39ceabeb87ea408a9fde6398876
7
+ data.tar.gz: ad11db916b235f4bc0aaca73aae01b36f38c222555a76004fa4ac7f7e34efa32bc4fb80768366536dfd29a6f56ac5ec0912c6ac7eb1be63e62c7e03cec44170b
@@ -0,0 +1,56 @@
1
+ # Chook Change Log
2
+
3
+ ## v 1.1.5 2020-12-11
4
+
5
+ - Add support for handling DeviceAddedToDEP webhook events
6
+ - Updated the handling of ComputerPolicyFinished events to reflect new JSON structure via a PolicyFinished subject class.
7
+ NOTE: The computer info in the PolicyFinished subject for ComputerPolicyFinished events is located in a hash in the 'computer' attribute of the subject. So to get the SN of the computer that finished a policy, you'd use `event.subject.computer[:serialNumber]`. See the file ..lib/chook/subject/policy_finished.rb.
8
+
9
+ ### IMPORTANT Note:
10
+ Version 1.1.5 is probably the last release of v.1x for chook. Version 2 will be a major reworking of the code. While the general principles will remain the same, a lot will be simplified, some will be jettisoned (e.g. the whole TestEvent aspect) and hopefully lots will be optimized to better handle more and faster incoming webhooks. We'll get some test code up to Github asap.
11
+
12
+ ## v 1.1.4, 2020-08-10
13
+
14
+ - Set the server process name to 'chook' - some OS utilities will see it
15
+ - remove event START messages from info logging, now only visible when log level is debug.
16
+ - Don't use ruby object IDs as event ids - ruby reuses them.
17
+ - Server uptime is displayed on the simple admin web UI.
18
+
19
+ ## v 1.1.3, 2019-10-28
20
+
21
+ - Named Handlers! You can create a handler with any file name, and put it in /Library/Application Support/Chook/NamedHandlers then call it specifically from a webhook in Jamf Pro using the url http[s]://your.chook.server.com/handler/handler-filename
22
+
23
+ ## v 1.1.2, 2019-01-24
24
+
25
+ - code cleanup & bugfixes
26
+
27
+ - thread ids show up in debug logging
28
+
29
+ - go back to calling Thread.new explicitly, so that the JSS gets immediate acknowlegment of reciept of the POST
30
+
31
+ - don't use sessions for the event-handling route
32
+
33
+ - update README.md to be more server focused, since thats the primary use of Chook
34
+
35
+ ## v 1.1.1, 2018-10-18
36
+
37
+ - Admin web page authentication is now separated from Webhooks HTTP Basic Auth.
38
+ It can be turned off completely, set to a single username/password, or pointed
39
+ at a Jamf Pro server for admin authentication. See the Admin Interface section
40
+ of README.md, and/or chook.conf.example for details.
41
+
42
+ ## v 1.1.0, 2018-10-15
43
+
44
+ For details about the new features, please see README.md
45
+
46
+ - Now requires 'thin' as the server engine.
47
+
48
+ - Supports SSL and HTTP Basic Authentication
49
+
50
+ - Server logging is now a thing, with access to logging from both internal and external handlers
51
+
52
+ - A simple admin interface is available by pointing your browser at the chook server
53
+
54
+ - Internal handlers are now stored as anonymous objects rather than Procs, and the handler code block
55
+ is stored as an instance method on the object. This means that either 'break' or 'return' will work
56
+ to exit a handler.
data/README.md CHANGED
@@ -1,97 +1,192 @@
1
-
2
1
  # Chook
3
2
 
3
+ Documentation is a work in progress. Please [get in touch](mailto:chook@pixar.com) for assistance. <3
4
+
5
+ ### IMPORTANT Note:
6
+ Version 1.1.5 is probably the last release of v.1x for chook. Version 2 will be a major reworking of the code. While the general principles will remain the same, a lot will be simplified, some will be jettisoned (e.g. the whole TestEvent aspect) and hopefully lots will be optimized to better handle more and faster incoming webhooks. We'll get some test code up to Github asap.
7
+
8
+
4
9
  - [Introduction](#introduction)
5
- - [The Framework](#the-framework)
6
- - [Event Handlers](#event-handlers)
7
- - [Internal Handlers](#internal-handlers)
8
- - [External Handlers](#external-handlers)
9
- - [Putting It Together](#putting-it-together)
10
- - [Events and Subjects](#events-and-subjects)
10
+ - [Installing Chook](#installing-chook)
11
11
  - [The Server](#the-server)
12
- - [Installing Chook alongside ruby-jss](#installing-chook-alongside-ruby-jss)
12
+ - [Server Configuration](#server-configuration)
13
+ - [SSL](#ssl)
14
+ - [Logging](#logging)
15
+ - [Admin Interface](#admin-interface)
16
+ - [Event Handlers](#event-handlers)
17
+ - [Internal Handlers - Ruby](#internal-handlers-ruby)
18
+ - [External Handlers - Any Language](#external-handlers-any-language)
19
+ - [Logging from handlers](#logging-from-handlers)
20
+ - [Pointing Jamf Pro at your Chook server](#pointing-jamf-pro-at-your-chook-server)
21
+ - [The Framework](#the-framework)
22
+ - [Events and Subjects](#events-and-subjects)
23
+ - [Putting It Together](#putting-it-together)
13
24
  - [TODOs](#todos)
14
25
 
15
-
16
26
  ## Introduction
17
27
 
18
28
  Chook is a Ruby module that implements a framework for working with webhook events
19
- sent by the JSS, the core of [Jamf Pro](https://www.jamf.com/products/jamf-pro/),
29
+ sent by a [Jamf Pro](https://www.jamf.com/products/jamf-pro/) Server,
20
30
  a management tool for Apple devices.
21
31
 
22
- Chook also provides a simple, sinatra-based HTTP server for handling those Events,
23
- and classes for sending simulated TestEvents to a webhook handling server.
32
+ Chook also provides a simple [sinatra](http://sinatrarb.com/)-based HTTP server for receiving and processing those Events, and classes for sending simulated TestEvents to any Jamf webhook handling server.
24
33
 
25
- ** You do not need to be a Ruby developer to use this framework! **
34
+ **You do not need to be a Ruby developer to use Chook!**
26
35
 
27
- The webhook handling server that comes with Chook can use "Event Handlers" written in
28
- any language. See _Event Handlers_ and _The Server_ below for more information.
36
+ The Chook webhook handling server can use "Event Handlers" written in any language. See below for more information.
29
37
 
30
- Chook is still in development. While many of the basics work,
31
- there is much to be done before it can be considered complete.
38
+ Although Chook integrates well with [ruby-jss](http://pixaranimationstudios.github.io/ruby-jss/index.html), especially for processing webhook events,
39
+ it's a separate tool. However, ruby-jss is required when using Jamf-based admin page authentication, or using sampling features to generate TestEvents.
32
40
 
33
- Although Chook integrates well with [ruby-jss](http://pixaranimationstudios.github.io/ruby-jss/index.html),
34
- it's a separate tool, and the two projects aren't dependent. However, ruby-jss
35
- does become a requirement when using sampling features to generate TestEvents.
41
+ For more detail about the Jamf Pro Webhooks API and the JSON data it passes, please see
42
+ [JAMF's developer reference.](http://developer.jamf.com/webhooks)
36
43
 
37
- For more detail about the JSS webhooks API and the JSON data it passes, please see
38
- [Bryson Tyrrell's documentation.](https://unofficial-jss-api-docs.atlassian.net/wiki/display/JRA/Webhooks+API)
44
+ **Note:** When enabling webhooks from your Jamf Pro server to be handled by the framework, you must
45
+ specify JSON in the "Content Type" section. This framework does not support XML and
46
+ will only generate Test Events in JSON format.
39
47
 
40
- **Note:** When creating webhooks from your JSS to be handled by the framework, you must
41
- specify JSON in the "Content Type" section. This framework does not support XML.
42
-
43
- ## The Framework
44
48
 
45
- The Chook framework abstracts webhook Events and their components as Ruby
46
- classes. When the JSON payload of a JSS webhook POST request is passed into the
47
- `Chook::Event.parse_event` method, an instance of the appropriate subclass
48
- of `Chook::Event` is returned, for example
49
- `Chook::Event::ComputerInventoryCompletedEvent`
49
+ ## Installing Chook
50
50
 
51
- Each Event instance contains these important attributes:
51
+ As with most Ruby gems: `gem install chook -n /usr/local/bin`
52
52
 
53
- * **webhook_id:** A read-only instance of the webhook ID stored in the JSS
54
- which caused the POST request. This attribute matches the "webhook[:id]"
55
- dictionary of the POSTed JSON.
53
+ It will automatically install "sinatra" and "thin", and their dependencies.
56
54
 
57
- * **webhook_name:** A read-only instance of the webhook name stored in the JSS
58
- which caused the POST request. This attribute matches the "webhook[:name]"
59
- dictionary of the POSTed JSON.
55
+ If you'll be using a Jamf Pro server to authenticate access to Chook's admin web page, or if you'll be generating Chook TestEvents using data sampled from a Jamf Pro server you'll also need to `gem install ruby-jss`
60
56
 
61
- * **subject:** A read-only instance of a `Chook::Subject::<Class>`
62
- representing the "subject" that accompanies the event that triggered the
63
- webhook. It comes from the "event" dictionary of the POSTed JSON, and
64
- different events come with different subjects attached. For example, the
65
- ComputerInventoryCompleted event comes with a "computer" subject containing
66
- data about the JSS computer that completed inventory.
57
+ ## The Server
67
58
 
68
- This is not full `JSS::Computer` object from the REST API, but rather a group
69
- of named attributes about that computer. At the moment, only the Chook Samplers
70
- module attempts to look up subject data from the API, but any
71
- Handlers written for the event could easily do a similar operation.
59
+ Chook comes with a simple HTTP(S) server that uses the Chook framework
60
+ to handle incoming webhook POST requests from a Jamf Pro server via a single URL `https://my.chookserver.org/handle_webhook_event`.
72
61
 
73
- * **event_json:** The JSON content from the POST request, parsed into
74
- a Ruby hash with symbolized keys (meaning the JSON key "deviceName" becomes
75
- the symbol :deviceName).
62
+ After Installing chook, just run `/usr/local/bin/chook-server` and then point your Jamf Pro webhooks at: http://my_hostname/handle_webhook_event
76
63
 
77
- * **raw_json:** A String containing the raw JSON from the POST
78
- request.
64
+ It will then process incoming webhook POST requests using whatever handlers
65
+ you have installed.
79
66
 
80
- * **handlers:** An Array of custom plug-ins for working with the
81
- event. See _Event Handlers_, below.
67
+ To automate it on a dedicated Mac, just make a LaunchDaemon plist to run
68
+ that command and keep it running.
82
69
 
70
+ ### Server Configuration
71
+
72
+ The Chook server looks for a config file at `/etc/chook.conf`. If not found, default
73
+ values are used. Full descriptions of the config values are provided in the sample
74
+ config file at:
75
+ /path/to/your/gem/folder/chook-<version>/data/chook.conf.example
76
+
77
+ Each config setting is on a single line like: `key: value`. Blank lines and those starting with # are ignored.
78
+
79
+ Here's a summary of possible configuration keys:
80
+
81
+ - port: The server port
82
+ - default = 443 (SSL), or 80 (no SSL)
83
+ - concurrency: Should events be processed simultaneously? (otherwise, one at a time)
84
+ - default = true
85
+ - handler_dir: The directory holding the event handler files to load.
86
+ - default = /Library/Application Support/Chook
87
+ - use_ssl: Should the server use SSL (https)
88
+ - default = false
89
+ - ssl_cert_path: If SSL is used, the path to the server certificate
90
+ - no default
91
+ - ssl_private_key_path: If SSL is used, the path to the certificate key
92
+ - no default
93
+ - log_file: The path to the server log file
94
+ - default = /var/log/chook-server.log
95
+ - log_level: The severity level for log entries
96
+ - default = info
97
+ - logs_to_keep: How many old log files to keep when rotating
98
+ - default = 10
99
+ - log_max_megs: How big can a log file get before it's rotated.
100
+ - default = 10
101
+ - webhooks_user: The username for Basic Authentication
102
+ - no default, leave unset for no authentication
103
+ - webhooks_user_pw: The file path, or command, to get the password for the webhooks_user.
104
+ - no default
105
+ - admin_user: the username for access to the Chook admin web page, or 'use_jamf'
106
+ - no default, leave unset for no authentication
107
+ - admin_pw: if the admin user is NOT 'use_jamf', The file path, or command, to get the password for the admin_user.
108
+ - no default
109
+ - admin_session_expires: How many seconds is an admin login valid?
110
+ - default: 86400 (24 hours)
111
+ - jamf_server: if admin_user is 'use_jamf', the Jamf Pro server to use for admin authentication
112
+ - default: none, but /etc/ruby-jss.conf is honored.
113
+ - jamf_port: if admin_user is 'use_jamf', the Jamf Pro server port to use for admin authentication
114
+ - default: none, but /etc/ruby-jss.conf is honored.
115
+ - jamf_use_ssl: if admin_user is 'use_jamf', use SSL to talk to the Jamf Pro server? true/false
116
+ - default: none, but /etc/ruby-jss.conf is honored.
117
+ - jamf_verify_cert: if admin_user is 'use_jamf', verify the SSL certificate from the Jamf Pro server? true/false
118
+ - default: none, but /etc/ruby-jss.conf is honored.
119
+ -
120
+ See the sample config file for details about all of these settings.
121
+
122
+ ### SSL
123
+
124
+ It is recommended to use SSL (https) if possible for security, although its beyond the scope
125
+ of this document to go into a lot of detail about SSL and certificates. That said, here
126
+ are some pointers:
127
+
128
+ - The certificate and key files should be in .pem format
129
+
130
+ - Make sure you use a certificate that can be verified by the JSS.
131
+ - This might involved adding a CA to the JSS's Java Keystore.
132
+
133
+ - If running on macOS, the 'thin' webserver and it's underlying 'eventmachine' gem may not
134
+ like the OS's openssl replacement 'libressl'.
135
+ - One solution is to use [homebrew](https://brew.sh/) to install openssl and then
136
+ install eventmachine using that openssl, something like this:
137
+
138
+ `brew install openssl ; brew link openssl --force ; gem install eventmachine -- --with-ssl-dir=/usr/local/`
139
+
140
+ ### Logging
141
+
142
+ The Chook server logs activity into the file defined in the `log_file` config setting,
143
+ `/var/log/chook-server.log` by default.
144
+
145
+ It uses a standard ruby [Logger](http://ruby-doc.org/stdlib-2.3.3/libdoc/logger/rdoc/index.html)
146
+ instance, which provides 5 severity levels: fatal (lowest), error, warn, info, and debug (highest).
147
+
148
+ The `log_level` config setting defines the level when the server starts up, and log
149
+ messages of that level or lower will be written to the log.
150
+
151
+ The log can automatically rotate when it reaches a certain size, as specified by the log_max_megs configuration setting, and the logs_to_keep settings tells chook how many it should keep in total - older log files will be deleted automatically.
152
+
153
+ If you want to manage the log rotation on your own, set logs_to_keep to zero, or leave it unset,
154
+ and the log will never automatically rotate.
155
+
156
+ See below for how to write to the Chook log from within a handler
157
+
158
+ ### Admin Interface
159
+
160
+ If you point your web browser at your Chook server `http(s)://your.chookserver.org/` , you'll see a simple admin interface.
161
+
162
+ If an `admin_user` is set in the configuration, you'll need to provide that name and password, or if `admin_user` is `use_jamf` you'll need to provide the username and password of any Jamf Pro user on
163
+ the server indicated in the config.
164
+
165
+ The first section provides a live-stream of the server log file, and provides a way to
166
+ change the server's log level on the fly. Note that this change affects the server itself
167
+ not just the view in your browser. If you'd like to stop the stream temporarily (e.g. to
168
+ scroll back, or select some text), just pause and unpause with the checkbox.
169
+
170
+ The second section lets you see which handlers are currently loaded, and if they are
171
+ internal or external. The (view) button shows the contents of the handler file.
172
+
173
+ There's also a button to reload the handlers from the handler directory without restarting the server - useful when you add, delete, or modify them.
174
+
175
+ The final section just shows your current /etc/chook.conf file, or if there is none,
176
+ the sample config file is shown, since it shows the default values.
177
+
178
+ The admin page cannot be used to edit or upload handlers or change the config. For security
179
+ reasons, you must do that on the server machine itself though normal administrative methods.
83
180
 
84
181
  ### Event Handlers
85
182
 
86
- A handler is a file containing code to run when a webhook event occurs. These
183
+ A handler is a file containing code to run when a webhook event is received. These
87
184
  files are located in a specified directory, `/Library/Application
88
- Support/Chook/` by default, and are loaded at runtime. It's up to the Jamf
89
- administrator to create these handlers to perform desired tasks. Each class of
90
- event can have as many handlers as desired, all will be executed when the event's
91
- `handle` method is called.
185
+ Support/Chook/` by default, and are loaded when the server starts, or the (reload)
186
+ button is clicked on the admin web page.
92
187
 
93
188
  Handler files must begin with the name of the event they handle, e.g.
94
- ComputerAdded, followed by: nothing, a dot, a dash, or an underscore. Handler
189
+ `ComputerAdded`, followed by: nothing, a dot, a dash, or an underscore. Handler
95
190
  filenames are case-insensitive.
96
191
 
97
192
  All of these file names are valid handlers for ComputerAdded events:
@@ -101,45 +196,108 @@ All of these file names are valid handlers for ComputerAdded events:
101
196
  - COMPUTERAdded_notify_team
102
197
  - Computeradded-update-ldap
103
198
 
104
- There are two kinds of handlers:
199
+ Each kind of event can have as many handlers as desired, all will be executed when webhook event
200
+ is recieved. If all four of the above files existed in the handler directory, every
201
+ ComputerAdded event would run all four of them.
202
+
203
+ There are two kinds of handlers, distinguished by their file-executability.
105
204
 
106
- #### Internal Handlers
205
+ #### Internal Handlers - Ruby
107
206
 
108
207
  These handlers are _non-executable_ files containing Ruby code. The code is
109
- loaded at runtime and executed in the context of the Chook Framework when
110
- called by an event.
208
+ loaded at runtime and executed as a thread in the Chook server process when
209
+ a matching event is received.
111
210
 
112
211
  Internal handlers must be defined as a [ruby code block](http://rubylearning.com/satishtalim/ruby_blocks.html) passed to the
113
212
  `Chook.event_handler` method. The block must take one parameter, the
114
213
  Chook::Event subclass instance being handled. Here's a simple example of
115
- a handler for a Chook::ComputerAddedEvent
214
+ a handler for a ComputerAdded webhook event.
116
215
 
117
216
  ```ruby
118
217
  Chook.event_handler do |event|
119
218
  cname = event.subject.deviceName
120
219
  uname = event.subject.realName
121
- puts "Computer '#{cname}' was just added to the JSS for user #{uname}."
220
+ event.logger.info "Computer '#{cname}' was just added to the JSS for user #{uname}."
221
+ end
222
+ ```
223
+
224
+ The code block, between `do` and `end`, takes one parameter which will be a Chook::HandledEvents::ComputerAddedEvent object. In this example the object is stored in the variable "event" and used inside the block.
225
+
226
+ This handler then extracts the "deviceName" and "realName" values from the subject
227
+ contained in the event, and uses them to log a message in the chook log.
228
+
229
+ The subject of an event is the thing that the event affected. In the case of ComputerAdded events, the subject is a Computer. In Chook, it's an object of the class Chook::HandledSubjects::ComputerAdded.
230
+
231
+ **NameSpacing**
232
+
233
+ Be careful when writing internal handlers - they all run in the same Ruby process!
234
+
235
+ Not only do they have to be thread-safe, but be wary of cluttering the default
236
+ namespace with constants or methods that might overwrite each other.
237
+
238
+ A good, very ruby-like, practice is to put the guts of your code into a Module or a Class
239
+ and use that from inside the handler definition. Here's an example using a Class:
240
+
241
+ ```ruby
242
+ require 'slack-em' # ficticious Slack-chat gem, for demonstation purposes
243
+
244
+ class ComputerAdder
245
+
246
+ SLACK_CHANNEL = '#mac-notifications'
247
+
248
+ def initialize(event)
249
+ @event = event
250
+ @comp_name = @event.subject.deviceName
251
+ @user_name = @event.subject.realName
252
+ end
253
+
254
+ def run
255
+ @event.logger.info "Adder Starting for computer #{@comp_name}"
256
+ notify_admins
257
+ @event.logger.info "Adder Finished for computer #{@comp_name}"
258
+ end
259
+
260
+ def notify_admins
261
+ msg = "Computer '#{@comp_name}' was just enrolled for user #{@user_name}."
262
+ SlackEm.send message: msg, channel: SLACK_CHANNEL
263
+ @event.logger.debug "Admins notified about computer #{@comp_name}"
264
+ end
265
+
266
+ end
267
+
268
+ Chook.event_handler do |event|
269
+ ComputerAddeder.new(event).run
122
270
  end
123
271
  ```
124
272
 
125
- In this example, the code block takes one parameter, which it expects to be
126
- a Chook::ComputerAddedEvent instance, and uses it in the variable "event."
127
- It then extracts the "deviceName" and "realName" values from the subject
128
- contained in the event, and uses them to send a message to stdout.
273
+ Here, the handler file defines a 'ComputerAdder' class that does all the work.
274
+ The handler block merely creates an instance of ComputerAdder, passing it the event,
275
+ and tells the ComputerAdder instance to run. The instance's run method can
276
+ then perform any steps desired.
277
+
278
+ In this example, the SLACK_CHANNEL constant is defined inside the ComputerAdder class.
279
+ Access to it from inside the class is done using just the constant itself. If you
280
+ need to access that particular value from outside of the class, you can
281
+ use ComputerAdder::SLACK_CHANNEL.
282
+
283
+ This way, similar handlers can have their own SLACK_CHANNEL constants and
284
+ there won't be any interference.
285
+
286
+ For more details about event and subject classes, see [The Framework](#the-framework)
129
287
 
130
- Internal handlers **must not** be executable files. Executability is how the
288
+ NOTE: Internal handlers **must not** be executable files. Executability is how the
131
289
  framework determines if a handler is internal or external.
132
290
 
133
- #### External Handlers
291
+ #### External Handlers - Any Language
134
292
 
135
293
  External handlers are _executable_ files that are executed when called by an
136
294
  event. They can be written in any language, but they must accept raw JSON on
137
295
  their standard input. It's up to them to parse that JSON and react to it as
138
- desired. In this case the Chook framework is merely a conduit for passing
139
- the Posted JSON to the executable program.
296
+ desired. In this case the Chook server is merely a conduit for passing
297
+ the Posted JSON from the Jamf Pro server to the executable program.
140
298
 
141
299
  Here's a simple example using bash and [jq](https://stedolan.github.io/jq/) to
142
- do the same as the ruby example above:
300
+ do something similar to the first ruby example above:
143
301
 
144
302
  ```bash
145
303
  #!/bin/bash
@@ -156,38 +314,128 @@ framework determines if a handler is internal or external.
156
314
  See `data/sample_handlers/RestAPIOperation-executable`
157
315
  for a more detailed bash example that handles RestAPIOperation events.
158
316
 
159
- ### Putting It Together
317
+ See the [Jamf Developer Site](https://developer.jamf.com/webhooks) for details about the JSON contents of webhook events.
160
318
 
161
- Here is a commented sample of ruby code that uses the framework to process a
162
- ComputerAdded Event:
319
+ ### Logging from handlers
320
+
321
+ **Internal handlers**
322
+
323
+ To write to the Chook log file from within an internal handler, use the `#logger` method of the `event` object
324
+ inside the handler block, like so:
163
325
 
164
326
  ```ruby
165
- # load the framework
166
- require 'chook'
327
+ Chook.event_handler do |event|
328
+ event.logger.debug "This line appears in the log if the level is debug"
329
+ event.logger.info "This line appears in the log if the level is info or debug"
330
+ event.logger.error "This line appears in the log if the level is error, warn, info, or debug"
331
+ end
332
+ ```
167
333
 
168
- # The framework comes with sample JSON files for each Event type.
169
- # In reality, a webserver would extract this from the data POSTed from the JSS
170
- posted_json = Chook.sample_jsons[:ComputerAdded]
334
+ If you want to log a Ruby exception with its backtrace, you can pass the entire
335
+ exception to the event logger's 'log_exception' method like this:
336
+ ```ruby
337
+ begin
338
+ # something horribly wrong happens here
339
+ rescue => execption
340
+ event.logger.log_exception exception
341
+ end
342
+ ```
171
343
 
172
- # Create Chook::HandledEvents::ComputerAddedEvent instance for the event
173
- event = Chook::HandledEvent.parse_event posted_json
344
+ Log entries written through event objects are preceded with `Event xxxxxxxxx:` where xxxxxxxxx is
345
+ an internal ID number for the specific even that wrote the entry.
174
346
 
175
- # Call the events #handle method, which will execute any ComputerAdded
176
- # handlers that were in the Handler directory when the framework was loaded.
177
- event.handle
347
+ **External handlers**
348
+
349
+ External Handlers can use a URL to make log entries by POSTing to `https://my.chookserver/log`
350
+
351
+ The request body must be a JSON object wth 2 keys 'level' and 'message' where both values are strings.
352
+
353
+ The 'level' must be one of the known log levels: fatal, error, warn, info, or debug. The message is a single line of text to be added to the log.
354
+
355
+ If your chook server is using Basic Authentication for webhook events, it must be provided for logging also.
356
+
357
+ Here's an example with curl, split to multi-line for clarity:
358
+
359
+ ```
360
+ curl -u 'auth-user:auth-pw' \
361
+ -H 'Content-Type: application/json' \
362
+ -X POST \
363
+ --data '{"level":"debug", "message":"It Worked"}' \
364
+ https://chookserver.myorg.org/log
178
365
  ```
179
366
 
180
- Of course, you can use the framework without using the built-in #handle method,
181
- and if you don't have any handlers in the directory, it won't do anything
182
- anyway. Instead you are welcome to use the objects as desired in your own
183
- Ruby code.
367
+ Messages logged via this url show up in the log preceded by `ExternalEntry: `
368
+
369
+ Any info needed to indentify a log entry with a specific event must be included in
370
+ your log message.
371
+
372
+ ### Pointing Jamf Pro at your Chook server
373
+
374
+ Once your server is up and running, and you have a handler or two in place, you can create webhooks in your Jamf Pro interface:
375
+
376
+ 1. Navigate to Settings => Global Management => Webhooks
377
+ 2. Click "New"
378
+ 3. Give your webhook a display name
379
+ 4. Enter the URL for your Chook server, ending with 'handle_webhook_event', e.g:
380
+ - `http://my.chookserver.edu/handle_webhook_event`
381
+ - `https://my.chookserver.edu:8443/handle_webhook_event`
382
+ 5. If you use Basic Authentication, enter the name and password
383
+ 6. The default timeouts should be OK, but raise them a bit if you're experiencing errors.
384
+ 7. Set the content type to JSON
385
+ 8. Select the event that triggers the webook
386
+ 9. Click "Enabled" at the top.
387
+ 10. Click "Save"
388
+
389
+ Watch the Chook log, with the level at info or debug, to see events come in.
390
+
391
+ ## The Framework
392
+
393
+ While most folks will get along fine using the chook server and writing handlers, the server is built upon a framework implemented in the `Chook` ruby module, available after doing`require 'chook'`. For those with very specific needs, this framework can be used to implement your own webhook handling service, or to simulate a JamfPro server sending webhook events to some handling service.
394
+
395
+ The Chook framework abstracts webhook Events and their components as Ruby classes, grouped in two namespaces: HandledEvents, and TestEvents.
396
+
397
+ When the JSON payload of a JSS webhook POST request is passed into the `Chook::Event.parse_event` method, an instance of the appropriate subclass of `Chook::Event` is returned, for example, given the JSON for a ComputerInventoryCompleted webhook event, a `Chook::Event::ComputerInventoryCompletedEvent` instance is returned by `Chook::Event.parse_event`.
398
+
399
+ Each such event instance contains these important attributes:
400
+
401
+ * **webhook_id:** The webhook ID stored in the JSS
402
+ which caused the POST request. This attribute matches the "webhook[:id]"
403
+ value of the POSTed JSON.
404
+
405
+ * **webhook_name:** A read-only instance of the webhook name stored in the JSS
406
+ which caused the POST request. This attribute matches the "webhook[:name]"
407
+ value of the POSTed JSON.
408
+
409
+ * **subject:** A read-only instance of a `Chook::Subject::<Class>`
410
+ representing the "subject" that accompanies the event that triggered the
411
+ webhook. It comes from the "event" object of the POSTed JSON, and
412
+ different events come with different subjects attached. For example, the
413
+ ComputerInventoryCompleted event comes with a "computer" subject containing
414
+ data about the JSS computer that completed inventory.
415
+
416
+ This is not a ruby-jss `JSS::Computer` object from the REST API, but rather a group
417
+ of named attributes about that computer.
418
+
419
+ * **event_json:** The JSON content from the POST request, parsed into
420
+ a Ruby hash with symbolized keys (meaning the JSON key "deviceName" becomes
421
+ the symbol :deviceName).
422
+
423
+ * **raw_json:** A String containing the raw JSON from the POST
424
+ request.
184
425
 
185
426
  ### Events and Subjects
186
427
 
187
428
  Here are the Event classes supported by the framework and the Subject classes
188
429
  they contain.
189
- For details about the attributes of each Subject, see [The Unofficial JSS API
190
- Docs](https://unofficial-jss-api-docs.atlassian.net/wiki/display/JRA/Webhooks+API).
430
+
431
+ For details about the attributes of each Subject, see [the Jamf Developer documentation](https://developer.jamf.com/webhooks).
432
+
433
+ **A special note about Subjects**
434
+
435
+ In Jamf's documentation, what Chook refers to as a 'Subject' is
436
+ called an 'event object' because it is a JSON 'object' (a.k.a. dictionary, hash, associative array)
437
+ labeled 'event'. We've chosen the word 'subject' to make talking about this thing a
438
+ bit more clear in the context of object-oriented programming.
191
439
 
192
440
  Each Event class is a subclass of `Chook::Event`, where all of their
193
441
  functionality is defined.
@@ -195,65 +443,69 @@ functionality is defined.
195
443
  The Subject classes aren't subclasses, but are dynamically-defined members of
196
444
  the `Chook::Subjects` module.
197
445
 
198
- | Event Classes | Subject Classes |
446
+ | Handled Event Classes | Handled Subject Classes |
199
447
  | -------------- | ------------ |
200
- | Chook::ComputerAddedEvent | Chook::Subjects::Computer |
201
- | Chook::ComputerCheckInEvent | Chook::Subjects::Computer |
202
- | Chook::ComputerInventoryCompletedEvent | Chook::Subjects::Computer |
203
- | Chook::ComputerPolicyFinishedEvent | Chook::Subjects::Computer |
204
- | Chook::ComputerPushCapabilityChangedEvent | Chook::Subjects::Computer |
205
- | Chook::JSSShutdownEvent | Chook::Subjects::JSS |
206
- | Chook::JSSStartupEvent | Chook::Subjects::JSS |
207
- | Chook::MobileDeviceCheckinEvent | Chook::Subjects::MobileDevice |
208
- | Chook::MobileDeviceCommandCompletedEvent | Chook::Subjects::MobileDevice |
209
- | Chook::MobileDeviceEnrolledEvent | Chook::Subjects::MobileDevice |
210
- | Chook::MobileDevicePushSentEvent | Chook::Subjects::MobileDevice |
211
- | Chook::MobileDeviceUnenrolledEvent | Chook::Subjects::MobileDevice |
212
- | Chook::PatchSoftwareTitleUpdateEvent | Chook::Subjects::PatchSoftwareTitleUpdate |
213
- | Chook::PushSentEvent | Chook::Subjects::Push |
214
- | Chook::RestAPIOperationEvent | Chook::Subjects::RestAPIOperation |
215
- | Chook::SCEPChallengeEvent | Chook::Subjects::SCEPChallenge |
216
- | Chook::SmartGroupComputerMembershipChangeEvent | Chook::Subjects::SmartGroup |
217
- | Chook::SmartGroupMobileDeviveMembershipChangeEvent | Chook::Subjects::SmartGroup |
218
-
219
-
220
- ## The Server
221
-
222
- Chook comes with a simple HTTP server that uses the Chook framework
223
- to handle all incoming webhook POST requests from the JSS via a single URL.
224
-
225
- To use it you'll need the [sinatra](http://www.sinatrarb.com/)
226
- ruby gem (`gem install sinatra`).
227
-
228
- After that, just run the `chook-server` command located in the bin
229
- directory for chook and then point your webhooks at:
230
- http://my_hostname/handle_webhook_event
231
-
232
- It will then process all incoming webhook POST requests using whatever handlers
233
- you have installed.
448
+ | Chook::HandledEvents::ComputerAddedEvent | Chook::HandledSubjects::Computer |
449
+ | Chook::HandledEvents::ComputerCheckInEvent | Chook::HandledSubjects::Computer |
450
+ | Chook::HandledEvents::ComputerInventoryCompletedEvent | Chook::HandledSubjects::Computer |
451
+ | Chook::HandledEvents::ComputerPolicyFinishedEvent | Chook::HandledSubjects::PolicyFinished |
452
+ | Chook::HandledEvents::ComputerPushCapabilityChangedEvent | Chook::HandledSubjects::Computer |
453
+ | Chook::HandledEvents::DeviceAddedToDEP | Chook::HandledSubjects::DEPDevice |
454
+ | Chook::HandledEvents::JSSShutdownEvent | Chook::HandledSubjects::JSS |
455
+ | Chook::HandledEvents::JSSStartupEvent | Chook::HandledSubjects::JSS |
456
+ | Chook::HandledEvents::MobileDeviceCheckinEvent | Chook::HandledSubjects::MobileDevice |
457
+ | Chook::HandledEvents::MobileDeviceCommandCompletedEvent | Chook::HandledSubjects::MobileDevice |
458
+ | Chook::HandledEvents::MobileDeviceEnrolledEvent | Chook::HandledSubjects::MobileDevice |
459
+ | Chook::HandledEvents::MobileDevicePushSentEvent | Chook::HandledSubjects::MobileDevice |
460
+ | Chook::HandledEvents::MobileDeviceUnenrolledEvent | Chook::HandledSubjects::MobileDevice |
461
+ | Chook::HandledEvents::PatchSoftwareTitleUpdateEvent | Chook::HandledSubjects::PatchSoftwareTitleUpdate |
462
+ | Chook::HandledEvents::PushSentEvent | Chook::HandledSubjects::Push |
463
+ | Chook::HandledEvents::RestAPIOperationEvent | Chook::HandledSubjects::RestAPIOperation |
464
+ | Chook::HandledEvents::SCEPChallengeEvent | Chook::HandledSubjects::SCEPChallenge |
465
+ | Chook::HandledEvents::SmartGroupComputerMembershipChangeEvent | Chook::HandledSubjects::SmartGroup |
466
+ | Chook::HandledEvent::SmartGroupMobileDeviveMembershipChangeEvent | Chook::HandledSubjects::SmartGroup |
467
+
468
+ | Test Event Classes | Test Subject Classes |
469
+ | -------------- | ------------ |
470
+ | Chook::TestEvents::ComputerAddedEvent | Chook::TestSubjects::Computer |
471
+ | Chook::TestEvents::ComputerCheckInEvent | Chook::TestSubjects::Computer |
472
+ | Chook::TestEvents::ComputerInventoryCompletedEvent | Chook::TestSubjects::Computer |
473
+ | Chook::TestEvents::ComputerPolicyFinishedEvent | Chook::TestSubjects::Computer |
474
+ | Chook::TestEvents::ComputerPushCapabilityChangedEvent | Chook::TestSubjects::Computer |
475
+ | Chook::TestEvents::MobileDeviceCheckinEvent | Chook::TestSubjects::MobileDevice |
476
+ | Chook::TestEvents::MobileDeviceCommandCompletedEvent | Chook::TestSubjects::MobileDevice |
477
+ | Chook::TestEvents::MobileDeviceEnrolledEvent | Chook::TestSubjects::MobileDevice |
478
+ | Chook::TestEvents::MobileDevicePushSentEvent | Chook::TestSubjects::MobileDevice |
479
+ | Chook::TestEvents::MobileDeviceUnenrolledEvent | Chook::TestSubjects::MobileDevice |
480
+ | Chook::TestEvents::PatchSoftwareTitleUpdateEvent | Chook::TestSubjects::PatchSoftwareTitleUpdate |
234
481
 
235
- To automate it on a dedicated machine, just make a LaunchDaemon plist to run
236
- that command and keep it running.
482
+ ### Putting It Together
237
483
 
238
- ## Installing Chook
484
+ Here is a commented sample of ruby code that uses the framework to process a
485
+ ComputerAdded Event:
239
486
 
240
- `gem install chook -n /usr/local/bin`. It will also install "sinatra."
487
+ ```ruby
488
+ # load the framework
489
+ require 'chook'
241
490
 
242
- Then fire up `irb` and `require chook` to start playing around.
491
+ # The framework comes with sample JSON files for each Event type.
492
+ # In reality, a webserver would extract this from the data POSTed from the JSS
493
+ posted_json = Chook.sample_jsons[:ComputerAdded]
243
494
 
244
- OR
495
+ # Create Chook::HandledEvents::ComputerAddedEvent instance for the event
496
+ event = Chook::HandledEvent.parse_event posted_json
245
497
 
246
- run `/usr/local/bin/chook` and point some JSS webhooks at that machine.
498
+ # Call the events #handle method, which will execute any ComputerAdded
499
+ # handlers that were in the Handler directory when the framework was loaded.
500
+ event.handle
501
+ ```
247
502
 
503
+ Of course, you can use the framework without using the built-in #handle method,
504
+ and if you don't have any handlers in the directory, it won't do anything
505
+ anyway. Instead you are welcome to use the objects as desired in your own
506
+ Ruby code.
248
507
 
249
508
  ## TODOs
250
509
 
251
- - Add SSL support to the server
252
- - Improved thread management for handlers
253
- - Logging and Debug options
254
- - Handler reloading for individual, or all, Event subclasses
255
510
  - Better YARD docs
256
- - Better namespace protection for internal handlers
257
- - Improved configuration
258
- - Proper documentation beyond this README
259
- - I'm sure there's more to do...
511
+ - more documentation beyond this README