././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1615765592.6638746 authprogs-0.7.5/0000775000175000017500000000000000000000000013114 5ustar00bribri00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615592907.0 authprogs-0.7.5/AUTHORS0000664000175000017500000000004500000000000014163 0ustar00bribri00000000000000Bri Hatch (daethnir) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1613938070.0 authprogs-0.7.5/AUTHORS.md0000664000175000017500000000004500000000000014562 0ustar00bribri00000000000000Bri Hatch (daethnir) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1399694414.0 authprogs-0.7.5/COPYING0000664000175000017500000003556400000000000014164 0ustar00bribri00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615592907.0 authprogs-0.7.5/DEVELOPMENT0000664000175000017500000000421300000000000014621 0ustar00bribri00000000000000 Authprogs Development ===================== Reporting Bugs -------------- Please file bug reports at the [github issues page] or reach out to the author directly. Contributions ------------- If you wish to make changes to authprogs, send a pull request. Note that I plan to be very anal about unit testing; this is security-related software after all. Any code you provide will be assumed to be under the public domain unless specified otherwise. Contributors are encouraged to reach out in advance to kibbiz about implementation. See the `TODO.md` file for ideas of things that need doing. Testing ------- Run local unit tests: $ python3 setup.py test When developing, it may be useful to run individual unit or per-file tests, e.g. $ python3 setup.py test -s authprogs.tests.test_rsync.RsyncTests.test_foo $ python3 setup.py test -s authprogs.tests.test_authprogs.AuthprogsTests.test_archive And install locally to do live tests $ sudo python3 setup.py install Spell check ----------- Enough spelingerrers have cropped up that it's worth having a defined pass for spell check. The repo includes `ispell` dictionaries which have been populated with words specifically for the prose, code, and yaml files. # Docs ispell -p .ispell_default $(git ls-tree -r $(git branch --show-current) --name-only | egrep -i '\.md$|\.rst$') # Code files ispell -p .ispell_code $(git ls-tree -r $(git branch --show-current) --name-only | egrep -i '\.py$') # yaml ispell -p .ispell_yaml $(git ls-tree -r $(git branch --show-current) --name-only | egrep -i '\.yaml$') Publishing ---------- Upload to test pypi: $ python3 setup.py sdist upload -r testpypi $ sudo pip3 install -U -i https://test.pypi.org/simple/ authprogs $ sudo pip3 install -U -i https://test.pypi.org/simple/ authprogs==X.Y.Z Test locally to verify all's good. Upload to prod pypi: $ python3 setup.py sdist upload $ sudo pip3 uninstall authprogs $ sudo pip3 install authprogs [github issues page]: https://github.com/daethnir/authprogs/issues [github repository]: https://github.com/daethnir/authprogs [ronn]: https://github.com/rtomayko/ronn ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1613947105.0 authprogs-0.7.5/DEVELOPMENT.md0000664000175000017500000000421300000000000015220 0ustar00bribri00000000000000 Authprogs Development ===================== Reporting Bugs -------------- Please file bug reports at the [github issues page] or reach out to the author directly. Contributions ------------- If you wish to make changes to authprogs, send a pull request. Note that I plan to be very anal about unit testing; this is security-related software after all. Any code you provide will be assumed to be under the public domain unless specified otherwise. Contributors are encouraged to reach out in advance to kibbiz about implementation. See the `TODO.md` file for ideas of things that need doing. Testing ------- Run local unit tests: $ python3 setup.py test When developing, it may be useful to run individual unit or per-file tests, e.g. $ python3 setup.py test -s authprogs.tests.test_rsync.RsyncTests.test_foo $ python3 setup.py test -s authprogs.tests.test_authprogs.AuthprogsTests.test_archive And install locally to do live tests $ sudo python3 setup.py install Spell check ----------- Enough spelingerrers have cropped up that it's worth having a defined pass for spell check. The repo includes `ispell` dictionaries which have been populated with words specifically for the prose, code, and yaml files. # Docs ispell -p .ispell_default $(git ls-tree -r $(git branch --show-current) --name-only | egrep -i '\.md$|\.rst$') # Code files ispell -p .ispell_code $(git ls-tree -r $(git branch --show-current) --name-only | egrep -i '\.py$') # yaml ispell -p .ispell_yaml $(git ls-tree -r $(git branch --show-current) --name-only | egrep -i '\.yaml$') Publishing ---------- Upload to test pypi: $ python3 setup.py sdist upload -r testpypi $ sudo pip3 install -U -i https://test.pypi.org/simple/ authprogs $ sudo pip3 install -U -i https://test.pypi.org/simple/ authprogs==X.Y.Z Test locally to verify all's good. Upload to prod pypi: $ python3 setup.py sdist upload $ sudo pip3 uninstall authprogs $ sudo pip3 install authprogs [github issues page]: https://github.com/daethnir/authprogs/issues [github repository]: https://github.com/daethnir/authprogs [ronn]: https://github.com/rtomayko/ronn ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615592907.0 authprogs-0.7.5/INSTALL0000664000175000017500000000167300000000000014154 0ustar00bribri00000000000000 INSTALLING ========== Regardless if you have checked out the source tarball or cloned the entire git repository, you should be able to install authprogs in the standard setuptools-ian way: $ python3 setup.py test $ sudo python3 setup.py install DEVELOPMENT =========== Authprogs source can be found at its [github repository] BUILD REQUIREMENTS ------------------ In order to generate the man page from the `doc/authprogs.md` file we require ronn, which can be found at [ronn] Or it may be available in your distro already, for example $ sudo apt-get install ronn or $ sudo apt-get install ruby-ronn In order to generate the html man page from `doc/authprogs.md` we require the markdown module which can be installed via $ sudo pip3 install markdown or it may be available from your distro already, for example $ sudo apt install python3-markdown There should be no other dependencies. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1613947105.0 authprogs-0.7.5/INSTALL.md0000664000175000017500000000167300000000000014553 0ustar00bribri00000000000000 INSTALLING ========== Regardless if you have checked out the source tarball or cloned the entire git repository, you should be able to install authprogs in the standard setuptools-ian way: $ python3 setup.py test $ sudo python3 setup.py install DEVELOPMENT =========== Authprogs source can be found at its [github repository] BUILD REQUIREMENTS ------------------ In order to generate the man page from the `doc/authprogs.md` file we require ronn, which can be found at [ronn] Or it may be available in your distro already, for example $ sudo apt-get install ronn or $ sudo apt-get install ruby-ronn In order to generate the html man page from `doc/authprogs.md` we require the markdown module which can be installed via $ sudo pip3 install markdown or it may be available from your distro already, for example $ sudo apt install python3-markdown There should be no other dependencies. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1613947700.0 authprogs-0.7.5/MANIFEST.in0000664000175000017500000000052400000000000014653 0ustar00bribri00000000000000include AUTHORS include AUTHORS.md include COPYING include DEVELOPMENT include DEVELOPMENT.md include INSTALL include INSTALL.md include README include README.md include README.rsync include README.rsync.md include TODO include TODO.md include doc/authprogs.1 include doc/authprogs.html include doc/authprogs.md include doc/description.rst ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1615765592.6638746 authprogs-0.7.5/PKG-INFO0000664000175000017500000000325200000000000014213 0ustar00bribri00000000000000Metadata-Version: 1.2 Name: authprogs Version: 0.7.5 Summary: SSH Command Authenticator Home-page: http://github.com/daethnir/authprogs Author: Bri Hatch Author-email: bri@ifokr.org Maintainer: Bri Hatch Maintainer-email: bri@ifokr.org License: GPLv2 Description: Authprogs --------- `authprogs` is an SSH command authenticator. It is invoked on an ssh server and decides if the command requested by the ssh client should be run or rejected based on logic in the `authprogs` configuration file. Passwordless SSH using ssh identities or pubkeys can enable all sorts of wonderful automation, for example running unattended batch jobs, slurping down backups, or pushing out code. Unfortunately a key, once trusted, is allowed by default to run anything on that system, not just the small set of commands you actually need. If the key is compromised, you are at risk of a security breach. This could be catastrophic, for example if the access is to the root account. Authprogs is run on the SSH server and compares the requested command against the `authprogs` configuration file/files. This enables `authprogs` to make intelligent decisions based on things such as the command itself, the SSH key that was used, the client IP, and such. `authprogs` is enabled by using the `command=` option in the `authorized_keys` file. For usage see the full authprogs man page in the doc directory. Keywords: authprogs ssh pubkey identity authorized_keys security Platform: UNKNOWN ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615592907.0 authprogs-0.7.5/README0000664000175000017500000000224700000000000014001 0ustar00bribri00000000000000 Authprogs ========= authprogs is an SSH command authenticator. It is invoked on an ssh server and decides if the command requested by the ssh client should be run or rejected based on logic in the authprogs configuration file. Passwordless SSH using ssh identities or pubkeys can enable all sorts of wonderful automation, for example running unattended batch jobs, slurping down backups, or pushing out code. Unfortunately a key, once trusted, is allowed by default to run anything on that system, not just the small set of commands you actually need. If the key is compromised, you are at risk of a security breach. This could be catastrophic, for example if the access is to the root account. Authprogs is run on the SSH server and compares the requested command against the authprogs configuration file/files. This enables authprogs to make intelligent decisions based on things such as the command itself, the SSH key that was used, the client IP, and such. authprogs is enabled by using the command= option in the authorized\_keys file. Installation and Usage ====================== See the full authprogs man page in the doc directory. Author ====== Bri Hatch ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1613943612.0 authprogs-0.7.5/README.md0000664000175000017500000000224700000000000014400 0ustar00bribri00000000000000 Authprogs ========= authprogs is an SSH command authenticator. It is invoked on an ssh server and decides if the command requested by the ssh client should be run or rejected based on logic in the authprogs configuration file. Passwordless SSH using ssh identities or pubkeys can enable all sorts of wonderful automation, for example running unattended batch jobs, slurping down backups, or pushing out code. Unfortunately a key, once trusted, is allowed by default to run anything on that system, not just the small set of commands you actually need. If the key is compromised, you are at risk of a security breach. This could be catastrophic, for example if the access is to the root account. Authprogs is run on the SSH server and compares the requested command against the authprogs configuration file/files. This enables authprogs to make intelligent decisions based on things such as the command itself, the SSH key that was used, the client IP, and such. authprogs is enabled by using the command= option in the authorized\_keys file. Installation and Usage ====================== See the full authprogs man page in the doc directory. Author ====== Bri Hatch ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615592907.0 authprogs-0.7.5/README.rsync0000664000175000017500000000324600000000000015136 0ustar00bribri00000000000000 # Rsync option support The overall plan is to fully parse the rsync command line. Not all features, however, can be restricted by `authprogs`, while others may have no security ramifications. Nonetheless the rsync command line **must** be considered valid. Support for more flags will be added as time allows, and preference will be given to those flags that are reported as actively needed. Parsing was done based on the rsync-3.1.2 source code. # How Rsync Works rsync converts the client command line into server rsync command via the server\_options function in options.c. Only those options that the server needs to know are actually passed onto the server. # Limitations Not all rsync options have been fully investigated, e.g. the `--files-from` / `--filter` / `--include` / `--exclude` ones. There ay be dragons. # Weirdness rsync overloads the -e flag. On the client it tells rsync which program to use for 'ssh'. On the server it indicates the protocol version to indicate the protocol. This option is either `-e.` to indicate no version, or `-e#.#` when a version has been negotiated. When running over ssh, there is no version, so this always starts as `-e`. rsync then appends some pre-release protocol version and behaviour flags information. These look like the single letter options above but they are not. (For example the `C` in `-e.C` means that the client supports a checksum seed order fix, not that the `-C` (`--cvs-exclude`) flag is being sent. Authprogs currently recognises the -e option and ignores its value. For reference, as of rsync-3.1.2 this will typically be `-e.LsfxC`) # See also For `authprogs`' rsync documentation and usage see authprogs.md. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1613943612.0 authprogs-0.7.5/README.rsync.md0000664000175000017500000000324600000000000015535 0ustar00bribri00000000000000 # Rsync option support The overall plan is to fully parse the rsync command line. Not all features, however, can be restricted by `authprogs`, while others may have no security ramifications. Nonetheless the rsync command line **must** be considered valid. Support for more flags will be added as time allows, and preference will be given to those flags that are reported as actively needed. Parsing was done based on the rsync-3.1.2 source code. # How Rsync Works rsync converts the client command line into server rsync command via the server\_options function in options.c. Only those options that the server needs to know are actually passed onto the server. # Limitations Not all rsync options have been fully investigated, e.g. the `--files-from` / `--filter` / `--include` / `--exclude` ones. There ay be dragons. # Weirdness rsync overloads the -e flag. On the client it tells rsync which program to use for 'ssh'. On the server it indicates the protocol version to indicate the protocol. This option is either `-e.` to indicate no version, or `-e#.#` when a version has been negotiated. When running over ssh, there is no version, so this always starts as `-e`. rsync then appends some pre-release protocol version and behaviour flags information. These look like the single letter options above but they are not. (For example the `C` in `-e.C` means that the client supports a checksum seed order fix, not that the `-C` (`--cvs-exclude`) flag is being sent. Authprogs currently recognises the -e option and ignores its value. For reference, as of rsync-3.1.2 this will typically be `-e.LsfxC`) # See also For `authprogs`' rsync documentation and usage see authprogs.md. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615592907.0 authprogs-0.7.5/TODO0000664000175000017500000000475100000000000013613 0ustar00bribri00000000000000 TODO List =========== Below are things that I can envision implementing or improving. The presence of something on this list does not mean that it will be implemented. Contributors are encouraged to reach out in advance to kibbiz about implementation. * security improvements * refuse to run if any files are world writable * logging improvements * Use standard python logging * add timestamps * change output of success * syslog support * add `--debug` to write traceback and more verbose errors * key installation improvements * allow arbitrary `authorized_keys` ssh options, for example `no-pty`, `permitopen`, etc. * `--force` to overwrite key entries in `authorized_keys` when installing keys * command line option for authprogs path * identify installation attempt of private keys * add 'restrict' to the pubkey entry if supported by the version of sshd * config rules improvements * chdir to a directory before running * set environment variables * set `$PATH` * restrictions additions * support for hostnames * time of day/week/etc * chroot to a different user via sudo before running * Would require your user has unrestricted sudo for this command * command matching improvements * case-insensitive pcre * whitespace support (clunky/worrisome) * shell regex command matching * forced command specification * allow you to match a command and then run something completely different * ability to function as a login shell * would lose `--name` functionality * rsync support * investigate --include / --exclude / --files-from * verify globbing support and security * support uploading to file that does not exist yet when using `files`. Currently it does a realpath check which fails since the file doesn't exist. * add option that allows access to any files under a given directory, rather than being explicit * create a cache for rsync\_realpaths to decrease lookups when files are listed in multiple rules * allow/disallow symlinks (-l) * support setting allowed rsync binary paths * replace rsync path with discovered path prior to running command - will help avoid a timing attack if an rsync binary is found in $PATH between check and exec. * scp support * support for `-d` (targetshouldbedirectory) * mock out shutils.which in unit tests to remove dependency on locally-installed scp binary ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1613943612.0 authprogs-0.7.5/TODO.md0000664000175000017500000000475100000000000014212 0ustar00bribri00000000000000 TODO List =========== Below are things that I can envision implementing or improving. The presence of something on this list does not mean that it will be implemented. Contributors are encouraged to reach out in advance to kibbiz about implementation. * security improvements * refuse to run if any files are world writable * logging improvements * Use standard python logging * add timestamps * change output of success * syslog support * add `--debug` to write traceback and more verbose errors * key installation improvements * allow arbitrary `authorized_keys` ssh options, for example `no-pty`, `permitopen`, etc. * `--force` to overwrite key entries in `authorized_keys` when installing keys * command line option for authprogs path * identify installation attempt of private keys * add 'restrict' to the pubkey entry if supported by the version of sshd * config rules improvements * chdir to a directory before running * set environment variables * set `$PATH` * restrictions additions * support for hostnames * time of day/week/etc * chroot to a different user via sudo before running * Would require your user has unrestricted sudo for this command * command matching improvements * case-insensitive pcre * whitespace support (clunky/worrisome) * shell regex command matching * forced command specification * allow you to match a command and then run something completely different * ability to function as a login shell * would lose `--name` functionality * rsync support * investigate --include / --exclude / --files-from * verify globbing support and security * support uploading to file that does not exist yet when using `files`. Currently it does a realpath check which fails since the file doesn't exist. * add option that allows access to any files under a given directory, rather than being explicit * create a cache for rsync\_realpaths to decrease lookups when files are listed in multiple rules * allow/disallow symlinks (-l) * support setting allowed rsync binary paths * replace rsync path with discovered path prior to running command - will help avoid a timing attack if an rsync binary is found in $PATH between check and exec. * scp support * support for `-d` (targetshouldbedirectory) * mock out shutils.which in unit tests to remove dependency on locally-installed scp binary ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1615765592.6598747 authprogs-0.7.5/authprogs/0000775000175000017500000000000000000000000015130 5ustar00bribri00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615765528.0 authprogs-0.7.5/authprogs/__init__.py0000664000175000017500000000007700000000000017245 0ustar00bribri00000000000000__version__ = '0.7.5' __author__ = 'Bri Hatch ' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615765528.0 authprogs-0.7.5/authprogs/authprogs.py0000775000175000017500000005605300000000000017532 0ustar00bribri00000000000000# vim: ts=4 et """authprogs: SSH command authenticator module. # vim: set ts=4 et Used to restrict which commands can be run via trusted SSH keys.""" # Copyright (C) 2013 Bri Hatch (daethnir) # # This file is part of authprogs. # # Authprogs is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License v2 as published by # the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from . import rsync as rsync_checker from . import scp as scp_checker import argparse import glob import io import ipaddress import os import pprint import re import shutil import subprocess import sys import textwrap import time import traceback import yaml try: from yaml import CLoader as Loader except ImportError: from yaml import Loader if sys.version_info.major >= 3: unicode = lambda x: x def pretty(thing): """Return pretty-printable version.""" ppthing = pprint.PrettyPrinter(indent=4) return ppthing.pformat(thing) class Error(Exception): """authprogs error class.""" pass class SSHEnvironmentError(Error): """Problem with the SSH server-side environment. These error messages are show directly to users, so be cautious in what you say. """ pass class ConfigError(Error): """Problem with the authprogs configuration.""" pass class CommandRejected(Error): """Client command rejected. These error messages are show directly to users, so be cautious in what you say. """ pass class InstallError(Error): """Problem with the installing an authorized_keys entry.""" pass class AuthProgs(object): # pylint: disable-msg=R0902 """AuthProgs class""" def __init__( self, logfile=None, configfile=None, configdir=None, debug=False, **kwargs ): """AuthProgs constructor. kwargs include: authprogs_binary: path to this binary, when creating authorized_keys entries. If not specified, determines from sys.argv[0] name: the name of this key, for matching in rules. """ self.debug = debug self.logfile = logfile self.client_ip = None self.silent = kwargs.get('silent') if logfile: self.logfh = open(logfile, 'a') else: self.logfh = False # Stores result of shutils.which # key is program name, val is realpath. self.which_cache = {} if kwargs.get('authprogs_binary'): self.authprogs_binary = kwargs['authprogs_binary'] else: self.authprogs_binary = os.path.abspath( os.path.abspath(sys.argv[0]) ) self.original_command_string = os.environ.get( 'SSH_ORIGINAL_COMMAND', '' ) self.original_command_list = self.original_command_string.split() self.keyname = kwargs.get('keyname') if not self.keyname: self.keyname = '' if ' ' in self.keyname or '\t' in self.keyname: self.log('FATAL: keyname contains space/tabs\n') raise Error('--keyname may contain neither spaces nor tabs.') self.yamldocs = None self.configfile = configfile self.configdir = configdir self.subvalidators = {} self.subvalidators['rsync'] = rsync_checker.RsyncValidator(self) self.subvalidators['scp'] = scp_checker.ScpValidator(self) def __del__(self): if self.logfh: self.logfh.close() def raise_and_log_error(self, error, message): """Raise error, including message and original traceback. error: the error to raise message: the user-facing error message """ self.log( 'raising {}, traceback {}\n'.format(error, traceback.format_exc()) ) raise error(message) def get_client_ip(self): """Return the client IP from the environment.""" if self.client_ip: return self.client_ip try: client = os.environ.get( 'SSH_CONNECTION', os.environ.get('SSH_CLIENT') ) self.client_ip = client.split()[0] self.logdebug('client_ip: {}\n'.format(self.client_ip)) return self.client_ip except: raise SSHEnvironmentError( 'cannot identify the ssh client IP address' ) def logdebug(self, message): """Log debugging information.""" if self.debug: self.log(message) def log(self, message): """Log information.""" if self.logfh: self.logfh.write(message) # pylint: disable-msg=E1103 def _glob(self, path): """Return glob results of path, or original if no match glob.glob returns a list only if there are actual matching files. So we always return the original if glob doesn't succeed so we can create files that don't yet exist, etc. Mocked out in unit tests, silly otherwise. """ results = glob.glob(path) return glob.glob(path) or path def _os_path_abspath(self, path): """Return abspath of path Mocked out in unit tests, silly otherwise. """ return os.path.abspath(path) def _os_path_expanduser(self, path): """Return pathname with ~ expansions. Mocked out in unit tests, silly otherwise. """ return os.path.expanduser(path) def _os_path_realpath(self, path): """Return canonical pathname. Mocked out in unit tests, silly otherwise. """ return os.path.realpath(path) def globpaths(self, path, expanduser=False, realpath=False): """Return paths for given file name after glob expansion. If expanduser is set then expand ~ to current user's home dir If realpath is set then convert to real path after resolving symlinks Always calls abspath to create a path that starts at / and resolve .. Perform glob expansion of name and returns list of canonicalised file names """ path = self._os_path_abspath(path) if expanduser: path = self._os_path_expanduser(path) if realpath: path = self.realpath(path) paths = self._glob(path) return paths def realpath(self, path): """Return the real path for the given file path. Returns canonicalised file name (eliminate symlinks, handle '..') """ return self._os_path_realpath(path) def check_keyname(self, rule): """If a key name is specified, verify it is permitted.""" keynames = rule.get('keynames') if not keynames: self.logdebug('no keynames requirement.\n') return True if not isinstance(keynames, list): keynames = [keynames] if self.keyname in keynames: self.logdebug('keyname "{}" matches rule.\n'.format(self.keyname)) return True else: self.logdebug( 'keyname "{}" does not match rule.\n'.format(self.keyname) ) return False def check_client_ip(self, rule): """If a client IP is specified, verify it is permitted.""" if not rule.get('from'): self.logdebug('no "from" requirement.\n') return True allow_from = rule.get('from') if not isinstance(allow_from, list): allow_from = [allow_from] def ipnet(addr): addr = unicode(addr) if addr.lower() in ('*', 'any'): addr = '0.0.0.0/0' try: return ipaddress.ip_network(addr, strict=False) except ValueError: return None allow_from = [ipnet(x) for x in allow_from] allow_from = filter(lambda x: x, allow_from) client_ip = ipaddress.ip_address(unicode(self.get_client_ip())) for allow in allow_from: if client_ip in allow: self.logdebug('client_ip {} in {}\n'.format(client_ip, allow)) return True self.logdebug('client_ip {} not in {}'.format(client_ip, allow_from)) return False def get_merged_config(self): """Get merged config file. Returns an open StringIO containing the merged config file. """ if self.yamldocs: return loadfiles = [] if self.configfile: loadfiles.append(self.configfile) if self.configdir: # Gets list of all non-dotfile files from configdir. loadfiles.extend( [ f for f in [ os.path.join(self.configdir, x) for x in os.listdir(self.configdir) ] if os.path.isfile(f) and not os.path.basename(f).startswith('.') ] ) merged_configfile = io.StringIO() merged_configfile.write('-\n') for thefile in loadfiles: self.logdebug('reading in config file {}\n'.format(thefile)) with open(thefile, 'r') as merge: merged_configfile.write(merge.read()) merged_configfile.write('\n-\n') merged_configfile.seek(0) self.logdebug( 'merged log file: """\n{}\n"""\n'.format(merged_configfile.read()) ) merged_configfile.seek(0) return merged_configfile def load(self): """Load our config, log and raise on error.""" try: merged_configfile = self.get_merged_config() self.yamldocs = yaml.load(merged_configfile, Loader=Loader) merged_configfile.close() # Strip out the top level 'None's we get from concatenation. # Functionally not required, but makes dumps cleaner. self.yamldocs = [x for x in self.yamldocs if x] self.logdebug('parsed_rules:\n{}\n'.format(pretty(self.yamldocs))) except (yaml.scanner.ScannerError, yaml.parser.ParserError): self.raise_and_log_error(ConfigError, 'error parsing config.') def dump_config(self): """Pretty print the configuration dict to stdout.""" yaml_content = self.get_merged_config() print( 'Configuration Source:\n\t{}\n'.format( yaml_content.read().replace('\n', '\n\t') ) ) yaml_content.close() try: self.load() print( 'Effective Configuration\n\t{}\n'.format( pretty(self.yamldocs).replace('\n', '\n\t') ) ) except ConfigError: sys.stderr.write( 'config parse error. try running with --logfile=/dev/tty\n' ) raise def install_key_data(self, keydata, target): """Install the key data into the open file.""" target.seek(0) contents = target.read() ssh_opts = 'no-port-forwarding' keydata = keydata.strip() if keydata in contents: raise InstallError( 'key data already in file - refusing to double-install.\n' ) if '\n' in keydata: raise InstallError( 'install file does not appear to be an ssh pubkey\n' ) command = '{} --run'.format(self.authprogs_binary) if self.logfile: command += ' --logfile={}'.format(self.logfile) if self.keyname: command += ' --keyname={}'.format(self.keyname) target.write( 'command="{command}",{ssh_opts} {keydata}\n'.format( command=command, keydata=keydata, ssh_opts=ssh_opts ) ) def install_key(self, keyfile, authorized_keys): """Install a key into the authorized_keys file.""" # Make the directory containing the authorized_keys # file, if it doesn't exist. (Typically ~/.ssh). # Ignore errors; we'll fail shortly if we can't # create the authkeys file. try: os.makedirs(os.path.dirname(authorized_keys), 0o700) except OSError: pass keydata = open(keyfile).read() target_fd = os.open(authorized_keys, os.O_RDWR | os.O_CREAT, 0o600) self.install_key_data(keydata, os.fdopen(target_fd, 'w+')) def find_match_rsync(self, rule): # pylint: disable-msg=R0911,R0912 """Handle rsync commands.""" return self.subvalidators['rsync'].validate_command( self.original_command_list[:], rule ) def _whichbin(self, name): """shutil.which wrapper for unit test mocking.""" return shutil.which(name) def valid_binary(self, name, allowed): """Determine if name is a valid binary in allowed. Does realpath expansion and returns actual pathname. """ # Return from cache if set and allowed if name in self.which_cache: if self.which_cache[name] in allowed: return self.which_cache[name] else: return else: self.which_cache[name] = None # Find it in our path foundbin = self._whichbin(name) if not foundbin: return # Convert to realpath, store, and return foundbin = os.path.realpath(foundbin) if foundbin not in allowed: return # Store this successful name in cache self.which_cache[name] = foundbin return foundbin def find_match_scp(self, rule): # pylint: disable-msg=R0911,R0912 """Handle scp commands.""" return self.subvalidators['scp'].validate_command( self.original_command_list[:], rule ) def find_match_command(self, rule): """Return a matching (possibly munged) command, if found in rule.""" command_string = rule['command'] command_list = command_string.split() self.logdebug( 'comparing "{}" to "{}"\n'.format( command_list, self.original_command_list ) ) if rule.get('allow_trailing_args'): self.logdebug( 'allow_trailing_args is true - comparing initial list.\n' ) # Verify the initial arguments are all the same if self.original_command_list[: len(command_list)] == command_list: self.logdebug('initial list is same\n') return {'command': self.original_command_list} else: self.logdebug('initial list is not same\n') elif rule.get('pcre_match'): if re.search(command_string, self.original_command_string): return {'command': self.original_command_list} elif command_list == self.original_command_list: return {'command': command_list} def find_match(self): """Load the config and find a matching rule. returns the results of find_match_command, a dict of the command and (in the future) other metadata. """ self.load() for yamldoc in self.yamldocs: self.logdebug('\nchecking rule """{}"""\n'.format(yamldoc)) if not yamldoc: continue if not self.check_client_ip(yamldoc): # Rejected - Client IP does not match continue if not self.check_keyname(yamldoc): # Rejected - keyname does not match continue rules = yamldoc.get('allow') if not isinstance(rules, list): rules = [rules] for rule in rules: rule_type = rule.get('rule_type', 'command') if rule_type == 'command': sub = self.find_match_command elif rule_type == 'scp': sub = self.find_match_scp elif rule_type == 'rsync': sub = self.find_match_rsync else: self.log( 'fatal: no such rule_type "{}"\n'.format(rule_type) ) self.raise_and_log_error( ConfigError, 'error parsing config.' ) match = sub(rule) if match: return match # No matches, time to give up. if not self.silent: sys.stderr.write( 'command "{}" rejected.\n'.format(self.original_command_string) ) raise CommandRejected( 'command "{}" denied.'.format(self.original_command_string) ) def exec_command(self): """Glean the command to run and exec. On problems, sys.exit. This method should *never* return. """ if not self.original_command_string: raise SSHEnvironmentError( 'no SSH command found; interactive shell disallowed.' ) command_info = { 'from': self.get_client_ip(), 'keyname': self.keyname, 'ssh_original_command': self.original_command_string, 'time': time.time(), } os.environ['AUTHPROGS_KEYNAME'] = self.keyname retcode = 126 try: match = self.find_match() command_info['command'] = match.get('command') self.logdebug('find_match returned "{}"\n'.format(match)) command = match['command'] retcode = subprocess.call(command) command_info['code'] = retcode self.log('result: {}\n'.format(command_info)) sys.exit(retcode) except (CommandRejected, OSError) as err: command_info['exception'] = '{}'.format(err) self.log('result: {}\n'.format(command_info)) sys.exit(retcode) def main(): # pylint: disable-msg=R0912,R0915 """Main.""" parser = argparse.ArgumentParser() parser.usage = textwrap.dedent( """\ %(prog)s {--run|--install_key|--dump_config} [options] SSH command authenticator. Used to restrict which commands can be run via trusted SSH keys. """ ) group = parser.add_argument_group( 'Run Mode Options', 'These options determine in which mode the authprogs program runs.', ) group.add_argument( '-r', '--run', dest='run', action='store_true', help='Act as ssh command authenticator. Use this ' 'when calling from authorized_keys.', ) group.add_argument( '--dump_config', dest='dump_config', action='store_true', help='Dump configuration (python format) to standard out and exit.', ) group.add_argument( '--install_key', dest='install_key', help='Install the named ssh public key file to authorized_keys.', metavar='FILE', ) group = parser.add_argument_group('Other Options') group.add_argument( '--keyname', dest='keyname', help='Name for this key, used when matching config blocks.', ) group.add_argument( '--configfile', dest='configfile', help='Path to authprogs configuration file. ' 'Defaults to ~/.ssh/authprogs.yaml', metavar='FILE', ) group.add_argument( '--configdir', dest='configdir', help='Path to authprogs configuration directory. ' 'Defaults to ~/.ssh/authprogs.d', metavar='DIR', ) group.add_argument( '--logfile', dest='logfile', help='Write logging info to this file. Defaults to no logging.', metavar='FILE', ) group.add_argument( '--debug', dest='debug', action='store_true', help='Write additional debugging information to --logfile', ) group.add_argument( '--silent', dest='silent', action='store_true', help='Do not tell user their command was rejected on failure.', ) group.add_argument( '--authorized_keys', dest='authorized_keys', default=os.path.expanduser('~/.ssh/authorized_keys'), help='Location of authorized_keys file for ' '--install_key. Defaults to ~/.ssh/authorized_keys', metavar='FILE', ) group.add_argument( '--version', action='store_true', help='Show version and exit.' ) args = parser.parse_args() if args.version: from . import __version__ print(__version__) sys.exit() if not args.configfile: cfg = os.path.expanduser('~/.ssh/authprogs.yaml') if os.path.isfile(cfg): args.configfile = cfg if not args.configdir: cfg = os.path.expanduser('~/.ssh/authprogs.d') if os.path.isdir(cfg): args.configdir = cfg if args.debug and not args.logfile: parser.error('--debug requires use of --logfile') ap = None try: ap = AuthProgs( logfile=args.logfile, # pylint: disable-msg=C0103 configfile=args.configfile, configdir=args.configdir, debug=args.debug, keyname=args.keyname, silent=args.silent, ) if args.dump_config: ap.dump_config() sys.exit(0) elif args.install_key: try: ap.install_key(args.install_key, args.authorized_keys) sys.stderr.write('Key installed successfully.\n') sys.exit(0) except InstallError as err: sys.stderr.write('Key install failed: {}'.format(err)) sys.exit(1) elif args.run: ap.exec_command() sys.exit('authprogs command returned - should never happen.') else: parser.error('Not sure what to do. Consider --help') except SSHEnvironmentError as err: ap.log( 'SSHEnvironmentError "{}"\n{}\n'.format(err, traceback.format_exc()) ) sys.exit('authprogs: {}'.format(err)) except ConfigError as err: ap.log('ConfigError "{}"\n{}\n'.format(err, traceback.format_exc())) sys.exit('authprogs: {}'.format(err)) except CommandRejected as err: sys.exit('authprogs: {}'.format(err)) except Exception as err: if ap: ap.log( 'Unexpected exception: {}\n{}\n'.format( err, traceback.format_exc() ) ) else: sys.stderr.write( 'Unexpected exception: {}\n{}\n'.format( err, traceback.format_exc() ) ) sys.exit('authprogs experienced an unexpected exception.') if __name__ == '__main__': sys.exit('This is a library only.') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615592688.0 authprogs-0.7.5/authprogs/rsync.py0000664000175000017500000003150700000000000016646 0ustar00bribri00000000000000from authprogs import authprogs import argparse import os import sys import glob # Valid rsync binaries # We don't want the client to pick a script # that looks like rsync, we want it to be one # of the official ones. ALLOWED_RSYNC_BINARIES = ['/usr/bin/rsync', '/usr/local/bin/rsync'] class ParserError(RuntimeError): """Runtime rsync command line parser error.""" pass class ArgumentParserWrapper(argparse.ArgumentParser): def error(self, message): raise RuntimeError(message) class RsyncValidator(object): """Rsync Parser class""" def __init__(self, authprogs): """AuthProgs rsync parser.""" self.authprogs = authprogs self.logdebug = authprogs.logdebug if os.environ.get('AUTHPROGS_DEBUG_RSYNC_STDERR'): self.logdebug = lambda *x: sys.stderr.write( "DEBUG: {}\n".format(x[0]) ) authprogs.logdebug = lambda *x: sys.stderr.write( "DEBUG: {}\n".format(x[0]) ) self.raise_and_log_error = authprogs.raise_and_log_error self.log = authprogs.log self.parser = None self.boolarg_deny_default = {} self.boolarg_allow_default = {} def validate_rsync_args(self, args): """Verify the rsync args are well formed. Does not actually validate that they are appropriate, just that we got what we expect. """ if not args.server: return False # First pathname for rsync server is always '.' if args.dot_path != '.': return # Possible future improvement: support filenames with spaces # or multiple files per request. # # The problem is choosing to support just one, or differentiating # the two. # # For example these: # rsync remote:'my documents' :bin /tmp # multiple files # rsync remote:'my documents bin' /tmp # single file # produce the identical SSH_ORIGINAL_COMMAND # # For now, we simply require that spaces aren't used. if not args.local_path: self.logdebug('No local path!!') return False if args.extra: self.logdebug('spaces in local pathname not supported') return False return True def fixup_command(self, command, rule): """Fix up the command before we process.""" # Verify binary is valid and replace with realpath version requested_bin = command.pop(0) rsync_bin = self.authprogs.valid_binary( requested_bin, ALLOWED_RSYNC_BINARIES ) if not rsync_bin: self.logdebug( 'skipping rsync processing, binary "{}"' ' not in approved list\n'.format(requested_bin) ) return command.insert(0, rsync_bin) return True def rsync_globpaths(self, name): """Return paths of filename after doing rsync-like expansion.""" paths = self.authprogs.globpaths(name, expanduser=True) return paths def expand_rule(self, rule): """Expand rule options and return new rule. Throws RuntimeError on problems. """ if rule.get('allow_archive'): for expand in ( 'allow_recursive', 'allow_links', 'allow_perms', 'allow_times', 'allow_group', 'allow_owner', 'allow_devices', 'allow_specials', ): if rule.get(expand) is False: raise authprogs.ConfigError( 'Bad rule setting: has both allow_archive and {}=false'.format( expand ) ) else: rule[expand] = True return rule def validate_command(self, command, rule): """Determine if command matches the provided rsync rule. Return if not allowed. Return {'command': [command]} if acceptable. """ rule = self.expand_rule(rule) if not self.fixup_command(command, rule): return orig_args = command[1:] args = self.parse_args(orig_args) self.logdebug("args: {}\n".format(args)) if not self.validate_rsync_args(args): return # Annoying-to-handle options if not self.check_verbose(args, rule): return if not self.check_info(args, rule): return if not self.check_debug(args, rule): return # Good old default-denied booleans for arg, ruleoption in self.boolarg_deny_default.items(): argname = arg.replace('-', '_')[2:] # Get parser version if getattr(args, argname) and not self.feature_allowed( rule, ruleoption, default=False ): self.logdebug( 'Denied --{} when {} not enabled\n'.format(arg, ruleoption) ) return # Good old default-allowed booleans for arg, ruleoption in self.boolarg_allow_default.items(): argname = arg.replace('-', '_')[2:] # Get parser version if getattr(args, argname) and not self.feature_allowed( rule, ruleoption, default=True ): self.logdebug( 'Denied --{} when {} not enabled\n'.format(arg, ruleoption) ) return if args.delete: if not self.feature_allowed(rule, 'allow_delete', default=False): self.logdebug('Denied --delete when allow_delete not set\n') return if args.sender: if not self.feature_allowed(rule, 'allow_download', default=False): self.logdebug('Denied download when allow_download not set\n') return else: if not self.feature_allowed(rule, 'allow_upload', default=False): self.logdebug('Denied upload when allow_upload not set\n') return if not self.check_file_restrictions(args, rule): return return {'command': command} def check_file_restrictions(self, args, rule): """Check paths/path_startswith restrictions.""" if 'paths' not in rule and 'path_startswith' not in rule: return True # If checking files or dirs (not yet implemented) # get the real paths, post globbing matching_paths = self.rsync_globpaths(args.local_path) if isinstance(matching_paths, list): unmatched = set(matching_paths) else: unmatched = set([matching_paths]) self.logdebug( f'args.local_path {args.local_path} and unmatched={unmatched}\n' ) if not unmatched: self.logdebug( 'Found no file match for {}\n'.format(args.local_path) ) return if 'paths' in rule: for filename in list(unmatched): for path in rule['paths']: if path.endswith('/'): path = path[:-1] if filename == path: unmatched.remove(filename) break if 'path_startswith' in rule: for filename in list(unmatched): for path_startswith in rule['path_startswith']: if not path_startswith.endswith('/'): path_startswith += '/' if (filename + '/').startswith(path_startswith): self.logdebug( 'path {} matches path_startswith {}\n'.format( filename, path_startswith ) ) unmatched.remove(filename) break if unmatched: self.logdebug( 'Following requested paths not matched: {}\n'.format( ';'.join(unmatched) ) ) return else: self.logdebug('All paths matched: {}\n'.format(';'.join(unmatched))) return True def feature_allowed(self, rule, ruleparam, wanted=True, default=True): """Check a generic allow rule. Default is the value if the ruleparam is not set at all If value of ruleparam value matches wanted, return True if ruleparam value not present then if default matches wanted return True Else return False """ paramvalue = rule.get(ruleparam, default) if paramvalue not in (True, False): self.raise_and_log_error( authprogs.ConfigError, 'Unknown value "{}" for {}.'.format(paramvalue, ruleparam), ) return paramvalue == wanted def check_verbose(self, args, rule): """Allow verbosity if request is at or below max verbosity.""" if not args.verbose: return True verbosity_setting = rule.get('allow_verbose', True) if verbosity_setting in (True, False): return verbosity_setting return args.verbose <= verbosity_setting def check_info(self, args, rule): """Check --info verbosity.""" if ( not args.info or args.info.lower() == 'none' or rule.get('allow_info', True) ): return True return False def check_debug(self, args, rule): """Check --debug verbosity.""" if ( not args.debug or args.debug.lower() == 'none' or rule.get('allow_debug', True) ): return True return False def boolarg(self, *args, ruleoption=None, ruledefault=False, **kwargs): """Add a boolean arg to our self.parser.""" self.parser.add_argument(*args, action='store_true', **kwargs) if ruleoption: longopt = [x for x in args if x.startswith('--')][0] if ruledefault: self.boolarg_allow_default[longopt] = ruleoption else: self.boolarg_deny_default[longopt] = ruleoption def parse_args(self, args): """Parse rsync args.""" self.parser = ArgumentParserWrapper(add_help=False) self.boolarg('--server') self.boolarg('--sender') self.boolarg( '-c', '--checksum', ruleoption='allow_checksum', ruledefault=True ) self.boolarg('-r', '--recursive', ruleoption='allow_recursive') self.boolarg('--del', dest='delete') self.boolarg('--delete', dest='delete') self.boolarg('--delete-after', dest='delete') self.boolarg('--delete-before', dest='delete') self.boolarg('--delete-delay', dest='delete') self.boolarg('--delete-during', dest='delete') self.boolarg('--delete-excluded', dest='delete') self.boolarg('--delete-missing-args', dest='delete') self.boolarg('-A', '--acls', ruleoption='allow_acls') self.boolarg('--devices', ruleoption='allow_devices') self.boolarg('-g', '--group', ruleoption='allow_group') self.boolarg('-l', '--links', ruleoption='allow_links') self.boolarg('-o', '--owner', ruleoption='allow_owner') self.boolarg('-p', '--perms', ruleoption='allow_perms') self.boolarg('--specials', ruleoption='allow_specials') self.boolarg( '-t', '--times', ruleoption='allow_times', ruledefault=True ) self.boolarg('-D') self.parser.add_argument('-v', '--verbose', action='count', default=0) self.parser.add_argument('--info') self.parser.add_argument('--debug') self.parser.add_argument('dot_path', nargs='?') # Should always be '.' self.parser.add_argument('local_path', nargs='?') # Is the local path self.parser.add_argument('extra', nargs='*') # extraneous arguments # Note: the '-e' argument is handled strangely in rsync. # On the server side it's a dot, followed by a select list of # client options, presumably for the purpose of logging. However # it is completely ignored by rsync server. # We could add support for that later, though it is of course # something the client could lie about to us. self.parser.add_argument('-e', '--rsh') try: rsync_args = self.parser.parse_args(args) except Exception as err: self.log('authprogs.rsync command parser failed: {}.\n'.format(err)) raise ParserError('authprogs.rsync parser failure') # Post process arguments, e.g. expand some helper arguments, --no-foo if rsync_args.D: rsync_args.specials = True rsync_args.devices = True return rsync_args if __name__ == '__main__': sys.exit('This is a library only.') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1613943612.0 authprogs-0.7.5/authprogs/scp.py0000664000175000017500000001333300000000000016272 0ustar00bribri00000000000000from authprogs import authprogs import argparse import os import sys # Valid scp binaries # We don't want the client to pick a script # that looks like scp, we want it to be one # of the official ones. ALLOWED_SCP_BINARIES = ['/usr/bin/scp', '/usr/local/bin/scp'] class ParserError(RuntimeError): """Runtime scp command line parser error.""" pass class ArgumentParserWrapper(argparse.ArgumentParser): def error(self, message): raise RuntimeError(message) class ScpValidator(object): """Scp Parser class""" def __init__(self, authprogs): """AuthProgs scp parser.""" self.authprogs = authprogs self.logdebug = authprogs.logdebug if os.environ.get('AUTHPROGS_DEBUG_SCP_STDERR'): self.logdebug = lambda *x: sys.stderr.write( "DEBUG: {}\n".format(x[0]) ) authprogs.logdebug = lambda *x: sys.stderr.write( "DEBUG: {}\n".format(x[0]) ) self.raise_and_log_error = authprogs.raise_and_log_error self.log = authprogs.log self.parser = None def fixup_command(self, command, rule): """Fix up the command before we process.""" # Verify binary is valid and replace with realpath version requested_bin = command.pop(0) scp_bin = self.authprogs.valid_binary( requested_bin, ALLOWED_SCP_BINARIES ) if not scp_bin: self.logdebug( 'skipping scp processing, binary "{}"' ' not in approved list\n'.format(requested_bin) ) return command.insert(0, scp_bin) return command def validate_command(self, command, rule): """Determine if command matches the provided scp rule. Return None if not allowed. Return {'command': [command]} if acceptable. """ orig_list = command[:] command = self.fixup_command(command, rule) if not command: return args = self.parse_args(command[1:]) if len(args.extra) != 1: self.log('scp cmdline parsing expecting exactly one path.') return if args.authprogs_reject: return filepath = args.extra[0] if args.download and args.upload: self.logdebug( 'client scp requested upload and download' ' simultaneously - rejecting.' ) return if not (args.download or args.upload): self.logdebug( 'client scp requested neither upload nor download' ' - rejecting.' ) if args.download: if not rule.get('allow_download'): self.logdebug('scp denied - downloading forbidden.\n') return if args.upload: if not rule.get('allow_upload'): self.log('scp denied - uploading forbidden.\n') return if 'allow_recursion' in rule: self.log( 'WARNING: deprecated option "allow_recursion" set in rule.' ' Update to allow_recursive.\n' ) vals = set( [rule.get('allow_recursive'), rule.get('allow_recursion')] ) if True in vals and False in vals: self.log( 'CRITICAL: both allow_recursive and allow_recursion are set,' ' but to different values. Skipping bad rule.\n' ) return if 'allow_recursive' not in rule: rule['allow_recursive'] = rule['allow_recursion'] if args.recursive: if not rule.get('allow_recursive'): self.log('scp denied - recursive transfers forbidden.\n') return if args.permissions: if not rule.get('allow_permissions', 'true'): self.log('scp denied - set/getting permissions forbidden.\n') return if 'files' in rule: self.log( 'WARNING: deprecated option "files" set in rule.' ' Update to paths.\n' ) if 'paths' in rule: rule['paths'].extend(rule['files']) else: rule['paths'] = rule['files'] if rule.get('paths'): files = rule.get('paths') if not isinstance(files, list): files = [files] if filepath not in files: self.log( 'scp denied - file "{}" - not in approved ' 'list {}\n'.format(filepath, files) ) return # Allow it! return {'command': command} def parse_args(self, args): """Parse scp args.""" self.parser = ArgumentParserWrapper(add_help=False) self.parser.add_argument( '-d', action='store_true', dest='targetshouldbedirectory' ) self.parser.add_argument('-f', action='store_true', dest='download') self.parser.add_argument('-t', action='store_true', dest='upload') self.parser.add_argument('-r', action='store_true', dest='recursive') self.parser.add_argument('-p', action='store_true', dest='permissions') self.parser.add_argument('-v', action='store_true', dest='verbose') self.parser.add_argument( '-S', action='store_true', dest='authprogs_reject' ) self.parser.add_argument('extra', nargs='*') try: scp_args = self.parser.parse_args(args) except Exception as err: self.log('authprogs.scp command parser failed: {}.\n'.format(err)) raise ParserError('authprogs.scp parser failure: {} '.format(err)) return scp_args ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1615765592.6598747 authprogs-0.7.5/authprogs.egg-info/0000775000175000017500000000000000000000000016622 5ustar00bribri00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615765592.0 authprogs-0.7.5/authprogs.egg-info/PKG-INFO0000664000175000017500000000325200000000000017721 0ustar00bribri00000000000000Metadata-Version: 1.2 Name: authprogs Version: 0.7.5 Summary: SSH Command Authenticator Home-page: http://github.com/daethnir/authprogs Author: Bri Hatch Author-email: bri@ifokr.org Maintainer: Bri Hatch Maintainer-email: bri@ifokr.org License: GPLv2 Description: Authprogs --------- `authprogs` is an SSH command authenticator. It is invoked on an ssh server and decides if the command requested by the ssh client should be run or rejected based on logic in the `authprogs` configuration file. Passwordless SSH using ssh identities or pubkeys can enable all sorts of wonderful automation, for example running unattended batch jobs, slurping down backups, or pushing out code. Unfortunately a key, once trusted, is allowed by default to run anything on that system, not just the small set of commands you actually need. If the key is compromised, you are at risk of a security breach. This could be catastrophic, for example if the access is to the root account. Authprogs is run on the SSH server and compares the requested command against the `authprogs` configuration file/files. This enables `authprogs` to make intelligent decisions based on things such as the command itself, the SSH key that was used, the client IP, and such. `authprogs` is enabled by using the `command=` option in the `authorized_keys` file. For usage see the full authprogs man page in the doc directory. Keywords: authprogs ssh pubkey identity authorized_keys security Platform: UNKNOWN ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615765592.0 authprogs-0.7.5/authprogs.egg-info/SOURCES.txt0000664000175000017500000000103100000000000020501 0ustar00bribri00000000000000AUTHORS AUTHORS.md COPYING DEVELOPMENT DEVELOPMENT.md INSTALL INSTALL.md MANIFEST.in README README.md README.rsync README.rsync.md TODO TODO.md setup.py authprogs/__init__.py authprogs/authprogs.py authprogs/rsync.py authprogs/scp.py authprogs.egg-info/PKG-INFO authprogs.egg-info/SOURCES.txt authprogs.egg-info/dependency_links.txt authprogs.egg-info/entry_points.txt authprogs.egg-info/not-zip-safe authprogs.egg-info/requires.txt authprogs.egg-info/top_level.txt doc/authprogs.1 doc/authprogs.html doc/authprogs.md doc/description.rst././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615765592.0 authprogs-0.7.5/authprogs.egg-info/dependency_links.txt0000664000175000017500000000000100000000000022670 0ustar00bribri00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615765592.0 authprogs-0.7.5/authprogs.egg-info/entry_points.txt0000664000175000017500000000007000000000000022115 0ustar00bribri00000000000000[console_scripts] authprogs = authprogs.authprogs:main ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576635498.0 authprogs-0.7.5/authprogs.egg-info/not-zip-safe0000664000175000017500000000000100000000000021050 0ustar00bribri00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615765592.0 authprogs-0.7.5/authprogs.egg-info/requires.txt0000664000175000017500000000000700000000000021217 0ustar00bribri00000000000000pyyaml ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615765592.0 authprogs-0.7.5/authprogs.egg-info/top_level.txt0000664000175000017500000000001200000000000021345 0ustar00bribri00000000000000authprogs ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1615765592.6638746 authprogs-0.7.5/doc/0000775000175000017500000000000000000000000013661 5ustar00bribri00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615592907.0 authprogs-0.7.5/doc/authprogs.10000664000175000017500000010670000000000000015763 0ustar00bribri00000000000000.\" generated with Ronn-NG/v0.8.0 .\" http://github.com/apjanke/ronn-ng/tree/0.8.0 .TH "AUTHPROGS" "1" "March 2021" "" "" .SH "NAME" \fBauthprogs\fR \- SSH command authenticator .SH "SYNOPSIS" \fBauthprogs \-\-run [options]\fR .P \fBauthprogs \-\-install_key [options]\fR .P \fBauthprogs \-\-dump_config [options]\fR .P \fBauthprogs \-\-help\fR .SH "DESCRIPTION" \fBauthprogs\fR is an SSH command authenticator\. It is invoked on an ssh server and decides if the command requested by the ssh client should be run or rejected based on logic in the \fBauthprogs\fR configuration file\. .P Passwordless SSH using ssh identities or pubkeys can enable all sorts of wonderful automation, for example running unattended batch jobs, slurping down backups, or pushing out code\. Unfortunately a key, once trusted, is allowed by default to run anything on that system, not just the small set of commands you actually need\. If the key is compromised, you are at risk of a security breach\. This could be catastrophic, for example if the access is to the root account\. .P Authprogs is run on the SSH server and compares the requested command against the \fBauthprogs\fR configuration file/files\. This enables \fBauthprogs\fR to make intelligent decisions based on things such as the command itself, the SSH key that was used, the client IP, and such\. .P \fBauthprogs\fR is enabled by using the \fBcommand=\fR option in the \fBauthorized_keys\fR file\. .SH "KEY INSTALLATION" You can install your ssh identities/pubkeys manually, or allow \fBauthprogs\fR to do the work for you\. .SH "MANUAL KEY INSTALLATION" You need to set up your \fB~/\.ssh/authorized_keys\fR file to force invocation of \fBauthprogs\fR for the key or keys you wish to protect\. .P A line of an unrestricted \fBauthorized_key\fR entry might look like this: .IP "" 4 .nf ssh\-rsa AAAAxxxxx\|\.\|\.\|\.xxxxx user@example\.com .fi .IP "" 0 .P When setting up this key to use \fBauthprogs\fR, you add a \fBcommand=\fR option to the very beginning of that line that points to the location where authprogs lives\. For example if \fBauthprogs\fR is in \fB/usr/bin/authprogs\fR, you would use this: .IP "" 4 .nf command="/usr/bin/authprogs \-\-run" ssh\-rsa AAAAxxxxx\|\.\|\.\|\.xxxxx user@example\.com .fi .IP "" 0 .P You must include \fB\-\-run\fR to let \fBauthprogs\fR know it is running in SSH command mode\. .P Authprogs has other command line options you may wish to include as well, for example .IP "" 4 .nf command="/usr/bin/authprogs \-\-keyname=backups \-\-run" ssh\-rsa AAAA\|\.\|\.\|\.xxxxx user@example\.com .fi .IP "" 0 .P Lastly, if you wish, ssh offers a number of other helpful restrictions you may wish to include that are separate from authprogs\. These can be appended right after (or before) the command="" section if you wish\. .IP "" 4 .nf command="/usr/bin/authprogs \-\-run",no\-port\-forwarding,no\-pty ssh\-rsa AAAA\|\.\|\.\|\.xxxxx user@example\.com .fi .IP "" 0 .P See the sshd(8) man page for more information about allowed \fBauthorized_keys\fR configuration options\. .SH "AUTOMATED KEY INSTALLATION" Authprogs is capable of adding your key to your \fBauthorized_keys\fR file (\fB~/\.ssh/authorized_keys\fR by default) programmatically\. It also disables ssh port forwarding by default for this key (a sensible default for most batch jobs\.) .P authprogs will refuse to install a key that is already present in the \fBauthorized_keys\fR file\. .P For example the following .IP "" 4 .nf authprogs \-\-install_key /path/to/backups_key\.pub \-\-keyname=backups .fi .IP "" 0 .P would cause the following line to be added to your \fB~/\.ssh/authorized_keys\fR file: .IP "" 4 .nf command="/usr/bin/authprogs \-\-keyname backups \-\-run",no\-port\-forwarding ssh\-rsa AAAA\|\.\|\.\|\.xxxxx user@example\.com .fi .IP "" 0 .SH "RUN MODE OPTIONS" Authprogs can run in several modes, depending on which of these command line switches you provide\. .TP \fB\-\-run\fR Act in run mode, as from an \fBauthorized_keys\fR file\. .TP \fB\-\-install_key filename\fR Install the key contained in the named file into your \fBauthorized_keys\fR file\. .TP \fB\-\-dump_config\fR Dump the configuration in a python\-style view\. Helpful only for debugging\. .TP \fB\-\-silent\fR Do not inform the user if their command has been rejected\. Default is to let them know it was rejected to prevent confusion\. .TP \fB\-\-help\fR Show help information .SH "OTHER OPTIONS" The following options may apply to multiple run modes, as appropriate\. .TP \fB\-\-keyname key_name\fR This option \'names\' the key, for help in crafting your rules\. Since an account may have multiple keys allowed, this helps us differentiate which one was used so we can make sensible choices\. .IP In run mode, this specifies which name is used when matching in the configuration, e\.g\. .IP "" 4 .nf command="/usr/bin/authprogs \-\-keyname backups \-\-run" \|\.\|\.\|\. .fi .IP "" 0 .IP In key installation mode, this adds the \fB\-\-keyname\fR option to the \fBauthorized_keys\fR entry\. .IP \fBkey_name\fR may contain no whitespace\. .TP \fB\-\-configfile\fR Specifies the \fBauthprogs\fR configuration file to read\. Defaults to \fB~/\.ssh/authprogs\.yaml\fR\. .IP In key installation mode, this adds the \fB\-\-configfile\fR option to the \fBauthorized_keys\fR entry\. .TP \fB\-\-configdir\fR Specifies the \fBauthprogs\fR configuration, in which multiple configuration files can be found\. Defaults to \fB~/\.ssh/authprogs\.d\fR if present\. .IP Files in the configuration directory are read as rules in filename order\. See CONFIGURATION for more info\. .SH "LIMITATIONS" Commands are executed via fork/exec, and are not processed through the shell\. This means you cannot have multiple commands separated by semicolons, pipelines, redirections, backticks, shell builtins, wildcards, variables, etc\. .P Also, you cannot have spaces in any arguments your command runs\. This is because the SSH server takes the command that was specified by the client and squashes it into the \fBSSH_ORIGINAL_COMMAND\fR variable\. By doing this it makes it impossible for us to know what spaces in \fBSSH_ORIGINAL_COMMAND\fR were between arguments and which were part of arguments\. .P Here are some commands that would not work through \fBauthprogs\fR: .IP "\[ci]" 4 \fBssh host "rm /tmp/foo; touch /tmp/success"\fR .IP "\[ci]" 4 \fBssh host "rm /tmp/*\.html"\fR .IP "\[ci]" 4 \fBssh host "cut \-d: \-f 1 /etc/passwd > /tmp/users"\fR .IP "\[ci]" 4 \fBssh host "touch \'/tmp/file with spaces\'"\fR .IP "\[ci]" 4 \fBssh host "for file in /tmp/*\.html; do w3m \-dump $file > $file\.txt; done"\fR .IP "" 0 .P You can work around these limitations by writing a shell script that does what you need and calling that from \fBauthprogs\fR, rather than attempting to run complicated command lines via ssh directly\. .SH "CONFIGURATION FILES" authprogs rules are maintained in one or more configuration files in YAML format\. .P The rules allow you to decide whether the client\'s command should be run based on criteria such as the command itself, the client IP address, and ssh key in use\. .P Rules can be read from a single file (\fB~/\.ssh/authprogs\.yaml\fR by default) or by putting files in a configuration directory (\fB~/\.ssh/authprogs\.d\fR)\. The configuration directory method is most useful when you want to be able to easily add or remove rules without manually editing a single configuration file, such as when installing rules via your configuration tool of choice\. .P All the \fBauthprogs\fR configuration files are concatenated together into one large yaml document which is then processed\. The files are concatenated in the following order: .IP "\[ci]" 4 \fB~/\.ssh/authprogs\.yaml\fR, if present .IP "\[ci]" 4 files in \fB~/\.ssh/authprogs\.d/\fR directory, in asciibetical order .IP "" 0 .P Dotfiles contained in a configuration directory are ignored\. The configuration directory is not recursed; only those files directly contained are processed\. .P Each rule in the configuration file/files is tested in order and once a match is found, processing stops and the command is run\. .P Rules are made of rule selection options (e\.g\. client IP address) and subrules (e\.g\. a list of allowed commands)\. All pieces must match for the command to be run\. .P The general format of a rule is as follows: .IP "" 4 .nf # First rule \- # Selection options # # All must match or we stop processing this rule\. selection_option_1: value selection_option_2: value # The allow block, aka subrules # # This lets us group a bunch of possible commands # into one rule\. Otherwise we\'d need a bunch of # rules where you repeat selection options\. allow: \- rule_type: value rule_param_1: value rule_param_2: value \- rule_type: value2 rule_param_1: value rule_param_2: value # Next rule \- selection_option_3: value \|\.\|\.\|\. .fi .IP "" 0 .P Some of the keys take single arguments, while others may take lists\. See the definition of each to understand the values it accepts\. .SH "RULE SELECTION OPTIONS" These configuration options apply to the entire rule, and help you limit under what conditions the rule matches\. .IP "\[ci]" 4 from: This is a single value or list of values that define what SSH client IP addresses are allowed to match this rule\. The client IP address is gleaned by environment variables set by the SSH server\. Any from value may be an IP address or a CIDR network\. .IP "" 0 .P Examples: .IP "" 4 .nf \- from: 192\.168\.1\.5 \|\.\|\.\|\. \- from: [192\.168\.0\.1, 10\.0\.0\.3] \|\.\|\.\|\. \- from: \- 192\.168\.0\.0/24 \- 10\.10\.0\.3 \|\.\|\.\|\. .fi .IP "" 0 .IP "\[ci]" 4 keynames: This is a single value or list of values that define which SSH pubkeys are allowed to match this rule\. The keyname is specified by the \fB\-\-keyname foo\fR parameter in the authprogs command line in the entry in \fBauthorized_keys\fR\. .IP "" 0 .P Examples: .IP "" 4 .nf \- keynames: backups \|\.\|\.\|\. \- keynames: [repo_push, repo_pull] \|\.\|\.\|\. \- keynames: \- repo_push \- repo_pull \|\.\|\.\|\. .fi .IP "" 0 .SH "ALLOW SUBRULE SECTION" The allow section of a rule is a single subrule or list of subrules\. .P Subrules can be simple, for example the explicit command match, or be more program\-aware such as scp support\. You specify which kind of subrule you want with the \fBrule_type\fR option: .IP "" 4 .nf \- allow: \- rule_type: command command: /bin/touch /tmp/timestamp \- command: /bin/rm /tmp/bar \- rule_type: scp allow_upload: true \|\.\|\.\|\. .fi .IP "" 0 .P See the separate subrules sections below for how to craft each type\. .SH "COMMAND SUBRULES" This section applies if \fBrule_type\fR is set to \fBcommand\fR or is not present at all\. .P The command requested by the client is compared to the command listed in the rule\. (Spaces are squashed together\.) If it matches, then the command is run\. .P Note that the command must be \fIexactly\fR the same; \fBauthprogs\fR is not aware of arguments supported by a command, so it cannot realise that \fB"ls \-la"\fR and \fB"ls \-a \-l"\fR and \fB"ls \-al"\fR and \fB"ls \-l \-a"\fR are all the same\. You can list multiple commands to allow you to accept variants of a command if necessary\. .P The simplest configuration looks like this: .IP "" 4 .nf \- allow: command: /bin/true .fi .IP "" 0 .P Or you can provide a list of commands: .IP "" 4 .nf \- allow: \- command: /bin/true \- command: /bin/false .fi .IP "" 0 .P A number of optional settings can tweak how command matching is performed\. .IP "\[ci]" 4 \fBallow_trailing_args: true\fR: This setting allows you to specify a partial command that will match as long as the command requested by the client is the same or longer\. This allows you to avoid listing every variant of a command that the client may wish to run\. .IP Examples: .IP "" 4 .nf \- allow: \- command: /bin/echo allow_trailing_args: true \- command: /bin/ls allow_trailing_args: true \- command: /bin/rm \-i allow_trailing_args: true .fi .IP "" 0 .IP "\[ci]" 4 \fBpcre_match: true\fR: Compare the command using pcre regular expressions, rather than doing an explicit match character by character\. The regex is \fInot\fR anchored at the beginning nor end of the string, so if you wish to anchor it is your responsibility to do so\. .IP Caution: never underestimate the sneakiness of an adversary who may find a way to match your regex and still do something nasty\. .IP Examples: .IP "" 4 .nf \- allow: \- # Touch the foo file, allowing any # optional command line params # before the filename command: ^touch\e\es+(\-\e\eS+\e\es+)*foo$ pcre_match: true \- # attempt to allow rm of files in /var/tmp # but actually would fail to catch malicious # commands e\.g\. /var/tmp/\.\./\.\./etc/passwd # # As I said, be careful with pcre matching!!! command: ^/bin/rm\e\es+(\-\e\eS+\e\es+)*/var/tmp/\e\eS*$ pcre_match: true .fi .IP "" 0 .IP "" 0 .SH "RSYNC SUBRULES" authprogs has special support for rsync file transfer\. You are not required to use this \- you could use a simple command subrules to match explicit rsync commands \- but using an rsync\-specific subrule offers you greater flexibility\. .P Rsync support is in beta, so please raise any bugs found\. Supporting the full set of rsync command line options is a moving target\. .P To specify rsync mode, use \fBrule_type: rsync\fR\. .P The rsync options are as follows\. .IP "\[ci]" 4 \fBrule_type: rsync\fR: This indicates that this is an rsync subrule\. .IP "\[ci]" 4 \fBallow_upload: false|true\fR: Allow files to be uploaded to the ssh server\. Defaults to false\. .IP "\[ci]" 4 \fBallow_download: false|true\fR: Allow files to be downloaded from the ssh server\. Defaults to false\. .IP "\[ci]" 4 \fBallow_archive: false|true\fR: Allow file archive, i\.e\. the options that are set when using \fB\-a\fR or \fB\-\-archive\fR\. This is used to simplify \fBauthprogs\fR configuration files\. Specifying this and negating one of he associated options (e\.g\. \fBallow_recursive: false\fR) is considered an error\. Defaults to false\. .IP "\[ci]" 4 \fBpaths\fR: a list of explicit files/directories that are allowed to match\. Files specified by the client will be resolved via \fBrealpath\fR to avoid any symlink trickery, so members of \fBpaths\fR must be the real paths\. .IP WARNING: specifying a directory in \fBpaths\fR would allow rsync to act on any files therein at potentially infinite depth, e\.g\. when \fBallow_recursive\fR is set, or the client uses \fB\-\-files\-from\fR\. If you want to restrict to specific files you must name them explicitly\. .IP See RSYNC SYMLINK SUPPORT for potential limitations to \fBpaths\fR\. .IP "\[ci]" 4 \fBpath_startswith\fR: a list of pathname prefixes that are allowed to match\. Files specified by the client will be resolved via \fBrealpath\fR and if they start with the name provided then they will be allowed\. .IP This is a simple prefix match\. For example if you had .IP "" 4 .nf path_startswith: [ /tmp ] .fi .IP "" 0 .IP then it would match all of the following .IP "" 4 .nf /tmp /tmp/ /tmpfiles # may not be what you meant! /tmp/foo\.txt /tmp/dir1/dir2/bar\.txt .fi .IP "" 0 .IP If you want it to match only a directory (and any infinite subdirectories) be sure to include a trailing slash, e\.g\. \fB/tmp/\fR .IP See RSYNC SYMLINK SUPPORT for potential limitations to \fBpaths\fR\. .IP "\[ci]" 4 \fBallow_acls: false|true\fR: Allow syncing of file ACLs\. (\fB\-\-acls\fR)\. Defaults to false\. .IP "\[ci]" 4 \fBallow_checksum: true|false\fR: Allow checksum method for identifying files that need syncing\. (\fB\-c\fR / \fB\-\-checksum\fR) Defaults to true\. .IP "\[ci]" 4 \fBallow_debug: true|false\fR: Allow fine\-grained debug verbosity\. (\fB\-\-debug FLAGS\fR)\. No support for sanity checking the debug flags that are specified\. Defaults to true\. .IP "\[ci]" 4 \fBallow_delete: false|true\fR: Allow any of the delete options\. (\fB\-\-del\fR \fB\-\-delete\fR \fB\-\-delete\-after\fR \fB\-\-delete\-before\fR \fB\-\-delete\-delay\fR \fB\-\-delete\-during\fR \fB\-\-delete\-excluded\fR \fB\-\-delete\-missing\-args\fR)\. Defaults to false\. .IP "\[ci]" 4 \fBallow_devices: false|true\fR: Allow syncing of device files\. (\fB\-\-devices\fR)\. Defaults to false\. .IP "\[ci]" 4 \fBallow_group: false|true\fR: Allow group change\. (\fB\-g \-\-group\fR)\. Defaults to false\. .IP "\[ci]" 4 \fBallow_info: true|false\fR: Allow fine\-grained info verbosity\. (\fB\-info FLAGS\fR)\. No support for sanity checking the info flags that are specified\. Defaults to true\. .IP "\[ci]" 4 \fBallow_links: false|true\fR: Allow copying symlinks as symlinks\. (\fB\-l \-\-links\fR)\. Defaults to false\. .IP "\[ci]" 4 \fBallow_group: false|true\fR: Allow ownership change\. (\fB\-o \-\-owner\fR)\. Defaults to false\. .IP "\[ci]" 4 \fBallow_perms: false|true\fR: Allow perms change\. (\fB\-p \-\-perms\fR)\. Defaults to false\. .IP "\[ci]" 4 \fBallow_recursive: false|true\fR: Allow recursive sync\. (\fB\-r \-\-recursive\fR)\. Defaults to false\. .IP "\[ci]" 4 \fBallow_specials: false|true\fR: Allow syncing of special files, e\.g\. fifos\. (\fB\-\-specials\fR)\. Defaults to false\. .IP "\[ci]" 4 \fBallow_times: true|false\fR: Allow setting synced file times\. (\fB\-t \-\-times\fR)\. Defaults to true\. .IP "\[ci]" 4 \fBallow_verbose: true|false|#\fR: Allow verbose output\. (\fB\-v \-\-verbose\fR)\. Rsync allows multiple \-v options, so this option accepts true (allow any verbosity), false (deny any verbosity), or a number which indicates the maximum number of \fB\-v\fR option that are allowed, e\.g\. \fB2\fR would allow \fB\-v\fR or \fB\-vv\fR but not \fB\-vvv\fR\. Defaults to true\. .IP "" 0 .SS "RSYNC COMMAND LINE OPTIONS" Not all rsync options are currently implemented in \fBauthprogs\fR\. .P If an option is listed as "\fInot implemented\fR" then there are two possibilities in how \fBauthprogs\fR will behave: .IP "" 4 .nf * if the option is no actually sent on the remote command line then `authprogs` is blissfully unaware and the command will succeed\. Many options are actually client\-side only\. We have not thoroughly investigated every single option yet\. * if the option is sent on the remote command line then `authprogs` will fail\. .fi .IP "" 0 .P Here is the list of rsync options and their current \fBauthprogs\fR support status: .IP "" 4 .nf rsync client arg authprogs support \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- \-\-append \-\-append\-verify \-\-backup\-dir \-\-bwlimit \-\-checksum\-seed \-\-chown converted to \-\-usermap and \-\-groupmap \-\-compare\-dest \-\-compress\-level \-\-contimeout \-\-copy\-dest \-\-copy\-unsafe\-links \-\-debug allow_debug \-\-del allow_delete \-\-delay\-updates \-\-delete allow_delete \-\-delete\-after allow_delete \-\-delete\-before allow_delete \-\-delete\-delay allow_delete \-\-delete\-during allow_delete \-\-delete\-excluded allow_delete \-\-delete\-missing\-args allow_delete \-\-devices allow_devices \-\-existing \-\-fake\-super \-\-files\-from \-\-force \-\-groupmap \-\-iconv \-\-ignore\-errors \-\-ignore\-existing \-\-ignore\-missing\-args \-\-info allow_info \-\-inplace \-\-link\-dest \-\-list\-only \-\-log\-file \-\-log\-file\-format \-\-max\-delete \-\-max\-size \-\-min\-size \-\-new\-compress \-\-no\-XXXXX (negating options, e\.g\. \-\-no\-r) \-\-numeric\-ids \-\-only\-write\-batch \-\-outbuf \-\-partial \-\-partial\-dir \-\-preallocate \-\-protocol \-\-read\-batch \-\-remove\-sent\-files # deprecated version of remove\-source\-files \-\-remove\-source\-files \-\-safe\-links \-\-size\-only \-\-skip\-compress \-\-specials allow_specials \-\-stats \-\-stop\-at \-\-suffix \-\-super \-\-time\-limit \-\-timeout \-\-usermap \-\-write\-batch \-0, \-\-from0 \-@, \-\-modify\-window \-A, \-\-acls allow_acls \-B, \-\-block\-size \-C, \-\-cvs\-exclude \-D allow_devices and allow_specials \-E, \-\-executability \-H, \-\-hard\-links \-I, \-\-ignore\-times \-J, \-\-omit\-link\-times \-K, \-\-keep\-dirlinks \-L, \-\-copy\-links \-O, \-\-omit\-dir\-times \-P Same as \-\-partial \-\-progress \-R, \-\-relative \-S, \-\-sparse \-T, \-\-temp\-dir \-W, \-\-whole\-file \-X, \-\-xattrs \-a, \-\-archive Same as \-rlptgoD; See those options \-\-progress \-b, \-\-backup \-c, \-\-checksum allow_checksum \-d, \-\-dirs \-f, \-\-filter \-g, \-\-group allow_group \-i, \-\-itemize\-changes \-k, \-\-copy\-dirlinks \-l, \-\-links allow_links \-m, \-\-prune\-empty\-dirs \-n, \-\-dry\-run \-o, \-\-owner allow_owner \-p, \-\-perms allow_perms \-r, \-\-recursive allow_recursive \-s, \-\-protect\-args \-t, \-\-times allow_times \-u, \-\-update \-v, \-\-verbose allow_verbose \-x, \-\-one\-file\-system \-y, \-\-fuzzy \-z, \-\-compress \-\-checksum\-choice=STR \-\-exclude\-from \-\-exclude \-\-include\-from \-\-include \-\-rsync\-path \-\-out\-format .fi .IP "" 0 .P The following are server\-side only options that are supported .IP "" 4 .nf \-e, \-\-rsh=COMMAND Value ignored (indicates protocol feature support) \-\-sender When present means download from server, when absent means upload to server\. \-\-server Always present on server .fi .IP "" 0 .P The following rsync client options are only relevant to daemon mode (i\.e\. rsync daemon listening on TCP directly without SSH) or do not end up on the server command line and are thus re not taken into consideration when determining if the command is or is not allowed: .IP "" 4 .nf \-\-address Client\-only option \-\-chmod Client\-only option (Permissions are indicated via rsync protocol, not command line flags\.) \-\-blocking\-io Client\-only option \-\-daemon Daemon\-only option \-\-msgs2stderr Client\-only option \-\-munge\-links Client\-only option \-\-no\-motd Client\-only option \-\-noatime Client\-only option \-\-password\-file Daemon\-only option \-\-port Client\-only option \-\-sockopts Daemon\-only option \-\-version Client\-only option \-4, \-\-ipv4 Client\-only option \-6, \-\-ipv6 Client\-only option \-8, \-\-8\-bit\-output Client\-only option \-F Client\-only option (see \-\-filter) \-M, \-\-remote\-option=OPTION Client\-only option \-h, \-\-human\-readable Client\-only option \-q, \-\-quiet Client\-only option .fi .IP "" 0 .SS "RSYNC BINARY PATH" Rsync must be at an official path to prevent a user\'s environment from choosing one of their programs over the official one\. Official paths are .IP "" 4 .nf * /usr/bin/rsync * /usr/local/bin/rsync .fi .IP "" 0 .P A user who specifies \-\-rsync\-path with a different value, or who has an rsync program earlier in their $PATH will be denied\. .SS "RSYNC SYMLINK SUPPORT" Rsync has multiple ways of handling symlinks depending on command line parameters and what component(s) of a path are symlinks\. .P If you are using \fBpaths\fR or \fBpaths_startswith\fR to limit what files may be uploaded/downloaded then its your responsibility to assure that symlink games are not used to exceed the desired restrictions\. .P For example if the file \fB/tmp/me\.txt\fR is a symlink to \fB/home/wbagg/me\.txt\fR and you had .IP "" 4 .nf \- rule\e_type: rsync allow_upload: true paths: \- /tmp/me\.txt .fi .IP "" 0 .P then if the user ran .IP "" 4 .nf rsync /some/local/file remote:/tmp/me\.txt .fi .IP "" 0 .P then rather than updating the file at \fB/home/wbagg\.me\.txt\fR, the symlink at \fB/tmp/me\.txt\fR would be replaced with a normal file\. .P A future update to \fBauthprogs\fR may attempt to handle symlinks by calling \fBos\.path\.realpath\fR prior to doing comparisons\. .SS "RSYNC PATHNAME GOTCHA" Say you wanted to restrict uploads to just the file \fB/tmp/foo\.txt\fR, you\'d use the following rsync subrule:: .IP "" 4 .nf \- rule\e_type: rsync allow_upload: true paths: \- /tmp/foo\.txt .fi .IP "" 0 .P From an end\-user perspective both of these commands would seem to be allowed from the client machine because they\'d create a file on the remote named \fB/tmp/foo\.txt\fR: .IP "" 4 .nf $ rsync foo\.txt remote:/tmp/foo\.txt # provide full target filename $ rsync foo\.txt remote:/tmp # imply source name for target .fi .IP "" 0 .P However you\'ll find that only the first one works! This is because \fBauthprogs\fR on the server side sees literally just \fB/tmp\fR in the second case\. .P Thus if you wanted to restrict uploads to just the file \fB/tmp/foo\.txt\fR then on the client side you \fBmust\fR run the first (explicit filename) rsync command\. .SS "RSYNC SUBRULE KNOWN AND POSSIBLE BUGS" .IP "\[ci]" 4 If uploading to a file that does not yet exist when you\'ve set \fBpaths\fR this will fail\. Adding a new \fBallow_create\fR option is the most likely solution here, but not yet implemented\. .IP "\[ci]" 4 No investigation of the rsync options \-\-include / \-\-exclude / \-\-files\-from has yet been performed \- may affect path matching security\. .IP "\[ci]" 4 Though we do expand file globs and check each individual path that is returned, we do not explicitly use these resolved files when calling rsync\. (Reason: it\'s possible we exceed the allowed size of a command line with globs that return many files\.) As such if rsync\'s glob and \fBshutils\.glob\fR have different behaviour we may have false positives or negatives\. .IP "\[ci]" 4 When \fBallow_download\fR is disabled client should not be able to get file contents\. However since rsync transfers checksums as part of its protocol it is possible that information about server file contents could be gleaned by comparing checksums to possible content checksums when doing uploads\. .IP "" 0 .SH "SCP SUBRULES" authprogs has special support for scp file transfer\. You are not required to use this \- you could use a simple command subrules to match explicit scp commands \- but using an scp\-specific subrule offers you greater flexibility\. .P To specify scp mode, use \fBrule_type: scp\fR\. .P The scp options are as follows\. .IP "\[ci]" 4 \fBrule_type: scp\fR: This indicates that this is an scp subrule\. .IP "\[ci]" 4 \fBallow_upload: false|true\fR: Allow files to be uploaded to the ssh server\. Defaults to false\. .IP "\[ci]" 4 \fBallow_download: false|true\fR: Allow files to be downloaded from the ssh server\. Defaults to false\. .IP "\[ci]" 4 \fBallow_recursive: false|true\fR: Allow recursive (\-r) file up/download\. Defaults to false\. .IP "\[ci]" 4 \fBallow_recursion: false|true\fR: Deprecated version of \fBallow_recursive\fR\. will be removed in 1\.0 release\. .IP "\[ci]" 4 \fBallow_permissions: true|false\fR: Allow scp to get/set the permissions of the file/files being transferred\. Defaults to false\. .IP "\[ci]" 4 \fBpaths\fR: The paths option allows you to specify which file or files are allowed to be transferred\. If this is not specified then transfers are not restricted based on filename\. .IP Examples: .IP "" 4 .nf \- allow: \- rule_type: scp allow_download: true paths: \- /etc/group \- /etc/passwd \- rule_type: scp allow_upload: true paths: [/tmp/file1, /tmp/file2] .fi .IP "" 0 .IP "" 0 .SH "EXAMPLES" Here is a sample configuration file with multiple rules, going from simple to more complex\. .P Note that this config can be spread around between the \fB~/\.ssh/authprogs\.yaml\fR and \fB~/\.ssh/authprogs\.d\fR directory\. .IP "" 4 .nf # All files should start with an initial solo dash \- # remember, we\'re being concatenated with all other # files! # Simple commands, no IP restrictions\. \- allow: \- command: /bin/tar czvf /backups/www\.tgz /var/www/ \- command: /usr/bin/touch /var/www/\.backups\.complete # Similar, but with IP restrictions \- from: [192\.168\.0\.10, 192\.168\.0\.15, 172\.16\.3\.3] allow: \- command: git \-\-git\-dir=/var/repos/foo/\.git pull \- command: sudo /etc/init\.d/apache2 restart # Some more complicated subrules \- # All of these \'allows\' have the same \'from\' restrictions from: \- 10\.1\.1\.20 \- 10\.1\.1\.21 \- 10\.1\.1\.22 \- 10\.1\.1\.23 allow: # Allow unrestricted ls \- command: /bin/ls allow_trailing_args: true # Allow any \'service apache2 (start|stop)\' commands via sudo \- command: sudo service apache2 allow_trailing_args:true # How about a regex? Allow wget of any https url, outputting # to /tmp/latest \- command: ^/usr/bin/wget\e\es+https://\e\eS+\e\es+\-O\e\es+/tmp/latest$ pcre_match: true # Allow some specific file uploads \- rule_type: scp allow_upload: true paths: \- /srv/backups/host1\.tgz \- /srv/backups/host2\.tgz \- /srv/backups/host3\.tgz # Allow rsync to upload everything, deny any download \- rule_type: rsync allow_upload: true # Allow rsync to recursively sync /tmp/foo/ to the server # in archive mode (\-a, or any subset of \-logptrD) # but do not allow download \- rule_type: rsync allow_upload: true allow_recursive: true allow_archive: true paths: \- /tmp/foo # Allow rsync to write some specific files and any individual # files under /data/lhc/ directory, such as /data/lhc/foo # or /data/lhc/subdir/foo\. # # Disallow download (explicitly listed) or recursive # upload (default false)\. \- rule_type: rsync allow_upload: true allow_download: false paths: \- /srv/htdocs/index\.html \- /srv/htdocs/status\.html path_startswith: \- /data/lhc/ .fi .IP "" 0 .SH "TROUBLESHOOTING" \fB\-\-dump_config\fR is your friend\. If your yaml config isn\'t parsing, consider \fB\-\-dump_config \-\-logfile=/dev/tty\fR for more debug output to find the error\. .SH "FILES" .IP "\[ci]" 4 \fB~/\.ssh/authorized_keys\fR: The default place your key should be installed and configured to call \fBauthprogs\fR\. The actual location can differ if your administrator has changed it\. .IP "\[ci]" 4 \fB~/\.ssh/authprogs\.yaml\fR: Default \fBauthprogs\fR configuration file\. Override with \-\-configfile\. .IP "\[ci]" 4 \fB~/\.ssh/authprogs\.d\fR: Default \fBauthprogs\fR configuration directory\. Override with \-\-configdir\. .IP "" 0 .SH "ENVIRONMENT" authprogs uses the following environment variables that are set by the sshd(8) binary: .IP "\[ci]" 4 \fBSSH_CONNECTION\fR: This is used to determine the client IP address\. .IP "\[ci]" 4 \fBSSH_CLIENT\fR: This is used to determine the client IP address if SSH_CONNECTION was not present\. .IP "\[ci]" 4 \fBSSH_ORIGINAL_COMMAND\fR: The (squashed) original SSH command that was issued by the client\. .IP "" 0 .P authprogs sets the following environment variables for use by the authenticated process .IP "\[ci]" 4 \fBAUTHPROGS_KEYNAME\fR: the value of the \-\-keyname command line\. Will be set to an empty string if no \-\-keyname was set\. .IP "" 0 .SH "EXIT STATUS" On unexpected error or rejecting the command \fBauthprogs\fR will exit 126\. .P If the command was accepted then it returns the exit code of the command that was run\. .P Note that if you\'re invoking ssh via another tool that program may provide a different exit status and provide a misleading error message when \fBauthprogs\fR returns a failure, For example \fBrsync\fR will exit 12 and assume a "protocol problem" rather than a rejection on the server side\. .SH "LOGGING AND DEBUGGING" If a \fB\-\-logfile\fR is specified then it will be opened in append mode and a line about each command that is attempted to be run will be written to it\. The line itself is in the form of a python dictionary\. .P If \fBauthprogs\fR is run with \fB\-\-debug\fR, then this logfile will get increased debugging information, including the configuration, rule matching status as they are checked, etc\. .SH "HISTORY" A perl version of \fBauthprogs\fR was originally published at https://www\.hackinglinuxexposed\.com/articles/20030115\.html in 2003\. This is a complete rewrite in python, with a more extensible configuration, and avoiding some of the limitations of the former\. .SH "SEE ALSO" ssh(1), sshd(8), scp(1)\. .SH "AUTHOR" Bri Hatch \fI\%mailto:bri@ifokr\.org\fR ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615592907.0 authprogs-0.7.5/doc/authprogs.html0000664000175000017500000011202700000000000016566 0ustar00bribri00000000000000

authprogs(1) -- SSH command authenticator

SYNOPSIS

authprogs --run [options]

authprogs --install_key [options]

authprogs --dump_config [options]

authprogs --help

DESCRIPTION

authprogs is an SSH command authenticator. It is invoked on an ssh server and decides if the command requested by the ssh client should be run or rejected based on logic in the authprogs configuration file.

Passwordless SSH using ssh identities or pubkeys can enable all sorts of wonderful automation, for example running unattended batch jobs, slurping down backups, or pushing out code. Unfortunately a key, once trusted, is allowed by default to run anything on that system, not just the small set of commands you actually need. If the key is compromised, you are at risk of a security breach. This could be catastrophic, for example if the access is to the root account.

Authprogs is run on the SSH server and compares the requested command against the authprogs configuration file/files. This enables authprogs to make intelligent decisions based on things such as the command itself, the SSH key that was used, the client IP, and such.

authprogs is enabled by using the command= option in the authorized_keys file.

KEY INSTALLATION

You can install your ssh identities/pubkeys manually, or allow authprogs to do the work for you.

MANUAL KEY INSTALLATION

You need to set up your ~/.ssh/authorized_keys file to force invocation of authprogs for the key or keys you wish to protect.

A line of an unrestricted authorized_key entry might look like this:

ssh-rsa AAAAxxxxx...xxxxx user@example.com

When setting up this key to use authprogs, you add a command= option to the very beginning of that line that points to the location where authprogs lives. For example if authprogs is in /usr/bin/authprogs, you would use this:

command="/usr/bin/authprogs --run" ssh-rsa AAAAxxxxx...xxxxx user@example.com

You must include --run to let authprogs know it is running in SSH command mode.

Authprogs has other command line options you may wish to include as well, for example

command="/usr/bin/authprogs --keyname=backups --run" ssh-rsa AAAA...xxxxx user@example.com

Lastly, if you wish, ssh offers a number of other helpful restrictions you may wish to include that are separate from authprogs. These can be appended right after (or before) the command="" section if you wish.

command="/usr/bin/authprogs --run",no-port-forwarding,no-pty ssh-rsa AAAA...xxxxx user@example.com

See the sshd(8) man page for more information about allowed authorized_keys configuration options.

AUTOMATED KEY INSTALLATION

Authprogs is capable of adding your key to your authorized_keys file (~/.ssh/authorized_keys by default) programmatically. It also disables ssh port forwarding by default for this key (a sensible default for most batch jobs.)

authprogs will refuse to install a key that is already present in the authorized_keys file.

For example the following

authprogs --install_key /path/to/backups_key.pub --keyname=backups

would cause the following line to be added to your ~/.ssh/authorized_keys file:

command="/usr/bin/authprogs --keyname backups --run",no-port-forwarding ssh-rsa AAAA...xxxxx user@example.com

RUN MODE OPTIONS

Authprogs can run in several modes, depending on which of these command line switches you provide.

  • --run: Act in run mode, as from an authorized_keys file.

  • --install_key filename: Install the key contained in the named file into your authorized_keys file.

  • --dump_config: Dump the configuration in a python-style view. Helpful only for debugging.

  • --silent: Do not inform the user if their command has been rejected. Default is to let them know it was rejected to prevent confusion.

  • --help: Show help information

OTHER OPTIONS

The following options may apply to multiple run modes, as appropriate.

  • --keyname key_name: This option 'names' the key, for help in crafting your rules. Since an account may have multiple keys allowed, this helps us differentiate which one was used so we can make sensible choices.

    In run mode, this specifies which name is used when matching in the configuration, e.g.

    command="/usr/bin/authprogs --keyname backups --run" ...
    

    In key installation mode, this adds the --keyname option to the authorized_keys entry.

    key_name may contain no whitespace.

  • --configfile: Specifies the authprogs configuration file to read. Defaults to ~/.ssh/authprogs.yaml.

    In key installation mode, this adds the --configfile option to the authorized_keys entry.

  • --configdir: Specifies the authprogs configuration, in which multiple configuration files can be found. Defaults to ~/.ssh/authprogs.d if present.

    Files in the configuration directory are read as rules in filename order. See CONFIGURATION for more info.

LIMITATIONS

Commands are executed via fork/exec, and are not processed through the shell. This means you cannot have multiple commands separated by semicolons, pipelines, redirections, backticks, shell builtins, wildcards, variables, etc.

Also, you cannot have spaces in any arguments your command runs. This is because the SSH server takes the command that was specified by the client and squashes it into the SSH_ORIGINAL_COMMAND variable. By doing this it makes it impossible for us to know what spaces in SSH_ORIGINAL_COMMAND were between arguments and which were part of arguments.

Here are some commands that would not work through authprogs:

  • ssh host "rm /tmp/foo; touch /tmp/success"
  • ssh host "rm /tmp/*.html"
  • ssh host "cut -d: -f 1 /etc/passwd > /tmp/users"
  • ssh host "touch '/tmp/file with spaces'"
  • ssh host "for file in /tmp/*.html; do w3m -dump $file > $file.txt; done"

You can work around these limitations by writing a shell script that does what you need and calling that from authprogs, rather than attempting to run complicated command lines via ssh directly.

CONFIGURATION FILES

authprogs rules are maintained in one or more configuration files in YAML format.

The rules allow you to decide whether the client's command should be run based on criteria such as the command itself, the client IP address, and ssh key in use.

Rules can be read from a single file (~/.ssh/authprogs.yaml by default) or by putting files in a configuration directory (~/.ssh/authprogs.d). The configuration directory method is most useful when you want to be able to easily add or remove rules without manually editing a single configuration file, such as when installing rules via your configuration tool of choice.

All the authprogs configuration files are concatenated together into one large yaml document which is then processed. The files are concatenated in the following order:

  • ~/.ssh/authprogs.yaml, if present
  • files in ~/.ssh/authprogs.d/ directory, in asciibetical order

Dotfiles contained in a configuration directory are ignored. The configuration directory is not recursed; only those files directly contained are processed.

Each rule in the configuration file/files is tested in order and once a match is found, processing stops and the command is run.

Rules are made of rule selection options (e.g. client IP address) and subrules (e.g. a list of allowed commands). All pieces must match for the command to be run.

The general format of a rule is as follows:

# First rule
-
  # Selection options
  #
  # All must match or we stop processing this rule.
  selection_option_1: value
  selection_option_2: value

  # The allow block, aka subrules
  #
  # This lets us group a bunch of possible commands
  # into one rule. Otherwise we'd need a bunch of
  # rules where you repeat selection options.

  allow:
    -
      rule_type: value
      rule_param_1: value
      rule_param_2: value
    -
      rule_type: value2
      rule_param_1: value
      rule_param_2: value

# Next rule
-
  selection_option_3: value
...

Some of the keys take single arguments, while others may take lists. See the definition of each to understand the values it accepts.

RULE SELECTION OPTIONS

These configuration options apply to the entire rule, and help you limit under what conditions the rule matches.

  • from: This is a single value or list of values that define what SSH client IP addresses are allowed to match this rule. The client IP address is gleaned by environment variables set by the SSH server. Any from value may be an IP address or a CIDR network.

Examples:

-
  from: 192.168.1.5
  ...

-
  from: [192.168.0.1, 10.0.0.3]
  ...

-
  from:
    - 192.168.0.0/24
    - 10.10.0.3
  ...
  • keynames: This is a single value or list of values that define which SSH pubkeys are allowed to match this rule. The keyname is specified by the --keyname foo parameter in the authprogs command line in the entry in authorized_keys.

Examples:

-
  keynames: backups
  ...

-
  keynames: [repo_push, repo_pull]
  ...

-
  keynames:
    - repo_push
    - repo_pull
  ...

ALLOW SUBRULE SECTION

The allow section of a rule is a single subrule or list of subrules.

Subrules can be simple, for example the explicit command match, or be more program-aware such as scp support. You specify which kind of subrule you want with the rule_type option:

-
  allow:
    -
      rule_type: command
      command: /bin/touch /tmp/timestamp
    -
      command: /bin/rm /tmp/bar
    -
      rule_type: scp
      allow_upload: true
...

See the separate subrules sections below for how to craft each type.

COMMAND SUBRULES

This section applies if rule_type is set to command or is not present at all.

The command requested by the client is compared to the command listed in the rule. (Spaces are squashed together.) If it matches, then the command is run.

Note that the command must be exactly the same; authprogs is not aware of arguments supported by a command, so it cannot realise that "ls -la" and "ls -a -l" and "ls -al" and "ls -l -a" are all the same. You can list multiple commands to allow you to accept variants of a command if necessary.

The simplest configuration looks like this:

-
  allow:
    command: /bin/true

Or you can provide a list of commands:

-
  allow:
    - command: /bin/true
    - command: /bin/false

A number of optional settings can tweak how command matching is performed.

  • allow_trailing_args: true: This setting allows you to specify a partial command that will match as long as the command requested by the client is the same or longer. This allows you to avoid listing every variant of a command that the client may wish to run.

    Examples:

    - allow: - command: /bin/echo allow_trailing_args: true - command: /bin/ls allow_trailing_args: true - command: /bin/rm -i allow_trailing_args: true

  • pcre_match: true: Compare the command using pcre regular expressions, rather than doing an explicit match character by character. The regex is not anchored at the beginning nor end of the string, so if you wish to anchor it is your responsibility to do so.

    Caution: never underestimate the sneakiness of an adversary who may find a way to match your regex and still do something nasty.

    Examples:

    - allow: - # Touch the foo file, allowing any # optional command line params # before the filename

        command: ^touch\\s+(-\\S+\\s+)*foo$
        pcre_match: true
      -
        # attempt to allow rm of files in /var/tmp
        # but actually would fail to catch malicious
        # commands e.g. /var/tmp/../../etc/passwd
        #
        # As I said, be careful with pcre matching!!!
    
        command: ^/bin/rm\\s+(-\\S+\\s+)*/var/tmp/\\S*$
        pcre_match: true
    

RSYNC SUBRULES

authprogs has special support for rsync file transfer. You are not required to use this - you could use a simple command subrules to match explicit rsync commands - but using an rsync-specific subrule offers you greater flexibility.

Rsync support is in beta, so please raise any bugs found. Supporting the full set of rsync command line options is a moving target.

To specify rsync mode, use rule_type: rsync.

The rsync options are as follows.

  • rule_type: rsync: This indicates that this is an rsync subrule.

  • allow_upload: false|true: Allow files to be uploaded to the ssh server. Defaults to false.

  • allow_download: false|true: Allow files to be downloaded from the ssh server. Defaults to false.

  • allow_archive: false|true: Allow file archive, i.e. the options that are set when using -a or --archive. This is used to simplify authprogs configuration files. Specifying this and negating one of he associated options (e.g. allow_recursive: false) is considered an error. Defaults to false.

  • paths: a list of explicit files/directories that are allowed to match. Files specified by the client will be resolved via realpath to avoid any symlink trickery, so members of paths must be the real paths.

WARNING: specifying a directory in paths would allow rsync to act on any files therein at potentially infinite depth, e.g. when allow_recursive is set, or the client uses --files-from. If you want to restrict to specific files you must name them explicitly.

See RSYNC SYMLINK SUPPORT for potential limitations to paths.

  • path_startswith: a list of pathname prefixes that are allowed to match. Files specified by the client will be resolved via realpath and if they start with the name provided then they will be allowed.

This is a simple prefix match. For example if you had

    path_startswith: [ /tmp ]

then it would match all of the following

    /tmp
    /tmp/
    /tmpfiles      # may not be what you meant!
    /tmp/foo.txt
    /tmp/dir1/dir2/bar.txt

If you want it to match only a directory (and any infinite subdirectories) be sure to include a trailing slash, e.g. /tmp/

See RSYNC SYMLINK SUPPORT for potential limitations to paths.

  • allow_acls: false|true: Allow syncing of file ACLs. (--acls). Defaults to false.

  • allow_checksum: true|false: Allow checksum method for identifying files that need syncing. (-c / --checksum) Defaults to true.

  • allow_debug: true|false: Allow fine-grained debug verbosity. (--debug FLAGS). No support for sanity checking the debug flags that are specified. Defaults to true.

  • allow_delete: false|true: Allow any of the delete options. (--del --delete --delete-after --delete-before --delete-delay --delete-during --delete-excluded --delete-missing-args). Defaults to false.

  • allow_devices: false|true: Allow syncing of device files. (--devices). Defaults to false.

  • allow_group: false|true: Allow group change. (-g --group). Defaults to false.

  • allow_info: true|false: Allow fine-grained info verbosity. (-info FLAGS). No support for sanity checking the info flags that are specified. Defaults to true.

  • allow_links: false|true: Allow copying symlinks as symlinks. (-l --links). Defaults to false.

  • allow_group: false|true: Allow ownership change. (-o --owner). Defaults to false.

  • allow_perms: false|true: Allow perms change. (-p --perms). Defaults to false.

  • allow_recursive: false|true: Allow recursive sync. (-r --recursive). Defaults to false.

  • allow_specials: false|true: Allow syncing of special files, e.g. fifos. (--specials). Defaults to false.

  • allow_times: true|false: Allow setting synced file times. (-t --times). Defaults to true.

  • allow_verbose: true|false|#: Allow verbose output. (-v --verbose). Rsync allows multiple -v options, so this option accepts true (allow any verbosity), false (deny any verbosity), or a number which indicates the maximum number of -v option that are allowed, e.g. 2 would allow -v or -vv but not -vvv. Defaults to true.

RSYNC COMMAND LINE OPTIONS

Not all rsync options are currently implemented in authprogs.

If an option is listed as "" then there are two possibilities in how authprogs will behave:

* if the option is no actually sent on the remote command line then
  `authprogs` is blissfully unaware and the command will succeed.
  Many options are actually client-side only. We have not thoroughly
  investigated every single option yet.

* if the option is sent on the remote command line then `authprogs`
  will fail.

Here is the list of rsync options and their current authprogs support status:

rsync client arg             authprogs support
----------------             -----------------

    --append                   <not implemented>
    --append-verify            <not implemented>
    --backup-dir               <not implemented>
    --bwlimit                  <not implemented>
    --checksum-seed            <not implemented>
    --chown                  converted to --usermap and --groupmap
    --compare-dest             <not implemented>
    --compress-level           <not implemented>
    --contimeout               <not implemented>
    --copy-dest                <not implemented>
    --copy-unsafe-links        <not implemented>
    --debug                  allow_debug
    --del                    allow_delete
    --delay-updates            <not implemented>
    --delete                 allow_delete
    --delete-after           allow_delete
    --delete-before          allow_delete
    --delete-delay           allow_delete
    --delete-during          allow_delete
    --delete-excluded        allow_delete
    --delete-missing-args    allow_delete
    --devices                allow_devices
    --existing                 <not implemented>
    --fake-super               <not implemented>
    --files-from               <not implemented>
    --force                    <not implemented>
    --groupmap                 <not implemented>
    --iconv                    <not implemented>
    --ignore-errors            <not implemented>
    --ignore-existing          <not implemented>
    --ignore-missing-args      <not implemented>
    --info                   allow_info
    --inplace                  <not implemented>
    --link-dest                <not implemented>
    --list-only                <not implemented>
    --log-file                 <not implemented>
    --log-file-format          <not implemented>
    --max-delete               <not implemented>
    --max-size                 <not implemented>
    --min-size                 <not implemented>
    --new-compress             <not implemented>
    --no-XXXXX                 <not implemented> (negating options, e.g. --no-r)
    --numeric-ids              <not implemented>
    --only-write-batch         <not implemented>
    --outbuf                   <not implemented>
    --partial                  <not implemented>
    --partial-dir              <not implemented>
    --preallocate              <not implemented>
    --protocol                 <not implemented>
    --read-batch               <not implemented>
    --remove-sent-files        <not implemented> # deprecated version of remove-source-files
    --remove-source-files      <not implemented>
    --safe-links               <not implemented>
    --size-only                <not implemented>
    --skip-compress            <not implemented>
    --specials               allow_specials
    --stats                    <not implemented>
    --stop-at                  <not implemented>
    --suffix                   <not implemented>
    --super                    <not implemented>
    --time-limit               <not implemented>
    --timeout                  <not implemented>
    --usermap                  <not implemented>
    --write-batch              <not implemented>
-0, --from0                    <not implemented>
-@, --modify-window            <not implemented>
-A, --acls                   allow_acls
-B, --block-size               <not implemented>
-C, --cvs-exclude              <not implemented>
-D                           allow_devices and allow_specials
-E, --executability            <not implemented>
-H, --hard-links               <not implemented>
-I, --ignore-times             <not implemented>
-J, --omit-link-times          <not implemented>
-K, --keep-dirlinks            <not implemented>
-L, --copy-links               <not implemented>
-O, --omit-dir-times           <not implemented>
-P                           Same as --partial --progress
-R, --relative                 <not implemented>
-S, --sparse                   <not implemented>
-T, --temp-dir                 <not implemented>
-W, --whole-file               <not implemented>
-X, --xattrs                   <not implemented>
-a, --archive                Same as -rlptgoD; See those options
    --progress                 <not implemented>
-b, --backup                   <not implemented>
-c, --checksum               allow_checksum
-d, --dirs                     <not implemented>
-f, --filter                   <not implemented>
-g, --group                  allow_group
-i, --itemize-changes          <not implemented>
-k, --copy-dirlinks            <not implemented>
-l, --links                  allow_links
-m, --prune-empty-dirs         <not implemented>
-n, --dry-run                  <not implemented>
-o, --owner                  allow_owner
-p, --perms                  allow_perms
-r, --recursive              allow_recursive
-s, --protect-args             <not implemented>
-t, --times                  allow_times
-u, --update                   <not implemented>
-v, --verbose                allow_verbose
-x, --one-file-system          <not implemented>
-y, --fuzzy                    <not implemented>
-z, --compress                 <not implemented>
    --checksum-choice=STR      <not implemented>
    --exclude-from             <not implemented>
    --exclude                  <not implemented>
    --include-from             <not implemented>
    --include                  <not implemented>
    --rsync-path               <not implemented>
    --out-format               <not implemented>

The following are server-side only options that are supported

-e, --rsh=COMMAND            Value ignored (indicates protocol feature support)
--sender                     When present means download from server,
                             when absent means upload to server.
--server                     Always present on server

The following rsync client options are only relevant to daemon mode (i.e. rsync daemon listening on TCP directly without SSH) or do not end up on the server command line and are thus re not taken into consideration when determining if the command is or is not allowed:

    --address               Client-only option
    --chmod                 Client-only option
                               (Permissions are indicated via rsync
                                protocol, not command line flags.)
    --blocking-io           Client-only option
    --daemon                Daemon-only option
    --msgs2stderr           Client-only option
    --munge-links           Client-only option
    --no-motd               Client-only option
    --noatime               Client-only option
    --password-file         Daemon-only option
    --port                  Client-only option
    --sockopts              Daemon-only option
    --version               Client-only option
-4, --ipv4                  Client-only option
-6, --ipv6                  Client-only option
-8, --8-bit-output          Client-only option
-F                          Client-only option (see --filter)
-M, --remote-option=OPTION  Client-only option
-h, --human-readable        Client-only option
-q, --quiet                 Client-only option

RSYNC BINARY PATH

Rsync must be at an official path to prevent a user's environment from choosing one of their programs over the official one. Official paths are

* /usr/bin/rsync
* /usr/local/bin/rsync

A user who specifies --rsync-path with a different value, or who has an rsync program earlier in their $PATH will be denied.

RSYNC SYMLINK SUPPORT

Rsync has multiple ways of handling symlinks depending on command line parameters and what component(s) of a path are symlinks.

If you are using paths or paths_startswith to limit what files may be uploaded/downloaded then its your responsibility to assure that symlink games are not used to exceed the desired restrictions.

For example if the file /tmp/me.txt is a symlink to /home/wbagg/me.txt and you had

- rule\_type: rsync
    allow_upload: true
    paths:
        - /tmp/me.txt

then if the user ran

rsync /some/local/file remote:/tmp/me.txt

then rather than updating the file at /home/wbagg.me.txt, the symlink at /tmp/me.txt would be replaced with a normal file.

A future update to authprogs may attempt to handle symlinks by calling os.path.realpath prior to doing comparisons.

RSYNC PATHNAME GOTCHA

Say you wanted to restrict uploads to just the file /tmp/foo.txt, you'd use the following rsync subrule::

- rule\_type: rsync
  allow_upload: true
  paths:
    - /tmp/foo.txt

From an end-user perspective both of these commands would seem to be allowed from the client machine because they'd create a file on the remote named /tmp/foo.txt:

$ rsync foo.txt remote:/tmp/foo.txt  # provide full target filename
$ rsync foo.txt remote:/tmp          # imply source name for target

However you'll find that only the first one works! This is because authprogs on the server side sees literally just /tmp in the second case.

Thus if you wanted to restrict uploads to just the file /tmp/foo.txt then on the client side you must run the first (explicit filename) rsync command.

RSYNC SUBRULE KNOWN AND POSSIBLE BUGS

  • If uploading to a file that does not yet exist when you've set paths this will fail. Adding a new allow_create option is the most likely solution here, but not yet implemented.

  • No investigation of the rsync options --include / --exclude / --files-from has yet been performed - may affect path matching security.

  • Though we do expand file globs and check each individual path that is returned, we do not explicitly use these resolved files when calling rsync. (Reason: it's possible we exceed the allowed size of a command line with globs that return many files.) As such if rsync's glob and shutils.glob have different behaviour we may have false positives or negatives.

  • When allow_download is disabled client should not be able to get file contents. However since rsync transfers checksums as part of its protocol it is possible that information about server file contents could be gleaned by comparing checksums to possible content checksums when doing uploads.

SCP SUBRULES

authprogs has special support for scp file transfer. You are not required to use this - you could use a simple command subrules to match explicit scp commands - but using an scp-specific subrule offers you greater flexibility.

To specify scp mode, use rule_type: scp.

The scp options are as follows.

  • rule_type: scp: This indicates that this is an scp subrule.

  • allow_upload: false|true: Allow files to be uploaded to the ssh server. Defaults to false.

  • allow_download: false|true: Allow files to be downloaded from the ssh server. Defaults to false.

  • allow_recursive: false|true: Allow recursive (-r) file up/download. Defaults to false.

  • allow_recursion: false|true: Deprecated version of allow_recursive. will be removed in 1.0 release.

  • allow_permissions: true|false: Allow scp to get/set the permissions of the file/files being transferred. Defaults to false.

  • paths: The paths option allows you to specify which file or files are allowed to be transferred. If this is not specified then transfers are not restricted based on filename.

    Examples:

    - allow: - rule_type: scp allow_download: true paths: - /etc/group - /etc/passwd - rule_type: scp allow_upload: true paths: [/tmp/file1, /tmp/file2]

EXAMPLES

Here is a sample configuration file with multiple rules, going from simple to more complex.

Note that this config can be spread around between the ~/.ssh/authprogs.yaml and ~/.ssh/authprogs.d directory.

# All files should start with an initial solo dash -
# remember, we're being concatenated with all other
# files!

# Simple commands, no IP restrictions.
-
  allow:
    - command: /bin/tar czvf /backups/www.tgz /var/www/
    - command: /usr/bin/touch /var/www/.backups.complete

# Similar, but with IP restrictions
-
  from: [192.168.0.10, 192.168.0.15, 172.16.3.3]
  allow:
    - command: git --git-dir=/var/repos/foo/.git pull
    - command: sudo /etc/init.d/apache2 restart

# Some more complicated subrules
-
  # All of these 'allows' have the same 'from' restrictions
  from:
    - 10.1.1.20
    - 10.1.1.21
    - 10.1.1.22
    - 10.1.1.23
  allow:
    # Allow unrestricted ls
    - command: /bin/ls
      allow_trailing_args: true

    # Allow any 'service apache2 (start|stop)' commands via sudo
    - command: sudo service apache2
      allow_trailing_args:true

    # How about a regex? Allow wget of any https url, outputting
    #  to /tmp/latest
    - command: ^/usr/bin/wget\\s+https://\\S+\\s+-O\\s+/tmp/latest$
      pcre_match: true

    # Allow some specific file uploads
    - rule_type: scp
      allow_upload: true
      paths:
        - /srv/backups/host1.tgz
        - /srv/backups/host2.tgz
        - /srv/backups/host3.tgz

    # Allow rsync to upload everything, deny any download
    - rule_type: rsync
      allow_upload: true

    # Allow rsync to recursively sync /tmp/foo/ to the server
    # in archive mode (-a, or any subset of -logptrD)
    # but do not allow download
    - rule_type: rsync
      allow_upload: true
      allow_recursive: true
      allow_archive: true
      paths:
        - /tmp/foo

    # Allow rsync to write some specific files and any individual
    #   files under /data/lhc/ directory, such as /data/lhc/foo
    #   or /data/lhc/subdir/foo.
    #
    # Disallow download (explicitly listed) or recursive
    #    upload (default false).
    - rule_type: rsync
      allow_upload: true
      allow_download: false
      paths:
        - /srv/htdocs/index.html
        - /srv/htdocs/status.html
      path_startswith:
        - /data/lhc/

TROUBLESHOOTING

--dump_config is your friend. If your yaml config isn't parsing, consider --dump_config --logfile=/dev/tty for more debug output to find the error.

FILES

  • ~/.ssh/authorized_keys: The default place your key should be installed and configured to call authprogs. The actual location can differ if your administrator has changed it.

  • ~/.ssh/authprogs.yaml: Default authprogs configuration file. Override with --configfile.

  • ~/.ssh/authprogs.d: Default authprogs configuration directory. Override with --configdir.

ENVIRONMENT

authprogs uses the following environment variables that are set by the sshd(8) binary:

  • SSH_CONNECTION: This is used to determine the client IP address.

  • SSH_CLIENT: This is used to determine the client IP address if SSH_CONNECTION was not present.

  • SSH_ORIGINAL_COMMAND: The (squashed) original SSH command that was issued by the client.

authprogs sets the following environment variables for use by the authenticated process

  • AUTHPROGS_KEYNAME: the value of the --keyname command line. Will be set to an empty string if no --keyname was set.

EXIT STATUS

On unexpected error or rejecting the command authprogs will exit 126.

If the command was accepted then it returns the exit code of the command that was run.

Note that if you're invoking ssh via another tool that program may provide a different exit status and provide a misleading error message when authprogs returns a failure, For example rsync will exit 12 and assume a "protocol problem" rather than a rejection on the server side.

LOGGING AND DEBUGGING

If a --logfile is specified then it will be opened in append mode and a line about each command that is attempted to be run will be written to it. The line itself is in the form of a python dictionary.

If authprogs is run with --debug, then this logfile will get increased debugging information, including the configuration, rule matching status as they are checked, etc.

HISTORY

A perl version of authprogs was originally published at https://www.hackinglinuxexposed.com/articles/20030115.html in 2003. This is a complete rewrite in python, with a more extensible configuration, and avoiding some of the limitations of the former.

SEE ALSO

ssh(1), sshd(8), scp(1).

AUTHOR

Bri Hatch bri@ifokr.org

././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1613943612.0 authprogs-0.7.5/doc/authprogs.md0000664000175000017500000010313700000000000016224 0ustar00bribri00000000000000authprogs(1) -- SSH command authenticator ========================================= ## SYNOPSIS `authprogs --run [options]` `authprogs --install_key [options]` `authprogs --dump_config [options]` `authprogs --help` ## DESCRIPTION `authprogs` is an SSH command authenticator. It is invoked on an ssh server and decides if the command requested by the ssh client should be run or rejected based on logic in the `authprogs` configuration file. Passwordless SSH using ssh identities or pubkeys can enable all sorts of wonderful automation, for example running unattended batch jobs, slurping down backups, or pushing out code. Unfortunately a key, once trusted, is allowed by default to run anything on that system, not just the small set of commands you actually need. If the key is compromised, you are at risk of a security breach. This could be catastrophic, for example if the access is to the root account. Authprogs is run on the SSH server and compares the requested command against the `authprogs` configuration file/files. This enables `authprogs` to make intelligent decisions based on things such as the command itself, the SSH key that was used, the client IP, and such. `authprogs` is enabled by using the `command=` option in the `authorized_keys` file. ## KEY INSTALLATION You can install your ssh identities/pubkeys manually, or allow `authprogs` to do the work for you. ## MANUAL KEY INSTALLATION You need to set up your `~/.ssh/authorized_keys` file to force invocation of `authprogs` for the key or keys you wish to protect. A line of an unrestricted `authorized_key` entry might look like this: ssh-rsa AAAAxxxxx...xxxxx user@example.com When setting up this key to use `authprogs`, you add a `command=` option to the very beginning of that line that points to the location where authprogs lives. For example if `authprogs` is in `/usr/bin/authprogs`, you would use this: command="/usr/bin/authprogs --run" ssh-rsa AAAAxxxxx...xxxxx user@example.com You must include `--run` to let `authprogs` know it is running in SSH command mode. Authprogs has other command line options you may wish to include as well, for example command="/usr/bin/authprogs --keyname=backups --run" ssh-rsa AAAA...xxxxx user@example.com Lastly, if you wish, ssh offers a number of other helpful restrictions you may wish to include that are separate from authprogs. These can be appended right after (or before) the command="" section if you wish. command="/usr/bin/authprogs --run",no-port-forwarding,no-pty ssh-rsa AAAA...xxxxx user@example.com See the sshd(8) man page for more information about allowed `authorized_keys` configuration options. ## AUTOMATED KEY INSTALLATION Authprogs is capable of adding your key to your `authorized_keys` file (`~/.ssh/authorized_keys` by default) programmatically. It also disables ssh port forwarding by default for this key (a sensible default for most batch jobs.) authprogs will refuse to install a key that is already present in the `authorized_keys` file. For example the following authprogs --install_key /path/to/backups_key.pub --keyname=backups would cause the following line to be added to your `~/.ssh/authorized_keys` file: command="/usr/bin/authprogs --keyname backups --run",no-port-forwarding ssh-rsa AAAA...xxxxx user@example.com ## RUN MODE OPTIONS Authprogs can run in several modes, depending on which of these command line switches you provide. * `--run`: Act in run mode, as from an `authorized_keys` file. * `--install_key filename`: Install the key contained in the named file into your `authorized_keys` file. * `--dump_config`: Dump the configuration in a python-style view. Helpful only for debugging. * `--silent`: Do not inform the user if their command has been rejected. Default is to let them know it was rejected to prevent confusion. * `--help`: Show help information ## OTHER OPTIONS The following options may apply to multiple run modes, as appropriate. * `--keyname key_name`: This option 'names' the key, for help in crafting your rules. Since an account may have multiple keys allowed, this helps us differentiate which one was used so we can make sensible choices. In run mode, this specifies which name is used when matching in the configuration, e.g. command="/usr/bin/authprogs --keyname backups --run" ... In key installation mode, this adds the `--keyname` option to the `authorized_keys` entry. `key_name` may contain no whitespace. * `--configfile`: Specifies the `authprogs` configuration file to read. Defaults to `~/.ssh/authprogs.yaml`. In key installation mode, this adds the `--configfile` option to the `authorized_keys` entry. * `--configdir`: Specifies the `authprogs` configuration, in which multiple configuration files can be found. Defaults to `~/.ssh/authprogs.d` if present. Files in the configuration directory are read as rules in filename order. See CONFIGURATION for more info. ## LIMITATIONS Commands are executed via fork/exec, and are not processed through the shell. This means you cannot have multiple commands separated by semicolons, pipelines, redirections, backticks, shell builtins, wildcards, variables, etc. Also, you cannot have spaces in any arguments your command runs. This is because the SSH server takes the command that was specified by the client and squashes it into the `SSH_ORIGINAL_COMMAND` variable. By doing this it makes it impossible for us to know what spaces in `SSH_ORIGINAL_COMMAND` were between arguments and which were part of arguments. Here are some commands that would not work through `authprogs`: * `ssh host "rm /tmp/foo; touch /tmp/success"` * `ssh host "rm /tmp/*.html"` * `ssh host "cut -d: -f 1 /etc/passwd > /tmp/users"` * `ssh host "touch '/tmp/file with spaces'"` * `ssh host "for file in /tmp/*.html; do w3m -dump $file > $file.txt; done"` You can work around these limitations by writing a shell script that does what you need and calling that from `authprogs`, rather than attempting to run complicated command lines via ssh directly. ## CONFIGURATION FILES authprogs rules are maintained in one or more configuration files in YAML format. The rules allow you to decide whether the client's command should be run based on criteria such as the command itself, the client IP address, and ssh key in use. Rules can be read from a single file (`~/.ssh/authprogs.yaml` by default) or by putting files in a configuration directory (`~/.ssh/authprogs.d`). The configuration directory method is most useful when you want to be able to easily add or remove rules without manually editing a single configuration file, such as when installing rules via your configuration tool of choice. All the `authprogs` configuration files are concatenated together into one large yaml document which is then processed. The files are concatenated in the following order: * `~/.ssh/authprogs.yaml`, if present * files in `~/.ssh/authprogs.d/` directory, in asciibetical order Dotfiles contained in a configuration directory are ignored. The configuration directory is not recursed; only those files directly contained are processed. Each rule in the configuration file/files is tested in order and once a match is found, processing stops and the command is run. Rules are made of rule selection options (e.g. client IP address) and subrules (e.g. a list of allowed commands). All pieces must match for the command to be run. The general format of a rule is as follows: # First rule - # Selection options # # All must match or we stop processing this rule. selection_option_1: value selection_option_2: value # The allow block, aka subrules # # This lets us group a bunch of possible commands # into one rule. Otherwise we'd need a bunch of # rules where you repeat selection options. allow: - rule_type: value rule_param_1: value rule_param_2: value - rule_type: value2 rule_param_1: value rule_param_2: value # Next rule - selection_option_3: value ... Some of the keys take single arguments, while others may take lists. See the definition of each to understand the values it accepts. ## RULE SELECTION OPTIONS These configuration options apply to the entire rule, and help you limit under what conditions the rule matches. * from: This is a single value or list of values that define what SSH client IP addresses are allowed to match this rule. The client IP address is gleaned by environment variables set by the SSH server. Any from value may be an IP address or a CIDR network. Examples: - from: 192.168.1.5 ... - from: [192.168.0.1, 10.0.0.3] ... - from: - 192.168.0.0/24 - 10.10.0.3 ... * keynames: This is a single value or list of values that define which SSH pubkeys are allowed to match this rule. The keyname is specified by the `--keyname foo` parameter in the authprogs command line in the entry in `authorized_keys`. Examples: - keynames: backups ... - keynames: [repo_push, repo_pull] ... - keynames: - repo_push - repo_pull ... ## ALLOW SUBRULE SECTION The allow section of a rule is a single subrule or list of subrules. Subrules can be simple, for example the explicit command match, or be more program-aware such as scp support. You specify which kind of subrule you want with the `rule_type` option: - allow: - rule_type: command command: /bin/touch /tmp/timestamp - command: /bin/rm /tmp/bar - rule_type: scp allow_upload: true ... See the separate subrules sections below for how to craft each type. ## COMMAND SUBRULES This section applies if `rule_type` is set to `command` or is not present at all. The command requested by the client is compared to the command listed in the rule. (Spaces are squashed together.) If it matches, then the command is run. Note that the command must be *exactly* the same; `authprogs` is not aware of arguments supported by a command, so it cannot realise that `"ls -la"` and `"ls -a -l"` and `"ls -al"` and `"ls -l -a"` are all the same. You can list multiple commands to allow you to accept variants of a command if necessary. The simplest configuration looks like this: - allow: command: /bin/true Or you can provide a list of commands: - allow: - command: /bin/true - command: /bin/false A number of optional settings can tweak how command matching is performed. * `allow_trailing_args: true`: This setting allows you to specify a partial command that will match as long as the command requested by the client is the same or longer. This allows you to avoid listing every variant of a command that the client may wish to run. Examples: - allow: - command: /bin/echo allow_trailing_args: true - command: /bin/ls allow_trailing_args: true - command: /bin/rm -i allow_trailing_args: true * `pcre_match: true`: Compare the command using pcre regular expressions, rather than doing an explicit match character by character. The regex is *not* anchored at the beginning nor end of the string, so if you wish to anchor it is your responsibility to do so. Caution: never underestimate the sneakiness of an adversary who may find a way to match your regex and still do something nasty. Examples: - allow: - # Touch the foo file, allowing any # optional command line params # before the filename command: ^touch\\s+(-\\S+\\s+)*foo$ pcre_match: true - # attempt to allow rm of files in /var/tmp # but actually would fail to catch malicious # commands e.g. /var/tmp/../../etc/passwd # # As I said, be careful with pcre matching!!! command: ^/bin/rm\\s+(-\\S+\\s+)*/var/tmp/\\S*$ pcre_match: true ## RSYNC SUBRULES authprogs has special support for rsync file transfer. You are not required to use this - you could use a simple command subrules to match explicit rsync commands - but using an rsync-specific subrule offers you greater flexibility. Rsync support is in beta, so please raise any bugs found. Supporting the full set of rsync command line options is a moving target. To specify rsync mode, use `rule_type: rsync`. The rsync options are as follows. * `rule_type: rsync`: This indicates that this is an rsync subrule. * `allow_upload: false|true`: Allow files to be uploaded to the ssh server. Defaults to false. * `allow_download: false|true`: Allow files to be downloaded from the ssh server. Defaults to false. * `allow_archive: false|true`: Allow file archive, i.e. the options that are set when using `-a` or `--archive`. This is used to simplify `authprogs` configuration files. Specifying this and negating one of he associated options (e.g. `allow_recursive: false`) is considered an error. Defaults to false. * `paths`: a list of explicit files/directories that are allowed to match. Files specified by the client will be resolved via `realpath` to avoid any symlink trickery, so members of `paths` must be the real paths. WARNING: specifying a directory in `paths` would allow rsync to act on any files therein at potentially infinite depth, e.g. when `allow_recursive` is set, or the client uses `--files-from`. If you want to restrict to specific files you must name them explicitly. See RSYNC SYMLINK SUPPORT for potential limitations to `paths`. * `path_startswith`: a list of pathname prefixes that are allowed to match. Files specified by the client will be resolved via `realpath` and if they start with the name provided then they will be allowed. This is a simple prefix match. For example if you had path_startswith: [ /tmp ] then it would match all of the following /tmp /tmp/ /tmpfiles # may not be what you meant! /tmp/foo.txt /tmp/dir1/dir2/bar.txt If you want it to match only a directory (and any infinite subdirectories) be sure to include a trailing slash, e.g. `/tmp/` See RSYNC SYMLINK SUPPORT for potential limitations to `paths`. * `allow_acls: false|true`: Allow syncing of file ACLs. (`--acls`). Defaults to false. * `allow_checksum: true|false`: Allow checksum method for identifying files that need syncing. (`-c` / `--checksum`) Defaults to true. * `allow_debug: true|false`: Allow fine-grained debug verbosity. (`--debug FLAGS`). No support for sanity checking the debug flags that are specified. Defaults to true. * `allow_delete: false|true`: Allow any of the delete options. (`--del` `--delete` `--delete-after` `--delete-before` `--delete-delay` `--delete-during` `--delete-excluded` `--delete-missing-args`). Defaults to false. * `allow_devices: false|true`: Allow syncing of device files. (`--devices`). Defaults to false. * `allow_group: false|true`: Allow group change. (`-g --group`). Defaults to false. * `allow_info: true|false`: Allow fine-grained info verbosity. (`-info FLAGS`). No support for sanity checking the info flags that are specified. Defaults to true. * `allow_links: false|true`: Allow copying symlinks as symlinks. (`-l --links`). Defaults to false. * `allow_group: false|true`: Allow ownership change. (`-o --owner`). Defaults to false. * `allow_perms: false|true`: Allow perms change. (`-p --perms`). Defaults to false. * `allow_recursive: false|true`: Allow recursive sync. (`-r --recursive`). Defaults to false. * `allow_specials: false|true`: Allow syncing of special files, e.g. fifos. (`--specials`). Defaults to false. * `allow_times: true|false`: Allow setting synced file times. (`-t --times`). Defaults to true. * `allow_verbose: true|false|#`: Allow verbose output. (`-v --verbose`). Rsync allows multiple -v options, so this option accepts true (allow any verbosity), false (deny any verbosity), or a number which indicates the maximum number of `-v` option that are allowed, e.g. `2` would allow `-v` or `-vv` but not `-vvv`. Defaults to true. ### RSYNC COMMAND LINE OPTIONS Not all rsync options are currently implemented in `authprogs`. If an option is listed as "" then there are two possibilities in how `authprogs` will behave: * if the option is no actually sent on the remote command line then `authprogs` is blissfully unaware and the command will succeed. Many options are actually client-side only. We have not thoroughly investigated every single option yet. * if the option is sent on the remote command line then `authprogs` will fail. Here is the list of rsync options and their current `authprogs` support status: rsync client arg authprogs support ---------------- ----------------- --append --append-verify --backup-dir --bwlimit --checksum-seed --chown converted to --usermap and --groupmap --compare-dest --compress-level --contimeout --copy-dest --copy-unsafe-links --debug allow_debug --del allow_delete --delay-updates --delete allow_delete --delete-after allow_delete --delete-before allow_delete --delete-delay allow_delete --delete-during allow_delete --delete-excluded allow_delete --delete-missing-args allow_delete --devices allow_devices --existing --fake-super --files-from --force --groupmap --iconv --ignore-errors --ignore-existing --ignore-missing-args --info allow_info --inplace --link-dest --list-only --log-file --log-file-format --max-delete --max-size --min-size --new-compress --no-XXXXX (negating options, e.g. --no-r) --numeric-ids --only-write-batch --outbuf --partial --partial-dir --preallocate --protocol --read-batch --remove-sent-files # deprecated version of remove-source-files --remove-source-files --safe-links --size-only --skip-compress --specials allow_specials --stats --stop-at --suffix --super --time-limit --timeout --usermap --write-batch -0, --from0 -@, --modify-window -A, --acls allow_acls -B, --block-size -C, --cvs-exclude -D allow_devices and allow_specials -E, --executability -H, --hard-links -I, --ignore-times -J, --omit-link-times -K, --keep-dirlinks -L, --copy-links -O, --omit-dir-times -P Same as --partial --progress -R, --relative -S, --sparse -T, --temp-dir -W, --whole-file -X, --xattrs -a, --archive Same as -rlptgoD; See those options --progress -b, --backup -c, --checksum allow_checksum -d, --dirs -f, --filter -g, --group allow_group -i, --itemize-changes -k, --copy-dirlinks -l, --links allow_links -m, --prune-empty-dirs -n, --dry-run -o, --owner allow_owner -p, --perms allow_perms -r, --recursive allow_recursive -s, --protect-args -t, --times allow_times -u, --update -v, --verbose allow_verbose -x, --one-file-system -y, --fuzzy -z, --compress --checksum-choice=STR --exclude-from --exclude --include-from --include --rsync-path --out-format The following are server-side only options that are supported -e, --rsh=COMMAND Value ignored (indicates protocol feature support) --sender When present means download from server, when absent means upload to server. --server Always present on server The following rsync client options are only relevant to daemon mode (i.e. rsync daemon listening on TCP directly without SSH) or do not end up on the server command line and are thus re not taken into consideration when determining if the command is or is not allowed: --address Client-only option --chmod Client-only option (Permissions are indicated via rsync protocol, not command line flags.) --blocking-io Client-only option --daemon Daemon-only option --msgs2stderr Client-only option --munge-links Client-only option --no-motd Client-only option --noatime Client-only option --password-file Daemon-only option --port Client-only option --sockopts Daemon-only option --version Client-only option -4, --ipv4 Client-only option -6, --ipv6 Client-only option -8, --8-bit-output Client-only option -F Client-only option (see --filter) -M, --remote-option=OPTION Client-only option -h, --human-readable Client-only option -q, --quiet Client-only option ### RSYNC BINARY PATH Rsync must be at an official path to prevent a user's environment from choosing one of their programs over the official one. Official paths are * /usr/bin/rsync * /usr/local/bin/rsync A user who specifies --rsync-path with a different value, or who has an rsync program earlier in their $PATH will be denied. ### RSYNC SYMLINK SUPPORT Rsync has multiple ways of handling symlinks depending on command line parameters and what component(s) of a path are symlinks. If you are using `paths` or `paths_startswith` to limit what files may be uploaded/downloaded then its your responsibility to assure that symlink games are not used to exceed the desired restrictions. For example if the file `/tmp/me.txt` is a symlink to `/home/wbagg/me.txt` and you had - rule\_type: rsync allow_upload: true paths: - /tmp/me.txt then if the user ran rsync /some/local/file remote:/tmp/me.txt then rather than updating the file at `/home/wbagg.me.txt`, the symlink at `/tmp/me.txt` would be replaced with a normal file. A future update to `authprogs` may attempt to handle symlinks by calling `os.path.realpath` prior to doing comparisons. ### RSYNC PATHNAME GOTCHA Say you wanted to restrict uploads to just the file `/tmp/foo.txt`, you'd use the following rsync subrule:: - rule\_type: rsync allow_upload: true paths: - /tmp/foo.txt From an end-user perspective both of these commands would seem to be allowed from the client machine because they'd create a file on the remote named `/tmp/foo.txt`: $ rsync foo.txt remote:/tmp/foo.txt # provide full target filename $ rsync foo.txt remote:/tmp # imply source name for target However you'll find that only the first one works! This is because `authprogs` on the server side sees literally just `/tmp` in the second case. Thus if you wanted to restrict uploads to just the file `/tmp/foo.txt` then on the client side you **must** run the first (explicit filename) rsync command. ### RSYNC SUBRULE KNOWN AND POSSIBLE BUGS * If uploading to a file that does not yet exist when you've set `paths` this will fail. Adding a new `allow_create` option is the most likely solution here, but not yet implemented. * No investigation of the rsync options --include / --exclude / --files-from has yet been performed - may affect path matching security. * Though we do expand file globs and check each individual path that is returned, we do not explicitly use these resolved files when calling rsync. (Reason: it's possible we exceed the allowed size of a command line with globs that return many files.) As such if rsync's glob and `shutils.glob` have different behaviour we may have false positives or negatives. * When `allow_download` is disabled client should not be able to get file contents. However since rsync transfers checksums as part of its protocol it is possible that information about server file contents could be gleaned by comparing checksums to possible content checksums when doing uploads. ## SCP SUBRULES authprogs has special support for scp file transfer. You are not required to use this - you could use a simple command subrules to match explicit scp commands - but using an scp-specific subrule offers you greater flexibility. To specify scp mode, use `rule_type: scp`. The scp options are as follows. * `rule_type: scp`: This indicates that this is an scp subrule. * `allow_upload: false|true`: Allow files to be uploaded to the ssh server. Defaults to false. * `allow_download: false|true`: Allow files to be downloaded from the ssh server. Defaults to false. * `allow_recursive: false|true`: Allow recursive (-r) file up/download. Defaults to false. * `allow_recursion: false|true`: Deprecated version of `allow_recursive`. will be removed in 1.0 release. * `allow_permissions: true|false`: Allow scp to get/set the permissions of the file/files being transferred. Defaults to false. * `paths`: The paths option allows you to specify which file or files are allowed to be transferred. If this is not specified then transfers are not restricted based on filename. Examples: - allow: - rule_type: scp allow_download: true paths: - /etc/group - /etc/passwd - rule_type: scp allow_upload: true paths: [/tmp/file1, /tmp/file2] ## EXAMPLES Here is a sample configuration file with multiple rules, going from simple to more complex. Note that this config can be spread around between the `~/.ssh/authprogs.yaml` and `~/.ssh/authprogs.d` directory. # All files should start with an initial solo dash - # remember, we're being concatenated with all other # files! # Simple commands, no IP restrictions. - allow: - command: /bin/tar czvf /backups/www.tgz /var/www/ - command: /usr/bin/touch /var/www/.backups.complete # Similar, but with IP restrictions - from: [192.168.0.10, 192.168.0.15, 172.16.3.3] allow: - command: git --git-dir=/var/repos/foo/.git pull - command: sudo /etc/init.d/apache2 restart # Some more complicated subrules - # All of these 'allows' have the same 'from' restrictions from: - 10.1.1.20 - 10.1.1.21 - 10.1.1.22 - 10.1.1.23 allow: # Allow unrestricted ls - command: /bin/ls allow_trailing_args: true # Allow any 'service apache2 (start|stop)' commands via sudo - command: sudo service apache2 allow_trailing_args:true # How about a regex? Allow wget of any https url, outputting # to /tmp/latest - command: ^/usr/bin/wget\\s+https://\\S+\\s+-O\\s+/tmp/latest$ pcre_match: true # Allow some specific file uploads - rule_type: scp allow_upload: true paths: - /srv/backups/host1.tgz - /srv/backups/host2.tgz - /srv/backups/host3.tgz # Allow rsync to upload everything, deny any download - rule_type: rsync allow_upload: true # Allow rsync to recursively sync /tmp/foo/ to the server # in archive mode (-a, or any subset of -logptrD) # but do not allow download - rule_type: rsync allow_upload: true allow_recursive: true allow_archive: true paths: - /tmp/foo # Allow rsync to write some specific files and any individual # files under /data/lhc/ directory, such as /data/lhc/foo # or /data/lhc/subdir/foo. # # Disallow download (explicitly listed) or recursive # upload (default false). - rule_type: rsync allow_upload: true allow_download: false paths: - /srv/htdocs/index.html - /srv/htdocs/status.html path_startswith: - /data/lhc/ ## TROUBLESHOOTING `--dump_config` is your friend. If your yaml config isn't parsing, consider `--dump_config --logfile=/dev/tty` for more debug output to find the error. ## FILES * `~/.ssh/authorized_keys`: The default place your key should be installed and configured to call `authprogs`. The actual location can differ if your administrator has changed it. * `~/.ssh/authprogs.yaml`: Default `authprogs` configuration file. Override with --configfile. * `~/.ssh/authprogs.d`: Default `authprogs` configuration directory. Override with --configdir. ## ENVIRONMENT authprogs uses the following environment variables that are set by the sshd(8) binary: * `SSH_CONNECTION`: This is used to determine the client IP address. * `SSH_CLIENT`: This is used to determine the client IP address if SSH_CONNECTION was not present. * `SSH_ORIGINAL_COMMAND`: The (squashed) original SSH command that was issued by the client. authprogs sets the following environment variables for use by the authenticated process * `AUTHPROGS_KEYNAME`: the value of the --keyname command line. Will be set to an empty string if no --keyname was set. ## EXIT STATUS On unexpected error or rejecting the command `authprogs` will exit 126. If the command was accepted then it returns the exit code of the command that was run. Note that if you're invoking ssh via another tool that program may provide a different exit status and provide a misleading error message when `authprogs` returns a failure, For example `rsync` will exit 12 and assume a "protocol problem" rather than a rejection on the server side. ## LOGGING AND DEBUGGING If a `--logfile` is specified then it will be opened in append mode and a line about each command that is attempted to be run will be written to it. The line itself is in the form of a python dictionary. If `authprogs` is run with `--debug`, then this logfile will get increased debugging information, including the configuration, rule matching status as they are checked, etc. ## HISTORY A perl version of `authprogs` was originally published at https://www.hackinglinuxexposed.com/articles/20030115.html in 2003. This is a complete rewrite in python, with a more extensible configuration, and avoiding some of the limitations of the former. ## SEE ALSO ssh(1), sshd(8), scp(1). ## AUTHOR Bri Hatch ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1613946956.0 authprogs-0.7.5/doc/description.rst0000664000175000017500000000214600000000000016741 0ustar00bribri00000000000000 Authprogs --------- `authprogs` is an SSH command authenticator. It is invoked on an ssh server and decides if the command requested by the ssh client should be run or rejected based on logic in the `authprogs` configuration file. Passwordless SSH using ssh identities or pubkeys can enable all sorts of wonderful automation, for example running unattended batch jobs, slurping down backups, or pushing out code. Unfortunately a key, once trusted, is allowed by default to run anything on that system, not just the small set of commands you actually need. If the key is compromised, you are at risk of a security breach. This could be catastrophic, for example if the access is to the root account. Authprogs is run on the SSH server and compares the requested command against the `authprogs` configuration file/files. This enables `authprogs` to make intelligent decisions based on things such as the command itself, the SSH key that was used, the client IP, and such. `authprogs` is enabled by using the `command=` option in the `authorized_keys` file. For usage see the full authprogs man page in the doc directory. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1615765592.6638746 authprogs-0.7.5/setup.cfg0000664000175000017500000000004600000000000014735 0ustar00bribri00000000000000[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1613947105.0 authprogs-0.7.5/setup.py0000664000175000017500000001171000000000000014626 0ustar00bribri00000000000000#!/usr/bin/env python """Authprogs setup.py""" # pylint: disable-msg=W0511 # pylint: disable-msg=R0904 import authprogs import os import shutil import subprocess import sys from setuptools import setup from setuptools.command.install import install from setuptools.command.sdist import sdist # Allowed version list if sys.version_info < (3, 3): sys.exit('Sorry, Python < 3.3 is not supported') # Documents that should be converted or renamed from markdown MARKDOWN2HTML = ['authprogs'] MARKDOWN2TEXT = [ 'AUTHORS', 'DEVELOPMENT', 'INSTALL', 'README', 'README.rsync', 'TODO', ] console_script = 'authprogs' def needsupdate(target, source): uptodate = ( os.path.exists(target) and os.stat(target).st_mtime >= os.stat(source).st_mtime ) return not uptodate def long_description(): """Read our long description from the fs.""" with open('doc/description.rst') as filed: return filed.read() class Converter(object): """Documentation conversion class.""" def __init__(self): """Init.""" self.created = [] def dd_docs(self): """Copy and convert various documentation files.""" top = os.path.join(os.path.dirname(__file__)) doc = os.path.join(top, 'doc') # Markdown to ronn to man page man_md = os.path.join(doc, 'authprogs.md') man_ronn = os.path.join(doc, 'authprogs.1.ronn') man_1 = os.path.join(doc, 'authprogs.1') # Create manpage try: if needsupdate(man_1, man_md): shutil.copy(man_md, man_ronn) self.created.append(man_ronn) retval = subprocess.call(['ronn', '-r', man_ronn]) if retval != 0: raise Exception( 'ronn man page conversion failed, ' 'returned {}'.format(retval) ) self.created.append(man_1) except: raise Exception( 'ronn required for manpage conversion - do you ' 'have it installed?' ) # Markdown files in docs dir get converted to .html for name in MARKDOWN2HTML: htmlfile = os.path.join(doc, '{}.html'.format(name)) source = os.path.join(doc, '{}.md'.format(name)) if not needsupdate(htmlfile, source): continue target = open(htmlfile, 'w') self.created.append(htmlfile) command = ['python3', '-m', 'markdown', source] proc = subprocess.run( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if proc.returncode != 0: raise Exception( 'Markdown conversion failed,' ' no output. {}'.format(proc.stderr.decode()) ) target.write(proc.stdout.decode()) target.close() # Markdown files in top level just get renamed sans .md for name in MARKDOWN2TEXT: target = os.path.join(top, name) source = os.path.join(top, '{}.md'.format(target)) if not needsupdate(target, source): continue shutil.copy(source, target) self.created.append(target) def rm_docs(self): """Remove converted docs.""" for filename in self.created: if os.path.exists(filename): os.unlink(filename) class APInstall(install): """Create man pages and share/doc files from markdown/etc source.""" def run(self): converter = Converter() converter.dd_docs() install.run(self) converter.rm_docs() class APSdist(sdist): """Convert markdown for sdist packaging.""" def run(self): converter = Converter() converter.dd_docs() sdist.run(self) converter.rm_docs() setup( name='authprogs', version=authprogs.__version__, description='SSH Command Authenticator', long_description=long_description(), keywords='authprogs ssh pubkey identity authorized_keys security', url='http://github.com/daethnir/authprogs', author='Bri Hatch', author_email='bri@ifokr.org', license='GPLv2', maintainer='Bri Hatch', maintainer_email='bri@ifokr.org', packages=['authprogs'], data_files=[ ('share/man/man1/', ['doc/authprogs.1']), ( 'share/doc/authprogs/', [ 'AUTHORS', 'DEVELOPMENT', 'COPYING', 'INSTALL', 'README', 'TODO', 'doc/authprogs.html', ], ), ], test_suite='authprogs.tests', setup_requires=['markdown'], install_requires=['pyyaml'], zip_safe=False, cmdclass={"install": APInstall, "sdist": APSdist}, entry_points={ 'console_scripts': [ '{} = authprogs.authprogs:main'.format(console_script) ] }, )