diff options
author | Tom Ryder <tom@sanctum.geek.nz> | 2017-10-05 18:06:47 +1300 |
---|---|---|
committer | Tom Ryder <tom@sanctum.geek.nz> | 2017-10-05 19:32:42 +1300 |
commit | 7969bce43d9c445d87a68080587298728d0d3e38 (patch) | |
tree | 6b0564dda9aecf08e3d465e4c52efb13984af419 /bin | |
parent | Add Module::Starter boilerplate for perlmodding (diff) | |
download | Mail-Run-Crypt-7969bce43d9c445d87a68080587298728d0d3e38.tar.gz Mail-Run-Crypt-7969bce43d9c445d87a68080587298728d0d3e38.zip |
Rename/separate out into app and module
This still needs a lot more work before release. In particular, I have
to figure out what I'm going to do about the `passphrase` option. It's
probably better to both not require it, in which case no signing is done
(only encryption), and to instead allow a path to a file to be
specified.
The other big puzzle would be how on earth to write automated tests for
it... I may end up imitating however Mail::GnuPG is testing itself.
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/croncrypt | 112 | ||||
-rwxr-xr-x | bin/runcrypt | 254 |
2 files changed, 254 insertions, 112 deletions
diff --git a/bin/croncrypt b/bin/croncrypt deleted file mode 100755 index 002dd56..0000000 --- a/bin/croncrypt +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env perl - -# -# croncrypt: Wrapper to sign and encrypt cron output and errors with PGP/MIME -# before sending them to the default MAILTO destination. -# -# CRONCRYPT_KEYID=0x0A1B2C3D4E5F6G7H -# CRONCRYPT_PASSPHRASE=hibbityboo -# MAILTO=tom@sanctum.geek.nz -# 0 1 * * * tom croncrypt rsync /home/tom/important-file /home/backups -# -# The main design goal is simplicity; just whack a «croncrypt» in front of all -# your cron tasks, provided they don't use pipes or stderr/stdout redirects, -# in which case you should consider putting it all into a script file anyway. -# -# Don't use your own GPG key for signing! Create a dedicated key just for -# croncrypt, and sign it locally with «gpg --lsign» maybe. -# -# Author: Tom Ryder <tom@sanctum.geek.nz> -# Copyright: 2014 Sanctum -# License: Artistic 2.0 <http://opensource.org/licenses/artistic-license-2.0> -# -# $Id$ -# -package Sanctum::Croncrypt; - -# Force me to write this properly -use strict; -use warnings; -use utf8; -use autodie; - -# Decree minimum Perl version required (v5.8). -use 5.008; - -# Decree package version to pacify Perl::Critic -our $VERSION = 0.1; - -# Pull in some required modules -use Carp; -use IPC::Run3; -use Mail::GnuPG; -use MIME::Entity; - -# Bail if run without arguments -if ( !@ARGV ) { - printf "%s\n", 'USAGE: croncrypt <command>'; - exit 1; -} - -# Bail if we don't have the environment variables we need -my @fails; -if ( !exists $ENV{'CRONCRYPT_KEYID'} ) { - push @fails, 'CRONCRYPT_KEYID is not set; set it to your key ID.'; -} -if ( !exists $ENV{'CRONCRYPT_PASSPHRASE'} ) { - push @fails, - 'CRONCRYPT_PASSPHRASE is not set; set it to your key\'s passphrase.'; -} -if ( !exists $ENV{'MAILTO'} ) { - push @fails, 'MAILTO is not set; set it to the message\'s destination.'; -} -if (@fails) { - foreach my $fail (@fails) { - printf {*STDERR} "croncrypt: FAIL: %s\n", $fail; - } - exit 1; -} - -# Read details from environment -my $recipient = $ENV{MAILTO}; -my $key = $ENV{CRONCRYPT_KEYID}; -my $passphrase = $ENV{CRONCRYPT_PASSPHRASE}; - -# Run the command in the arguments and wait for it to finish -my ( @output, @errors ); -run3( \@ARGV, undef, \@output, \@errors ); - -# If there was output, mail it -if (@output) { - my $subject = sprintf 'croncrypt output: %s', join q{ }, @ARGV; - mail( $subject, \@output ); -} - -# If there were errors, mail them -if (@errors) { - my $subject = sprintf 'croncrypt errors: %s', join q{ }, @ARGV; - mail( $subject, \@errors ); -} - -# Send the message to the address in $ENV{MAILTO} -sub mail { - my ( $subject, $content ) = @_; - - # Build MIME object with plaintext message - my $mime = MIME::Entity->build( - To => $recipient, - Subject => $subject, - Data => $content, - ); - - # Encrypt the MIME object - my $mgpg = Mail::GnuPG->new( - key => $key, - passphrase => $passphrase, - ); - $mgpg->mime_signencrypt( $mime, $recipient ); - - # Send it - return $mime->send(); -} - diff --git a/bin/runcrypt b/bin/runcrypt new file mode 100755 index 0000000..d9a24fc --- /dev/null +++ b/bin/runcrypt @@ -0,0 +1,254 @@ +#!/usr/bin/env perl +package main; + +# Force me to write this properly +use strict; +use warnings; +use utf8; + +# Target Debian-Squeeze-era Perl +# We may be able to go older; not sure yet +use 5.010; + +# Import required modules +use Carp; +use Getopt::Long::Descriptive; +use Mail::Run::Crypt; + +# Specify package version +our $VERSION = '0.01'; + +# Name ourselves +our $SELF = 'runcrypt'; + +# Read command-line options +my ( $opt, $usage ) = describe_options( + "$SELF %o COMMAND [ARG1...]", + + # Key ID defaults to environment RUNCRYPT_KEYID if set + [ + 'keyid|k=s', + 'OpenPGP key ID', + { default => $ENV{RUNCRYPT_KEYID} // undef }, + ], + + # Key passphrase defaults to environment RUNCRYPT_PASSPHRASE if set + [ + 'passphrase|p=s', + 'OpenPGP passphrase', + { default => $ENV{RUNCRYPT_PASSPHRASE} // undef }, + ], + + # MAILTO address defaults to environment MAILTO if set + [ + 'mailto|m=s', + 'Mail destination address (MAILTO)', + { default => $ENV{RUNCRYPT_MAILTO} // $ENV{MAILTO} // undef }, + ], + + # Instance name (for email subjects) defaults to $SELF + [ + 'name|n=s', + 'Instance name (included in subject lines)', + { default => $SELF }, + ], + + # Newline + [], + + # Help option + [ 'help', 'print usage message and exit', { shortcircuit => 1 } ], +); + +# Print help if requested +if ( $opt->help ) { + print $usage->text + or carp 'Failed stdout write'; + exit 0; +} + +# Bail if run without arguments +if ( !@ARGV ) { + printf {*STDERR} $usage->text + or carp 'Failed stderr write'; + exit 2; +} + +# Create an MCC object +my $mrc = Mail::Run::Crypt->new( + keyid => $opt->keyid, + passphrase => $opt->passphrase, + mailto => $opt->mailto, + name => $opt->name, +); + +# Run the command given in the arguments, exiting appropriately +$mrc->run(@ARGV); +exit $mrc->bail; + +__END__ + +=pod + +=encoding utf8 + +=for stopwords +runcrypt decrypt stdout stderr GPG OpenPGP tradename licensable MERCHANTABILITY + +=head1 NAME + +runcrypt - Encrypt and mail output from command in arguments + +=head1 USAGE + + runcrypt + [--keyid KEYID] + [--passphrase PASSPHRASE] + [--mailto MAILTO] + [--name NAME] + COMMAND [ARG1 ...] + +=head1 DESCRIPTION + +This program applies C<Mail::Run::Crypt> to run a command and send any output +or error content to the specified address. More information is available in the +documentation for that module. + +=head1 REQUIRED ARGUMENTS + +The arguments beyond the options are used as the command name to run: + + runcrypt rsync -a /mnt/a remote:mnt/b + +=head1 OPTIONS + +=over 4 + +=item C<--keyid> + +The GnuPG key ID that should be used to sign and encrypt the messages. This +defaults to the value of the environment variable C<RUNCRYPT_KEYID>. + +=item C<--passphrase> + +The passphrase use to decrypt the key. This defaults to the value of the +environment variable C<RUNCRYPT_PASSPHRASE>. + +=item C<--mailto> + +The recipient address for the encryption portion of the email. This defaults to +the value of the environment variable C<RUNCRYPT_MAILTO> if that is set, or +C<MAILTO> failing that, to make it suitable for use in a C<crontab(5)> file. + +=item C<--name> + +The name for this instance of the module, which will be used as the first word +of the subject line of any email messages it sends. This defaults to +C<runcrypt>, which is probably good enough in most cases. + +=head1 DIAGNOSTICS + +=over 4 + +=item Failed stdout write + +Usage information could not be written to the standard output stream. + +=item Failed stderr write + +Usage information could not be written to the standard error stream. + +=head1 EXIT STATUS + +The program exits with the same exit value of the command that it ran, or 127 +if the command could not be run at all. See the C<bail()> method in +C<Mail::Run::Crypt>. + +=head1 CONFIGURATION + +You will need to have a functioning GnuPG public key setup for this to work, +including the secret key. You should definitely not use your personal key; +generate one specifically for mail signing and encryption instead. + +=head1 DEPENDENCIES + +=over 4 + +=item * + +Perl 5.10 or newer + +=item * + +C<Carp> + +=item * + +C<Getopt::Long::Descriptive> + +=item * + +C<Mail::Run::Crypt> + +=back + +=head1 INCOMPATIBILITIES + +This module uses C<Mail::GnuPG> and other GPG-specific code, so it won't work +with any other OpenPGP implementations. + +=head1 BUGS AND LIMITATIONS + +Providing the C<passphrase> directly from an environment variable is not great. +The documentation needs to make that clear and offer a less bad alternative, +probably specifying the path to a secure file that it refuses to use unless it +has sufficiently restrictive permissions. + +This is very hard to test and the author has not yet figured out how to do +that. + +=head1 AUTHOR + +Tom Ryder C<< <tom@sanctum.geek.nz> >> + +=head1 LICENSE AND COPYRIGHT + +Copyright (C) 2017 Tom Ryder + +This program is free software; you can redistribute it and/or modify it under +the terms of the Artistic License (2.0). You may obtain a copy of the full +license at: + +L<http://www.perlfoundation.org/artistic_license_2_0> + +Any use, modification, and distribution of the Standard or Modified Versions is +governed by this Artistic License. By using, modifying or distributing the +Package, you accept this license. Do not use, modify, or distribute the +Package, if you do not accept this license. + +If your Modified Version has been derived from a Modified Version made by +someone other than you, you are nevertheless required to ensure that your +Modified Version complies with the requirements of this license. + +This license does not grant you the right to use any trademark, service mark, +tradename, or logo of the Copyright Holder. + +This license includes the non-exclusive, worldwide, free-of-charge patent +license to make, have made, use, offer to sell, sell, import and otherwise +transfer the Package with respect to any patent claims licensable by the +Copyright Holder that are necessarily infringed by the Package. If you +institute patent litigation (including a cross-claim or counterclaim) against +any party alleging that the Package constitutes direct or contributory patent +infringement, then this Artistic License to you shall terminate on the date +that such litigation is filed. + +Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND +CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR +NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. +UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY +OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +=cut |