-
Notifications
You must be signed in to change notification settings - Fork 353
Middleware
Munki supports middleware modules that can alter an HTTP(S) request to work with servers (often cloud-based) that require specific headers, keys, or encrypted/signed requests.
Middleware for Munki 7 must be implemented as a loadable dylib.
Several popular middleware modules have been ported to Swift using Munki 7's middleware protocol, and are believed to work:
- https://github.com/munki/DemoMiddleware (this one does nothing useful other than to demonstrate how a middleware module has access to the request details)
- https://github.com/munki/AzureStorageMiddleware
- https://github.com/munki/BunnyNetMiddleware
- https://github.com/munki/CloudFrontMiddleware
- https://github.com/munki/GCSMiddleware
- https://github.com/munki/S3Middleware
Though these ported middleware modules are believed to work, they are not officially supported at this time. My preference would be for others who actually use the modules to take over development, support, and maintenance.
Middleware modules must be installed in a /usr/local/munki/middleware directory. Only a single module will be loaded, so only a single middleware module should be installed in that directory.
This optional feature, introduced in Munki 2.7, allows a Munki admin to use third party code, or create their own code to manipulate Munki's HTTP requests. The primary use case for this feature is to provide interaction with APIs, although it is not limited to that. Middleware is activated by the presence of a Python file. If it's there it gets called, if it's not it won't, it's as simple as that.
Middleware gets imported at runtime as a Python module so the middleware file must be written in Python. Nothing is stopping you from then calling another executable from Python, so in that sense, you're not limited to Python. At the beginning of the Munki run, Munki searches for "middleware*.py", it then tries to import it then looks for the function, "process_request_options". The options are then sent through, examined, changed, or not, before heading to the server.
You may only use one middleware file. If you have more then one of middleware file you will have unpredictable results.
For the middleware to work, you will need the following:
Munki is looking for files the start with "middleware" and end with ".py". Examples of good and bad middleware filenames:
- middleware.py👍
-
middleware👎 -
my_middleware.py👎 - middleware_cloudfoo.py👍
The middleware file must live in the root of the munkitools folder (/usr/local/munki).
Root must be the owner of the file and be able to read it. Since its imported by python, the executable part isn't necessary. It's important to note that if you plan on storing sensitive information inside you should restrict access.
sudo chown root /usr/local/munki/middleware*.py
sudo chmod 600 /usr/local/munki/middleware*.pyThis is the function that Munki is looking for. Think of it as the "main" fuction for the middleware. If Munki doesn't find this function it will abandon the middleware, and continue on as if it wasn't there.
Example function
def process_request_options(options):
print('***Requesting: %s' % options.get('url'))
return optionsThis is a basic example: Munki sends the request options, we print the requested url and then return the options unchanged.
process_request_options has a single input parameter: the options dictionary (described in detail below). It must return the options dictionary (possibly modified).
Query Params Example
from urllib import urlencode
QUERY_PARAMS = {"username": "apiuser", "password": "secret password"}
def encode_params(data):
"""Encode parameters in a piece of data.
Will successfully encode parameters when passed as a dict or a list of
2-tuples. Order is retained if data is a list of 2-tuples but arbitrary
if parameters are supplied as a dict.
"""
result = []
for k, vs in QUERY_PARAMS.items():
if isinstance(vs, basestring) or not hasattr(vs, "__iter__"):
vs = [vs]
for v in vs:
if v is not None:
result.append(
(
k.encode("utf-8") if isinstance(k, str) else k,
v.encode("utf-8") if isinstance(v, str) else v,
)
)
return urlencode(result, doseq=True)
def process_request_options(options):
url = options.get("url")
if "swscan.apple.com" not in url:
print("URL Before: %s" % options.get("url"))
# concat url and query string
options["url"] = options["url"] + "?" + encode_params(QUERY_PARAMS)
print("URL After: %s" % options.get("url"))
return optionsIn the example above we are joining the URL from Munki and query string together.
http://munki.example.com/catalogs/production
http://munki.example.com/catalogs/production?username=apiuser&password=secret+password
Headers Example
HEADERS = {"api_key": "123_IAM_A_KEY", "api_secret": "SECRETKEY"}
def process_request_options(options):
url = options.get("url")
if "swscan.apple.com" not in url:
print("Headers before: %s" % options["additional_headers"])
# Merge headers with Munki
options["additional_headers"].update(HEADERS)
print("Headers after: %s" % options["additional_headers"])
return optionsSince the 'additional_headers' key is a dictionary it is easy for us to update retaining the User-Agent and Authorization headers.
{u'Authorization': u'Basic bXVua2k6WUhuUXhWWFFh==', 'User-Agent': u'managedsoftwareupdate/2.6.1 Darwin/15.4.0'}
{'api_secret': 'SECRETKEY', 'api_key': '123_IAM_A_KEY', u'Authorization': u'Basic bXVua2k6WUhuUXhWWFFh==', 'User-Agent': u'managedsoftwareupdate/2.6.1 Darwin/15.4.0'}
These are all the options that can be changed with the middleware. Most of the time you'll only be interested in the url and the additional_headers
| Key | Type | Description | Default Value |
|---|---|---|---|
| url | String | The 'url' you are requesting. | |
| additional_headers | Dictionary | These are the HTTP headers that are going in the get request. List of HTTP header fields | {'User-Agent': u'managedsoftwareupdate/%MUNKI_VER% Darwin/%OS_VER%'} |
| follow_redirects | String | Explained here | None |
| resume | Boolean | If True, Gurl will attempt to resume an interrupted download. **You should probably leave this alone.
|
False |
| download_only_if_changed | Boolean | If destinationpath already exists, you can set 'onlyifnewer' to true to indicate you only want to download the file only if it's newer on the server. **You should probably leave this alone. | False |
| cache_data | NSObject | **Don't touch this. | |
| logging_function | Function | The logging function in use. **You should probably leave this alone. |
Here are some middleware modules to try:
| Provider | URL |
|---|---|
| Amazon CloudFront | https://github.com/AaronBurchfield/CloudFront-Middleware |
| Amazon s3 | https://github.com/waderobson/s3-auth |
| Backblaze B2 | https://github.com/sphen13/B2-Middleware |
| Google Cloud storage | https://github.com/waderobson/gcs-auth |
| Azure Blob Storage | https://github.com/okieselbach/Munki-Middleware-Azure-Storage |
| Bunny.net CDN | https://github.com/ofirgalcon/bunny.net-middleware |
- Getting Started
- Overview
- Discussion Group
- Demonstration Setup
- Glossary
- Frequently Asked Questions
- Contributing to Munki
- Release Notes
- Introduction
- Customizing Managed Software Center's sidebar
- version_script
- Removed features
- Python removal
- Installing Munki 6's Python
- PPPC/TCC for Munki 7
- Middleware for Munki 7
- Logging
- Installation package changes
- Launchd job changes
- Introduction
- Staging macOS Installers
- Apple update changes
- Default Installs (Munki 6.1)
- Conditional Application Data (Munki 6.5)
- Introduction
- Munki Links
- Product Icons
- Screenshots In Product Descriptions
- Client Customization
- Custom Help Content
- Featured Items
- Update Notifications:
- Introduction
- iconimporter
- makepkginfo
- munkiimport
- managedsoftwareupdate
- makecatalogs
- manifestutil
- repoclean
- Preferences
- Default Repo Detection
- Default Manifest Resolution
- Managed Preferences Support In Munki
- Apple Software Updates With Munki
- Additional macOS configuration for Munki:
- Pkginfo Files
- Supported Pkginfo Keys
- Pre And Postinstall Scripts
- Munki And AutoRemove
- Blocking Applications
- ChoiceChangesXML
- CopyFromDMG
- nopkg items
- How Munki Decides What Needs To Be Installed
- Default Installs
- Removal of Unused Software
- Upgrading macOS:
- Apple Updates:
- Securing the Munki repo
- Preflight And Postflight Scripts
- Report Broken Client
- MSC Logging
- Munki With Git
- Bootstrapping With Munki
- License Seat Tracking
- LaunchD Jobs and Changing When Munki Runs
- Web Request Middleware
- Repo Plugins
- Downgrading Software
- Downgrading Munki tools
- Authorized Restarts
- Allowing Untrusted Packages
- About Munki's Embedded Python
- Customizing Python for Munki
- Configuration Profile Emulation
- AutoPkg
- Repackaging
- Creating Disk Images
- Stupid Munki Tricks
- Troubleshooting
- Professional Support
- Known Issues and Workarounds
- Building Munki packages
- Munki packages and restarts
- Signing Munki
- Removing Munki
- More Links And Tools
- Munki Configuration Script
- Who's Using Munki
- Munki 3 Information
- Munki 4 Information
- macOS Monterey Info
- macOS Ventura info
- Pkginfo For Apple Software Updates
- Managing Configuration Profiles
- softwareupdate and Configuration profile notes
- Modular Imaging with Munki
- Microsoft Office
- Adobe Products
- Upgrading macOS: