xolo-server 1.0.0 → 2.0.2

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +42 -4
  3. data/bin/xoloserver +3 -0
  4. data/data/client/xolo +1233 -0
  5. data/lib/optimist_with_insert_blanks.rb +1216 -0
  6. data/lib/xolo/core/base_classes/configuration.rb +238 -0
  7. data/lib/xolo/core/base_classes/server_object.rb +112 -0
  8. data/lib/xolo/core/base_classes/title.rb +884 -0
  9. data/lib/xolo/core/base_classes/version.rb +641 -0
  10. data/lib/xolo/core/constants.rb +85 -0
  11. data/lib/xolo/core/exceptions.rb +52 -0
  12. data/lib/xolo/core/json_wrappers.rb +43 -0
  13. data/lib/xolo/core/loading.rb +59 -0
  14. data/lib/xolo/core/output.rb +292 -0
  15. data/lib/xolo/core/security_cmd.rb +128 -0
  16. data/lib/xolo/core/version.rb +21 -0
  17. data/lib/xolo/core.rb +47 -0
  18. data/lib/xolo/server/app.rb +7 -0
  19. data/lib/xolo/server/configuration.rb +243 -38
  20. data/lib/xolo/server/constants.rb +10 -0
  21. data/lib/xolo/server/helpers/auth.rb +19 -2
  22. data/lib/xolo/server/helpers/autopkg.rb +157 -0
  23. data/lib/xolo/server/helpers/client_data.rb +90 -60
  24. data/lib/xolo/server/helpers/file_transfers.rb +412 -82
  25. data/lib/xolo/server/helpers/jamf_pro.rb +31 -7
  26. data/lib/xolo/server/helpers/log.rb +2 -0
  27. data/lib/xolo/server/helpers/maintenance.rb +1 -0
  28. data/lib/xolo/server/helpers/notification.rb +4 -3
  29. data/lib/xolo/server/helpers/pkg_signing.rb +16 -12
  30. data/lib/xolo/server/helpers/progress_streaming.rb +9 -12
  31. data/lib/xolo/server/helpers/subscriptions.rb +119 -0
  32. data/lib/xolo/server/helpers/titles.rb +27 -3
  33. data/lib/xolo/server/helpers/versions.rb +23 -11
  34. data/lib/xolo/server/mixins/changelog.rb +9 -16
  35. data/lib/xolo/server/mixins/title_jamf_access.rb +375 -390
  36. data/lib/xolo/server/mixins/title_ted_access.rb +50 -8
  37. data/lib/xolo/server/mixins/version_jamf_access.rb +118 -129
  38. data/lib/xolo/server/mixins/version_ted_access.rb +34 -4
  39. data/lib/xolo/server/object_locks.rb +2 -1
  40. data/lib/xolo/server/routes/auth.rb +2 -2
  41. data/lib/xolo/server/routes/jamf_pro.rb +11 -1
  42. data/lib/xolo/server/routes/maint.rb +2 -1
  43. data/lib/xolo/server/routes/subscriptions.rb +126 -0
  44. data/lib/xolo/server/routes/title_editor.rb +1 -1
  45. data/lib/xolo/server/routes/titles.rb +26 -11
  46. data/lib/xolo/server/routes/uploads.rb +0 -14
  47. data/lib/xolo/server/routes/versions.rb +14 -13
  48. data/lib/xolo/server/routes.rb +15 -23
  49. data/lib/xolo/server/title.rb +100 -77
  50. data/lib/xolo/server/version.rb +178 -18
  51. data/lib/xolo/server.rb +8 -0
  52. metadata +20 -11
data/lib/xolo/core.rb ADDED
@@ -0,0 +1,47 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ # Load the core xolo functionality
10
+ #
11
+ # This is done automatically when you `require 'xolo/admin'` or
12
+ # `require 'xolo/server'`
13
+
14
+ # Ruby Standard Libraries
15
+ ######
16
+ require 'English'
17
+ require 'date'
18
+ require 'time'
19
+ require 'pathname'
20
+ require 'json'
21
+
22
+ # Other Gems to include at this level
23
+ require 'pixar-ruby-extensions'
24
+
25
+ # Internal requires - order matters
26
+ require 'xolo/core/loading'
27
+ require 'xolo/core/version'
28
+ require 'xolo/core/constants'
29
+ require 'xolo/core/exceptions'
30
+
31
+ # The main module
32
+ module Xolo
33
+
34
+ extend Xolo::Core::Loading
35
+ include Xolo::Core::Version
36
+ include Xolo::Core::Constants
37
+ include Xolo::Core::Exceptions
38
+
39
+ end # module Xolo
40
+
41
+ require 'xolo/core/json_wrappers'
42
+ require 'xolo/core/security_cmd'
43
+ require 'xolo/core/base_classes/configuration'
44
+ require 'xolo/core/base_classes/server_object'
45
+ require 'xolo/core/base_classes/title'
46
+ require 'xolo/core/base_classes/version'
47
+ require 'xolo/core/output'
@@ -19,6 +19,7 @@ module Xolo
19
19
  ##############################
20
20
  ##############################
21
21
 
22
+ # register Sinatra extensions - this 'extends' the app class with the extension's methods
22
23
  register Xolo::Server::Routes
23
24
  register Xolo::Server::Routes::Auth
24
25
  register Xolo::Server::Routes::Maint
@@ -27,9 +28,13 @@ module Xolo
27
28
  register Xolo::Server::Routes::Titles
28
29
  register Xolo::Server::Routes::Versions
29
30
  register Xolo::Server::Routes::Uploads
31
+ register Xolo::Server::Routes::Subscriptions
30
32
 
33
+ # include helper modules - this 'includes' the app class with the helper module's methods
34
+ # making them available as instance methods in routes and views
31
35
  helpers Xolo::Core::Constants
32
36
  helpers Xolo::Core::JSONWrappers
37
+ helpers Xolo::Core::SecurityCmd
33
38
  helpers Xolo::Server::Helpers::Log
34
39
  helpers Xolo::Server::Helpers::Notification
35
40
  helpers Xolo::Server::Helpers::Auth
@@ -41,7 +46,9 @@ module Xolo
41
46
  helpers Xolo::Server::Helpers::PkgSigning
42
47
  helpers Xolo::Server::Helpers::ProgressStreaming
43
48
  helpers Xolo::Server::Helpers::ClientData
49
+ helpers Xolo::Server::Helpers::Subscriptions
44
50
  helpers Xolo::Server::Helpers::Maintenance
51
+ helpers Xolo::Server::Helpers::AutoPkg
45
52
 
46
53
  # Sinatra setup
47
54
  ##############################
@@ -79,6 +79,18 @@ module Xolo
79
79
  # The attribute keys we maintain, and their definitions
80
80
  KEYS = {
81
81
 
82
+ # @!attribute test_server
83
+ # @return [Boolean] Is this a development/testing server? If so, Objects in Jamf will have
84
+ # the prefix 'Xolo-Test-' rather than just 'Xolo-' to avoid confusion and collision with production objects,
85
+ # and some other things may be different as well.
86
+ test_server: {
87
+ type: :boolean,
88
+ desc: <<~ENDDESC
89
+ Is this a development/testing server? If so, Objects in Jamf will have the prefix 'xolotest-' rather than just 'xolo-'
90
+ to avoid confusion and collision with production objects, and some other things may be different as well.
91
+ ENDDESC
92
+ },
93
+
82
94
  # @!attribute ssl_cert
83
95
  # @return [String] A command, path, or value for the SSL Cert.
84
96
  ssl_cert: {
@@ -89,7 +101,8 @@ module Xolo
89
101
  desc: <<~ENDDESC
90
102
  The SSL Certificate for the https server in .pem format. When the server starts, it will be read from here, and securely stored in #{SSL_CERT_FILE}.
91
103
 
92
- If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time. The command must return the certificate to standard output. This is useful when using a secret-storage system to manage secrets.
104
+ If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time.
105
+ The command must return the certificate to standard output. This is useful when using a secret-storage system to manage secrets.
93
106
 
94
107
  If the value is a path to a readable file, the file's contents are used.
95
108
 
@@ -109,7 +122,8 @@ module Xolo
109
122
  desc: <<~ENDDESC
110
123
  The private key for the SSL Certificate in .pem format. When the server starts, it will be read from here, and securely stored in #{SSL_KEY_FILE}/
111
124
 
112
- If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time. The command must return the certificate to standard output. This is useful when using a secret-storage system to manage secrets.
125
+ If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time.
126
+ The command must return the certificate to standard output. This is useful when using a secret-storage system to manage secrets.
113
127
 
114
128
  If the value is a path to a readable file, the file's contents are used.
115
129
 
@@ -135,7 +149,8 @@ module Xolo
135
149
  required: true,
136
150
  type: :string,
137
151
  desc: <<~ENDDESC
138
- The name of a Jamf account-group (not a User group) that allows the use of 'xadm' to create and maintain titles and versions. Users of xadm must be in this group, and provide their valid Jamf credentials.
152
+ The name of a Jamf account-group (not a User group) that allows the use of 'xadm' to create and maintain titles and versions.
153
+ Users of xadm must be in this group, and provide their valid Jamf credentials.
139
154
  ENDDESC
140
155
  },
141
156
 
@@ -144,7 +159,9 @@ module Xolo
144
159
  server_admin_jamf_group: {
145
160
  type: :string,
146
161
  desc: <<~ENDDESC
147
- The name of a Jamf account-group (not a User group) that allows the use of the server admin commands of 'xadm', including --run-server-cleanup, --update-client-data, --rotate-server-logs and --set-server-log-level.
162
+ The name of a Jamf account-group (not a User group) that allows the use of the server admin commands of 'xadm', including --run-server-cleanup,
163
+ --update-client-data, --rotate-server-logs and --set-server-log-level.
164
+
148
165
  Members of this group can also use the xadm commands that require the 'admin_jamf_group' group.
149
166
  If unset, no one can use the server admin commands.
150
167
  ENDDESC
@@ -156,7 +173,8 @@ module Xolo
156
173
  default: Xolo::Server::Log::DFT_LOG_DAYS_TO_KEEP,
157
174
  type: :integer,
158
175
  desc: <<~ENDDESC
159
- The server log is rotated daily. How many days of log files should be kept? All logs are kept in #{Xolo::Server::LOG_DIR}. The current file is named '#{Xolo::Server::LOG_FILE_NAME}', older files are appended with '.0' for yesterday, '.1' for the previous day, etc.
176
+ The server log is rotated daily. How many days of log files should be kept? All logs are kept in #{Xolo::Server::LOG_DIR}.
177
+ The current file is named '#{Xolo::Server::LOG_FILE_NAME}', older files are appended with '.0' for yesterday, '.1' for the previous day, etc.
160
178
  ENDDESC
161
179
  },
162
180
 
@@ -166,7 +184,9 @@ module Xolo
166
184
  default: Xolo::Server::Log::DFT_LOG_COMPRESS_AFTER_DAYS,
167
185
  type: :integer,
168
186
  desc: <<~ENDDESC
169
- Once a log file is rotated, how many days before it is compressed? Compressed logs are named '#{Xolo::Server::LOG_FILE_NAME}.XX.bz2'. It can be accessed using the various bzip2 tools (bzip2, bunzip2, bzcat, bzgrep, etc). If this number is negative, or larger than log_days_to_keep, no logs will be compressed, if it is zero, all older logs will be compressed.
187
+ Once a log file is rotated, how many days before it is compressed? Compressed logs are named '#{Xolo::Server::LOG_FILE_NAME}.XX.bz2'.
188
+ It can be accessed using the various bzip2 tools (bzip2, bunzip2, bzcat, bzgrep, etc). If this number is negative, or larger than log_days_to_keep,
189
+ no logs will be compressed, if it is zero, all older logs will be compressed.
170
190
  ENDDESC
171
191
  },
172
192
 
@@ -177,18 +197,23 @@ module Xolo
177
197
  alert_tool: {
178
198
  type: :string,
179
199
  desc: <<~ENDDESC
180
- Server errors or other events that happen as part of xadm actions are reported to the xadm user. But sometimes such events happen outside of the scope of a xadm session. While these events will be logged, you might want them reported to a server administrator in real time.
200
+ Server errors or other events that happen as part of xadm actions are reported to the xadm user. But sometimes such events happen outside of the scope
201
+ of a xadm session. While these events will be logged, you might want them reported to a server administrator in real time.
181
202
 
182
203
  This value is either:
183
204
 
184
- - a command (path to executable plus CLI args) on the Xolo server which will accept an error or other alert message on standard input and send it somewhere where it'll be seen by an appropriate audiance, be that an email address, a Slack channel - anything you'd like.
205
+ - a command (path to executable plus CLI args) on the Xolo server which will accept an error or other alert message on standard input and send it
206
+ somewhere where it'll be seen by an appropriate audiance, be that an email address, a Slack channel - anything you'd like.
185
207
 
186
- - or a string "email:email_address" where email_address is the email address to send alerts to. In this case, the server will send an email to that address using the smtp_server and email_from configuration values.
208
+ - or a string "#{Xolo::Server::Helpers::Notification::ALERT_TOOL_EMAIL_PREFIX}email_address" where email_address is the email address to send alerts to.
209
+ In this case, the server will send an email to that address using the smtp_server and email_from configuration values.
187
210
 
188
211
  Fictional command example:
189
212
  /path/to/slackerator --sender xolo-server --channel xolo-alerts --icon dante
190
213
  Fictional email example:
191
- email:xolo-server-admins@myschool.edu
214
+ #{Xolo::Server::Helpers::Notification::ALERT_TOOL_EMAIL_PREFIX}xolo-server-admins@myschool.edu
215
+
216
+ While not required, it is strongly recommended to use this, so that server admins are made aware of issues that need their attention.
192
217
  ENDDESC
193
218
  },
194
219
 
@@ -220,7 +245,8 @@ module Xolo
220
245
  desc: <<~ENDDESC
221
246
  The password to unlock the keychain used for package signing.
222
247
 
223
- If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time. The command must return the certificate to standard output. This is useful when using a secret-storage system to manage secrets.
248
+ If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time.
249
+ The command must return the certificate to standard output. This is useful when using a secret-storage system to manage secrets.
224
250
 
225
251
  If the value is a path to a readable file, the file's contents are used.
226
252
 
@@ -235,16 +261,36 @@ module Xolo
235
261
  sign_pkgs: {
236
262
  type: :boolean,
237
263
  desc: <<~ENDDESC
238
- When someone uses xadm to upload a .pkg, and it isn't signed, should the server sign it before uploading to Jamf's Distribution Point(s)?
264
+ When a .pkg, is received for a version and it isn't signed, should the server sign it before uploading to Jamf's Distribution Point(s)?
239
265
 
240
266
  If you set this to true, it will use the same keychain and identity as the 'pkg_signing_identity' config value to sign the pkg, using the keychain you installed at:
241
267
  /Library/Application Support/xoloserver/xolo-pkg-signing.keychain-db
242
268
 
269
+ Packages must be signed distribution packages to work with the `xadm deploy` command, which uses MDM to push the package to client machines.
270
+
243
271
  NOTE: While it may seem insecure to allow the server to sign pkgs, consider:
244
272
  - Users of xadm are authenticated and authorized to use the server (see 'admin_jamf_group')
245
273
  - You don't need to distribute your signing certificates to a wide group of individual developers.
246
274
  - While you need to trust your xadm users not to upload a malicious pkg, this would be true
247
275
  even if you deployed the certs to them, so keeping the certs on the server is more secure.
276
+
277
+ See also the 'sign_autopkg_pkgs' abd 'create_distribution_pkgs' config values.
278
+ ENDDESC
279
+ },
280
+
281
+ # @!attribute create_distribution_pkgs
282
+ # @return [Boolean] When processing uploaded pkgs, should we wrap component pkgs in distribution pkgs?
283
+ create_distribution_pkgs: {
284
+ type: :boolean,
285
+ desc: <<~ENDDESC
286
+ When a .pkg, is received for a version and it is a component package, should the server wrap it in a distribution package before uploading to Jamf's Distribution Point(s)?
287
+
288
+ If you set this to true, component packages will be wrapped in a distribution package using the productbuild tool, like so:
289
+ /usr/bin/productbuild –package /path/to/recieved.pkg /path/to/distribution_package.pkg
290
+
291
+ If sign_pkgs is true, the distribution package will be signed, otherwise it will be unsigned.
292
+
293
+ Packages must be signed distribution packages to work with the `xadm deploy` command, which uses MDM to push the package to client machines.
248
294
  ENDDESC
249
295
  },
250
296
 
@@ -256,7 +302,8 @@ module Xolo
256
302
  desc: <<~ENDDESC
257
303
  The name of a Jamf account-group (not a User group) whose members may set a title's release_groups to 'all'.
258
304
 
259
- When this is set, and someone not in this group tries to set a title's release_groups to 'all', they will get a message telling them to contact the person or group named in 'release_to_all_contact' to get approval.
305
+ When this is set, and someone not in this group tries to set a title's release_groups to 'all', they will get a message telling
306
+ them to contact the person or group named in 'release_to_all_contact' to get approval.
260
307
 
261
308
  To approve the request, one of the members of this group must run 'xadm edit-title <title> --release-groups all'.
262
309
 
@@ -270,7 +317,8 @@ module Xolo
270
317
  required: false,
271
318
  type: :string,
272
319
  desc: <<~ENDDESC
273
- When release_to_all_jamf_group is set, and someone not in that group tries to set a title's release_groups to 'all', they are told to use this contact info to get approval.
320
+ When release_to_all_jamf_group is set, and someone not in that group tries to set a title's release_groups to 'all', they are
321
+ old to use this contact info to get approval.
274
322
 
275
323
  This string could be an email address, a chat channel, a phone number, etc.
276
324
 
@@ -305,11 +353,13 @@ module Xolo
305
353
  default: Xolo::Server::Helpers::Maintenance::DFT_DEPRECATED_LIFETIME_DAYS,
306
354
  type: :integer,
307
355
  desc: <<~ENDDESC
308
- When a version is deprecated, it will be automatically deleted by the nightly cleanup this many days later. If set to 0 or less, deprecated versions will never be deleted.
356
+ When a version is deprecated, it will be automatically deleted by the nightly cleanup this many days later. If set to 0 or less,
357
+ deprecated versions will never be deleted.
309
358
 
310
359
  Deprecated versions are those that have been released, but a newer version has been released since then.
311
360
 
312
- WARNING: If you set this to 0 or less, you will need to manually delete deprecated versions. Keeping them around can cause confusion and clutter in the GUI, and use up disk space.
361
+ WARNING: If you set this to 0 or less, you will need to manually delete deprecated versions. Keeping them around can cause confusion
362
+ and clutter in the GUI, and use up disk space.
313
363
  ENDDESC
314
364
  },
315
365
 
@@ -323,7 +373,8 @@ module Xolo
323
373
 
324
374
  Skipped versions are those that were never released, but a newer version has been released.
325
375
 
326
- WARNING: If you set this to true, you will need to manually delete skipped versions. Keeping them around can cause confusion and clutter in the GUI, and use up disk space.
376
+ WARNING: If you set this to true, you will need to manually delete skipped versions. Keeping them around can cause confusion
377
+ and clutter in the GUI, and use up disk space.
327
378
  ENDDESC
328
379
  },
329
380
 
@@ -336,9 +387,11 @@ module Xolo
336
387
  default: Xolo::Server::Helpers::Maintenance::DFT_UNRELEASED_PILOTS_NOTIFICATION_DAYS,
337
388
  type: :integer,
338
389
  desc: <<~ENDDESC
339
- If the newest pilot of a title has not been released in this many days, notify someone about it monthly, asking to release it or delete it. If set to 0 or less, these notifications are disabled.
390
+ If the newest pilot of a title has not been released in this many days, notify someone about it monthly, asking to release it or delete it.
391
+ If set to 0 or less, these notifications are disabled.
340
392
 
341
- Notifications are sent on the first of the month via email to the title's contact email address, and the alert_tool (if defined). Default is 180 days (about 6 months).
393
+ Notifications are sent on the first of the month via email to the title's contact email address, and the alert_tool (if defined).
394
+ Default is 180 days (about 6 months).
342
395
 
343
396
  Pilot versions are those that have been added for testing, but not yet released.
344
397
 
@@ -353,7 +406,8 @@ module Xolo
353
406
  smtp_server: {
354
407
  type: :string,
355
408
  desc: <<~ENDDESC
356
- The hostname of the SMTP server to use for sending email. This is a server that can recieve email for your organizaion from the xolo server. Used for sending alerts and notifications. If not set, no email notifications will be sent.
409
+ The hostname of the SMTP server to use for sending email. This is a server that can recieve email for your organizaion from the xolo server.
410
+ Used for sending alerts and notifications. If not set, no email notifications will be sent.
357
411
  ENDDESC
358
412
  },
359
413
 
@@ -364,7 +418,8 @@ module Xolo
364
418
  desc: <<~ENDDESC
365
419
  The email address to use as the 'from' address for emails sent by xolo. This should be a valid email address that can recieve replies.
366
420
 
367
- Will default to '#{Xolo::Server::Helpers::Notification::DFT_EMAIL_FROM}@<hostname>' if not set. The human-readable part of the address will be 'Xolo Server on <hostname>'.
421
+ Will default to '#{Xolo::Server::Helpers::Notification::DFT_EMAIL_FROM}@<hostname>' if not set. The human-readable part of the address
422
+ will be 'Xolo Server on <hostname>'.
368
423
  ENDDESC
369
424
  },
370
425
 
@@ -395,7 +450,6 @@ module Xolo
395
450
  # @!attribute jamf_gui_hostname
396
451
  # @return [String] The hostname of the Jamf Pro server used for links to the GUI webapp
397
452
  jamf_gui_hostname: {
398
- required: true,
399
453
  type: :string,
400
454
  desc: <<~ENDDESC
401
455
  The hostname of the Jamf Pro server used for links to the GUI webapp, if different from the jamf_hostname.
@@ -460,11 +514,17 @@ module Xolo
460
514
  # @return [String] The username to use when connecting to the Jamf Pro API
461
515
  jamf_api_user: {
462
516
  required: true,
517
+ load_method: :data_from_command_file_or_string,
463
518
  type: :string,
464
519
  desc: <<~ENDDESC
465
520
  The username of the Jamf account for connecting to the Jamf Pro APIs.
466
- TODO: Document the permissions needed by this account.
467
- TODO: Allow using api-clients
521
+
522
+ If you start this value with a vertical bar '|', everything after the bar is a shell command to be executed by the server at start-time.
523
+ The command must return the password to standard output. This is useful when using a secret-storage system to manage secrets.
524
+
525
+ If the value is a path to a readable file, the file's contents are used.
526
+
527
+ Otherwise the value is used as the password.
468
528
  ENDDESC
469
529
  },
470
530
 
@@ -478,7 +538,8 @@ module Xolo
478
538
  desc: <<~ENDDESC
479
539
  The password for the username that connects to the Jamf Pro APIs.
480
540
 
481
- If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time. The command must return the certificate to standard output. This is useful when using a secret-storage system to manage secrets.
541
+ If you start this value with a vertical bar '|', everything after the bar is a shell command to be executed by the server at start-time.
542
+ The command must return the password to standard output. This is useful when using a secret-storage system to manage secrets.
482
543
 
483
544
  If the value is a path to a readable file, the file's contents are used.
484
545
 
@@ -488,14 +549,28 @@ module Xolo
488
549
  ENDDESC
489
550
  },
490
551
 
552
+ # @!attribute jamf_api_client
553
+ # @return [Boolean] The provided jamf_api_user is an API client, not a normal user, and the
554
+ # jamf_api_pw is the API client's secret.
555
+ jamf_use_api_client: {
556
+ type: :boolean,
557
+ desc: <<~ENDDESC
558
+ If true, the provided jamf_api_user is an API client's "client_id", not a normal username, and the jamf_api_pw is the API client's 'client_secret'.
559
+
560
+ If false, the jamf_api_user is a normal user, and the jamf_api_pw is that user's password.
561
+ ENDDESC
562
+ },
563
+
491
564
  # @!attribute jamf_auto_accept_xolo_eas
492
- # @return [Boolean] should we auto-accept the Jamf patch title eas?
565
+ # @return [Boolean] should we auto-accept the Jamf patch title eas for managed titles?
493
566
  jamf_auto_accept_xolo_eas: {
494
567
  type: :boolean,
495
568
  desc: <<~ENDDESC
496
- For titles fully maintained by Xolo, should we auto-accept the Patch Title Extension Attributes that come from the uploaded version_script from xadm?
569
+ For titles managed by Xolo, should we auto-accept the Patch Title Extension Attributes that come from the uploaded version_script from xadm?
497
570
 
498
571
  Default is false, meaning all Title EAs must be manually accepted in the Jamf Pro Web UI.
572
+
573
+ EAs must always be manually accepted for subscribed titles, since the code the run is not under the control of Xolo.
499
574
  ENDDESC
500
575
  },
501
576
 
@@ -510,28 +585,28 @@ module Xolo
510
585
  desc: <<~ENDDESC
511
586
  After a .pkg is uploaded to the Xolo server by someone using xadm, it must then be uploaded to the Jamf distribution point(s) to be available for installation.
512
587
 
513
- If this value is 'api', and you are using Jamf Pro 11.6 or later, Xolo will use the Jamf API to upload the package to your primary distribution point. API uploads are only available in Jamf Pro 11.6 and later, and will only upload to the primary distribution point. Syncing to other distribution points is not supported by the API.
588
+ If your principal distribution point is a Cloud Distribution Point and you're using Jamf Pro 11.6 or later, you can set this value to 'api', and xoloserver will use the Jamf Pro API to upload the pkg to the Cloud Distribution Point. This is the simplest option, and is recommended if it works for your environment.
514
589
 
515
- If this value is a path, it is to an executable on the xolo server that will do the upload to the distribution point(s). This tool can be anything you like, as long as it can upload a .pkg to the Jamf distribution point(s) you use.
590
+ If your principal is not a Cloud Distribution Point, xoloserver can use an custom external tool to do the upload.
591
+
592
+ To do so, set this value to a path to an executable on the xolo server machine that will do the upload to the distribution point(s). This tool can be anything you like, as long as it can upload a .pkg to the Jamf distribution point(s) you use.
516
593
 
517
594
  It will be run with two arguments:
518
- - First, The display name of the Jamf Package object the .pkg is used with
595
+ - First, The display name of the Jamf Package record the .pkg is used with
519
596
  - Then the path to the .pkg file on the Xolo server, which will be uploaded
520
597
  to the Jamf distribution point(s).
521
598
 
522
- So if the executable is '/usr/local/bin/jamf-pkg-uploader' then when Xolo recieves a .pkg to be uploaded to Jamf, it will run something like:
599
+ So if the executable is '/usr/local/bin/jamf-pkg-uploader' then when xoloserver recieves a .pkg to be uploaded to Jamf, it will run something like:
523
600
 
524
601
  /usr/local/bin/jamf-pkg-uploader 'CoolApp' '/Library/Application Support/xoloserver/tmpfiles/CoolApp.pkg'
525
602
 
526
603
  Where 'CoolApp' is the name of the Jamf Package object that will use this .pkg, and '/Library/Application Support/xoloserver/tmpfiles/CoolApp.pkg' is the location where it was stored on the Xolo server when xadm uploaded it.
527
604
 
528
- The upload tool can itself run other tools as needed, e.g. one to upload
529
- to all fileshare distribution points, and another to upload to a Cloud dist. point.
530
- or it can do all the things itself.
605
+ The upload tool can itself run other tools as needed, e.g. one to upload to all fileshare distribution points, and another to upload to a Cloud dist. point, or it can do all the things itself.
531
606
 
532
- After that tool runs, the copy of the .pkg on the server ( '/Library/Application Support/xoloserver/tmpfiles/CoolApp.pkg' in the example above) will be deleted.
607
+ After that tool runs, the copy of the .pkg on the xolo server ('/Library/Application Support/xoloserver/tmpfiles/CoolApp.pkg' in the example above) will be deleted.
533
608
 
534
- An external tool is used here because every Jamf Pro customer has different needs for this, e.g. various cloud and file-server distribution points. While the packages/upload endpoint of the Jamf Pro API (v11.6+) will upload to the primary distribution point, it won't upload to all the others you might have.
609
+ An external tool is used here because every Jamf Pro customer has different needs for this depending on their environment of distribution points.
535
610
  ENDDESC
536
611
  },
537
612
 
@@ -541,7 +616,8 @@ module Xolo
541
616
  forced_exclusion: {
542
617
  type: :string,
543
618
  desc: <<~ENDDESC
544
- If you have any jamf computers who should never even know that xolo exists, and should never have any software installed via xolo, put them into a group and put that group's name here.
619
+ If you have any jamf computers who should never even know that xolo exists, and should never have any software installed via xolo, put them
620
+ into a group and put that group's name here.
545
621
 
546
622
  An example would be a group of machines that should have a very minimalist management footprint, only enforcing basic security settings and nothing else.
547
623
 
@@ -616,7 +692,8 @@ module Xolo
616
692
  desc: <<~ENDDESC
617
693
  The password for the username that connects to the Title Editor API.
618
694
 
619
- If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time. The command must return the certificate to standard output. This is useful when using a secret-storage system to manage secrets.
695
+ If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time.
696
+ The command must return the certificate to standard output. This is useful when using a secret-storage system to manage secrets.
620
697
 
621
698
  If the value is a path to a readable file, the file's contents are used.
622
699
 
@@ -624,6 +701,134 @@ module Xolo
624
701
 
625
702
  Be careful of security concerns when passwords are stored in files.
626
703
  ENDDESC
704
+ },
705
+
706
+ # @!attribute subscription_webhook_token
707
+ # @return [String] A command, path, or value for the authentication token used by Jamf Pro to send
708
+ # PatchSoftwareTitleUpdated webhook events for subscribed titles.
709
+ subscription_webhook_token: {
710
+ load_method: :data_from_command_file_or_string,
711
+ private: true,
712
+ type: :string,
713
+ desc: <<~ENDDESC
714
+ The authentication token used by Jamf Pro to send PatchSoftwareTitleUpdated webhook events for subscribed titles.
715
+
716
+ This value can be string, but should be treated like a password, and should be changed both here and on the Jamf Pro server if it is ever suspected of being compromised.
717
+
718
+ A good way to generate a random token is to use the following command in Terminal:
719
+
720
+ openssl rand -hex 16
721
+
722
+ When configuring the webhook in Jamf Pro, use "Header Authentication" to send this JSON to use as the header:
723
+
724
+ {"Authorization":"Bearer <token>"}
725
+
726
+ Replacing <token> with the value set here.
727
+
728
+ If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time.
729
+ The command must return the password to standard output. This is useful when using a secret-storage system to manage secrets.
730
+
731
+ If the value is a path to a readable file, the file's contents are used.
732
+
733
+ Otherwise the value is used as the token.
734
+
735
+ Be careful of security concerns when secrets are stored in files.
736
+ ENDDESC
737
+ },
738
+
739
+ # @!attribute subscription_updated_alert_tool
740
+ # @return [String] A cli tool or an email address to notify when a subscription updated webhook event
741
+ # is received, but the title is not configured to use autopkg to fetch the new version.
742
+ # If unset, the default alert mechanism is used. See 'alert_tool' for details.
743
+ subscription_updated_alert_tool: {
744
+ type: :string,
745
+ desc: <<~ENDDESC
746
+ If a title is a subscription from a Patch Source, but is not configured to use autopkg to fetch new versions, when a PatchSoftwareTitleUpdated webhook
747
+ event is received from Jamf Pro, an alert should be sent to notify someone to add the new version manually.
748
+
749
+ Leave this unset to use the default alert mechanism defined by the 'alert_tool' configuration value.
750
+
751
+ Otherwise, the value is interpreted the same as for 'alert_tool', either a command to run, or an email address prefixed by "#{Xolo::Server::Helpers::Notification::ALERT_TOOL_EMAIL_PREFIX}".
752
+ ENDDESC
753
+ },
754
+
755
+ # @!attribute autopkg_executable
756
+ # @return [String] The path to the autopkg executable. If unset, titles cannot use autopkg to acquire
757
+ # new .pkgs
758
+ autopkg_executable: {
759
+ type: :string,
760
+ desc: <<~ENDDESC
761
+ The path to the autopkg executable on the server host. If unset, titles cannot use autopkg to acquire
762
+ new .pkgs.
763
+
764
+ AutoPkg must be installed, configured, and maintained on the xoloserver host separately from the xoloserver itself.
765
+
766
+ NOTE: AutoPkg recipes are always run with '-k FAIL_RECIPES_WITHOUT_TRUST_INFO=yes', and will fail if you have not 'trusted'
767
+ them by making an override. See the AutoPkg docs for details.
768
+ ENDDESC
769
+ },
770
+
771
+ # @!attribute autopkg_user
772
+ # @return [String] The name of a local, non-root user that will run actually the autopkg executable as needed.
773
+ autopkg_user: {
774
+ type: :string,
775
+ desc: <<~ENDDESC
776
+ The name of a local, non-root user that will run the autopkg executable as needed. AutoPkg should never be run as root.
777
+
778
+ If unset, titles cannot use autopkg to acquire new .pkgs.
779
+
780
+ AutoPkg must be installed, configured, and maintained on the xoloserver host separately from the xoloserver itself.
781
+
782
+ NOTE: AutoPkg recipes are always run with '-k FAIL_RECIPES_WITHOUT_TRUST_INFO=yes', and will fail if you have not 'trusted'
783
+ them by making an override. See the AutoPkg docs for details.
784
+ ENDDESC
785
+ },
786
+
787
+ # @!attribute autopkg_user_keychain_pw
788
+ # @return [String] A command, path, or value for the password to unlock the autopkg_user's login keychain.
789
+ autopkg_user_keychain_pw: {
790
+ required: true,
791
+ load_method: :data_from_command_file_or_string,
792
+ private: true,
793
+ type: :string,
794
+ desc: <<~ENDDESC
795
+ The password for the login keychain of the autopkg_user.
796
+
797
+ Some autopkg recipes may attempt to sign apps or packages, which requires access to the autopkg_user's login keychain.
798
+
799
+ Since the user is not expected to be logged into the macOS GUI on the server (and even if it were, the xoloserver process doesn't have
800
+ access to the GUI context) the keychain will be locked, and must be unlocked by the server before running autopkg recipes that need it.
801
+
802
+ This config value is used to unlock that keychain when running autopkg recipes. The keychain used is the one located at
803
+ /Users/<autopkg_user>/Library/Keychains/login.keychain-db. Be sure the desired signing identities are in that keychain, and that the
804
+ keychain is set to allow various executables to access them without prompting, including the autopkg itself, codesign, productsign,
805
+ pkgbuild, and productbuild.
806
+
807
+ If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time.
808
+ The command must return the password to standard output. This is useful when using a secret-storage system to manage secrets.
809
+
810
+ If the value is a path to a readable file, the file's contents are used.
811
+
812
+ Otherwise the value is used as the password.
813
+
814
+ Be careful of security concerns when passwords are stored in files.
815
+ ENDDESC
816
+ },
817
+
818
+ # @!attribute sign_autopkg_pkgs
819
+ # @return [Boolean] Should the server sign any unsigned pkgs acquired via autopkg?
820
+ sign_autopkg_pkgs: {
821
+ type: :boolean,
822
+ desc: <<~ENDDESC
823
+ This is just like sign_pkgs, but only applies to pkgs acquired via autopkg.
824
+
825
+ Be sure you trust your autopkg recipes to only download safe pkgs if you enable this.
826
+
827
+ Packages must be signed distribution packages to work with the `xadm deploy` command, which uses MDM to push the package to client machines.
828
+
829
+ NOTE: AutoPkg recipes are always run with '-k FAIL_RECIPES_WITHOUT_TRUST_INFO=yes', and will fail if you have not 'trusted'
830
+ them by making an override. See the AutoPkg docs for details.
831
+ ENDDESC
627
832
  }
628
833
 
629
834
  }.freeze
@@ -63,6 +63,16 @@ module Xolo
63
63
  # full object name if appropriate (e.g. Package objects)
64
64
  JAMF_OBJECT_NAME_PFX = 'xolo-'
65
65
 
66
+ # Jamf objects from a test server are named with this prefix followed by <title>-<version>
67
+ # See also: Xolo::Server::Configuration
68
+ JAMF_TEST_OBJECT_NAME_PFX = 'xolotest-'
69
+
70
+ # when processing things via Jamf webhooks, this is the session[:admin]
71
+ WEBHOOK_HANDLER_ADMIN_USERNAME = 'xolo-webhook-handler'
72
+
73
+ # TODO: remove this stuff when ruby-jss supports Patch Titles via JPAPI
74
+ JPAPI_PATCH_TITLE_ENDPOINT = 'v2/patch-software-title-configurations'
75
+
66
76
  end # module Constants
67
77
 
68
78
  end # Server
@@ -53,6 +53,9 @@ module Xolo
53
53
  '/maint/shutdown-server'
54
54
  ].freeze
55
55
 
56
+ # the route used by Jamf Webhooks to notify Xolo of Patch Title updates
57
+ PATCH_TITLE_UPDATED_WEBHOOK_ROUTE = '/subscribed-title-updates'
58
+
56
59
  # The loopback address for IPV4, aka 'localhost'
57
60
  IPV4_LOOPBACK = '127.0.0.1'
58
61
 
@@ -67,7 +70,7 @@ module Xolo
67
70
  end
68
71
 
69
72
  # If a request comes in from one of our known IP addresses
70
- # with a valid internal_auth_toke in the headers, then the request is allowed.
73
+ # with a valid internal_auth_token in the headers, then the request is allowed.
71
74
  #
72
75
  # This allows the xolo server to send requests to itself without needing
73
76
  # to authenticate, as is needed for some kinds of maintenance tasks
@@ -83,6 +86,12 @@ module Xolo
83
86
  @internal_auth_token_header ||= "Bearer #{SecureRandom.hex(64)}"
84
87
  end
85
88
 
89
+ # @return [String] The auth token to be used in the Authorization header of webhook event requests
90
+ #####################
91
+ def self.jamf_webhook_auth_token_header
92
+ @jamf_webhook_auth_token_header ||= "Bearer #{Xolo::Server.config.subscription_webhook_token}"
93
+ end
94
+
86
95
  # Instance methods
87
96
  #####################
88
97
  #####################
@@ -128,6 +137,12 @@ module Xolo
128
137
  Socket.ip_address_list.map(&:ip_address)
129
138
  end
130
139
 
140
+ # @return [Boolean] Is the jamf webhook auth token in the headers of the request?
141
+ #####################
142
+ def jamf_webhook_auth_token_ok?
143
+ request.env['HTTP_AUTHORIZATION'] == Xolo::Server::Helpers::Auth.jamf_webhook_auth_token_header
144
+ end
145
+
131
146
  # is the given username a member of the admin_jamf_group?
132
147
  # or the server_admin_jamf_group?
133
148
  # If not, they are not allowed to talk to the xolo server.
@@ -212,6 +227,7 @@ module Xolo
212
227
  log_debug "Checking if '#{username}' is a member of the Jamf AccountGroup '#{groupname}'"
213
228
 
214
229
  # This isn't well implemented in ruby-jss, so use c_get directly
230
+ # TODO: use JP API when ruby-jss supports it
215
231
  jgroup = jamf_cnx.c_get("accounts/groupname/#{groupname}")[:group]
216
232
 
217
233
  if jgroup[:ldap_server]
@@ -223,7 +239,8 @@ module Xolo
223
239
  false
224
240
  end
225
241
 
226
- # Try to authenticate the jamf user trying to log in to xolo
242
+ # Try to authenticate the jamf user trying to log in to xolo.
243
+ # This must be a regular Jamf user acct, not an API client.
227
244
  #
228
245
  # @param admin [String] The jamf acct name of the person seeking access
229
246
  #