Compare commits

...

74 Commits

Author SHA1 Message Date
Manuel Amador (Rudd-O)
658e6dfa08 Fix scriptlet to reenable unit when obsolete wantedbys are removed. 2025-02-27 00:32:43 +00:00
Manuel Amador (Rudd-O)
86fa5c509c Fix scriptlet to remove obsolete starts. 2025-02-26 23:57:29 +00:00
Manuel Amador (Rudd-O)
15edce34a8 Actually bind to qubes-network.service which starts only when network needs to be routed. 2025-02-26 23:43:45 +00:00
Manuel Amador (Rudd-O)
3a214bdfe1 Merge remote-tracking branch 'origin/master' 2024-02-29 03:53:31 +00:00
Manuel Amador (Rudd-O)
f7bfd46bdc Ensure the forward rule is added after connection tracking.
Also improve tests and add Tox for mypy and pytest.
2024-02-29 03:24:36 +00:00
Manuel Amador (Rudd-O)
26b0b2a357 New build strategies. 2024-02-22 17:57:50 +00:00
Manuel Amador (Rudd-O)
7289f59867 Use QUBES_RELEASES. 2024-02-21 22:18:38 +00:00
Rudd-O
2bc9d929f9
Link to upgrade instructions. 2024-02-20 17:04:10 +00:00
Rudd-O
022414a4f8
Instructions to upgrade to Qubes OS 4.2 2024-02-20 17:03:58 +00:00
Manuel Amador (Rudd-O)
ff3d6b55f2 Ensure conflicts so it is not attempted to upgrade with the wrong base dep. 2024-02-20 14:44:24 +00:00
Manuel Amador (Rudd-O)
ef4845548f Insert custom postrouting chain before masquerade. 2024-02-20 08:57:43 +00:00
Manuel Amador (Rudd-O)
7b5cae5b0e Neuter masquerading of outbound traffic from VMs in routing mode. 2024-02-06 03:54:35 +00:00
Manuel Amador (Rudd-O)
e9e65f7da1 Improve documentation some more. 2024-02-06 03:54:08 +00:00
Manuel Amador (Rudd-O)
da69c75642 Improve user documentation. 2024-02-06 03:10:10 +00:00
Manuel Amador (Rudd-O)
3f58f6bda6 Do not need subprocess here. 2024-02-06 03:03:02 +00:00
Manuel Amador (Rudd-O)
e5534a5225 Better instructions to build packages. 2024-02-06 02:54:35 +00:00
Manuel Amador (Rudd-O)
cf2945e742 Qubes 4.2 support. 2024-02-06 02:45:35 +00:00
Manuel Amador (Rudd-O)
06c5b1b0ae F37 2023-02-24 10:24:18 +00:00
Manuel Amador (Rudd-O)
e7c4a2115a Tag 0.0.19. 2022-10-26 16:58:07 +00:00
Manuel Amador (Rudd-O)
a1288b8466 Python interpreter fixed at 3 in spec file. 2022-10-26 16:52:27 +00:00
Manuel Amador (Rudd-O)
a972656553 Blacken. 2022-10-26 16:49:15 +00:00
Manuel Amador (Rudd-O)
0e75677fab F36 2022-10-26 16:49:15 +00:00
Manuel Amador (Rudd-O)
f34c1a6574 Disable testing, . 2022-10-26 16:49:15 +00:00
Manuel Amador (Rudd-O)
5fbf15dc4b Disable testing. 2022-10-26 16:49:15 +00:00
Rudd-O
f0c6387fbf
Merge pull request #15 from gunkaaa/patch-1
Fix broken link and image embeds
2022-10-13 23:15:08 +00:00
gunkaaa
df694b870a
Update README.md
Fix images with spaces in filenames not displaying on github
2022-10-14 02:27:06 +13:00
gunkaaa
2b7be939fe
Fix broken link to "enable remote management..." 2022-10-14 02:23:19 +13:00
Manuel Amador (Rudd-O)
60b952171e Doc updates. 2021-10-29 00:44:49 +00:00
Manuel Amador (Rudd-O)
581a913999 Tag 0.0.18. 2021-10-29 00:43:28 +00:00
Manuel Amador (Rudd-O)
21c09202cc Tag 0.0.17. 2021-10-29 00:24:42 +00:00
Manuel Amador (Rudd-O)
4e6c87fb36 Merge branch 'master' into r4.1 2021-10-28 23:08:28 +00:00
Manuel Amador (Rudd-O)
b201701442 Documentation buffups. 2021-06-16 10:43:17 +00:00
Manuel Amador (Rudd-O)
aa664192d6 Build.parameters updated. 2021-05-18 13:10:25 +00:00
Manuel Amador (Rudd-O)
4b6100efce Add build.parameters. 2021-03-30 01:54:55 +00:00
Manuel Amador (Rudd-O)
498c3c4105 Tag 0.0.16. 2021-03-29 17:04:53 +00:00
Manuel Amador (Rudd-O)
6b8520f076 Tag 0.0.15. 2021-03-05 02:08:24 +00:00
Manuel Amador (Rudd-O)
754b710dd3 Package fix. 2021-03-05 02:08:18 +00:00
Manuel Amador (Rudd-O)
1e4ed5dda9 New version. 2021-03-03 20:33:36 +00:00
Manuel Amador (Rudd-O)
6d43fccf60
Fix README formatting. 2020-12-01 16:32:44 +00:00
Manuel Amador (Rudd-O)
524f2f4341 Merge branch 'master' into r4.1 2020-12-01 16:29:52 +00:00
Manuel Amador (Rudd-O)
4757c887f6 Merge remote-tracking branch 'origin/master' 2020-11-16 09:28:20 +00:00
Rudd-O
a08a6e1200
Merge pull request #12 from resynth1943/patch-1
Fix image embeds
2020-11-16 09:27:35 +00:00
Qt Resynth
7dc7dd0922
Fix image embeds
These don't work at all in GFM, and this fixes that :-)
2020-11-16 08:32:09 +00:00
Manuel Amador (Rudd-O)
9b02a2382d Tag 0.0.13. 2020-09-25 17:07:02 +00:00
Manuel Amador (Rudd-O)
3f7e284095 Tag 0.0.12. 2020-09-25 16:53:44 +00:00
Manuel Amador (Rudd-O)
42c527c179 Beware that qdb now returns bytes instead of strings. 2020-09-25 16:53:37 +00:00
Manuel Amador (Rudd-O)
b92f096dcc Blacken. 2020-09-25 16:53:14 +00:00
Manuel Amador (Rudd-O)
a057e31578 Require Python3 setuptools. 2020-04-15 18:40:45 +00:00
Manuel Amador (Rudd-O)
43bdf5fd04 Added license, moving the code to GPLv2+. 2020-04-14 09:40:15 +00:00
Manuel Amador (Rudd-O)
49ab132bf5 Merge branch 'r4.0' 2020-04-14 08:16:02 +00:00
Manuel Amador (Rudd-O)
45c19fb1f7 Account for the horrible mishmash of Python stuff. 2020-04-14 04:51:02 +00:00
Manuel Amador (Rudd-O)
710b18a1ed Include dom0 agent in source. 2020-04-14 03:48:07 +00:00
Manuel Amador (Rudd-O)
605f5f4c73 Update to Python 3. 2020-04-14 01:47:04 +00:00
Manuel Amador (Rudd-O)
08d21839c2 Update readme for 4.1 branch. 2020-04-14 01:42:27 +00:00
Manuel Amador (Rudd-O)
ddf5bd36b8 Add 4.1 release patch. 2020-04-14 01:41:42 +00:00
Manuel Amador (Rudd-O)
4167afed98 No package for dom0. 2020-04-14 00:12:47 +00:00
Manuel Amador (Rudd-O)
87a189c5cb Transform the Qubes network server into a routing manager. 2020-04-13 23:55:15 +00:00
Manuel Amador (Rudd-O)
98d23ba8fe Merge remote-tracking branch 'origin/master' 2020-04-13 23:54:18 +00:00
Manuel Amador (Rudd-O)
da99eaf897 Qubes 3 note. 2020-04-13 23:52:58 +00:00
Manuel Amador (Rudd-O)
1061f30b67 0.0.10 2019-03-15 00:39:10 +00:00
Manuel Amador (Rudd-O)
650577dcf6 sitearch python2. 2019-03-15 00:38:46 +00:00
Rudd-O
dd70e2d0a6
Merge pull request #8 from strugee/patch-1
Fix Markdown display on GitHub
2018-11-26 11:39:39 +00:00
AJ Jordan
dd14f90816
Fix Markdown display on GitHub 2018-11-25 21:42:47 -05:00
Manuel Amador (Rudd-O)
590ce707ad Add documentation that explains what is going on with the new daemon. 2018-11-21 03:09:55 +00:00
Manuel Amador (Rudd-O)
e25b341d7c Fix deploy on debian. Bump version. 2018-11-18 09:08:23 +00:00
Manuel Amador (Rudd-O)
922e3180f3 Eclipse settings checked in. 2018-11-18 09:07:55 +00:00
Manuel Amador (Rudd-O)
c5d909e193 Brown paper bug. Fixed. 2018-11-18 08:57:41 +00:00
Manuel Amador (Rudd-O)
efdda01dd0 Bump version and prepare to package. 2018-11-18 08:36:19 +00:00
Manuel Amador (Rudd-O)
4e522c56a7 Fix build. 2018-11-18 02:15:59 +00:00
Manuel Amador (Rudd-O)
7edd1eaf9f Added Jenkinsfile. 2018-10-19 12:13:47 +00:00
Manuel Amador (Rudd-O)
8a1e8b5150 Excludefrom enhancement. 2018-10-19 12:13:35 +00:00
Manuel Amador (Rudd-O)
3847a37456 Install instructions made even more detailed. 2016-11-06 00:24:29 +00:00
Manuel Amador (Rudd-O)
6e6a02fdf2 Note that users of rpmbuild should install rpm-build. Bump version. 2016-11-06 00:19:33 +00:00
Manuel Amador (Rudd-O)
5bdbdab221 Update the documentation. 2016-11-06 00:11:14 +00:00
32 changed files with 6148 additions and 1012 deletions

9
.gitignore vendored
View File

@ -1,4 +1,13 @@
rpm/
pkgs/
*.pyc
*.pyo
*~
*.tar.gz
*.rpm
.*.swp
build
*.egg-info
src/*.service
.mypy_cache
.tox

17
.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>qubes-network-server</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.python.pydev.PyDevBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.python.pydev.pythonNature</nature>
</natures>
</projectDescription>

5
.pydevproject Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?eclipse-pydev version="1.0"?><pydev_project>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python interpreter</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
</pydev_project>

339
COPYING Normal file
View File

@ -0,0 +1,339 @@
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
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

5
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,5 @@
// https://github.com/Rudd-O/shared-jenkins-libraries
@Library('shared-jenkins-libraries@master') _
genericFedoraRPMPipeline(null, null, null, null, TestStrategySkipTests())

View File

@ -1,20 +1,47 @@
BINDIR=/usr/bin
LIBDIR=/usr/lib64
SBINDIR=/usr/local/sbin
UNITDIR=/etc/systemd/system
DESTDIR=
PROGNAME=qubes-network-server
PYTHON=/usr/bin/python3
all: src/qubes-routing-manager.service
src/qubes-routing-manager.service: src/qubes-routing-manager.service.in
sed 's|@SBINDIR@|$(SBINDIR)|g' < $< > $@
ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
.PHONY: clean dist rpm srpm install-template install-dom0 test
clean:
find -name '*.pyc' -o -name '*~' -print0 | xargs -0 rm -f
rm -f *.tar.gz *.rpm
cd $(ROOT_DIR) || exit $$? ; find -name '*.pyc' -o -name '*~' -print0 | xargs -0 rm -f
cd $(ROOT_DIR) || exit $$? ; rm -rf *.tar.gz *.rpm
cd $(ROOT_DIR) || exit $$? ; rm -rf *.egg-info build .mypy_cache
dist: clean
DIR=qubes-network-server-`awk '/^Version:/ {print $$2}' qubes-network-server.spec` && FILENAME=$$DIR.tar.gz && tar cvzf "$$FILENAME" --exclude "$$FILENAME" --exclude .git --exclude .gitignore -X .gitignore --transform="s|^|$$DIR/|" --show-transformed *
rpm: dist
T=`mktemp -d` && rpmbuild --define "_topdir $$T" -ta qubes-network-server-`awk '/^Version:/ {print $$2}' qubes-network-server.spec`.tar.gz || { rm -rf "$$T"; exit 1; } && mv "$$T"/RPMS/*/* "$$T"/SRPMS/* . || { rm -rf "$$T"; exit 1; } && rm -rf "$$T"
@which rpmspec || { echo 'rpmspec is not available. Please install the rpm-build package with the command `dnf install rpm-build` to continue, then rerun this step.' ; exit 1 ; }
cd $(ROOT_DIR) || exit $$? ; excludefrom= ; test -f .gitignore && excludefrom=--exclude-from=.gitignore ; DIR=`rpmspec -q --queryformat '%{name}-%{version}\n' *spec | head -1` && FILENAME="$$DIR.tar.gz" && tar cvzf "$$FILENAME" --exclude="$$FILENAME" --exclude=.git --exclude=.gitignore $$excludefrom --transform="s|^|$$DIR/|" --show-transformed *
srpm: dist
T=`mktemp -d` && rpmbuild --define "_topdir $$T" -ts qubes-network-server-`awk '/^Version:/ {print $$2}' qubes-network-server.spec`.tar.gz || { rm -rf "$$T"; exit 1; } && mv "$$T"/SRPMS/* . || { rm -rf "$$T"; exit 1; } && rm -rf "$$T"
@which rpmbuild || { echo 'rpmbuild is not available. Please install the rpm-build package with the command `dnf install rpm-build` to continue, then rerun this step.' ; exit 1 ; }
cd $(ROOT_DIR) || exit $$? ; rpmbuild --define "_srcrpmdir ." -ts `rpmspec -q --queryformat '%{name}-%{version}.tar.gz\n' *spec | head -1`
install:
install -Dm 755 src/usr/bin/qvm-static-ip -t $(DESTDIR)/$(BINDIR)/
install -Dm 644 src/usr/lib64/python2.7/site-packages/qubes/modules/*.py -t $(DESTDIR)/$(LIBDIR)/python2.7/site-packages/qubes/modules
rpm: dist
@which rpmbuild || { echo 'rpmbuild is not available. Please install the rpm-build package with the command `dnf install rpm-build` to continue, then rerun this step.' ; exit 1 ; }
cd $(ROOT_DIR) || exit $$? ; rpmbuild --define "_srcrpmdir ." --define "_rpmdir builddir.rpm" -ta `rpmspec -q --queryformat '%{name}-%{version}.tar.gz\n' *spec | head -1`
cd $(ROOT_DIR) ; mv -f builddir.rpm/*/* . && rm -rf builddir.rpm
install-template: all
PYTHONDONTWRITEBYTECODE=1 python3 routingmanagersetup.py install $(PYTHON_PREFIX_ARG) -O0 --root $(DESTDIR)
install -Dm 755 src/qubes-routing-manager -t $(DESTDIR)/$(SBINDIR)/
sed -i "s,^#!.*,#!$(PYTHON)," $(DESTDIR)/$(SBINDIR)/qubes-routing-manager
install -Dm 644 src/qubes-routing-manager.service -t $(DESTDIR)/$(UNITDIR)/
# Python 3 is always used for Qubes admin package.
install-dom0:
PYTHONDONTWRITEBYTECODE=1 python3 networkserversetup.py install $(PYTHON_PREFIX_ARG) -O0 --root $(DESTDIR)
install: install-dom0 install-template
test:
tox --current-env

1
Makefile.builder Normal file
View File

@ -0,0 +1 @@
RPM_SPEC_FILES=qubes-network-server.spec

287
README.md
View File

@ -1,15 +1,23 @@
#Qubes network server
# Qubes network server
This software lets you turn your [Qubes OS](https://www.qubes-os.org/) machine into a network server, enjoying all the benefits of Qubes OS (isolation, secure inter-VM process communication, ease of use) with none of the drawbacks of setting up your own Xen server.
This software lets you turn your [Qubes OS 4.2](https://www.qubes-os.org/) machine into
a network server, enjoying all the benefits of Qubes OS (isolation, secure
inter-VM process communication, ease of use) with none of the drawbacks
of setting up your own Xen server.
##Why?
This release is only intended for use with Qubes OS 4.2. Older Qubes OS releases
will not support it. For Qubes OS 4.1, check branch `r4.1`.
Qubes OS is a magnificent operating system, but there are so many use cases that its networking model cannot crack:
**Important note about upgrades**: when you upgrade your system from Qubes OS 4.1 to
Qubes OS 4.2, if you have this package installed in your template, the template will
likely **fail to update**. Please consult [our upgrade instructions](doc/distupgrade.md)
for information on how to proceed.
## Why?
Qubes OS is a magnificent operating system. That said, there are many use cases its networking
model does not work well for:
* As an automated integration testing system. Qubes OS would be
phenomenal for this, and its automation tools would make it
extremely easy to bring up and tear down entire environments.
If only those environments could network with each other securely!
* Remote management of Qubes OS instances. Vanilla Qubes OS cannot
easily be managed remotely. A better networking model would allow
for orchestration tools — such as
@ -18,8 +26,12 @@ Qubes OS is a magnificent operating system, but there are so many use cases that
within each VM.
* Anything that involves a secure server, serving data to people or
machines, simply cannot be done under vanilla Qubes OS.
* As an automated integration testing system. Qubes OS would be
phenomenal for this, and its automation tools would make it
extremely easy to bring up and tear down entire environments.
If only those environments could network with each other securely!
##Enhanced networking model
### The traditional Qubes networking model
The traditional Qubes OS networking model contemplates a client-only
use case. User VMs (AppVMs or StandaloneVMs) are attached to ProxyVMs,
@ -27,61 +39,172 @@ which give the user control over outbound connections taking place from
user VMs. ProxyVMs in turn attach to NetVMs, which provide outbound
connectivity for ProxyVMs and other user VMs alike.
![Standard Qubes OS network model](doc/Standard Qubes OS network model.png?raw=true "Standard Qubes OS network model")
![Standard Qubes OS network model](./doc/Standard%20Qubes%20OS%20network%20model.png)
No provision is made for running a server in a virtualized environment,
such that the server's ports are accessible by (a) other VMs (b) machines
beyond the perimeter of the NetVM. To the extent that such a thing is
possible, it is only possible by painstakingly maintaining firewall rules
for multiple VMs, which need to carefully override the existing firewall
rules, and require careful thought not to open the system to unexpected
attack vectors. The Qubes OS user interface provides no help either.
beyond the perimeter of the NetVM. By default, firewall rules in NetVMs
prevent traffic from reaching any VM attached to them. Furthermore, even
with custom, permissive firewall rules, the IP addresses of VMs attached
to any NetVM are not visible "on the other side of the NetVM", so firewall
rules can only help with something like DNAT. Finally, such custom firewalls
require careful thought not to open the system to unexpected attack vectors.
The Qubes OS user interface provides no means to set this up either.
Qubes network server changes all that.
### The Qubes network server networking model
![Qubes network server model](doc/Qubes network server model.png?raw=true "Qubes network server model")
Qubes network server builds on the Qubes security model and enhances it
to optionally permit traffic to user VMs.
![Qubes network server model](./doc/Qubes%20network%20server%20model.png)
With the Qubes network server software, it becomes possible to make
network servers in user VMs available to other machines, be them
peer VMs in the same Qubes OS system or machines connected to
a physical link shared by a NetVM. You get actual, full, GUI control
over network traffic, both exiting the VM and entering the VM, with
exactly the same Qubes OS user experience you are used to.
a physical link shared by a NetVM. Those network server VMs also
obey the Qubes OS outbound firewall rules controls, letting you run
services with outbound connections restricted using the standard Qubes OS
firewall system.
This is all, of course, opt-in, so the standard Qubes OS network security
model remains in effect until you decide to share network servers.
model remains in effect until you decide to enable the feature on any
particular VM.
##How to use this software
The only drawback of this method is that it requires you to attach
VMs meant to be exposed to the network directly to a NetVM, rather than
through a ProxyVM. VMs exposed through a ProxyVM will not be visible
to machines on the same network as the NetVM.
Once installed (see below), usage of the software is straightforward.
Here are documents that will help you take advantage of Qubes
network server:
## How to use this software
Once installed (**see below for installation instructions**), usage of
the software is straightforward.
These sample instructions assume:
* the software is properly installed (see below),
* you understand the distinction between dom0 and qubes,
* you already have an AppVM VM set up, named `testvm`, and
* your `sys-net` VM is attached to a network with subnet `192.168.16.0/24`
— this, of course, may vary depending on your local router configuration.
*Do not proceed any further if you do not yet meet these requirements.*
First, **attach** the VM you want to expose to the network
to a NetVM that has an active network connection:
`qvm-prefs -s testvm netvm sys-net`
Set an **IP** address on `testvm` belonging to the same LAN as `sys-net`:
`qvm-prefs -s testvm ip 192.168.16.25`
**Restart** the `testvm` VM if it was already running.
**Configure** routing method; to enable the network server feature for
your `testvm` VM, all you have to do in your AdminVM (`dom0`) is run
the following command:
`qvm-features testvm routing-method forward`
Now `testvm` is exposed to the network with address `192.168.16.25`, as well
as to other VMs attached to `NetVM`.
Finally, adjust **input firewall rules** on `testvm` to permit traffic coming from
machines in your LAN. `testvm` will have the standard Qubes OS firewall
rules stopping inbound traffic. To solve that issue, you can use a sample
rule in `testvm`:
```
sudo nft add rule qubes custom-input ip saddr 192.168.16.0/24 ct state new,established,related counter accept
```
You can make these rules persistent by [following instructions on the Qubes
OS firewall documentation page](https://www.qubes-os.org/doc/firewall/#enabling-networking-between-two-qubes).
Note that you do not need to change the `custom-forward` chain at all
on any qube -- Qubes network server manages that for you transparently
in your `NetVM`.
Here are documents that will help you take advantage of Qubes network server:
* [Setting up your first server](doc/Setting up your first server.md)
* [Setting up an SSH server](doc/Setting up an SSH server.md)
## Setup
##Installation
Package installation consists of two steps (**the package creation instructions are below**):
Installation is extremely easy:
1. Deploy the `qubes-core-admin-addon-network-server` RPM to your `dom0`.
2. Deploy the `qubes-network-server` RPM to the TemplateVM backing your
NetVM (which must be a Fedora instance). If your NetVM is a StandaloneVM,
then you must deploy this RPM to the NetVM directly.
* Prepare an RPM with the `make rpm` command on the local
directory of your clone.
* Copy the prepared RPM to the dom0 of your Qubes OS
machine.
* Install the RPM with `rpm -ivh`.
* Restart Qubes Manager, if it is running.
(Right-click on its notification icon, select *Exit*, then
relaunch it from the *System* menu.)
After that, to make it all take effect:
Qubes OS does not provide any facility to copy files from
a VM to the dom0. To work around this, you can use `qvm-run`:
1. Power off the TemplateVM.
2. Reboot the NetVM.
You're done. You can verify that the necessary component is running by launching
a terminal in your NetVM, then typing the following:
```
qvm-run --pass-io vmwiththerpm 'cat /home/user/path/to/qubes-network-server*rpm' > qns.rpm
systemctl status qubes-routing-manager.service
```
This lets you fetch the RPM file to the dom0, and save it as `qns.rpm`.
The routing manager should show as `enabled` and `active` in the terminal
output, with no errors. You can now follow the usage instructions above.
### How to build the packages to install
You will first build the `qubes-core-admin-addon-network-server` RPM.
To build this package, you will need to use a `chroot` jail containing
a Fedora installation of the exact same release as your `dom0` (Fedora 37
for Qubes 4.2). You can do this using `toolbox` (for maximum safety
within a disposable qube):
```
dnf install -y toolbox
toolbox create -r 37
toolbox enter fedora-toolbox-37
# Bam! You have a shell in an isolated Fedora 37 instance now.
```
Within the toolbox, all your normal files from your home directory are
visible. Change into the directory that contains this source code,
then type `make rpm`. You may have to install some packages within your
toolbox -- use `dnf install git rpm-build make coreutils tar gawk findutils systemd systemd-rpm-macros`
to get the minimum dependency set installed. Don't worry -- nothing within
the toolbox affects
Once built, in the source directory you will find the
`qubes-core-admin-addon-network-server` RPM built for your dom0.
To build the `qubes-network-server` RPM that goes in your template, you
can simply use a DisposableVM running the same Fedora release as your NetVM.
Build said package as follows:
```
# Dependencies
dnf install git rpm-build make coreutils tar gawk findutils systemd systemd-rpm-macros
# Source code
cd /path/to/qubes-network-server
# <make sure you are in the correct branch now>
make rpm
```
The process will output a `qubes-network-server-*.noarch.rpm` in the
directory where it ran. Fish it out and copy it into the template where
you'll install it.
You can power off the DisposableVM now.
### Upgrading to new / bug-fixing releases
Follow the same procedures as above, but when asked to install the packages
with `rpm -ivh`, change it to `rpm -Uvh` (uppercase U for upgrade).
Always restart your NetVMs between upgrades.
## Theory of operation
@ -89,37 +212,65 @@ Qubes OS relies on layer 3 (IP) routing. VMs in its networked tree send traffic
their default route interfaces, which upstream VMs receive and masquerade out of their own
default route interfaces.
Qubes network server slightly changes this when a networked VM — a VM which has had its
`static_ip` attribute set with `qvm-static-ip` — exists on the networked tree. As soon
as a networked VM boots up, Qubes network server:
Qubes network server slightly changes this when a VM gets its `routing-method` feature set
to `forward`. As soon as the feature is enabled with that value, or the VM in question
boots up, Qubes network server:
* sets a static `/32` route on every upstream VM to the networked VM's static IP,
directing the upstream VMs to route traffic for that IP to their VIFs where
they may find the networked VM
* enables ARP neighbor proxying for the static IP on every upstream VM, such that
every upstream VM announces itself to their own upstream VMs (and LAN, in the
case of NetVMs) as the networked VM
* sets firewall rules on every upstream VM that allow normal non-masquerading forwarding
to and from the IP of the networked VM
* (depending on the Qubes firewall policy of the networked VM) sets rules on every
upstream ProxyVM that allow for certain classes of inbound traffic
* (depending on the Qubes firewall policy of the networked VM) sets rules directly
on the networked VM that allow for certain classes of inbound traffic
* enables ARP neighbor proxying (and, if using IPv6, NDP neighbor proxying) in the NetVM
* sets firewall rules in the NetVM that neuter IP masquerading on traffic coming from
the networked VM
* sets firewall rules in the NetVM that allow traffic from other VMs to reach the
networked VM, neutering the default Qubes OS rule that normally prohibits this
The end result is instantaneous networking — machines upstream from the networked VM,
including machines in the physical LAN, can "see", ping, and connect to the networked
VM, provided that the firewall policy permits it. You do not need to set up any
special host-only routes on machines trying to access your networked VM — provided
that the static IP is on the same routable subnet as its upstream VM's, Qubes
network server does its magic automatically.
The above have the effect of exposing the networked VM to:
Of course, LAN machines connecting to the networked VM believe that the networked VM
possesses the MAC address of its upstream NetVM (just as if the upstream NetVM had a
second IP address and was serving traffic from it), but in reality, that is just an
illusion created by Qubes network server. This does have implications for your own
network security policy, in that the networked VM appears (from a MAC perspective)
to share a network card with its upstream NetVM.
* other AppVMs attached to the same NetVM
* other machines attached to the same physical network the NetVM is attached to
##Troubleshooting
Now all machines in the same LAN will be able to reach the networked VM.
Here is a step-by-step explanation of how IP traffic to and from the networked
VM happens, when the `routing-method` is set to `forward` on the networked VM:
The actions that the network server software performs are logged to the journal of each of the involved VMs. Generally, for each VM that has its own `static_ip` address set, this software will perform actions on that VM, on its parent ProxyVM, and on its grandparent NetVM. In case of problems, tailing the journal (`sudo journalctl -b`) on those three VMs simultaneously can be extremely useful to figure out what is going on.
1. Machine in LAN asks for the MAC address of the networked VM's IP address.
2. NetVM sees the ARP/NDP request and responds by proxying it to the networked VM.
3. Networked VM replies to the ARP/NDP request back to the NetVM.
4. NetVM relays the ARP/NDP reply back to the network, but substitutes its own
MAC address in the reply.
5. Machine in LAN sends local IP packet to the IP of the networked VM's IP address,
but destined to the MAC address of the NetVM.
6. The NetVM sees the IP packet, and routes it to the networked VM.
7. The Networked VM receives the IP packet.
8. If the networked VM needs to respond, it sends an IP packet back to the
machine in LAN.
9. NetVM notices packet comes from the networked VM, and instead of masquerading it,
it lets the packet through unmodified, with the source IP address of the
networked VM.
The end result is practical networking with no need to set up routing tables on
machines attempting to access the networked VM.
Of course, if you want machines in the LAN to have access to the networked VM, you
must still set an appropriate `ip` preference on the networked VM. For example, if
your physical LAN had subnet `1.2.3.0/24`, and you want machines in your physical LAN
to connect to a networked VM, you must set the `ip` preference of the networked VM
to a previously-unused IP within the range `1.2.3.1` and `1.2.3.255`. Failing that,
you must assign a host-specific route on the source machine which uses the NetVM
as the gateway, and uses the IP address of the networked VM (see `qvm-prefs` output)
as the destination address.
## Limitations
* HVMs are not supported at all at this time. This will change over time, and
you can help it change faster by submitting a pull request with HVM support.
* Interposing a ProxyVM between a networked VM and the NetVM is not currently
supported. This is implementable in principle, but would require a recursive
walk that encompasses the entire network link from NetVM through intermediate
ProxyVMs.
## Troubleshooting
The actions that the `qubes-routing-manager` service performs are logged to the journal
of the NetVM where the `qubes-routing-manager` service is running.
In case of problems, tailing the journal (`sudo journalctl -fa`) on the NetVM will be
extremely useful to figure out what the problem is.

46
TODO
View File

@ -1,37 +1,13 @@
To do list:
* Make the system do the right thing (withdraw ip neigh /
ip route / iptables rules) when VMs power off or when
their network gets detached.
Right now the rules are only reconfigured when:
* a VM starts (ancestor VMs get reconfigured)
* a VM gets unpaused (same as before)
* a VM network gets attached (same as before)
* a VM's FW rules get altered (parent ProxyVM and sibling
VMs get reconfigured, and this reconfiguration only
affects iptables rules)
* Make the system do the right thing when `static_ip`
is changed / enabled / disabled, without requiring a
VM restart.
* Key point (but not only point): appvm fwrules that
were setup need to be un-setup, which means that
our current algorithm "look at VMs with static_ip"
will not work to un-setup those fwrules.
* Define very clearly when fw state is modified
for appvm, as that requires execution of code
in the appvm, and tracking how and when to
undo that state transition.
* VM's entire IP and everything will be different,
and this setup only occurs during initial boot of the
VM, so it may be inevitable to force a restart of
the VM. It depends on what kind of stuff depends on
the IP being set early on boot. VM rounting tables,
ifconfig, stuff like ip neigh on the ancestor VMS,
firewall rules, et cetera.
* Evaluate network access permissions when appvm
is attached to netvm, vs attached to proxyvm to netvm,
vs attached to proxyvm to proxyvm to netvm.
* Prolly need to write some important automated tests.
* Document entry points of the plugin that activate
code from the plugin, and under which circumstances / events
these pieces of code run.
* Package up `dom0` component so it's installable via
RPM. Alternatively, upstream it completely.
* Make the system more robust by setting the right
`ip neigh / ip route` rules to force incoming traffic
to go to the specific VIF that backs the exposed VM.
* Instead of / in addition to proxy ARP/NDP, use static
MAC addresses set at runtime, for each VM.
* Support interposing ProxyVMs between NetVMs and AppVMs.
* (Maybe) set up firewall rules on AppVM to obey its designated
firewall rules, bringing back support for the GUI. This
probably needs a conversation with the Qubes OS core devs.

1
build.parameters Normal file
View File

@ -0,0 +1 @@
["QUBES_RELEASES": "4.2"]

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -14,86 +14,73 @@ First of all, [install Qubes network server](https://github.com/Rudd-O/qubes-net
## Set up needed VMs
You'll need three VMs on the network server:
You'll need two VMs on the network server:
1. A NetVM which will be attached to the network interface mentioned above.
For the purposes of this example, we'll call this `exp-net`.
2. A ProxyVM which will be attached to the NetVM.
This we'll call `exp-firewall`.
3. A StandaloneVM which will be attached to the ProxyVM. The role of this
machine is to give you control over `dom0` and other VMs on the system.
This we'call `exp-manager`.
This we'call `exp-ssh`.
Create them if you do not already have them. Once you have created them,
start the StandaloneVM `exp-manager` you created, and then verify that you
can ping your manager machine from it.
start the StandaloneVM `exp-ssh` you created, and then verify that networking
works within `exp-ssh`.
Power off `exp-manager` when your test is complete.
## Set static address on `exp-manager`
## Set static address on `exp-ssh`
On your server's `dom0`, run the command:
```
qvm-static-ip -s exp-manager static_ip x.y.z.w
qvm-prefs -s exp-ssh static_ip x.y.z.w
```
`x.y.z.w` must be an IP address available on the same network that both
your `exp-net` and your manager machine share.
Power `exp-manager` back on, and verify that you can still ping your
manager machine from it.
Shut down `exp-ssh` back on, start it back up again,
and verify that you can still ping your manager machine from it.
Verify that you can ping the new IP address you gave to `exp-manager`
## Enable forward-style routing for `exp-ssh`
```
qvm-features exp-ssh routing-method forward
```
Now verify that you can ping the new IP address you gave to `exp-ssh`
from your manager machine. This should work fine.
## Harden the firewall on `exp-manager`
## Adjust the firewall on `exp-ssh`
At this point, `exp-manager` is accessible on your network, so it's best
At this point, `exp-ssh` is accessible on your network, so it's best
to set up a firewall rule permitting only SSH access from the manager
machine, and denying all other access to anyone.
If you are new to firewall rules in Qubes, [check out this quite
good overview of them](https://www.qubes-os.org/doc/qubes-firewall/).
[See the documentation for Qubes OS](https://www.qubes-os.org/doc/firewall/#where-to-put-firewall-rules)
to understand more about firewalls in AppVMs
Launch the Qubes Manager preferences window for the `exp-manager` VM.
Go to the *Firewall rules* tab and select *Deny network access
except...* from the top area.
## Enable and start SSH on the `exp-ssh` VM
Add a new network rule (use the plus button). On the *Address* box,
you're going to write `from-a.b.c.d`, where `a.b.c.d` is the IP address
of your manager machine. Select the *TCP* protocol, and type `22`
(the SSH port) on the *Service* box. Click OK.
([See the documentation for qubes-network-server](https://github.com/Rudd-O/qubes-network-server)
to understand more about firewalling rules in Qubes network server.)
Back on the main dialog, click *OK*.
## Enable and start SSH on the `exp-manager` VM
In a terminal window of `exp-manager`, run:
In a terminal window of `exp-ssh`, run:
```
sudo systemctl enable sshd.service
sudo systemctl start sshd.service
sudo systemctl enable --now sshd.service
```
This will start the OpenSSH server on the `exp-manager` VM.
This will start the OpenSSH server on the `exp-ssh` VM.
Test that you can connect via SSH from the manager machine to
the `exp-manager` VM. You will not be able to log in, because
the `exp-ssh` VM. You will not be able to log in, because
no password is set up, but we will fix that shortly.
## Set up SSH authentication
On the `exp-manager` VM, set a password on the `user` user:
On the `exp-ssh` VM, set a password on the `user` user:
```
sudo passwd user
```
On the manager machine, copy your SSH public key to `exp-manager`:
On the manager machine, copy your SSH public key to `exp-ssh`:
```
ssh-copy-id user@x.y.z.w
@ -101,7 +88,7 @@ ssh-copy-id user@x.y.z.w
This will prompt you for the password you set up. Enter it.
Now kill the `user` password on `exp-manager`:
Now kill the `user` password on `exp-ssh`:
```
sudo passwd -d user
@ -110,7 +97,7 @@ sudo passwd -l user
Good news! You can now remotely log in, from your manager machine,
to your Qubes OS server. You are also able to run commands on the
`exp-manager` VM, directly from your manager machine.
`exp-ssh` VM, directly from your manager machine.
Should you want to run commands on *other* VMs of your Qubes OS server,
then learn how to [enable remote management of your Qubes network server](https://github.com/Rudd-O/ansible-qubes/tree/master/doc/Remote management of Qubes OS servers.md).
then learn how to [enable remote management of your Qubes network server](https://github.com/Rudd-O/ansible-qubes/tree/master/doc/Remote%20management%20of%20Qubes%20OS%20servers.md).

View File

@ -2,83 +2,81 @@
To illustrate, we'll proceed with an example VM `httpserver` which
is meant to be a standalone VM that contains files, being served by
a running HTTP server (port 80) within it. This VM is attached to
a ProxyVM `server-proxy`, which in turn is connected to a NetVM
`sys-net`, with IP address `192.168.1.4` on a local network
a running HTTP server (port 80) within it. This VM is attached to a
NetVM `sys-net`, with IP address `192.168.1.4` on a local network
`192.168.1.0/24`. Our goal will be to make `httpserver` accessible
to your laptop on the same physical network, which we'll assume has
IP address `192.168.1.8`.
to your client laptop on the same physical network, which we'll
assume has IP address `192.168.1.8`.
##Assign a static address
First step is to assign an address — let's make it `192.168.1.6`
to `httpserver`:
to `httpserver` (of course, you should make sure that this IP
address isn't used by any other equipment in your network):
```
qvm-static-ip -s httpserver static_ip 192.168.1.6
qvm-prefs -s httpserver ip 192.168.1.6
```
##Restart VM
Due to limitations in this release of the code, you must power off
the `httpserver` VM and then power it back on.
Due to limitations in how the IP address is set on the VM, you must
power off the `httpserver` VM and then power it back on.
## Enable forward-style routing to the VM
```
qvm-feature httpserver routing-method forward
```
Now the IP of the `httpserver` VM is visible to your laptop, but
it's got the standard Qubes OS firewall rules that all AppVMs have,
so next we'll adjust that.
##Set firewall rules on VM
If you are new to firewall rules in Qubes, [check out this quite
good overview of them](https://www.qubes-os.org/doc/qubes-firewall/).
The normal way to set up AppVM firewall rules is
[documented here](https://www.qubes-os.org/doc/firewall/#where-to-put-firewall-rules).
Launch the Qubes Manager preferences window for the `httpserver` VM.
Go to the *Firewall rules* tab and select *Deny network access
except...* from the top area. *Allow ICMP traffic* but deny
*DNS queries*.
For the purposes of this demo, all you have to run inside `httpserver`
is this:
Finally, add a new network rule (use the plus button). On the
*Address* box, you're going to write `from-192.168.1.8`. Select
the *TCP* protocol, and type `80` on the *Service* box. Click OK.
```
sudo iptables -I INPUT 1 -p tcp --dport 8080 -j ACCEPT
```
Note the trick here — any address whose text begins with
`from-` gets transformed into an incoming traffic rule, as opposed
to the standard rules that control only outbound traffic.
(This method of setting firewall rules makes them go away when you
restart the AppVM. Refer to the link in this section to make them
stick around after a VM restart.)
**Security note**: the default "allow all" firewall leaves all ports
of the VM accessible to the world. To the extent that you can avoid
it, do not use the "allow all" firewall setting at all.
## Start a Python HTTP server
Back on the main dialog, click *OK*.
In your `httpserver` VM, run:
```
python3 -m http.server
```
##That's it!
You'll be able to ping, from your laptop, the address `192.168.1.6`.
You will also be able to point your browser at it, and it will
render the served pages from the HTTP server running directly on
`httpserver`.
Save from ICMP, no other port or protocol will be allowed for
inbound connections.
You'll also note that `httpserver` has received no permission to
engage in any sort of outbound network traffic.
You will now be able to point your browser at http://192.168.1.6:8080/,
and it will render the served pages from the HTTP server running
directly on `httpserver`.
##Inter-VM network communication
This software isn't limited to just letting network servers be
accessible from your physical network. VMs can talk among each
other too. Simple instructions:
* Set up a static IP address for each VM.
* Set up the appropriate rules to let them talk to each other.
VMs so authorized can talk to each other over the network,
even when they do not share a ProxyVM between them, of course,
so long as their ProxyVMs share the same NetVM.
other too. A pair of VMs whose feature `routing-method` has been
set to `forward` are authorized to talk to each other over the
network, so long as they are attached to the same NetVM.
##Disabling network server
Two-step process. Step one:
One-step process:
```
qvm-static-ip -s httpserver static_ip none
qvm-feature --delete httpserver routing-method
```
Step two: power the VM off, then start it back up.
You're done.

64
doc/distupgrade.md Normal file
View File

@ -0,0 +1,64 @@
# How to upgrade a Qubes network server from Qubes OS 4.1 to Qubes OS 4.2
The [standard instructions to upgrade Qubes OS systems](https://www.qubes-os.org/doc/upgrade/4.2/)
will fail to work. The instructions tell you to run something to the effect of:
```
qubes-dist-upgrade --all-pre-reboot <other flags>
```
then reboot, then run:
```
qubes-dist-upgrade --all-post-reboot <other flags>
```
The pre-reboot phase will fail if run without the following precautions.
## Step by step instructions
First, build a `qubes-network-server` RPM with the instructions provided
by this package's [README.md](../README.md) file. Then, for each template
where `qubes-network-server` is installed, deposit your build of the
`qubes-network-server` RPM in a folder `/root/update` of the template,
and run the command `createrepo_c /root/update` (you may have to install
package `createrepo_c` via `dnf` to run it).
Now build a `qubes-core-admin-addon-network-server` package for your dom0,
then copy the file to your profile directory into dom0. Remember this
package has to be built *in the same Fedora release (37)* as the Qubes OS
4.2 dom0 (the `toolbox` command in a disposable qube is handy for this!).
Now open the file `/etc/dnf/dnf.conf` on every template qube where you
did the above, then add an `exclude=qubes-network-server` setting under
its `[main]` section.
Remove the currently-installed `qubes-core-admin-addon-network-server`
package from your dom0 (using `dnf remove`).
Run the pre-reboot phase.
Install the recently-built `qubes-core-admin-addon-network-server` package
into dom0 (using `dnf install` with the path to the RPM file).
Reboot.
Before running the post-reboot phase, remove the setting you added to the
`dnf.conf` file of each template you modified. Finally, add the file
`/etc/yum.repos.d/local.repo` with the following contents:
```
[local]
name=Local packages
baseurl=file:///root/update
enabled=1
gpgcheck=0
metadata_expire=15
```
Now run the post-reboot phase. The template upgrade should succeed now.
To finalize, delete folder `/root/update` and file `/etc/yum.repos.d/local.repo`
from every template that has it.
You are now updated to Qubes OS 4.2 and `qubes-network-server` is ready.

26
networkserversetup.py Normal file
View File

@ -0,0 +1,26 @@
import os
import setuptools
if __name__ == "__main__":
version = (
open(os.path.join(os.path.dirname(__file__), "qubes-network-server.spec"))
.read()
.strip()
)
version = [v for v in version.splitlines() if v.startswith("Version:")][0]
version = version.split()[-1]
setuptools.setup(
name="qubesnetworkserver",
version=version,
author="Manuel Amador (Rudd-O)",
author_email="rudd-o@rudd-o.com",
description="Qubes network server dom0 component",
license="GPL2+",
url="https://github.com/Rudd-O/qubes-network-server",
packages=("qubesnetworkserver",),
entry_points={
"qubes.ext": [
"qubesnetworkserver = qubesnetworkserver:QubesNetworkServerExtension",
],
},
)

View File

@ -3,43 +3,130 @@
%define mybuildnumber %{?build_number}%{?!build_number:1}
Name: qubes-network-server
Version: 0.0.5
Version: 0.1.6
Release: %{mybuildnumber}%{?dist}
Summary: Turn your Qubes OS into a network server
BuildArch: noarch
License: GPLv3+
License: GPLv2+
URL: https://github.com/Rudd-O/qubes-network-server
Source0: https://github.com/Rudd-O/%{name}/archive/{%version}.tar.gz#/%{name}-%{version}.tar.gz
Source0: https://github.com/Rudd-O/%{name}/archive/{%version}.tar.gz#/%{name}-%{version}.tar.gz
BuildRequires: make
BuildRequires: coreutils
BuildRequires: tar
BuildRequires: gawk
BuildRequires: findutils
BuildRequires: python3
BuildRequires: python3-rpm-macros
BuildRequires: systemd-rpm-macros
BuildRequires: python3-tox-current-env
BuildRequires: python3-mypy
BuildRequires: python3-pytest
Requires: qubes-core-dom0
Requires: qubes-core-agent-networking >= 4.2
Conflicts: qubes-core-agent < 4.2
Requires: python3
Requires: python3-qubesdb
Requires: nftables
%description
This package lets you turn your Qubes OS into a network server.
This package lets you turn your Qubes OS into a network server. Install this
in the TemplateVM of your NetVM. Then install the companion
qubes-core-admin-addon-network-server package in your dom0.
Please see README.md enclosed in the package for instructions on how to use
this software.
%package -n qubes-core-admin-addon-network-server
Summary: dom0 administrative extension for Qubes network server
BuildRequires: make
BuildRequires: coreutils
BuildRequires: tar
BuildRequires: findutils
BuildRequires: python3
BuildRequires: python3-rpm-macros
BuildRequires: python3-setuptools
Requires: python3
Requires: qubes-core-dom0 >= 4.2
Conflicts: qubes-core-dom0 < 4.2
%description -n qubes-core-admin-addon-network-server
This package lets you turn your Qubes OS into a network server. Install this
in your dom0. Then install the companion qubes-network-server package in the
TemplateVM of your NetVM.
Please see README.md enclosed in the package for instructions on how to use
this software.
%prep
%setup -q
%build
# variables must be kept in sync with install
make DESTDIR=$RPM_BUILD_ROOT BINDIR=%{_bindir} LIBDIR=%{_libdir}
make DESTDIR=$RPM_BUILD_ROOT SBINDIR=%{_sbindir} UNITDIR=%{_unitdir} PYTHON=%{__python3}
%install
rm -rf $RPM_BUILD_ROOT
# variables must be kept in sync with build
make install DESTDIR=$RPM_BUILD_ROOT BINDIR=%{_bindir} LIBDIR=%{_libdir}
make install DESTDIR=$RPM_BUILD_ROOT SBINDIR=%{_sbindir} UNITDIR=%{_unitdir} PYTHON=%{__python3}
mkdir -p "$RPM_BUILD_ROOT"/%{_presetdir}
echo 'enable qubes-routing-manager.service' > "$RPM_BUILD_ROOT"/%{_presetdir}/75-%{name}.preset
%check
tox --current-env
%files
%attr(0755, root, root) %{_bindir}/qvm-static-ip
%attr(0644, root, root) %{_libdir}/python2.7/site-packages/qubes/modules/*.py*
%attr(0755, root, root) %{_sbindir}/qubes-routing-manager
%attr(0644, root, root) %{python3_sitelib}/qubesroutingmanager/*
%{python3_sitelib}/qubesroutingmanager-*.egg-info
%attr(0644, root, root) %{_presetdir}/75-%{name}.preset
%config %attr(0644, root, root) %{_unitdir}/qubes-routing-manager.service
%doc README.md TODO
%files -n qubes-core-admin-addon-network-server
%attr(0644, root, root) %{python3_sitelib}/qubesnetworkserver/*
%{python3_sitelib}/qubesnetworkserver-*.egg-info
%post
%systemd_post qubes-routing-manager.service
%posttrans
# Remove old unit enablement paths.
reenable=0
if [ -h %{_sysconfdir}/systemd/system/multi-user.target.wants/qubes-routing-manager.service ]
then
reenable=1
rm -f %{_sysconfdir}/systemd/system/multi-user.target.wants/qubes-routing-manager.service
fi
if [ -h %{_sysconfdir}/systemd/system/qubes-iptables.service.wants/qubes-routing-manager.service ]
then
reenable=1
rm -f %{_sysconfdir}/systemd/system/qubes-iptables.service.wants/qubes-routing-manager.service
fi
if [ $reenable = 1 ]
then
mkdir -p %{_sysconfdir}/systemd/system/qubes-network.service.wants
ln -sf %{_unitdir}/qubes-routing-manager.service %{_sysconfdir}/systemd/system/qubes-network.service.wants/qubes-routing-manager.service
fi
exit 0
%preun
%systemd_preun qubes-routing-manager.service
%postun
%systemd_postun_with_restart qubes-routing-manager.service
%post -n qubes-core-admin-addon-network-server
%systemd_post qubesd.service
%postun -n qubes-core-admin-addon-network-server
%systemd_postun_with_restart qubesd.service
%changelog
* Mon Apr 13 2020 Manuel Amador (Rudd-O) <rudd-o@rudd-o.com>
- Update to Qubes 4.0
* Tue Oct 11 2016 Manuel Amador (Rudd-O) <rudd-o@rudd-o.com>
- Initial release

View File

@ -0,0 +1,112 @@
import qubes.ext
def l(text, *parms):
return # This exists only to debug.
if parms:
text = text % parms
import sys
print("nsext:", text, file=sys.stderr)
sys.stderr.flush()
l("loaded")
class QubesNetworkServerExtension(qubes.ext.Extension):
def shutdown_routing_for_vm(self, netvm, appvm):
l("shutdown routing for vm %s %s", netvm, appvm)
self.reload_routing_for_vm(netvm, appvm, True)
def reload_routing_for_vm(self, netvm, appvm, shutdown=False):
"""Reload the routing method for the VM."""
l("reload routing for vm %s %s shutdown %s", netvm, appvm, shutdown)
if not netvm.is_running():
return
for addr_family in (4, 6):
ip = appvm.ip6 if addr_family == 6 else appvm.ip
if ip is None:
continue
# report routing method
self.setup_forwarding_for_vm(netvm, appvm, ip, remove=shutdown)
def setup_forwarding_for_vm(self, netvm, appvm, ip, remove=False):
"""
Record in Qubes DB that the passed VM may be meant to have traffic
forwarded to and from it, rather than masqueraded from it and blocked
to it.
The relevant incantation on the command line to assign the forwarding
behavior is `qvm-features <VM> routing-method forward`. If the feature
is set on the TemplateVM upon which the VM is based, then that counts
as the forwarding method for the VM as well.
The counterpart code in qubes-firewall handles setting up the NetVM
with the proper networking configuration to permit forwarding without
masquerading behavior.
If `remove` is True, then we remove the respective routing method from
the Qubes DB instead.
"""
l("setup forwarding for vm vm %s %s %s remove %s", netvm, appvm, ip, remove)
if ip is None:
return
routing_method = appvm.features.check_with_template(
"routing-method", "masquerade"
)
base_file = "/qubes-routing-method/{}".format(ip)
if remove:
netvm.untrusted_qdb.rm(base_file)
elif routing_method == "forward":
netvm.untrusted_qdb.write(base_file, "forward")
else:
netvm.untrusted_qdb.write(base_file, "masquerade")
@qubes.ext.handler(
"domain-feature-set:routing-method",
"domain-feature-delete:routing-method",
)
def on_routing_method_changed(self, vm, ignored_feature, **kwargs):
# pylint: disable=no-self-use,unused-argument
l("routing method changed %s", vm)
if "oldvalue" not in kwargs or kwargs.get("oldvalue") != kwargs.get("value"):
if vm.netvm:
self.reload_routing_for_vm(vm.netvm, vm)
@qubes.ext.handler("domain-qdb-create")
def on_domain_qdb_create(self, vm, event, **kwargs):
"""Fills the QubesDB with firewall entries."""
# pylint: disable=unused-argument
l("domain create %s %s", vm, event)
if vm.netvm:
self.reload_routing_for_vm(vm.netvm, vm)
@qubes.ext.handler("domain-start")
def on_domain_started(self, vm, event, **kwargs):
# pylint: disable=unused-argument
l("domain started %s %s", vm, event)
try:
for downstream_vm in vm.connected_vms:
self.reload_routing_for_vm(vm, downstream_vm)
except AttributeError:
pass
@qubes.ext.handler("domain-shutdown")
def on_domain_shutdown(self, vm, event, **kwargs):
# pylint: disable=unused-argument
l("domain shutdown %s %s", vm, event)
try:
for downstream_vm in self.connected_vms:
self.shutdown_routing_for_vm(vm, downstream_vm)
except AttributeError:
pass
if vm.netvm:
self.shutdown_routing_for_vm(vm.netvm, vm)
@qubes.ext.handler("net-domain-connect")
def on_net_domain_connect(self, vm, event):
# pylint: disable=unused-argument
l("domain connect %s %s", vm, event)
if vm.netvm:
self.reload_routing_for_vm(vm.netvm, vm)

View File

@ -0,0 +1,379 @@
#!/usr/bin/python3
import json
import logging
import subprocess
from typing import TypedDict, Any, cast, Literal, Union
ADDRESS_FAMILIES = Union[Literal["ip"], Literal["ip6"]]
class Chain(TypedDict):
name: str
family: str
table: str
handle: int
type: str
hook: str
prio: int
policy: str
class Table(TypedDict):
family: str
name: str
handle: int
class Metainfo(TypedDict):
version: str
release_name: str
json_schema_version: int
class Rule(TypedDict):
family: str
table: str
chain: str
handle: int
expr: list[dict[str, Any]]
class ChainContainer(TypedDict):
chain: Chain
class MetainfoContainer(TypedDict):
metainfo: Metainfo
class TableContainer(TypedDict):
table: Table
class RuleContainer(TypedDict):
rule: Rule
class NFTablesOutput(TypedDict):
nftables: list[ChainContainer | MetainfoContainer | TableContainer | RuleContainer]
ADDRESS_FAMILY_IPV6 = "ip6"
ADDRESS_FAMILY_IPV4 = "ip"
TABLE_NAME = "qubes"
FORWARD_CHAIN_NAME = "forward"
POSTROUTING_CHAIN_NAME = "postrouting"
ROUTING_MANAGER_CHAIN_NAME = "qubes-routing-manager"
ROUTING_MANAGER_POSTROUTING_CHAIN_NAME = "qubes-routing-manager-postrouting"
NFTABLES_CMD = "nft"
def get_table(address_family: ADDRESS_FAMILIES, table: str) -> NFTablesOutput:
return cast(
NFTablesOutput,
json.loads(
subprocess.check_output(
[NFTABLES_CMD, "-n", "-j", "list", "table", address_family, table],
text=True,
)
),
)
def add_chain(address_family: ADDRESS_FAMILIES, table: str, chain: str) -> None:
subprocess.check_output(
[
NFTABLES_CMD,
"-n",
"-j",
"add",
"chain",
address_family,
table,
chain,
],
text=True,
)
def append_rule_at_end(
address_family: ADDRESS_FAMILIES, table: str, chain: str, *rest: str
) -> None:
subprocess.check_output(
[
NFTABLES_CMD,
"-n",
"-j",
"add",
"rule",
address_family,
table,
chain,
]
+ list(rest),
text=True,
)
def append_counter_at_end(
address_family: ADDRESS_FAMILIES, table: str, chain: str, *rest: str
) -> None:
subprocess.check_output(
[
NFTABLES_CMD,
"-n",
"-j",
"add",
"rule",
address_family,
table,
chain,
"counter",
]
+ list(rest),
text=True,
)
def _append_or_insert_rule(
where: Literal["add"] | Literal["insert"],
address_family: ADDRESS_FAMILIES,
table: str,
chain: str,
handle: int,
*rest: str,
) -> None:
subprocess.check_output(
[
NFTABLES_CMD,
"-n",
"-j",
where,
"rule",
address_family,
table,
chain,
"position",
str(handle),
]
+ list(rest),
text=True,
)
def append_rule_after(
address_family: ADDRESS_FAMILIES, table: str, chain: str, handle: int, *rest: str
) -> None:
_append_or_insert_rule("add", address_family, table, chain, handle, *rest)
def insert_rule_before(
address_family: ADDRESS_FAMILIES, table: str, chain: str, handle: int, *rest: str
) -> None:
_append_or_insert_rule("insert", address_family, table, chain, handle, *rest)
def delete_rule(
address_family: ADDRESS_FAMILIES, table: str, chain: str, handle: int
) -> None:
subprocess.check_output(
[
NFTABLES_CMD,
"-n",
"-j",
"delete",
"rule",
address_family,
table,
chain,
"handle",
str(handle),
],
text=True,
)
def setup_plain_forwarding_for_address(source: str, enable: bool, family: int) -> None:
logging.info("Handling forwarding for address %s family %s.", source, family)
af = cast(
ADDRESS_FAMILIES,
ADDRESS_FAMILY_IPV6 if family == 6 else ADDRESS_FAMILY_IPV4,
)
# table ip qubes {
# set downstream {
# type ipv4_addr
# elements = { 10.137.0.10, 10.250.4.13 }
# }
# ...
existing_table_output = get_table(af, TABLE_NAME)
existing_table_items = existing_table_output["nftables"]
existing_chains = [x["chain"] for x in existing_table_items if "chain" in x] # type: ignore
existing_rules = [x["rule"] for x in existing_table_items if "rule" in x] # type: ignore
try:
forward_chain = [x for x in existing_chains if x["name"] == FORWARD_CHAIN_NAME][
0
]
postrouting_chain = [
x for x in existing_chains if x["name"] == POSTROUTING_CHAIN_NAME
][0]
except IndexError:
logging.warn(
"No forward or postrouting chains in table %s, not setting up forwarding",
TABLE_NAME,
)
return
for chain_name in [
ROUTING_MANAGER_CHAIN_NAME,
ROUTING_MANAGER_POSTROUTING_CHAIN_NAME,
]:
chain: None | Chain = None
try:
chain = [x for x in existing_chains if x["name"] == chain_name].pop()
except IndexError:
pass
if not chain:
logging.info(
"Adding %s chain to table %s and counter to chain",
chain_name,
TABLE_NAME,
)
add_chain(af, TABLE_NAME, chain_name)
append_counter_at_end(
af,
TABLE_NAME,
chain_name,
)
def is_oifgroup_2(rule):
return (
rule["chain"] == forward_chain["name"]
and len(rule["expr"]) == 3
and (
rule["expr"][0].get("match", {}).get("op") == "=="
and rule["expr"][0]
.get("match", {})
.get("left", {})
.get("meta", {})
.get("key")
== "oifgroup"
and rule["expr"][0].get("match", {}).get("right") == 2
)
and (rule["expr"][-1].get("drop", "not none") is None)
)
def is_postrouting_masquerade(rule):
return (
rule["chain"] == postrouting_chain["name"]
and len(rule["expr"]) == 1
and "masquerade" in rule["expr"][0]
)
for parent_chain, child_chain_name, previous_rule_detector, insertor in [
(
forward_chain,
ROUTING_MANAGER_CHAIN_NAME,
is_oifgroup_2,
insert_rule_before,
),
(
postrouting_chain,
ROUTING_MANAGER_POSTROUTING_CHAIN_NAME,
is_postrouting_masquerade,
insert_rule_before,
),
]:
jump_rule: None | Rule = None
try:
jump_rule = [
x
for x in existing_rules
if x["chain"] == parent_chain["name"]
and x["family"] == af
and len(x["expr"]) == 1
and x["expr"][0].get("jump", {}).get("target") == child_chain_name
].pop()
except IndexError:
pass
if not jump_rule:
try:
previous_rule = [
x for x in existing_rules if previous_rule_detector(x)
][0]
except IndexError:
logging.warn(
"Cannot find appropriate previous rule in chain %s of table %s, not setting up forwarding",
parent_chain["name"],
TABLE_NAME,
)
logging.info(
"Adding rule to jump from chain %s to chain %s in table %s",
parent_chain["name"],
child_chain_name,
TABLE_NAME,
)
insertor(
af,
TABLE_NAME,
parent_chain["name"],
previous_rule["handle"],
"jump",
child_chain_name,
)
def detect_ip_rule(rule: Rule, chain_name: str, ip: str, mode: str):
return (
rule["chain"] == chain_name
and len(rule["expr"]) == 2
and rule["expr"][0].get("match", {}).get("op", {}) == "=="
and rule["expr"][0]["match"]
.get("left", {})
.get("payload", {})
.get("protocol", "")
== af
and rule["expr"][0]["match"]["left"]["payload"].get("field", "") == mode
and rule["expr"][0].get("match", {}).get("right", []) == ip
and "accept" in rule["expr"][1]
)
for chain_name, mode in [
(ROUTING_MANAGER_CHAIN_NAME, "daddr"),
(ROUTING_MANAGER_POSTROUTING_CHAIN_NAME, "saddr"),
]:
address_rules = [
x for x in existing_rules if detect_ip_rule(x, chain_name, source, mode)
]
if enable and not address_rules:
logging.info(
"Adding accept rule on chain %s for %s.",
chain_name,
source,
)
append_rule_at_end(
af,
TABLE_NAME,
chain_name,
af,
mode,
source,
"accept",
)
elif not enable and address_rules:
logging.info(
"Removing %s accept rules from chain %s for %s.",
len(address_rules),
chain_name,
source,
)
for rule in reversed(sorted(address_rules, key=lambda r: r["handle"])):
delete_rule(af, TABLE_NAME, chain_name, rule["handle"])

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,162 @@
import os
from unittest import mock
from qubesroutingmanager import setup_plain_forwarding_for_address
def get_fixture(name):
with open(os.path.join(os.path.dirname(__file__), "fixtures", name)) as f:
return f.read()
def mock_collector(output: str):
final_args = []
class MockedPopen:
def __init__(self, args, **kwargs):
final_args.append(args[3:])
self.args = args
self.returncode = 0
def __enter__(self):
return self
def __exit__(self, exc_type, value, traceback):
pass
def communicate(self, input=None, timeout=None):
stdout = output
stderr = ""
self.returncode = 1
return stdout, stderr
def poll(self):
return 0
return final_args, MockedPopen
def test_partial_add_completes_the_add():
got, MockedPopen = mock_collector(get_fixture("partially_added.json"))
expected = [
["list", "table", "ip", "qubes"],
["add", "chain", "ip", "qubes", "qubes-routing-manager-postrouting"],
[
"add",
"rule",
"ip",
"qubes",
"qubes-routing-manager-postrouting",
"counter",
],
[
"insert",
"rule",
"ip",
"qubes",
"postrouting",
"position",
"67",
"jump",
"qubes-routing-manager-postrouting",
],
[
"add",
"rule",
"ip",
"qubes",
"qubes-routing-manager-postrouting",
"ip",
"saddr",
"10.250.4.13",
"accept",
],
]
with mock.patch("subprocess.Popen", MockedPopen):
setup_plain_forwarding_for_address("10.250.4.13", True, 4)
assert got == expected
def test_forward_rule_added_before_oifgroup_2():
got, MockedPopen = mock_collector(get_fixture("no_routing_manager.json"))
expected = [
["list", "table", "ip", "qubes"],
["add", "chain", "ip", "qubes", "qubes-routing-manager"],
[
"add",
"rule",
"ip",
"qubes",
"qubes-routing-manager",
"counter",
],
["add", "chain", "ip", "qubes", "qubes-routing-manager-postrouting"],
[
"add",
"rule",
"ip",
"qubes",
"qubes-routing-manager-postrouting",
"counter",
],
[
"insert",
"rule",
"ip",
"qubes",
"forward",
"position",
"79",
"jump",
"qubes-routing-manager",
],
[
"insert",
"rule",
"ip",
"qubes",
"postrouting",
"position",
"67",
"jump",
"qubes-routing-manager-postrouting",
],
[
"add",
"rule",
"ip",
"qubes",
"qubes-routing-manager",
"ip",
"daddr",
"10.250.4.13",
"accept",
],
[
"add",
"rule",
"ip",
"qubes",
"qubes-routing-manager-postrouting",
"ip",
"saddr",
"10.250.4.13",
"accept",
],
]
with mock.patch("subprocess.Popen", MockedPopen):
setup_plain_forwarding_for_address("10.250.4.13", True, 4)
assert got == expected
def test_forwarding_does_not_add_twice():
got, MockedPopen = mock_collector(get_fixture("fully_added.json"))
expected = [
["list", "table", "ip", "qubes"],
]
with mock.patch("subprocess.Popen", MockedPopen):
setup_plain_forwarding_for_address("10.250.4.13", True, 4)
assert got == expected

View File

@ -0,0 +1,135 @@
#!/usr/bin/python3
"""
This program reads the /qubes-firewall/{ip}/qubes-routing-method file
for any firewall configuration, then configures the network to obey
the routing method for the VM. If the routing method is "masquerade",
then nothing happens. If, however, the routing method is "forward",
then VM-specific rules are enacted in the VM's attached NetVM to allow
traffic coming from other VMs and the outside world to reach this VM.
"""
import glob
import logging
import os
import socket
import qubesdb # type: ignore
from qubesroutingmanager import setup_plain_forwarding_for_address
FORWARD_ROUTING_METHOD = "forward"
def _s(v):
if isinstance(v, bytes):
return v.decode("utf-8")
return v
class AdjunctWorker(object):
def __init__(self):
self.qdb = qubesdb.QubesDB()
@staticmethod
def is_ip6(addr):
return addr.count(":") > 0
def setup_proxy_arp_ndp(self, enabled, family):
# If any of the IP addresses is assigned the forward routing method,
# then enable proxy ARP/NDP on the upstream interfaces, so that the
# interfaces in question will impersonate the IP addresses in question.
# Ideally, this impersonation would be exclusively done for the
# specific IP addresses in question, but it is not clear to me how
# to cause this outcome to take place.
if family == 6:
globber = "/proc/sys/net/ipv6/conf/*/proxy_ndp"
name = "proxy NDP"
elif family == 4:
globber = "/proc/sys/net/ipv4/conf/*/proxy_arp"
name = "proxy ARP"
else:
return
if enabled:
action = "Enabling"
val = "1\n"
else:
action = "Disabling"
val = "0\n"
matches = glob.glob(globber)
for m in matches:
iface = m.split("/")[6]
if iface in ("all", "lo") or iface.startswith("vif"):
# No need to enable it for "all", or VIFs, or loopback.
continue
with open(m, "w+") as f:
oldval = f.read()
if oldval != val:
logging.info("%s %s on interface %s.", action, name, iface)
f.seek(0)
f.write(val)
def handle_addr(self, addr):
# Setup plain forwarding for this specific address.
routing_method = _s(self.qdb.read("/qubes-routing-method/{}".format(addr)))
setup_plain_forwarding_for_address(
addr,
routing_method == FORWARD_ROUTING_METHOD,
6 if self.is_ip6(addr) else 4,
)
# Manipulate proxy ARP for all known addresses.
methods = [
(_s(k).split("/")[2], _s(v))
for k, v in self.qdb.multiread("/qubes-routing-method/").items()
]
mmethods = {
4: [m[1] for m in methods if not self.is_ip6(m[0])],
6: [m[1] for m in methods if self.is_ip6(m[0])],
}
for family, methods in mmethods.items():
self.setup_proxy_arp_ndp(
FORWARD_ROUTING_METHOD in methods,
family,
)
def list_targets(self):
return set(_s(t).split("/")[2] for t in self.qdb.list("/qubes-routing-method/"))
def sd_notify(self, state):
"""Send notification to systemd, if available"""
# based on sdnotify python module
if "NOTIFY_SOCKET" not in os.environ:
return
addr = os.environ["NOTIFY_SOCKET"]
if addr[0] == "@":
addr = "\0" + addr[1:]
try:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.connect(addr)
sock.sendall(state.encode())
except BaseException:
# generally ignore error on systemd notification
pass
def main(self):
logging.basicConfig(level=logging.INFO)
self.qdb.watch("/qubes-routing-method/")
for source_addr in self.list_targets():
self.handle_addr(source_addr)
self.sd_notify("READY=1")
try:
for watch_path in iter(self.qdb.read_watch, None):
# ignore writing rules itself - wait for final write at
# source_addr level empty write (/qubes-firewall/SOURCE_ADDR)
watch_path = _s(watch_path)
if watch_path.count("/") != 2:
continue
source_addr = watch_path.split("/")[2]
self.handle_addr(source_addr)
except OSError: # EINTR
# signal received, don't continue the loop
return

21
routingmanagersetup.py Normal file
View File

@ -0,0 +1,21 @@
import os
import setuptools
if __name__ == "__main__":
version = (
open(os.path.join(os.path.dirname(__file__), "qubes-network-server.spec"))
.read()
.strip()
)
version = [v for v in version.splitlines() if v.startswith("Version:")][0]
version = version.split()[-1]
setuptools.setup(
name="qubesroutingmanager",
version=version,
author="Manuel Amador (Rudd-O)",
author_email="rudd-o@rudd-o.com",
description="Qubes network server network qube (template) component",
license="GPL2+",
url="https://github.com/Rudd-O/qubes-network-server",
packages=("qubesroutingmanager",),
)

8
src/qubes-routing-manager Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/python3
if __name__ == "__main__":
from qubesroutingmanager.worker import AdjunctWorker
w = AdjunctWorker()
w.main()

View File

@ -0,0 +1,13 @@
[Unit]
Description=Configure the network to allow network server VMs
Documentation=https://github.com/Rudd-O/qubes-network-server
After=qubes-network.service qubes-iptables.service
BindsTo=qubes-iptables.service
ConditionPathExists=/var/run/qubes-service/qubes-network
[Service]
Type=notify
ExecStart=@SBINDIR@/qubes-routing-manager
[Install]
WantedBy=qubes-network.service

View File

@ -1,170 +0,0 @@
#!/usr/bin/python2
# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
from qubes.qubes import QubesVmCollection
from qubes.qubes import QubesVmLabels
from qubes.qubes import QubesHost
from qubes.qubes import system_path
from optparse import OptionParser
import subprocess
import os
import sys
import re
from qubes.qubes import vmm
def do_list(vm):
label_width = 19
fmt="{{0:<{0}}}: {{1}}".format(label_width)
print fmt.format ("name", vm.name)
if hasattr(vm, 'static_ip'):
print fmt.format("static_ip", str(vm.static_ip) if vm.static_ip else "unset")
def do_get(vms, vm, prop):
if not hasattr(vm, prop):
print >>sys.stderr, "VM '{}' has no attribute '{}'".format(vm.name,
prop)
return
if getattr(vm, prop, None) is None:
# not set or set to None
return
else:
print str(getattr(vm, prop))
def set_static_ip(vms, vm, args):
if len (args) != 1:
print >> sys.stderr, "Missing value ('static_ip')!"
return False
arg = args[0]
if not arg or arg == "none" or arg == "None" or arg == "unset":
arg = None
# TODO(ruddo): validate the argument!
setattr(vm, "static_ip", arg)
return True
properties = {
"static_ip": set_static_ip,
}
def do_set(vms, vm, property, args):
if property not in properties.keys():
print >> sys.stderr, "ERROR: Wrong property name: '{0}'".format(property)
return False
if not hasattr(vm, property):
print >> sys.stderr, "ERROR: Property '{0}' not available for this VM".format(property)
return False
try:
return properties[property](vms, vm, args)
except Exception as err:
print >> sys.stderr, "ERROR: %s" % str(err)
return False
def main():
usage = "usage: %prog -l [options] <vm-name>\n"\
"usage: %prog -g [options] <vm-name> <property>\n"\
"usage: %prog -s [options] <vm-name> <property> [...]\n"\
"List/set networking-related per-VM properties."
parser = OptionParser (usage)
parser.add_option("-l", "--list", action="store_true", dest="do_list",
default=False)
parser.add_option("-s", "--set", action="store_true", dest="do_set",
default=False)
parser.add_option ("-g", "--get", action="store_true", dest="do_get",
default=False)
parser.add_option("--force-root", action="store_true", dest="force_root",
default=False,
help="Force to run, even with root privileges")
parser.add_option ("--offline-mode", dest="offline_mode",
action="store_true", default=False,
help="Offline mode")
(options, args) = parser.parse_args ()
if (len (args) < 1):
parser.error ("You must provide at least the vmname!")
vmname = args[0]
if hasattr(os, "geteuid") and os.geteuid() == 0:
if not options.force_root:
print >> sys.stderr, "*** Running this tool as root is strongly discouraged, this will lead you in permissions problems."
print >> sys.stderr, "Retry as unprivileged user."
print >> sys.stderr, "... or use --force-root to continue anyway."
exit(1)
if options.do_list + options.do_set + options.do_get > 1:
print >> sys.stderr, "You can provide at most one of -l, -g and -s at " \
"the same time!"
exit(1)
if options.offline_mode:
vmm.offline_mode = True
if options.do_set:
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_writing()
qvm_collection.load()
else:
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_reading()
qvm_collection.load()
qvm_collection.unlock_db()
vm = qvm_collection.get_vm_by_name(vmname)
if vm is None or vm.qid not in qvm_collection:
print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(vmname)
exit(1)
if options.do_set:
if len (args) < 2:
print >> sys.stderr, "You must specify the property you wish to set..."
print >> sys.stderr, "Available properties:"
for p in properties.keys():
if hasattr(vm, p):
print >> sys.stderr, "--> '{0}'".format(p)
exit (1)
property = args[1]
if do_set(qvm_collection, vm, property, args[2:]):
qvm_collection.save()
qvm_collection.unlock_db()
else:
qvm_collection.unlock_db()
exit(1)
elif options.do_get or len(args) == 2:
do_get(qvm_collection, vm, args[1])
else:
# do_list
do_list(vm)
main()

View File

@ -1,400 +0,0 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
import datetime
import base64
import hashlib
import fcntl
import logging
import lxml.etree
import os
import pipes
import re
import shutil
import subprocess
import sys
import textwrap
import time
import uuid
import xml.parsers.expat
import signal
from qubes import qmemman
from qubes import qmemman_algo
import libvirt
from qubes.qubes import QubesException
from qubes.qubes import QubesVm as OriginalQubesVm
from qubes.qubes import register_qubes_vm_class
from qubes.qubes import dry_run
fw_encap = textwrap.dedent("""
mkdir -p /run/fortress/firewall
f=$(mktemp --tmpdir=/run/fortress/firewall)
cat > "$f"
chmod +x "$f"
bash -e "$f"
ret=$?
rm -f "$f"
exit $ret
""")
def locked(programtext):
if not programtext.strip():
return programtext
return "(\nflock 200\n" + programtext + "\n) 200>/var/run/xen-hotplug/vif-lock\n"
def logger(programtext):
if not programtext.strip():
return programtext
return "exec 1> >(logger -s -t fortress) 2>&1\n" + programtext
class QubesVm(OriginalQubesVm):
def get_attrs_config(self):
attrs = OriginalQubesVm.get_attrs_config(self)
attrs["static_ip"] = {
"attr": "static_ip",
"default": None,
"order": 70,
"save": lambda: str(getattr(self, "static_ip")) if getattr(self, "static_ip") is not None else 'none'
}
return attrs
@property
def ip(self):
if self.netvm is not None:
if getattr(self, "static_ip") is not None:
return getattr(self, "static_ip")
return self.netvm.get_ip_for_vm(self.qid)
else:
return None
@property
def netmask(self):
if self.netvm is not None:
if getattr(self, "static_ip") is not None:
# Netmasks for VMs that have a static IP are always host-only.
return "255.255.255.255"
return self.netvm.netmask
else:
return None
def start(self, verbose = False, preparing_dvm = False, start_guid = True,
notify_function = None, mem_required = None):
if dry_run:
return
xid = OriginalQubesVm.start(self, verbose, preparing_dvm, start_guid, notify_function, mem_required)
if not preparing_dvm:
self.adjust_proxy_arp(verbose=verbose, notify_function=notify_function)
self.adjust_own_firewall_rules()
return xid
def unpause(self):
self.log.debug('unpause()')
if dry_run:
return
if not self.is_paused():
raise QubesException ("VM not paused!")
self.libvirt_domain.resume()
self.adjust_proxy_arp()
self.adjust_own_firewall_rules()
def attach_network(self, verbose = False, wait = True, netvm = None):
self.log.debug('attach_network(netvm={!r})'.format(netvm))
if dry_run:
return
ret = OriginalQubesVm.attach_network(self, verbose, wait, netvm)
self.adjust_proxy_arp(verbose)
return ret
def adjust_proxy_arp(self, verbose = False, notify_function=None):
def collect_downstream_vms(vm, vif):
if not hasattr(vm, "connected_vms"):
return list()
vms_below_me = list(vm.connected_vms.values())
vms_below_me = [(vm, vif if vif else vm.vif) for vm in vms_below_me]
for v, vif in vms_below_me:
vms_below_me.extend(collect_downstream_vms(v, vif))
return vms_below_me
def addroute(ip, dev, netmask):
# This function adds routes and proxy ARP entries for the IP pointed at the
# device that the VM (IP) is behind.
dev = dev.replace("+", "0")
return "\n".join([
"if ! ip route | grep -qF %s\\ dev\\ %s ; then" % (pipes.quote(ip), pipes.quote(dev)),
"ip route replace %s/%s dev %s metric 20001" % (pipes.quote(ip), pipes.quote(netmask), pipes.quote(dev)),
"fi",
"echo 1 > /proc/sys/net/ipv4/conf/%s/forwarding" % (pipes.quote(dev),),
"echo 1 > /proc/sys/net/ipv4/conf/%s/proxy_arp" % (pipes.quote(dev),),
"for dev in `ip link | awk -F ':' '/^[0-9]+: (eth|en|wl)/ { print $2 }'`",
"do",
" ip neigh add proxy %s dev $dev" % (pipes.quote(ip),),
"done",
])
class addfwrule(object):
rules = None
addrule = textwrap.dedent("""
declare -A savedrules
addrule() {
local table="$1"
local chain="$2"
local rule="$3"
local before="$4"
if [ "${savedrules[$table]}" == "" ] ; then
savedrules["$table"]=$(iptables-save -t "$table")
fi
if echo "${savedrules[$table]}" | grep -q :"${chain}" ; then
true
else
savedrules["$table"]=$(
echo "${savedrules[$table]}" | while read x
do
echo "$x"
if [ "$x" == '*'"$table" ]
then
echo "${table}: new chain ${chain}" >&2
echo ":${chain} - [0:0]"
fi
done
)
fi
if [ "x$before" == "x" ] ; then
before=COMMIT
elif [ "x$before" == "xbeginning" ] ; then
before=beginning
else
before="-A $chain $before"
fi
if [ "$before" != "beginning" ] && echo "${savedrules[$table]}" | grep -qF -- "-A $chain $rule" ; then
return
fi
local echoed=false
savedrules["$table"]=$(
echo "${savedrules[$table]}" | while read x
do
if [ "beginning" == "$before" -a "$echoed" == "false" ] && echo "$x" | grep -q '^-A '
then
echo "${table}: adding rule -A ${chain} ${rule} to the beginning" >&2
echo "-A $chain $rule"
echoed=true
elif [ "$x" == "$before" ]
then
echo "${table}: adding rule -A ${chain} ${rule} before ${before}" >&2
echo "-A $chain $rule"
fi
if [ "beginning" == "$before" -a "$x" == "-A $chain $rule" ]
then
true
else
echo "$x"
fi
done
)
}
flushrules() {
local table="$1"
local chain="$2"
if [ "${savedrules[$table]}" == "" ] ; then
savedrules["$table"]=$(iptables-save -t "$table")
fi
savedrules["$table"]=$(
echo "${savedrules[$table]}" | while read x
do
if echo "$x" | grep -q "^-A $chain " ; then
echo "${table}: flushing rule $x" >&2
else
echo "$x"
fi
done
)
}
addfwrules() {
# This function creates the FORTRESS-ALLOW-FORWARD filter chain
# and adds rules permitting forwarding of traffic
# sent by the VM and destined to the VM.
local ipnetmask="$1"
addrule filter FORWARD "-j FORTRESS-ALLOW-FORWARD" "-i vif+ -o vif+ -j DROP"
addrule filter FORTRESS-ALLOW-FORWARD "-s $ipnetmask -j ACCEPT"
addrule filter FORTRESS-ALLOW-FORWARD "-d $ipnetmask -j ACCEPT"
}
addprrules() {
# This function creates the FORTRESS-SKIP-MASQ nat chain
# and the FORTRESS-ANTISPOOF raw chain
# and adds rules defeating masquerading and anti-spoofing
# for the IP (machine) so long as it comes from / goes to
# the VIF that the machine is behind.
local ipnetmask="$1"
local vif="$2"
addrule nat POSTROUTING "-j FORTRESS-SKIP-MASQ" "-j MASQUERADE"
addrule nat FORTRESS-SKIP-MASQ "-s $ipnetmask -j ACCEPT"
addrule nat FORTRESS-SKIP-MASQ "-d $ipnetmask -j ACCEPT"
addrule raw PREROUTING "-j FORTRESS-ANTISPOOF" beginning
addrule raw FORTRESS-ANTISPOOF "-s $ipnetmask -j ACCEPT"
}
commitrules() {
for table in "${!savedrules[@]}" ; do
echo "${savedrules[$table]}" | iptables-restore -T "$table"
done
}
flushrules filter FORTRESS-ALLOW-FORWARD
flushrules nat FORTRESS-SKIP-MASQ
flushrules raw FORTRESS-ANTISPOOF
""")
def _add(self, ip, dev, netmask, typ):
netmask = sum([bin(int(x)).count('1') for x in netmask.split('.')])
dev = dev.replace("+", "0")
text = ""
if typ == "forward":
text += "addfwrules %s/%s\n" % (pipes.quote(ip), netmask)
elif typ == "postrouting":
text += "addprrules %s/%s %s\n" % (pipes.quote(ip), netmask, pipes.quote(dev))
if not self.rules:
self.rules = []
self.rules.append(text)
def addfw(self, ip, dev, netmask):
return self._add(ip, dev, netmask, "forward")
def addpr(self, ip, dev, netmask):
return self._add(ip, dev, netmask, "postrouting")
def commit(self):
if not self.rules:
return ""
return self.addrule + "\n".join(self.rules) + "\ncommitrules\n"
programs = []
staticipvms = []
ruler = addfwrule()
# For every VM downstream of mine.
for vm, vif in collect_downstream_vms(self, None):
# If the VM is running, and it has an associated VIF
# and it has a static IP:
if vm.static_ip and vif and vm.is_running():
staticipvms.append(vm.name)
# Add ip neighs of and routes to the VM.
# pointed at the VIF that the VM is behind.
programs.append(addroute(vm.ip, vif, vm.netmask))
# Add prerouting and postrouting rules for the VM
# that defeat masquerading and anti-spoofing.
ruler.addpr(vm.ip, vif, vm.netmask)
# If I am a NetVM, then, additionally.
if self.type == "NetVM":
# Add filter rules for the VM
# that allow it to communicate with other VMs.
ruler.addfw(vm.ip, vif, vm.netmask)
if ruler.commit():
programs.append(ruler.commit())
if not programs:
pass
elif not self.is_running() or self.is_paused():
msg = "Not running routing programs on %s (VM is paused or off)" % (self.name,)
if notify_function:
notify_function("info", msg)
elif verbose:
print >> sys.stderr, "-->", msg
else:
programs = logger(locked("\n".join(programs)))
if not staticipvms:
msg = "Enabling preliminary routing configuration on %s" % (self.name,)
else:
msg = "Enabling routing of %s on %s" % (", ".join(staticipvms), self.name)
if notify_function:
notify_function("info", msg)
elif verbose:
print >> sys.stderr, "-->", msg
# for x in programs.splitlines(False):
# print >> sys.stderr, "---->", x
p = self.run(fw_encap, user="root", gui=False, wait=True, passio_popen=True, autostart=False)
p.stdin.write(programs)
p.stdin.close()
p.stdout.read()
retcode = p.wait()
if retcode:
msg = "Routing commands on %s failed with return code %s" % (self.name, retcode)
if notify_function:
notify_function("error", msg)
elif verbose:
print >> sys.stderr, "-->", msg
if self.netvm:
self.netvm.adjust_proxy_arp(
verbose=verbose,
notify_function=notify_function
)
def adjust_own_firewall_rules(self, ruleset_script=None):
ruleset_script_path = os.path.join(
os.path.dirname(self.firewall_conf),
"firewall.conf.sh"
)
f = open(ruleset_script_path, "a+b")
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
try:
if ruleset_script:
f.seek(0)
f.truncate(0)
f.write(ruleset_script)
f.flush()
else:
f.seek(0)
ruleset_script = f.read()
if ruleset_script:
try:
ruleset_script = logger(locked(ruleset_script))
p = self.run(fw_encap, user="root", gui=False, wait=True, passio_popen=True, autostart=False)
p.stdin.write(ruleset_script)
p.stdin.close()
p.stdout.read()
retcode = p.wait()
f.seek(0)
f.truncate(0)
f.flush()
except QubesException, e:
pass
finally:
f.close()
register_qubes_vm_class(QubesVm)

View File

@ -1,46 +0,0 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
import sys
import libvirt
from qubes.qubes import QubesNetVm as OriginalQubesNetVm
from qubes.qubes import register_qubes_vm_class,vmm,dry_run
from qubes.qubes import defaults,system_path,vm_files
from qubes.qubes import QubesVmCollection,QubesException
class QubesNetVm(OriginalQubesNetVm):
@property
def netmask(self):
if getattr(self, "static_ip"):
return "255.255.255.255"
return self.__netmask
@property
def network(self):
return self.__network
register_qubes_vm_class(QubesNetVm)

View File

@ -1,183 +0,0 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
from datetime import datetime
import sys
import libvirt
import pipes
from qubes.qubes import QubesProxyVm as OriginalQubesProxyVm
from qubes.qubes import register_qubes_vm_class,vmm,dry_run
from qubes.qubes import defaults,system_path,vm_files
from qubes.qubes import QubesVmCollection,QubesException
yum_proxy_ip = '10.137.255.254'
yum_proxy_port = '8082'
class QubesProxyVm(OriginalQubesProxyVm):
def write_iptables_qubesdb_entry(self):
self.qdb.rm("/qubes-iptables-domainrules/")
iptables = "# Generated by Qubes Core on {0}\n".format(datetime.now().ctime())
iptables += "*filter\n"
iptables += ":INPUT DROP [0:0]\n"
iptables += ":FORWARD DROP [0:0]\n"
iptables += ":OUTPUT ACCEPT [0:0]\n"
iptables += ":PR-QBS-FORWARD - [0:0]\n"
# Strict INPUT rules
iptables += "-A INPUT -i vif+ -p udp -m udp --dport 68 -j DROP\n"
iptables += "-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED " \
"-j ACCEPT\n"
iptables += "-A INPUT -p icmp -j ACCEPT\n"
iptables += "-A INPUT -i lo -j ACCEPT\n"
iptables += "-A INPUT -j REJECT --reject-with icmp-host-prohibited\n"
iptables += "-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED " \
"-j ACCEPT\n"
# Allow dom0 networking
iptables += "-A FORWARD -i vif0.0 -j ACCEPT\n"
# Engage in firewalling for VMs
iptables += "-A FORWARD -j PR-QBS-FORWARD\n"
# Deny inter-VMs networking
iptables += "-A FORWARD -i vif+ -o vif+ -j DROP\n"
iptables += "COMMIT\n"
self.qdb.write("/qubes-iptables-header", iptables)
vms = [vm for vm in self.connected_vms.values()]
vms_rulesets = []
for vm in vms:
vm_iptables = ""
iptables="*filter\n"
conf = vm.get_firewall_conf()
xid = vm.get_xid()
if xid < 0: # VM not active ATM
continue
ip = vm.ip
if ip is None:
continue
# Anti-spoof rules are added by vif-script (vif-route-qubes), here we trust IP address
accept_action = "ACCEPT"
reject_action = "REJECT --reject-with icmp-host-prohibited"
if conf["allow"]:
default_action = accept_action
rules_action = reject_action
else:
default_action = reject_action
rules_action = accept_action
for rule in conf["rules"]:
if getattr(vm, "static_ip", None) and rule["address"].startswith("from-"):
ruletext = "-s {0} -d {1}".format(rule["address"][len("from-"):], ip)
if rule["netmask"] != 32:
ruletext += "/{0}".format(rule["netmask"])
if rule["proto"] is not None and rule["proto"] != "any":
ruletext += " -p {0}".format(rule["proto"])
if rule["portBegin"] is not None and rule["portBegin"] > 0:
ruletext += " --dport {0}".format(rule["portBegin"])
if rule["portEnd"] is not None and rule["portEnd"] > rule["portBegin"]:
ruletext += ":{0}".format(rule["portEnd"])
ruletext += " -j {0}\n".format(rules_action)
iptables += "-A PR-QBS-FORWARD " + ruletext
vm_iptables += "-A FORTRESS-INPUT " + ruletext
continue
iptables += "-A PR-QBS-FORWARD -s {0} -d {1}".format(ip, rule["address"])
if rule["netmask"] != 32:
iptables += "/{0}".format(rule["netmask"])
if rule["proto"] is not None and rule["proto"] != "any":
iptables += " -p {0}".format(rule["proto"])
if rule["portBegin"] is not None and rule["portBegin"] > 0:
iptables += " --dport {0}".format(rule["portBegin"])
if rule["portEnd"] is not None and rule["portEnd"] > rule["portBegin"]:
iptables += ":{0}".format(rule["portEnd"])
iptables += " -j {0}\n".format(rules_action)
if conf["allowDns"] and self.netvm is not None:
# PREROUTING does DNAT to NetVM DNSes, so we need self.netvm.
# properties
iptables += "-A PR-QBS-FORWARD -s {0} -p udp -d {1} --dport 53 -j " \
"ACCEPT\n".format(ip,self.netvm.gateway)
iptables += "-A PR-QBS-FORWARD -s {0} -p udp -d {1} --dport 53 -j " \
"ACCEPT\n".format(ip,self.netvm.secondary_dns)
iptables += "-A PR-QBS-FORWARD -s {0} -p tcp -d {1} --dport 53 -j " \
"ACCEPT\n".format(ip,self.netvm.gateway)
iptables += "-A PR-QBS-FORWARD -s {0} -p tcp -d {1} --dport 53 -j " \
"ACCEPT\n".format(ip,self.netvm.secondary_dns)
if conf["allowIcmp"]:
iptables += "-A PR-QBS-FORWARD -s {0} -p icmp -j ACCEPT\n".format(ip)
if getattr(vm, "static_ip", None):
iptables += "-A PR-QBS-FORWARD -d {0} -p icmp -j ACCEPT\n".format(ip)
vm_iptables += "-A FORTRESS-INPUT -d {0} -p icmp -j ACCEPT\n".format(ip)
if conf["allowYumProxy"]:
iptables += "-A PR-QBS-FORWARD -s {0} -p tcp -d {1} --dport {2} -j ACCEPT\n".format(ip, yum_proxy_ip, yum_proxy_port)
else:
iptables += "-A PR-QBS-FORWARD -s {0} -p tcp -d {1} --dport {2} -j DROP\n".format(ip, yum_proxy_ip, yum_proxy_port)
iptables += "-A PR-QBS-FORWARD -s {0} -j {1}\n".format(ip, default_action)
if getattr(vm, "static_ip", None):
iptables += "-A PR-QBS-FORWARD -d {0} -j {1}\n".format(ip, default_action)
vm_iptables += "-A FORTRESS-INPUT -d {0} -j {1}\n".format(ip, default_action)
vm_iptables += "COMMIT\n"
vms_rulesets.append((vm, vm_iptables))
iptables += "COMMIT\n"
self.qdb.write("/qubes-iptables-domainrules/"+str(xid), iptables)
# no need for ending -A PR-QBS-FORWARD -j DROP, cause default action is DROP
self.write_netvm_domid_entry()
self.rules_applied = None
self.qdb.write("/qubes-iptables", 'reload')
for vm, ruleset in vms_rulesets:
shell_ruleset = "echo Adjusting firewall rules to: >&2\n"
shell_ruleset += "echo %s >&2\n" % pipes.quote(ruleset.strip())
shell_ruleset += "data=$(iptables-save -t filter)\n"
shell_ruleset += 'if ! echo "$data" | grep -q -- "^:FORTRESS-INPUT" ; then\n'
shell_ruleset += ' data=$(echo "$data" | sed "s/^:INPUT/:FORTRESS-INPUT - [0:0]\\n\\0/")\n'
shell_ruleset += "fi\n"
shell_ruleset += 'if ! echo "$data" | grep -q -- "-A INPUT -j FORTRESS-INPUT" ; then\n'
shell_ruleset += ' data=$(echo "$data" | sed -r "s|-A INPUT -i vif. -j REJECT --reject-with icmp-host-prohibited|-A INPUT -j FORTRESS-INPUT\\n\\0|")\n'
shell_ruleset += "fi\n"
shell_ruleset += 'data=$(echo "$data" | grep -v ^COMMIT$)\n'
shell_ruleset += 'data=$(echo "$data" | grep -v -- "-A FORTRESS-INPUT")\n'
shell_ruleset += 'data="$data\n"%s\n' % pipes.quote(ruleset)
shell_ruleset += 'echo "$data" | iptables-restore -T filter\n'
vm.adjust_own_firewall_rules(shell_ruleset)
register_qubes_vm_class(QubesProxyVm)

10
tox.ini Normal file
View File

@ -0,0 +1,10 @@
[tox]
envlist = basepython
[testenv]
deps =
pytest
mypy
commands =
pytest -vv
mypy -p qubesroutingmanager