Please support this project on Patreon.
PenguinDome is a minimalist MDM solution for Linux laptop and desktop computers. It strives to be simple, secure, and easily extendable. Unlike other MDM solutions which invest a lot of effort in policy enforcement, PenguinDome is focused on monitoring and reporting.
We wrote PenguinDome at Quantopian to help keep our Linux devices compliant with our IT and cybersecurity policies without investing a lot of effort in deploying yet another big, complicated enterprise security application. Most of the MDM solutions on the market don't support Linux at all, and we were reluctant to invest a lot of time and money into deploying one of the few that does.
We like to give our engineers as much leeway as possible in their technology choices, so we don't require a specific Linux distribution or "build." After discovering how challenging it was to roll out MDM to our devices running Windows and Mac OS -- which are much more heterogeneous, closed systems -- we knew that we would run into even more complications rolling out a commercial MDM solution for Linux.
We think others might benefit from our approach, so we are releasing PenguinDome as an open-source project for others to use and contribute to.
Although we are using PenguinDome in production at Quantopian to manage our Linux devices, it's still very early-stage software. For example:
-
There's no web app, no fancy user interface, no system-tray icons for clients, no integrations with third-party services. At this point it's pretty much all command-line-based. We're not against adding bells and whistles; we just haven't felt the need for them yet ourselves.
-
Deployment requires quite a bit of manual effort.
-
Although much of the code is generic and should work on most Linux distributions, the distro-specific code is heavily weighted toward Ubuntu and Arch Linux, the two distros our engineers use.
-
Several of the data-collection plugins which are enabled by default are particular to our environment rather than generally applicable.
We hope that if others decide to take up the baton and deploy PenguinDome in other environments, they will submit pull requests which make the more configurable and deployable for varied environments, as well as expanding support for additional Linux distributions.
In a nutshell:
-
scripts run on client devices to collect data and submit them to a central server;
-
the server sticks the data into a MongoDB database; and
-
a script periodically looks for client problems in the data and warns about them so that they can be addressed.
There are some additional wrinkles related to logging, automatic client updates, and secure communication between the clients and server, but the three bullet points capture PenguinDome's essential functionality.
The two strictest requirements for PenguinDome are Python 3.7 or newer on both the clients and server, and GnuPG 2.1.11+ on the clients and 2.1.15+ on the server.
Aside from that, there are of course various OS package dependencies
for the clients and server. These are defined in the
ubuntu-packages.txt
and arch-packages.txt
files in the client
and server
subdirectories of the code base. These packages are
installed on the client by client/client-setup.sh
and on the server
by server/server-setup.sh
; it should be relatively easy to teach
these scripts to know about additional OS distributions and create
package lists for these distributions using the existing package lists
as a guide.
PenguinDome also uses a bunch of Python packages, all of which are
deployed into a private virtualenv on the clients and server using
pip
at install time.
virtualenv
are created with native python3 -m venv
tool now.
Existing virtualenv
created previously with virtualenv
continue
to work as-is.
The use of netifaces
package in client plugins requires pip
to compile it, hence the requirement of ubuntu python-dev
or
equivalent on the client side.
There's a nascent attempt in the arch
subdirectory to build Pacman
client packages for Arch Linux, but these don't entirely work right
now, so for the time being, Arch clients use the same deployment
scripts as other distributions.
Both the server and client use cron
, though the aforementioned Arch
package attempts to use systemd
on the client instead. The server
process is managed as a systemd
service, though it should be easy
enough to manage it differently on distributions that don't have
systemd
.
Finally, you need a MongoDB server to hold PenguinDome's data.
Copy the whole source tree onto the machine you're going to use as the
server. Run server/server-setup.sh
as root (obviously, it would be
ideal if the server didn't need to run as root after installation, but
we haven't implemented this yet; we'll gladly accept patches!). The
setup script installs the necessary OS patches, configures the server
virtualenv, installs the necessary Python packages, asks you a bunch
of configuration questions (discussed below), and sets up the
necessary configuration files for the server and the clients.
Once that's done, run bin/client_release
as root. This generates a
numbered tar file in var/client_releases
which is then downloaded
and installed on clients as described below.
Whenever any of the files used on the clients is changed, you run
bin/client_release
again, and a new release is generated and
downloaded and installed automatically by existing clients.
The server setup script creates server/settings.yml
and
client/settings.yml
. The former is used on the server, and the
latter is deployed to clients in the release tar files described
below. You can also edit these files directly, instead of or in
addition to using the setup script, but note that you do need to run
the setup script at least once, because it does other necessary things
besides creating the settings files. See
server/settings.yml.template
and client/settings.yml.template
for
documentation of what goes in these files.
The server uses cron
to periodically run an audit script which
reports on outstanding issues.
To install the client, you take the most recent release tar file in
var/client_releases
, copy it to the client, unpack it in a directory
somewhere (anywhere will work, but /opt/penguindome
is relatively
standard), and run client/client-setup.sh -y
as root.
There are two ways to get the tar file onto the client. First, you can
copy it out-of-band and distribute it however you see fit. Second, you
can use the /PenguinDome/v1/download_release
server endpoint. For
example:
sudo mkdir /opt/penguindome
curl -s http://server-host:port/penguindome/v1/download_release > \
penguindome.tar
sudo tar -C /opt/penguindome -x -f penguindome.tar
sudo /opt/penguindome/client/client-setup.sh -y
Note that the release tar files contain sensitive information, such as
SSL certificates and GnuPG keypairs use to secure communication
between the client and server, so access to them should be restricted
to the people in your organization who need them. The
download_release
endpoint can be restricted using IP ranges or
password authentication, as explained in below in "Server
authentication", or of course you can use a firewall on the server
itself to restrict access to your internal IPs.
The server setup script asks the questions necessary to set up basic, initial server and client configurations. In addition, there are advanced configuration options that are not handled by the setup script.
-
What port should the server listen on? The port number of the PenguinDome web service. If you decide to run the web service behind nginx or a proxy or something, the port number that the server listens on may be different from the port number clients use to connect. Here, you're being asked what port the server, not the clients, should bind to.
-
Do you want the server to use SSL? If you say yes here, then you will be prompted for the locations of your certificate and key files. If you want to use a self-signed certificate generated by PenguinDome, say no here and then use
bin/configure-ports
afterward to set it up (you will need to use theconfigure-port
andconfigure-client
subcommands; run them with--help
to get more info). If you do that, then the self-signed certificate is included in client releases and used by the clients to verify the server certificate, so it is secure. -
Database host:port specifies the MongoDB host name and port number the server should connect to. If it's a replicaset, you can specify more than one (you'll keep being prompted until you hit Enter to indicate you're done specifying hosts). If you leave off the port, the default port number 27017 is used.
-
Replicaset name and Database name should be obvious.
-
Database username and Database password can be blank if the database doesn't require authentication, obviously a very bad idea unless you're running it on the same host as the server and it's heavily protected!
-
Server logbook handler, Server logging level, and possibly also Server syslog facility, Server syslog host, and Server syslog port control how log messages are handled on the server, via Logbook. Later in the script, you'll be asked the same questions for the client. Note that in addition to the logging configured here, the server and client both do full debug logging locally in
var/log/penguindome.log
(which is rotated automatically) within their installation directory. -
Do you want to enable the audit cron job? If you say no, then both the periodic audit and the Arch Security information collector cron jobs won't be installed. If you want the Arch Security jobs but you don't want the audit job, then install the crontab and then edit it by hand to comment out the audit job.
-
What email address should get the audit output? This is just the
MAILTO
setting in the crontab. -
URL base for clients to reach server is the first part of the URL the client should use to connect to the server. As noted above, the port number could be different if you're putting the server behind some sort of proxy.
-
Google geolocation API key, if any is necessary if you want to use the PenguinDome's geolocation functionality. See this page for additional information.
-
How often (minutes) do you want to collect data? controls how often each client should run its plugin scripts. The scripts are not invasive or burdensome, though some of them (most notably the geolocation script) take the better part of a minute to run, so it's probably not a useful to set this to less than 2.
-
How often (minutes) do you want re-try submits? You might as well leave this set to every minute unless you've got a really good reason not to (if you've managing so many clients that that's too much for the server to handle, we'd love to hear about it!).
That's the end of the configuration settings that the setup script asks about. It then asks some additional questions about whether to add the server to systemd, enable and/or start the systemd service, install or replace the crontab, and build a client release with the new client settings.
The setup script can only configure a single port for the server to listen on, but the server can actually be configured to listen on any number of ports, and SSL can be configured separately on every port.
The easiest way to do advanced port configuration is with the
bin/configure_ports
script which is installed by the server setup
script. Run bin/configure_ports --help
as root for additional
information.
There are a number of reasons why you might want the server to listen on multiple ports. For example:
-
You want to use a self-signed SSL certificate for clients to communicate with the server, but you want a non-SSL port for the
download_release
endpoint so that users can download the client without getting a self-signed certificate error. -
You need to renew your self-signed SSL certificate, and you want the old and new certificates active at the same time on different ports for a seamless transition.
-
Similarly if you want to switch clients from SSL to non-SSL or vice versa.
-
Similarly if for whatever reason you need to change the port that clients are connecting to.
One of the things bin/configure_ports
allows you to do is mark a
port "deprecated," which will cause the server to track clients that
are still using it as open issues. This way, you can easily determine
when it is safe to remove a deprecated port.
As noted above, you don't want your client release files to be
accessible to the public, because they contain security-sensitive
information which should not be distributed outside your organization.
Therefore, the server allows the download_release
endpoint to be
authenticated by IP addresses and ranges (IPv4 and IPv6) and/or
username / password pairs. See "Server authentication" below for
details.
One of the concerns with any MDM platform is Quis custodiet ipsos custodes? or, "Who watches the watchmen?" When private information such as a device's current location is accessible to administrators, then how does one prevent a malicious administrator -- who by necessity needs to have access to the MDM data to do their job -- from accessing private information without a legitimate business need?
PenguinDome solves this problem as follows:
-
MongoDB field selectors are used to designate certain data submitted by clients as private. For example, to keep geolocation data, which is stored in the database under
{plugins: {geolocation: location: {...}}}
, you would configure secret-keeping on the selectorplugins.geolocation.location
. -
A special, separate secret-keeping GnuPG keypair is generated, to be used for the server to encrypt private data.
-
The private key of that keypair is split into several shares, and the original private key can only be reconstructed when several of those shares are provided. The total number of shares and the number required to reconstruct the original key (called the "combine threshold") are configurable.
-
The private key shares are distributed to different administrators, who store them separately and securely, and then the original private key is removed from the server.
-
Clients encrypt private data using the secret-keeping public key before submitting it to the server. (As a backup, the server checks if the private data weren't encrypted on the client, and if so, encrypts them.)
-
If there is ever a need to decrypt and view private data, at least {combine threshold} administrators must provide their shares to reconstruct the private key, at which point the data can be decrypted and viewed.
Secret-keeping is managed by the server-side script
bin/secret_keeping
. This script allows you to show the current
configuration; add or remove secret-data selectors; enable or disable
secret-keeping, including generating the necessary keypair and
splitting up the private key into shares for distribution to
secret-keepers; encrypt and decrypt secret data persistently into the
database; or view secret data without decrypting it persistently. Run
bin/secret_keeping --help
for additional information.
The following server endpoints can currently be configured to require authentication:
<style> table, th, td { border: 1px solid black; border-collapse: collapse; padding: 5px; } </style>Purpose | Endpoint | settings.xml location | Authentication mandatory? |
---|---|---|---|
Downloading client releases | /penguindome/v1/download_release |
server_auth:download_release | no |
Initiating remote shells | /penguindome/v1/server_pipe/server/create |
server_auth:pipe_create | yes |
The following authentication types can be configured for each endpoint:
- IP addresses and ranges (both IPv4 and IPv6)
- Username / password pairs dedicated to an individual endpoint
- Username / passwords configured server-wide
- Names groups of server-wide users
Any combination of these can be used. If any one authentication passes for a particular endpoint, access is permitted. For endpoints designated mandatory above, the server throws an exception when the endpoint is accessed if no authentication is configured for it.
Here's an example settings.yml
fragment to illustrate how you
authentication of these endpoints is configured:
users:
fred: $pbkdf2-sha256$200000$AcAYQ8j5X0tpzRkD4HwvxQ$.EIGFP/1iRPNabcPHw8bOvR.MbYVWFXauC2jJV5hleo
groups:
server_admins:
- fred
server_auth:
download_release:
ipranges:
- 127.0.0.1
- fe00::0
- 192.168.4.0/24
users:
- fred
passwords:
download_user: $pbkdf2-sha256$200000$kDKGUCpl7B3jXGutFYLwHg$lQfA3HrPZVIVjKCUhDkDYMtkeuTRKbb6UxqnUeLzV0k
pipe_create:
groups:
- server_admins
You can use bin/save_password
on the server to hash and store a
password for a user in server/settings.yml
either server-wide (i.e.,
in the top-level users
section) or for a specific endpoint. Run it
with --help
for more information.
The bin/client_parameters
utility allows you to list, set, and unset
client-specific parameters. Two parameters are currently supported:
user_clients
, which is described below (see "Special audit handling for users with multiple computers")user_email
, which indicates the email address of a client's user, used to email the user about issues with the client
The issues audit that is run out of cron on the server basically just
invokes the script bin/issues audit
, displays the results to stdout,
and logs them as well.
The issues that it generates output about fall into three categories:
-
clients matching MongoDB query specs hard-coded in the
issues
script; -
issues flagged by code built into the
issues
script (i.e., SSL certificates nearing expiration, clients that have had pending patches for a long time); and -
issues flagged by the server during normal operation (i.e., clients connecting on deprecated ports).
If you decide not to use any of the plugins that ship by default with
PenguinDome, you may need to remove the corresponding MongoDB query
specs from server/issues.py
. You may also want to tweak the issues
list at the top of the script to add other queries and/or change the
configurations, e.g., the grace periods before alerts are generated,
of the issues enumerated there.
Clearly, this could be easier to configure than MongoDB query specs hard-coded in a script. Patches are welcome. ;-)
A situation which occurs frequently enough that special handling is justified is when a user has more than one computer and uses one of them most of the time. For example, a user may have a primary and backup computer and use the latter only when the former is out of commission for some reason, or a user may have a computer at work for during the week and a second one at home they only tend to use during the weekends.
This causes spurious "not-reporting" audit reports to be generated for the computer that is used infrequently.
To address this, you can configure the user_clients
parameter on a
group of clients to link them all together. When you set this
parameter (using bin/client_parameters
) on one client to be a list
of one or more other clients, the reciprocal parameters are
automatically set on all the other clients you specify. Then, the
issues audit suppresses not-reporting alerts about all of the clients
in the linked group if any of them have reported recently.
However, this dispensation only lasts for up to a month at a time, i.e., after a month of not reporting the issues audit will complain even about computers that are linked to others that have reported.
If you need to patch PenguinDome files on individual clients, as
opposed to creating a new release with changes that go to all clients,
you can do so using the server-side bin/patch_hosts
utility. Run
bin/patch_hosts --help
for additional information.
For general-purpose, one-time data collection, any scripts found in
the client/commands
directory on clients are executed, their output
is collected and submitted to the server, and they are deleted after
successful execution (i.e., after they exit with a status of 0).
You can get a one-time command onto clients in one of two ways:
-
You can save a script into
client/commands
on the server and then runbin/client_release
. -
You can use the
bin/client_command
script to add a script file or shell command to one or more clients as a patch. Runbin/client_command --help
for more information.
PenguinDome supports running a remote shell on any client that is on the network and checking in periodically with the PenguinDome server.
Running a remote shell is (obviously) a security-sensitive operation. Because of this, a full transcript of each shell session is logged, and the server endpoint used on the server to set up a remote shell session requires authentication to be configured as described above. It is highly recommended to configure the endpoint with username / password authentication, with dedicated usernames and passwords for each PenguinDome administrator, so that the server can log who initiated each remote shell.
To initiate a remote shell, run bin/client_shell
hostname
on the server and enter your username and password
when prompted. A message will then be displayed, telling you that the
script is waiting for the client to respond to the remote shell
request. You should get a shell prompt within a few minutes when the
client checks in to the server.
As long as you set TERM properly, you should be able to use full-screen editors, type ctrl-C and ctrl-Z, etc., within the remote shell. You can exit normally from the shell, e.g. by typing ctrl-d or "exit", or you can hit Enter and type "~." to terminate the connection (note that although that's the same escape sequence used by SSH, that's just to make it easy to remember; the remote shell connection doesn't actually use SSH).
Remote shells are disconnected automatically if they're idle for a while.
Wiping a client is a special case of the one-time commands
functionality described above. When you run bin/client_wipe
hostname
on the server, it queues server/files/wipe.sh
on
the specified host for execution the next time the host checks in.
This script wipes all non-system-user home directories, kills all of
their processes, and then deletes their accounts.
The server utility bin/issues
is used to review or modify open
issues. It has the following subcommands:
- audit -- audit and display open issues
- snooze, unsnooze -- snooze issues (suppress alerts about them) for a specified number of hours or days
- suspend, unsuspend -- suspend hosts until the next time they report to the server
- *open -- manually open new issues
- close -- manually close open issues rather than waiting for the audit script or server to detect that they are resolved and close them automatically
As usual, run bin/issues --help
for more information.
Plugins are the workhorses of PenguinDome. They live in the directory
client/plugins
. They can be any executable, although currently all
the plugins that ship with PenguinDome are Python scripts. They take
no arguments and are expected to output JSON. The output of each
plugin is stored in the client's document in MongoDB under the key
{plugins: {plugin-name: ...}}
, where plugin-name is
the name of the plugin script with its extension removed.
Plugins are responsible for avoiding "flapping," i.e., frequent changes in their output which don't reflect substantive changes in what they are monitoring. For example:
-
The
screenlock
plugin, knows that it can't detect whether a screen-lock is in use unless someone is logged in with X running, so when no one is, it returns cached data from when someone was last logged in. -
The
hd_encryption
plugin sorts the list of devices that it returns, to avoid changes showing up in the logs that are actually nothing more than the order of devices in the list changing.
Plugins should return the (JSON-encoded) string "unknown" to indicate
that they are unable to answer the question they are designed to
answer. For example, the geolocation
plugin returns "unknown" when
it is unable to contact Google's geolocation API, or when an API key
for it has not been configured.
Plugins which output timestamps should use UTC. See below for how to
include datetime
objects in your JSON output.
The PenguinDome library provides some useful tools for plugins to use:
-
from penguindome import cached_data
-- Caches and returns the specified data, if data is specified, or returns previously cached data ifNone
is specified. This is useful for using cached data when conditions on the client temporarily prevent accurate data from being collected. See sample usages in theguest_session
andscreenlock
plugins. -
from penguindome import var_dir
-- Thevar_dir
variable contains the full path of a directory into which plugins can store consistent state. Make sure the names of files and directories you create withinvar_dir
are unambiguously associated with your plugin, so avoid overwriting other people's data. -
from penguindome.client import get_setting
-- Fetches a setting fromclient/settings.xml
. See the help string for details. NOTE: It is important to importget_setting
frompenguindome.client
, not frompenguindome
. -
from penguindome.client import get_logger
-- Configures Logbook logging as specified inclient/settings.xml
and returns a logger which the caller can then use to emit logs. NOTE: It is important to importget_logger
frompenguindome.client
, not frompenguindome
. -
import penguindome.json as json
-- A JSON encoder / decoder built on top of Python's built-injson
implementation, which looks for dictionary keys ending in_at
and tries to encode them asdatetime
s. Similarly, the server looks for such keys in plugin output and attempts to decode them intodatetime
s before storing them into the database. -
from penguindome.plugin_tools import find_who_x_users
-- Returns a list of(username, $DISPLAY)
tuples of users who appear, from the output ofwho
, to be logged into X. -
from penguindome.plugin_tools import find_xinit_users
-- Similarly for users who appear to be logged in viaxinit
. -
from penguindome.plugin_tools import find_x_users
-- Returns the merged output offind_who_x_users
andfind_x_users
. -
from penguindome.plugin_tools import DBusUser
-- A class for executing commands within a user's running DBus context. See the help strings on the class for more information.
The guts of the logic for collecting information about clients is in
the plugin scripts. you can run them standalone for debugging by first
doing . var/client-venv/bin/activate
to activate the virtual
environment so you have access to all the necessary Python modules.
Some of them are more likely than others to require modification for
different environments. In particular:
-
os_updates
currently works on Ubuntu and Arch Linux, assuming that you enable the script on the server that periodically downloads notifications from the Arch Security mailing list. To support other Linux distributions, add another*_checker
function in the plugin modeled after thearch_checker
andubuntu_checker
functions that are already there, and add it to thecheckers
variable near the bottom of the script. -
guest_session
knows how to check if guest sessions are disabled in LightDM, assuming that its configuration files are stored in the standard location. It also knows how to check if the user(s) running X are doing so viaxinit
, and if so assumes that there are no guest sessions. If the plugin can't locate LightDM and confirm that guest sessions are disabled or confirm thatxinit
is being used, then it returns "unknown" and you'll probably need to enhance it to add support for the display managers used by your clients. -
screenlock
knows how to detect GNOME Screensaver as well asxautolock
being used with eitherslock
ori3lock
. You may need to add support for other screensavers used by your clients. -
hd_encryption
-- Checks for LUKS encryption, either directly on a raw disk device or through LVM. I've generalized it enough to work with the various different HD encryption setups used by our clients, though additional work may need to be done to make it support configurations I didn't anticipate. -
eraagent
-- If you're not using the ESET ERA Agent, you can delete this. If you delete it on the server before building the release you deploy to clients, it won't be active on the clients. If you delete it after deploying to clients and then build a new release withbin/client_release
, then the clients will delete it when they download and update to the new release. -
eset
-- If you're not using ESET antivirus, you can delete this. -
prey
-- Detects if the Prey client is installed and running. We're not actually using it right now, so it's inlibrary/client/plugins
rather thanclient/plugins
and therefore isn't deployed to clients in the default configuration. If you want to use it, move it toclient/plugins
.
The other plugin scripts not listed here are more generic, and will probably work on a wide variety of Linux distributions with little or no modification.
The client script bin/client-cron
is called every minute out of the
crontab installed by client/client-setup.sh
. It does the following:
-
Asks the server for any pending updates (new releases, patches) and installs them by calling the
bin/update
script. -
If an update was installed, or if the number of minutes configured in
schedule:collect_interval
has elapsed, runs plugins and client commands using thebin/collect
script. -
If any plugins or client commands, or if there is collected output from previously run plugins or client commands that has not yet been successfully submitted to the server and
schedule:submit_interval
as elapsed, then submit collected data to the server using thebin/submit
script.
When you initiate a remote shell on the server, several things happen:
-
The
client_shell
script tells the PenguinDome web server that it's requesting a shell. -
The web server initializes a proxy I/O pipe for this shell instance, assigns a unique identifier to it, and returns it to the
client_shell
script. -
The
client_shell
script tells the server to send a patch script down to the client. -
The
client_shell
script connects to the proxy I/O pipe on the web server and waits to start receiving I/O from the client. -
The client checks in with the server and downloads and runs the patch script.
-
The patch script launches the shell process and acts as an I/O passthrough between the shell and the client end of the proxy I/O pipe on the web server.
-
Once the
client_shell
script detects that the client has connected, it starts acting as an I/O passthrough between the user's terminal and the server end of the proxy I/O pipe on the web server.
The "proxy I/O pipe" mentioned above uses keep-alive connections to the web server and periodic polling for new data transmitted from the other side of the pipe. WebSockets would have been a reasonable alternative to this implementation, but when I looked at the various WebSocket implementations available to layer on top of Flask, they all looked like various different levels of painful to use, so I decided to roll my own.
The code to make this all work is in penguindome/shell/__init__.py
and penguindome/shell/client.py
, along with the code in the server
for managing the proxy I/O pipes.
The server supports the following queries from clients:
-
/penguindome/v1/submit
for submitting plugin or command results (called bybin/submit
on the client) -
/penguindome/v1/update
for downloading a new release and/or patches as needed (called bybin/update
on the client) -
/penguindome/v1/acknowledge_patch
for acknowledging that a particular patch has been applied successfully on the client so it can be unqueued on the server (also called bybin/update
on the client) -
/penguindome/v1/download_release
for downloading a tar file containing the most recent client release, as documented above. -
/penguindome/v1/server_pipe/server/create
for theclient_shell
script to use when initiating a remote shell request. -
/penguindome/v1/server_pipe/client/open
for clients to use when responding to remote shell requests. -
/penguindome/v1/server_pipe/{server or client}/{send or receive}
for the server and client ends of remote shell connections to talk to each other. -
/penguinddome/v1/server_pipe/{server or client}/close
for the server and client ends of remote shell connections to terminate and clean up the connection.
All of the API endpoints except download_release
should be called
with POST
.
All of the POST
endpoints except server_pipe
send and receive
require two form fields:
-
data
contains the JSON-encoded query data. -
signature
contains a detached GnuPG signature for the data. More on this below.
Rather than using GnuPG signatures, the server_pipe
endpoints use
AES encryption using a random key and IV created when the pipe is
created.
You can contribute to PenguinDome by opening an issue, submitting a PR, or commenting on existing ones.
Tests are implemented in pytest and are in the tests
subdirectory. To run the tests, first install all of the packages in
client/requirements.txt
, server/requirements.txt
, and
requirements_dev.txt
, then run python3 -m pytest
.
There aren't very many unit tests yet. Moving forward, new tests should be added to cover any committed changes, to make it less likely that a change breaks something. If you have useful changes to submit but you need help writing tests, feel free to submit a PR without the the tests and someone may be able to help.
Tests should pass on Python 3.7, 3.8, 3.9, and 3.10 before the
corresponding changes go onto master. there is a tox
configuration
in tox.ini
to support that, but you may need to update it to reflect
where you've installed the various required Python versions.
Note the following idiosyncrasies about using tox
to test multiple
Python versions:
-
As best as I can tell, bad things happen if you try to install and run
tox
within a virtualenv, because then you end up using a virtualenv within a virtualenv or something like that and things do not work. Therefore,tox
is not listed inrequirements_dev.txt
, and you should install it in your base OS rather than in the virtualenv (if any) you use for developing PenguinDome. You can and should run the unit tests within your virtualenv usingpython3 -m pytest
as mentioned above, but when it's time to runtox
to test all Python versions, make sure to runtox
from a shell in which no virtualenv is activated. This shouldn't be this hard, but alas apparently it is. -
tox
won't notice if you modifyclient/requirements.txt
,server/requirements.txt
, orrequirements_dev.txt
after the first time you run it. You should recursively remove.tox
after modifying any of the requirements files before runningtox
to execute tests.
When the server is configured with server/server-setup.sh
, two GnuPG
keypairs are generated, one for the server and one for the clients.
All of the files in releases that go down to the clients are signed
with the server's private key, and clients verify that the files are
intact (i.e., the signatures are valid) before using them. Plugins,
command scripts, etc., that aren't signed by the server's private key
won't be run on the clients.
The client key is used to sign all API requests sent by the clients to the server. The server won't process any request that doesn't have a valid signature.
All the clients use the same private key.
The architecture allows for rotating either the server or the client key, though that's not yet fully supported by the code.
The database contains the following collections:
-
clients
contains all the data sent in by clients. Client hostnames are assumed to be unique, so if two computers with the same hostname are reporting to the server at the same time, bad things will happen. -
audit_trail
contains an audit trail of changes to client data. -
patches
contains patches that have been deployed to clients in the past and/or are still pending deployment. -
issues
is used to keep track of current and previous issues with clients, including the bookkeeping information necessary to determine when to alert about them. -
arch_security_updates
DEPRECATED -
client_parameters
contains client-specific paramaters currently used only bybin/client_parameters
andbin/issues audit
.
If you want to use the geolocation plugin, you'll need an API key from Google. See https://developers.google.com/maps/documentation/geolocation/intro.
PenguinDome was written by Jonathan Kamens at Quantopian, Inc. Thank you to Quantopian for supporting the development and open-sourcing of this project.
Quantopian, Inc. licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.