├── .github └── ISSUE_TEMPLATE │ └── Bug_report.md ├── .gitignore ├── .kitchen.yml ├── .travis.yml ├── LICENSE ├── README.md ├── SECURITY.md ├── defaults └── main.yml ├── files ├── local.conf └── tor-generator ├── handlers └── main.yml ├── meta └── main.yml ├── playbook-examples ├── exit-relays │ ├── 10_4_instances_per_IP.yml │ ├── 11_4_instances_per_IP_prometheus.yml │ ├── 12_4_instances_per_IP_prometheus-alertrules.yml │ ├── 8_reduced_exitpolicy.yml │ └── 9_custom_exit_policy.yml └── non-exit-relays │ ├── 1_contactInfo.yml │ ├── 2_keyexpiry_90days.yml │ ├── 3_nickname.yml │ ├── 4_custom_ports.yml │ ├── 5_single_instance.yml │ ├── 6_four_instances.yml │ └── 7_alpha-apt-repo.yml ├── tasks ├── apt_prepare.yml ├── configure.yml ├── freebsd_prepare.yml ├── freebsd_service.yml ├── ip-list.yml ├── linux_service.yml ├── main.yml ├── openbsd_prepare.yml ├── openbsd_service.yml └── rpm_prepare.yml ├── templates ├── nginx-reverse-proxy-for-metricsport ├── prometheus-alert-rules ├── prometheus-scrape-configs ├── tor-exit-notice.html └── torrc ├── test ├── integration │ └── default │ │ ├── 2publicIPs-exit-node.yml │ │ ├── 2publicIPs-guard-metricsport.yml │ │ ├── 2publicIPs-guard-node.yml │ │ ├── 2publicIPs-guard4-metricsport.yml │ │ ├── exit-node.yml │ │ ├── exit-node4.yml │ │ ├── exit-per-instance-exitpolicy.yml │ │ ├── guard-alpha-node.yml │ │ ├── guard-blackbox-exporter.yml │ │ ├── guard-metricsport-blackbox-alertrules.yml │ │ ├── guard-metricsport-blackbox-customalertrules.yml │ │ ├── guard-metricsport-blackbox.yml │ │ ├── guard-nightly-happy-families.yml │ │ ├── guard-nightly-metricsport-blackbox.yml │ │ ├── guard-nightly-node.yml │ │ ├── guard-node.yml │ │ ├── guard-node4.yml │ │ ├── mixed-node.yml │ │ └── vars │ │ ├── dry-run-vars.yml │ │ ├── exit-conf │ │ └── per-instance-exitpolicy └── vagrant_provisions │ ├── freebsd_vagrant_provision.rb │ └── openbsd_vagrant_provision.rb └── vars ├── os_Debian.yml ├── os_FreeBSD.yml ├── os_OpenBSD.yml ├── os_RedHat.yml └── private_IPv4_only.yml /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Version information (please include the following information):** 17 | - ansible version 18 | - ansible-relayor version (please try to reproduce the bug against the git master branch) 19 | 20 | **Playbook information** 21 | Please add a minimal playbook to reproduce the bug. 22 | 23 | **OS information** 24 | Please add information about the target and control machine OS and version. 25 | 26 | **Debug information** 27 | Please add the output of your `ansible-playbook` run using "-vvv". 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .kitchen 2 | *.retry 3 | Gemfile.lock 4 | -------------------------------------------------------------------------------- /.kitchen.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: vagrant 4 | customize: 5 | memory: 512 6 | 7 | platforms: 8 | - name: debian-stable 9 | driver: 10 | box: bento/debian-12 11 | - name: ubuntu-lts 12 | driver: 13 | box: bento/ubuntu-24.04 14 | - name: freebsd 15 | driver: 16 | box: bento/freebsd-14.1 17 | provision: True 18 | vagrantfiles: 19 | - test/vagrant_provisions/freebsd_vagrant_provision.rb 20 | 21 | provisioner: 22 | name: ansible_push 23 | chef_bootstrap_url: nil 24 | idempotency_test: true 25 | fail_non_idempotent: true 26 | diff: true 27 | 28 | transport: 29 | max_ssh_sessions: 6 30 | 31 | suites: 32 | - name: t-guard 33 | provisioner: 34 | playbook: "test/integration/default/guard-node.yml" 35 | - name: t-guard4 36 | provisioner: 37 | playbook: "test/integration/default/guard-node4.yml" 38 | - name: t-exit 39 | provisioner: 40 | playbook: "test/integration/default/exit-node.yml" 41 | - name: t-exit4 42 | provisioner: 43 | playbook: "test/integration/default/exit-node4.yml" 44 | - name: t-guard-alpha 45 | provisioner: 46 | playbook: "test/integration/default/guard-alpha-node.yml" 47 | excludes: 48 | - freebsd 49 | - name: t-guard-nightly 50 | provisioner: 51 | playbook: "test/integration/default/guard-nightly-node.yml" 52 | excludes: 53 | - freebsd 54 | - name: t-guard-nightly-happy-families 55 | provisioner: 56 | playbook: "test/integration/default/guard-nightly-happy-families.yml" 57 | excludes: 58 | - freebsd 59 | - name: t-guard-metricsport-blackbox-exporter 60 | provisioner: 61 | playbook: "test/integration/default/guard-metricsport-blackbox.yml" 62 | - name: t-guard-metricsport-blackbox-alertrules 63 | provisioner: 64 | playbook: "test/integration/default/guard-metricsport-blackbox-alertrules.yml" 65 | - name: t-guard-metricsport-blackbox-customalertrules 66 | provisioner: 67 | playbook: "test/integration/default/guard-metricsport-blackbox-customalertrules.yml" 68 | - name: t-guard-nightly-metricsport-blackbox-exporter 69 | provisioner: 70 | playbook: "test/integration/default/guard-nightly-metricsport-blackbox.yml" 71 | excludes: 72 | - freebsd 73 | - name: t-guard-blackbox-exporter 74 | provisioner: 75 | playbook: "test/integration/default/guard-blackbox-exporter.yml" 76 | # test for the tor_ExitRelaySetting_file feature to run 77 | # exit and non-exit tor instances on a single machine 78 | - name: t-mixed 79 | provisioner: 80 | playbook: "test/integration/default/mixed-node.yml" 81 | - name: t-exit-per-instance-exitpolicy 82 | provisioner: 83 | playbook: "test/integration/default/exit-per-instance-exitpolicy.yml" 84 | - name: t-guard-2publicIPs-metricsport 85 | driver: 86 | network: 87 | - ["private_network", {ip: "192.0.2.10"}] 88 | - ["private_network", {ip: "198.51.100.10"}] 89 | provisioner: 90 | playbook: "test/integration/default/2publicIPs-guard-metricsport.yml" 91 | - name: t-guard4-2publicIPs-metricsport 92 | driver: 93 | network: 94 | - ["private_network", {ip: "192.0.2.10"}] 95 | - ["private_network", {ip: "198.51.100.10"}] 96 | provisioner: 97 | playbook: "test/integration/default/2publicIPs-guard4-metricsport.yml" 98 | - name: t-guard-2publicIPs 99 | driver: 100 | network: 101 | - ["private_network", {ip: "192.0.2.10"}] 102 | - ["private_network", {ip: "198.51.100.10"}] 103 | provisioner: 104 | playbook: "test/integration/default/2publicIPs-guard-node.yml" 105 | - name: t-exit-2publicIPs 106 | driver: 107 | network: 108 | - ["private_network", {ip: "192.0.2.10"}] 109 | - ["private_network", {ip: "198.51.100.10"}] 110 | provisioner: 111 | playbook: "test/integration/default/2publicIPs-exit-node.yml" 112 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: python 3 | python: 4 | - '3.6' 5 | 6 | install: 7 | - pip install ansible-lint 8 | 9 | script: 10 | - ansible-lint . 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ansible-relayor 2 | ---------------- 3 | This is an ansible role for tor relay operators. 4 | An introduction to relayor can be found **[here](https://medium.com/@nusenu/deploying-tor-relays-with-ansible-6612593fa34d)**. 5 | 6 | Email Support: relayor-support AT riseup.net 7 | 8 | The main focus of this role is to automate as many steps as possible for a tor relay 9 | operator including key management (OfflineMasterKey). 10 | Deploying a new tor server is as easy as adding a new host to the inventory, 11 | no further manual configuration is required. 12 | 13 | This role only manages tor instances as per the current settings and variables. 14 | If you change the configuration after a complete playbook run, to reduce the number of tor instances, for example by 15 | reducing the value of `tor_maxPublicIPs`, this role will not remove the previously configured tor instances 16 | from your server. Tor instances on a server are identified by their IPv4 and ORPort combination. 17 | Changing the ORPort (using the `tor_ports` variable) after initial rollout, effectively means creating new 18 | tor instances (not changing them), this is the reason why changing the `tor_ports` variable should be avoided after the initial rollout. 19 | 20 | Keeping the tor package updated (an important task of running a relay) is not in scope of this ansible role. 21 | We recommend you enable automatic updates to keep your relay well maintained if your OS supports that. 22 | The Tor Relay Guide contains instructions on how to enable automatic software updates for [Debian/Ubuntu](https://community.torproject.org/relay/setup/guard/debian-ubuntu/updates/) 23 | and [FreeBSD](https://community.torproject.org/relay/setup/guard/freebsd/updates/). 24 | 25 | This ansible role does not aim to support tor bridges. 26 | 27 | Main benefits for a tor relay operator 28 | -------------------------------------- 29 | - **automation** - no more manual setup tasks 30 | - security: **[offline Ed25519 master keys](https://support.torproject.org/#relay-operators_offline-ed25519)** are generated on the ansible host and are never exposed to the relay 31 | - **easy Ed25519 signing key renewal** (valid for 30 days by default - configurable) 32 | - security: compartmentalization: every tor instance is run with a distinct user 33 | - automatically makes use of IPv6 IPs (if available) 34 | - automatic tor instance generation (two by default - configurable) 35 | - enables tor's Sandbox feature by default on Debian-based systems 36 | - easily restore a relay setup (the ansible host becomes a backup location for all keys out of the box) 37 | - easily choose between exit relay/non-exit relay mode using a single boolean 38 | - automatic deployment of a [tor exit notice html](https://gitweb.torproject.org/tor.git/plain/contrib/operator-tools/tor-exit-notice.html) page via tor's DirPort (on exits only) 39 | - automatic MyFamily management 40 | - automatic Happy Families key management (requires tor version 0.4.9.2-alpha or newer) 41 | - **prometheus integration** (when enabled) 42 | - nginx reverse proxy config autogeneration to protect tor's MetricsPort (behind basic auth / HTTPS) 43 | - prometheus scrape config autogeneration for MetricsPort 44 | - blackbox-exporter scrape config autogeneration to monitor reachability of ORPorts and DirPorts 45 | - ship prometheus alert rules for tor 46 | 47 | Installation 48 | ------------ 49 | 50 | This ansible role is available on galaxy https://galaxy.ansible.com/ui/standalone/roles/nusenu/relayor/ 51 | 52 | ```ansible-galaxy role install nusenu.relayor``` 53 | 54 | Requirements 55 | ------------ 56 | Control Machine Requirements 57 | 58 | - do **not** run this role with `become: yes` 59 | - tor >= 0.4.8 60 | - python-netaddr package must be installed (ansible-utils version must be >= v4.0.0 if using netaddr >= v1.0.0. See [#245](https://github.com/nusenu/ansible-relayor/issues/245)) 61 | - required commands: sort, uniq, wc, cut, sed, xargs 62 | - openssl >= 1.0.0 63 | - ansible >= 2.16.14 64 | - bash under /bin/bash 65 | 66 | Managed Node Requirements 67 | 68 | - a non-root user with sudo permissions 69 | - python 70 | - static IPv4 address(es) 71 | - we can use multiple public IPs 72 | - if you have no public IP we will use a single private IP (and assume NAT) 73 | - systemd (all Linux-based systems) 74 | 75 | Prometheus Server Requirements (only when using prometheus features of this role) 76 | 77 | - promtool must be installed on the prometheus server and in the PATH of the root user 78 | 79 | Supported Operating Systems 80 | --------------------------- 81 | 82 | - Debian 12 83 | - FreeBSD 14.2 84 | - Ubuntu 24.04 85 | 86 | Supported Tor Releases 87 | ----------------------- 88 | - tor >= 0.4.8.x 89 | 90 | Example Playbook 91 | ---------------- 92 | 93 | A minimal playbook using ansible-relayor to setup non-exit relays could look like this: 94 | 95 | ```yaml 96 | --- 97 | 98 | - hosts: relays 99 | vars: 100 | tor_ContactInfo: relay-operator@example.com 101 | roles: 102 | - nusenu.relayor 103 | ``` 104 | 105 | For more examples see the playbook-examples folder. 106 | 107 | Changed torrc defaults 108 | ---------------------- 109 | 110 | This role changes the defaults of the following torrc options to use safer options by default 111 | but you can still explicitly configure them via `tor_config`: 112 | 113 | * `NoExec` 0 -> 1 114 | * `Sandbox` 0 -> 1 (on Debian only) 115 | 116 | Role Variables 117 | -------------- 118 | All variables mentioned here are optional. 119 | 120 | * `tor_ContactInfo` string 121 | - Sets the relay's ContactInfo field. 122 | - This setting is mandatory. 123 | - Operators are encouraged to use the [ContactInfo Information Sharing Specification](https://nusenu.github.io/ContactInfo-Information-Sharing-Specification/) to publish useful contact information. 124 | 125 | * `tor_signingkeylifetime_days` integer 126 | - all tor instances created by relayor run in [OfflineMasterKey](https://www.torproject.org/docs/tor-manual.html.en#OfflineMasterKey) mode 127 | - this setting defines the lifetime of Ed25519 signing keys in days 128 | - indirectly defines **how often you have to run your ansible playbook to ensure your relay keys do not expire** 129 | - **a tor instance in OfflineMasterKey mode automatically stops when his key/cert expires, so this is a crucial setting!** 130 | - lower values (eg. 7) are better from a security point of view but require more frequent playbook runs 131 | - default: 30 132 | 133 | * `tor_config` dictionary 134 | - this dictionary contains torrc settings and their value, for available options see the 'SERVER OPTIONS' section in tor's manual. 135 | - each setting can only be set once (regardless what tor's manpage says) 136 | - this dictionary can be used to set any torrc option but NOT the following: `OfflineMasterKey`, `RunAsDaemon`, `Log`, `SocksPort`, `OutboundBindAddress`, `User`, `DataDirectory`, `ORPort`, `OutboundBindAddress`, `DirPort`, `SyslogIdentityTag`, `PidFile`, `MetricsPort`, `MetricsPortPolicy`, `ControlSocket`, `CookieAuthentication`, `Nickname`, `ExitRelay`, `IPv6Exit`, `ExitPolicy`, `RelayBandwidthRate`, `RelayBandwidthBurst`, `SigningKeyLifetime` 137 | 138 | * `tor_ports` dictionary 139 | - This var allows you to 140 | - select tor's ORPort and DirPort 141 | - decide how many tor instances you want to run per IP address (default 2) - make sure to not run more than allowed per IP address 142 | - disable DirPorts by setting them to 0 143 | - HINT: choose ORPorts wisely and *never* change them again, at least not those deployed already, adding more without changing deployed once is fine. 144 | - tor's 'auto' feature is NOT supported 145 | - default: 146 | - instance 1: ORPort 9000, DirPort 9001 147 | - instance 2: ORPort 9100, DirPort 9101 148 | 149 | * `tor_maxPublicIPs` integer 150 | - Limits the amount of public IPs we will use to generate instances on a single host. 151 | - Indirectly limits the amount of instances we generate per host. 152 | - default: 1 153 | 154 | * `tor_offline_masterkey_dir` folderpath 155 | - default: ~/.tor/offlinemasterkeys 156 | - Defines the location where on the ansible control machine we store relay keys (Ed25519 and RSA) 157 | - Within that folder ansible will create a subfolder for every tor instance. 158 | - see the [documentation](https://github.com/nusenu/ansible-relayor/wiki/How-to-migrate-all-tor-instances-of-one-server-to-another) if you want to migrate instances to a new server 159 | - **note**: do not manually mangle file and/or foldernames/content in these tor DataDirs 160 | 161 | * `tor_nickname` string 162 | - defines the nickname tor instances will use 163 | - all tor instances on a host will get the same nickname 164 | - to use the server's hostname as the nickname set it to `{{ ansible_hostname }}` 165 | - non-alphanum chars are automatically removed and nicknames longer than 19 characters are truncated to meet tor's nickname requirements 166 | - tor_nicknamefile overrules this setting 167 | - default: none 168 | 169 | * `tor_nicknamefile` filepath 170 | - this is a simple comma separated csv file stored on the ansible control machine specifying nicknames 171 | - first column: instance identifier (inventory_hostname-ip_orport) 172 | - second column: nickname 173 | - one instance per line 174 | - all instances MUST be present in the csv file 175 | - non-alphanum chars are automatically removed and nicknames longer than 19 characters are truncated to meet tor's nickname requirements 176 | - default: not set 177 | 178 | * `tor_gen_ciiss_proof_files` boolean 179 | - generate the rsa-fingerprint.txt and ed25519-master-pubkey.txt proof files on the control machine for publishing according to [ContactInfo spec](https://nusenu.github.io/ContactInfo-Information-Sharing-Specification/#proof) 180 | - default paths are: ~/.tor/rsa-fingerprint.txt and ~/.tor/ed25519-master-pubkey.txt 181 | - the files are overwritten if they exist 182 | - the location of the output folder can be configured using the variable `tor_ciiss_proof_folder` 183 | - the filename is hardcoded to the one required by the specification and can not be configured 184 | - default: false 185 | 186 | * `tor_ciiss_proof_folder` folderpath 187 | - defines the output folder for generated proof files 188 | - default: ~/.tor 189 | 190 | * `tor_LogLevel` string 191 | - sets tor's LogLevel 192 | - default: notice 193 | 194 | * `tor_alpha` boolean 195 | - **currently broken:** for details see the [upstream bugreport](https://gitlab.torproject.org/tpo/core/tor/-/issues/41042) 196 | - Set to true if you want to use Tor alpha version releases. 197 | - Note: This setting does not ensure an installed tor is upgraded to the alpha release. 198 | - This setting is supported on Debian/Ubuntu only (ignored on other platforms). 199 | - default: false 200 | 201 | * `tor_nightly_builds` boolean 202 | - Set to true if you want to use Tor nightly builds repo from deb.torproject.org. 203 | - nightly builds follow the tor git main branch. 204 | - Only supported on Debian and Ubuntu (ignored on other platforms). 205 | - default: false 206 | 207 | * `tor_ExitRelay` boolean 208 | - You have to set this to true if you want to enable exiting for all or some tor instances on a server 209 | - If this var is not true this will be a non-exit relay 210 | - If you want to run a mixed server (exit and non-exit tor instances) use `tor_ExitRelaySetting_file` for per-instance configuration in additon to this var 211 | - default: false 212 | 213 | * `tor_ExitRelaySetting_file` filepath 214 | - this is a simple comma separated csv file stored on the ansible control machine defining the `ExitRelay` torrc setting for each tor instance (instead of server-wide) 215 | - first column: instance identifier (inventory_hostname-ip_orport) 216 | - second column: "exit" for exit tor instances, any other value (including empty) for non-exit tor instances 217 | - this var is ignored if tor_ExitRelay is false 218 | 219 | * `tor_RelayBandwidthRate_file` filepath 220 | - this is a simple comma separated csv file stored on the ansible control machine defining the `RelayBandwidthRate` torrc setting for each tor instance (instead of server-wide) 221 | - first column: instance identifier (inventory_hostname-ip_orport) 222 | - second column: value as accepted by `RelayBandwidthRate` (see tor manpage) 223 | 224 | * `tor_RelayBandwidthBurst_file` filepath 225 | - this is a simple comma separated csv file stored on the ansible control machine defining the `RelayBandwidthBurst` torrc setting for each tor instance (instead of server-wide) 226 | - first column: instance identifier (inventory_hostname-ip_orport) 227 | - second column: value as accepted by `RelayBandwidthBurst` (see tor manpage) 228 | 229 | * `tor_ExitNoticePage` boolean 230 | - specifies whether we display the default tor exit notice [html page](https://gitweb.torproject.org/tor.git/plain/contrib/operator-tools/tor-exit-notice.html) on the DirPort 231 | - only relevant if we are an exit relay 232 | - default: true 233 | 234 | * `tor_exit_notice_file` filepath 235 | - path to a HTML file on the control machine that you would like to display (via the DirPort) instead of the default [tor-exit-notice.html](https://gitweb.torproject.org/tor.git/plain/contrib/operator-tools/tor-exit-notice.html) provided by the Tor Project 236 | - only relevant if we are an exit relay and if `tor_ExitNoticePage` is true 237 | 238 | * `tor_AbuseEmailAddress` email-address 239 | - if set this email address is used on the tor exit notice [html page](https://gitweb.torproject.org/tor.git/plain/contrib/operator-tools/tor-exit-notice.html) published on the DirPort 240 | - you are encouraged to set it if you run an exit 241 | - only relevant if we are an exit relay 242 | - Note: if you use your own custom tor-exit-notice template this var is ignored if you do not include it in your template. 243 | - default: not set 244 | 245 | * `tor_ExitPolicy` array 246 | - specify your custom exit policy 247 | - only relevant if `tor_ExitRelay` is true 248 | - see defaults/main.yml for an example on how to set it 249 | - default: reduced exit policy (https://gitlab.torproject.org/legacy/trac/-/wikis/doc/ReducedExitPolicy) 250 | 251 | * `tor_ExitPolicy_file` filepath 252 | - this is a simple semicolon separated csv file stored on the ansible control machine defining the `ExitPolicy` torrc setting for each tor instance (instead of server-wide) 253 | - first column: instance identifier (inventory_hostname-ip_orport) 254 | - second column: value as accepted by `ExitPolicy` (see tor manpage) 255 | - example content: "myrelay-192.168.1.1_443;reject *:25,reject *:123" 256 | - only relevant if `tor_ExitRelay` is true 257 | - this can be combined with the `tor_ExitPolicy` setting and will override it (this is more specific) 258 | - only tor instances that you want to have a specific exit policy for are required to be listed in the file (others can be omitted) 259 | - default: not set 260 | 261 | * `tor_IPv6` boolean 262 | - autodetects if you have IPv6 IPs and enables an IPv6 ORPort accordingly 263 | - you can opt-out by setting it to false 264 | - default: true 265 | 266 | * `tor_IPv6Exit` boolean 267 | - enables IPv6 exit traffic 268 | - only relevant if `tor_ExitRelay` and `tor_IPv6` are true and we have an IPv6 address 269 | - default: true (unlike tor's default) 270 | 271 | * `tor_enableMetricsPort` boolean 272 | - if true enable tor's MetricsPort on the localhost IP address 127.0.0.1 and allow the same IP to access it (MetricsPortPolicy) 273 | - enabling this setting automatically disables `OverloadStatistics` if it is not enabled explicitly (so tor will not publish/upload the data to directory authorities because we use MetricsPort locally) 274 | - default: false 275 | 276 | * `tor_prometheus_host` hostname 277 | - this variable is only relevant if `tor_enableMetricsPort` is `true` or `tor_blackbox_exporter_host` is set 278 | - if you want to enable relayor's prometheus integration you have to set this variable to your prometheus host 279 | - it defines on which host ansible should generate the prometheus scrape configuration to scrape tor's MetricsPort 280 | - this host must be available in ansible's inventory file 281 | - default: undefined (no scrape config is generated) 282 | 283 | * `tor_prometheus_confd_folder` folderpath 284 | - only relevant if you want to use prometheus 285 | - this folder must exist on `tor_prometheus_host` 286 | - relayor places prometheus scrape_configs in this folder 287 | - the prometheus global config must point to this folder (`scrape_config_files`) 288 | - we generate one yml file per server in this folder, for the filename see `tor_prometheus_scrape_file` 289 | - default: `/etc/prometheus/conf.d` 290 | 291 | * `tor_MetricsPort_offset` integer 292 | - defines the TCP MetricsPort used on the first tor instance running on a host 293 | - additional tor instances will use an incremented port number 33301, 33302, ... 294 | - so if you run N instances on a host, the next N-1 ports after this port have to be unused on 127.0.0.1 so tor can use them as MetricsPorts 295 | - default: 33300 296 | 297 | * `tor_prometheus_scrape_file` filename 298 | - only relevant if `tor_prometheus_host` is defined and `tor_enableMetricsPort` or `tor_blackbox_exporter_host` is set 299 | - defines the filename for per server [scrape_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config) files 300 | on the prometheus server inside the `tor_prometheus_confd_folder` 301 | - the filename MUST be host specific, each host has its own scrape config file on the prometheus server to support the ansible-playbook `--limit` cli option 302 | - depending on `tor_enableMetricsPort` and `tor_blackbox_exporter_host`, the scrape config files will contain scrape jobs for the tor 303 | MetricsPort (behind a reverse proxy for TLS/basic auth) and/or scrape jobs for ORPort/DirPort TCP probes via blackbox exporter 304 | - the file content is sensitive (contains scrape credentials) and gets these file permissions: 0640 (owner: root, group: `tor_prometheus_group`) 305 | - the generated scrape config files will automatically be enriched with a few useful prometheus labels depending on your torrc settings, see the "Prometheus Labels" section in this README 306 | - default: `tor_{{ ansible_fqdn }}.yml` 307 | 308 | * `tor_prometheus_group` string 309 | - only relevant if you want to use prometheus 310 | - defines the group name used for prometheus file permissions (prometheus.yml, scrape config files, alert rules file) 311 | - default: prometheus 312 | 313 | * `tor_prom_labels` dictionary 314 | - arbitrary number of prometheus label value pairs 315 | - can be set on a per server level, not on a per instance level 316 | - for an example see `defaults/main.yml` 317 | - default: empty dictionary 318 | 319 | * `tor_blackbox_exporter_host` hostname:port 320 | - when set, relayor adds the necessary prometheus scrape config for blackbox exporter TCP propes in the file defined by `tor_prometheus_scrape_file` 321 | - monitors all relay ORPorts and when set DirPorts on IPv4 and IPv6 (if enabled) using a TCP connect check 322 | - this feature is not supported on relays behind NAT 323 | - defines where prometheus finds the blackbox exporter, it can also run on the prometheus server itself, in that case it would be 127.0.0.1:9115 324 | - the host is written into the resulting prometheus scrape config 325 | - blackbox_exporter must have a simple [tcp_probe](https://github.com/prometheus/blackbox_exporter/blob/master/CONFIGURATION.md#tcp_probe) module named "tcp_connect" configured 326 | - relayor does not install or configure [blackbox_exporter](https://github.com/prometheus/blackbox_exporter) 327 | - default: undefined 328 | 329 | * `tor_blackbox_exporter_scheme` string 330 | - defines the protocol prometheus uses to connect to the blackbox exporter (http or https) 331 | - default: http 332 | 333 | * `tor_blackbox_exporter_username` string 334 | - only relevant when `tor_blackbox_exporter_host` is set 335 | - allows you to define the username if your blackbox exporter requires HTTP basic authentication 336 | - if you do not set a username the scrape config will not include HTTP basic auth credentials 337 | - default: undefined (no HTTP basic auth) 338 | 339 | * `tor_blackbox_exporter_password` string 340 | - only relevant when `tor_blackbox_exporter_host` is set 341 | - allows you to the the username if your blackbox exporter requires HTTP basic auth 342 | - the default generates a 20 character random string using the Ansible password lookup 343 | - default: `"{{ lookup('password', '~/.tor/prometheus/blackbox_exporter_password') }}"` 344 | 345 | * `tor_metricsport_nginx_config_file` filepath 346 | - this variable is only relevant if `tor_enableMetricsPort` is true and `tor_prometheus_host` is set 347 | - it defines the filepath where the nginx reverse proxy configuration for MetricsPort will be stored on the relay 348 | - this file has to be included in your webserver configuration on the relay to make MetricsPort accessible for remote prometheus scraping 349 | - the folder has to be present on the server already (relayor does not create it) 350 | - default: `/etc/nginx/promexporters/tor_metricsports_relayor.conf` 351 | 352 | * `tor_gen_prometheus_alert_rules` boolean 353 | - only relevant when `tor_enableMetricsPort` is enabled 354 | - set to `true` if you want to generate [prometheus alert rules](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/) on the prometheus server (`tor_prometheus_host`) 355 | - the file location is defined by `tor_prometheus_rules_file` 356 | - default: false (no rules are generated) 357 | 358 | * `tor_prometheus_rules_file` filepath 359 | - only relevant when `tor_gen_prometheus_alert_rules` is `true` 360 | - defines where on the prometheus server (`tor_prometheus_host`) relayor will generate the rules file (the folder has to be present) 361 | - the file has to be in the folder that is included by your prometheus config (rule_files) and usually is required to end with .rules 362 | - relayor ships a default set of alert rules and you can optionally add your custom alert rules as well (via `tor_prometheus_custom_alert_rules`) 363 | - file owner/group: root, file permissions: 0644 364 | - default: `/etc/prometheus/rules/ansible-relayor.rules` 365 | 366 | * `tor_prometheus_alert_rules` dictionary 367 | - defines the prometheus alert rules 368 | - rules are validated using promtool automatically 369 | - see `defaults/main.yml` for the default rules 370 | 371 | * `tor_prometheus_custom_alert_rules` dictionary 372 | - if you want to add your user defined rules, add them to this dictinary, it expects the same format as in `tor_prometheus_alert_rules` 373 | - rules defined in this dictionary are also written to `tor_prometheus_rules_file` 374 | - this allows you to make use of new rules shipped by new relayor versions while still maintaining your user defined rules 375 | - rules are validated using promtool automatically 376 | - default: undefined 377 | 378 | * `tor_gen_metricsport_htpasswd` boolean 379 | - this variable is only relevant if `tor_enableMetricsPort` is true 380 | - when this var is set to true, we create the htpasswd file that can be used by a webserver on the relay to protect tor's MetricsPort with HTTP basic auth 381 | - the file will be owned by root and readable by the webserver's group (www-data/www - depending on the OS) 382 | - we do NOT install the webserver, use another role for that. 383 | - the password is [automatically generated](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/password_lookup.html) and 20 characters long (each server gets a distinct password) 384 | - the path to the file on the relay is defined in `tor_metricsport_htpasswd_file` 385 | - the plaintext password is written to a file on the ansible control machine (see `tor_prometheus_scrape_password_folder`) 386 | - default: true 387 | 388 | * `tor_metricsport_htpasswd_file` filepath 389 | - only relevant if `tor_enableMetricsPort` and `tor_gen_metricsport_htpasswd` are set to true 390 | - it defines the filepath to the htpasswd file (containing username and password hash) on the relay 391 | - default: `/etc/nginx/tor_metricsport_htpasswd` 392 | 393 | * `tor_prometheus_scrape_password_folder` folderpath 394 | - only relevant if `tor_enableMetricsPort` is true 395 | - ansible will automatically generate one unique and random 20 character password per host (not per tor instance) to protect the MetricsPort via nginx (HTTP basic auth) 396 | - this variable defines the folder where ansible will store the passwords in plaintext (password lookup) 397 | - the filenames within that folder match the hostname (inventory_hostname) and can not be configured 398 | - the passwords can easily be rotated by removing all files in this folder and rerunning ansible-playbook again 399 | - the variable must contain a trailing `/` 400 | - default: `~/.tor/prometheus/scrape-passwords/` 401 | 402 | * `tor_prometheus_scrape_port` integer 403 | - defines what destination port is used to reach the scrape target (`MetricsPort`) via nginx 404 | - default: 443 405 | 406 | * `tor_enableControlSocket` boolean 407 | - if `true` create a ControlSocket file for every tor instance (i.e. to be used for nyx) 408 | - access control relies on filesystem permissions 409 | - to give a user access to a specific tor instance's controlsocket file you 410 | - have to add the user to the primary group of the tor instance 411 | - the path to the socket file(s) is /var/run/tor-instances/$instance-id/control 412 | - this setting affects all instances on a given server 413 | - per instance configuration is not supported 414 | - default: false 415 | 416 | * `tor_freebsd_somaxconn` integer 417 | - configure kern.ipc.somaxconn on FreeBSD 418 | - by default we increase this value to at least 1024 419 | - if the value is higher than that we do not touch it 420 | 421 | * `tor_freebsd_nmbclusters` integer 422 | - configure kern.ipc.nmbclusters on FreeBSD 423 | - by default we increase this value to at least 30000 424 | - if the value is higher than that we do not touch it 425 | 426 | * `tor_package_state` string 427 | - specify what package state the tor package should have 428 | - possible values: present, latest (not supported on FreeBSD) 429 | - Note: The repository metadata is not updated, so setting this to latest does not give you any guarantees if it actually is the latest version. 430 | - default: present 431 | 432 | * `tor_binary` string 433 | - name of the tor binary on the control machine used to generate the offline and family keys 434 | - if the tor binary is not named "tor" on your control machine, you have to change the default (for example on Whonix workstations) 435 | - default: tor 436 | 437 | * `tor_happy_family` boolean 438 | - set to `true` to enable happy families 439 | - this will generate a happy family key and distribute it to all managed relays 440 | - enabling happy families will not disable the old MyFamily torrc option 441 | - requires tor version >=0.4.9.2-alpha on the control node and the relay 442 | - **NOTE**: future relayor releases will enable happy families by default and ignore this setting when the happy families feature reaches a tor stable release 443 | - default: false 444 | 445 | * `tor_local_happy_family_folder` folderpath 446 | - specify the folderpath on the control machine where family keys are stored 447 | - ensure this folder is not accessible to unauthorized parties as it contains the happy family private key file. 448 | - default: ~/.tor 449 | 450 | * `tor_happy_family_basename` filename 451 | - file basename of the .secret_family_key file 452 | - default: happyfamily 453 | 454 | Prometheus Labels 455 | ----------------- 456 | 457 | When `tor_enableMetricsPort` is enabled we also populate the following prometheus labels: 458 | 459 | * `id`: identifies the tor instance by IP_ORPort. Example value: 198.51.100.10_9000 460 | * `relaytype`: value is either "exit" or "nonexit" depending on `tor_ExitRelay` 461 | * `tor_nickname`: when nicknames are defined (`tor_nicknamefile` or `tor_nickname`) this label is added 462 | * `service`: "torrelay" 463 | 464 | You can add additional prometheus labels using `tor_prom_labels`. 465 | 466 | 467 | Available Role Tags 468 | -------------------- 469 | 470 | Using ansible tags is optional but allows you to speed up playbook runs if 471 | you are managing many servers. 472 | 473 | There are OS specific tags: 474 | 475 | * debian (includes ubuntu) 476 | * freebsd 477 | 478 | Task oriented tags: 479 | 480 | * **renewkey** - takes care of renewing online Ed25519 keys only (assumes that tor instances are fully configured and running already) 481 | * install - installs tor but does not start or enable it 482 | * createdir - creates (empty) directories on the ansible host only, useful for migration 483 | * promconfig - regenerates prometheus related configs (scrape config, blackbox exporter, nginx) 484 | * reconfigure - regenerates config files (tor and promconfig) and reloads tor (requires previously configured tor instances) 485 | 486 | So if you have a big family and you are about to add an FreeBSD host you typically 487 | make two steps 488 | 489 | 1. install the new server by running only against the new server (-l) and only the os specific tag (freebsd) 490 | 491 | `ansible-playbook yourplaybook.yml -l newserver --tags freebsd` 492 | 493 | 2. then reconfigure all servers (MyFamily) by running the 'reconfigure' tag against all servers. 494 | 495 | `ansible-playbook yourplaybook.yml --tags reconfigure` 496 | 497 | Security Considerations 498 | ------------------------ 499 | This ansible role makes use of tor's OfflineMasterKey feature without requiring any manual configuration. 500 | 501 | The offline master key feature exposes only a temporary signing key to the relay (valid for 30 days by default). 502 | This allows to recover from a complete server compromise without losing a relay's reputation (no need to bootstrap a new permanent master key from scratch). 503 | 504 | Every tor instance is run with a distinct system user. A per-instance user has only access to his own (temporary) keys, but not to those of other instances. 505 | We do not ultimately trust every tor relay we operate (we try to perform input validation when we use relay provided data on the ansible host or another relay). 506 | 507 | **Be aware that the ansible control machine stores ALL your relay keys (RSA and Ed25519) and when enabled happy family keys as well, 508 | apply security measures accordingly.** 509 | 510 | If you make use of the prometheus integration the ansible control machine will also store all your prometheus scrape credentials under `~/.tor/prometheus/`. 511 | Rotating these credentials is very easy though: You can simply remove that folder and run ansible-playbook again. 512 | 513 | Every tor server host gets its own set of prometheus credentials, so a compromised host should not allow an attacker to scrape all your other tor instances on other hosts. 514 | A local attacker can scrape all tor MetricsPorts (when enabled) on that host because tor's MetricsPort does not support authentication. 515 | 516 | Integration Testing 517 | ----------------------- 518 | 519 | This ansible role comes with a .kitchen.yml file, that can be used 520 | to test relayor - using different configurations - against Vagrant Virtualbox machines. 521 | It is primarily used for development/integration testing (spot regressions) 522 | but you can also use it to get familiar with relayor in such a local playground environment. 523 | These tor relays will not join the network since they are only created for testing purposes. 524 | 525 | kitchen will download Vagrant boxes from Vagrant cloud to create test VMs. 526 | 527 | To get started install the required gem packages: 528 | 529 | ```bash 530 | gem install test-kitchen kitchen-ansiblepush kitchen-vagrant 531 | ``` 532 | 533 | List available test instances with `kitchen list`. 534 | 535 | Then you can run all tests or just select specific instances, for example: `kitchen test t-guard-debian-stable`. 536 | 537 | Note that to run tests, you also need Vagrant and VirtualBox. 538 | 539 | Origins 540 | ------- 541 | https://github.com/david415/ansible-tor (changed significantly since then) 542 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Reporting Security Bugs 2 | ----------------------- 3 | 4 | Please report security issues via github issues. 5 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | tor_user: _tor 4 | tor_packages: tor 5 | tor_package_state: present 6 | tor_binary: tor 7 | 8 | tor_config: {} 9 | tor_prom_labels: {} 10 | # example: 11 | # tor_prom_labels: 12 | # label1: "value1" 13 | # label2: "value2" 14 | 15 | tor_gen_ciiss_proof_files: false 16 | tor_ciiss_proof_folder: '~/.tor' 17 | 18 | tor_happy_family: false 19 | tor_local_happy_family_folder: '~/.tor' 20 | tor_happy_family_basename: happyfamily 21 | 22 | # The tor_ports dictionary defines how many instances are created per 23 | # available IP address and what ports are used. 24 | # min entries: 1 25 | # max entries: 2 26 | tor_ports: 27 | - orport: 9000 28 | dirport: 9001 29 | - orport: 9100 30 | dirport: 9101 31 | 32 | 33 | # Limit the amount of used IP addresses 34 | # by default to 1, this limits the amount 35 | # of generated Tor instances to 2 per host. 36 | # Specifying tor_ips overwrites this limitation. 37 | tor_maxPublicIPs: 1 38 | 39 | # We only use a single private IP by default since 40 | # we can not reliably tell whether we are NATed 41 | # to more than a single public IP address 42 | tor_maxPrivateIPs: 1 43 | 44 | # we are a non-exit by default 45 | # setting this to true will enable reduced exit policy 46 | # and allow custom exit policies via tor_ExitPolicy 47 | # Logic is implemented in templates/torrc 48 | tor_ExitRelay: false 49 | 50 | # deploy default tor-exit-notice.html if we are an exit 51 | tor_ExitNoticePage: true 52 | 53 | # automatically detect IPv6 addresses and enable IPv6 ORPort 54 | tor_IPv6: true 55 | 56 | # enable IPv6 exiting 57 | # the following var only matters if 58 | # tor_ExitRelay and tor_IPv6 is true 59 | tor_IPv6Exit: true 60 | 61 | # on FreeBSD we increase the following kernel 62 | # settings if they are lower than that. 63 | # minimal kern.ipc.somaxconn value 64 | tor_freebsd_somaxconn: 1024 65 | # minimal kern.ipc.nmbclusters value 66 | tor_freebsd_nmbclusters: 30000 67 | 68 | # Attention: we will run chown tor_user on these folders 69 | tor_DataDir: /var/lib/tor 70 | tor_PidDir: /var/run/tor-instances 71 | 72 | # Create a backup of the torrc file on the tor server whenever the file is changed. 73 | # You might want to disable this if you use other means of backups or version 74 | # control, for example etckeeper. 75 | tor_backup_torrc: true 76 | 77 | # specify tor's loglevel 78 | tor_LogLevel: notice 79 | 80 | # Where do you want to store key material on the 81 | # ansible control machine? 82 | tor_offline_masterkey_dir: ~/.tor/offlinemasterkeys 83 | 84 | # How long should online keys/certs be valid before expiring? 85 | tor_signingkeylifetime_days: 30 86 | 87 | # this var is used for apt sources.list entries 88 | tor_distribution_release: "{{ ansible_lsb.codename }}" 89 | 90 | tor_available_public_ipv4s: "{{ ansible_all_ipv4_addresses | ansible.utils.ipv4('address') | ansible.utils.ipaddr('public') }}" 91 | tor_v4ips: "{{ tor_available_public_ipv4s[0:tor_maxPublicIPs] }}" 92 | tor_ipv4_count: "{{ tor_v4ips | length | int }}" 93 | 94 | tor_available_public_ipv6s: "{{ ansible_all_ipv6_addresses| ansible.utils.ipv6('address') | ansible.utils.ipaddr('public') }}" 95 | # we can not use more IPv6 IPs than we have IPv4 IPs so we truncate (but fewer is ok) 96 | tor_v6ips: "{{ tor_available_public_ipv6s[0:tor_ipv4_count|int] }}" 97 | 98 | tor_apt_update_cache: true 99 | 100 | tor_RunAsDaemon: 1 101 | 102 | tor_enableControlSocket: false 103 | 104 | tor_alpha: false 105 | tor_nightly_builds: false 106 | 107 | tor_alpha_version: experimental 108 | 109 | # MetricsPort variables 110 | tor_enableMetricsPort: false 111 | tor_MetricsPort_offset: 33300 112 | 113 | # prometheus (tor MetricsPort) related vars 114 | tor_prometheus_group: prometheus 115 | tor_blackbox_exporter_scheme: http 116 | tor_blackbox_exporter_password: "{{ lookup('password', '~/.tor/blackbox_exporter_password') }}" 117 | tor_prometheus_scrape_password_folder: "~/.tor/prometheus/scrape-passwords/" 118 | tor_prometheus_scrape_port: 443 119 | tor_gen_metricsport_htpasswd: true 120 | # the folder of the following file has to be present on the tor server already 121 | tor_metricsport_nginx_config_file: "/etc/nginx/promexporters/tor_metricsports_relayor.conf" 122 | tor_metricsport_htpasswd_file: "/etc/nginx/tor_metricsport_htpasswd" 123 | tor_metricsport_htpasswd_file_owner: "www-data" 124 | tor_metricsport_user: TorMetricsPortBehindNginx 125 | tor_prometheus_confd_folder: "/etc/prometheus/conf.d" 126 | tor_prometheus_scrape_file: "tor_{{ ansible_fqdn }}.yml" 127 | tor_gen_prometheus_alert_rules: false 128 | tor_prometheus_rules_file: "/etc/prometheus/rules/ansible-relayor.rules" 129 | tor_prometheus_alert_rules: 130 | - alert: TorFlagRunningMissing 131 | expr: 'tor_relay_flag{type="Running"} != 1' 132 | for: 3h 133 | labels: 134 | severity: critical 135 | - alert: TorFlagSybilPresent 136 | expr: 'tor_relay_flag{type="Sybil"} != 0' 137 | labels: 138 | severity: critical 139 | - alert: TorTCPPortsExhausted 140 | expr: 'rate(tor_relay_load_tcp_exhaustion_total[15m]) > 0' 141 | labels: 142 | severity: critical 143 | - alert: TorCertificateExpiryUnder15Days 144 | expr: 'floor((tor_relay_signing_cert_expiry_timestamp - time())/86400) < 15' 145 | labels: 146 | severity: warning 147 | annotations: 148 | description: '{% raw %}{{ $labels.id }} key certificate will expire in {{ $value }} days.{% endraw %}' 149 | - alert: TorCertificateExpiryUnder1Day 150 | expr: 'floor((tor_relay_signing_cert_expiry_timestamp - time())/3600) < 24' 151 | labels: 152 | severity: critical 153 | annotations: 154 | description: '{% raw %}{{ $labels.id }} key certificate will expire and auto-shutdown in {{ $value }} hours.{% endraw %}' 155 | - alert: TorHighDnsTimeoutRate 156 | expr: '(sum by (instance)(rate(tor_relay_exit_dns_error_total{reason="tor_timeout"}[15m])))/(sum by (instance)(rate(tor_relay_exit_dns_query_total[15m])))*100 > 1.5' 157 | for: 15m 158 | labels: 159 | severity: warning 160 | annotations: 161 | summary: 'DNS timeout rate is above 1.5% for over 10 minutes' 162 | description: '{% raw %}{{ printf "%.3f" $value }}% of tor DNS queries on {{ $labels.instance }} result in a timeout.{% endraw %}' 163 | - alert: TorOnionskinsDropped 164 | expr: 'rate(tor_relay_load_onionskins_total{action="dropped"}[15m]) > 0' 165 | for: 15m 166 | labels: 167 | severity: warning 168 | annotations: 169 | description: '{% raw %}Relay {{ $labels.id }} is dropping onionskins for over 15 minutes.{% endraw %}' 170 | # - alert: TorFlagExitChanged 171 | # expr: 'changes(tor_relay_flag{type="Exit"}[15m]) > 0' 172 | # labels: 173 | # severity: informational 174 | # annotations: 175 | # description: "{% raw %}The exit flag changed on relay {{ $labels.id }}. Was that intended?{% endraw %}" 176 | # - alert: TorFlagChanged 177 | # expr: 'changes(tor_relay_flag{type=~"Guard|Fast|Stable"}[15m]) > 0' 178 | # labels: 179 | # severity: informational 180 | # annotations: 181 | # description: '{% raw %}The {{ $labels.type }} flag changed on relay {{ $labels.id }}.{% endraw %}' 182 | 183 | # Path to the tor-exit-notice HTML file 184 | tor_exit_notice_file: tor-exit-notice.html 185 | 186 | # List of blacklisted DNS resolvers 187 | # 188 | # Google DNS IPs: 189 | # 8.8.8.8 190 | # 8.8.4.4 191 | # 2001:4860:4860::8888 192 | # 2001:4860:4860::8844 193 | # taken from: https://developers.google.com/speed/public-dns/docs/using#google_public_dns_ip_addresses 194 | # 195 | # OpenDNS IPs: 196 | # 208.67.222.123 197 | # 208.67.220.123 198 | # 208.67.222.222 199 | # 208.67.220.220 200 | # 2620:119:35::35 201 | # 2620:119:53::53 202 | # taken from: https://www.opendns.com/setupguide/ 203 | # taken from: https://www.opendns.com/about/innovations/ipv6/ 204 | # 205 | # CloudFlare DNS IPs: 206 | # 1.1.1.1 207 | # 1.0.0.1 208 | # 2606:4700:4700::1111 209 | # 2606:4700:4700::1001 210 | # taken from: https://1.1.1.1/ 211 | # 212 | # Quad9 DNS IPs: 213 | # 9.9.9.9 214 | # 9.9.9.10 215 | # 149.112.112.10 216 | # 149.112.112.112 217 | # 2620:fe::fe 218 | # 2620:fe::9 219 | # 2620:fe::10 220 | # 2620:fe::fe:10 221 | # taken from: https://www.quad9.com/faq/ 222 | # 223 | # Level 3 224 | # 4.2.2.1-6 225 | # 226 | # Potential future candidates: 227 | # ---------------------------- 228 | # 229 | # ISC.org: 204.152.184.76 230 | # 231 | # Background: 232 | # https://nymity.ch/dns-traffic-correlation/. 233 | # https://medium.com/@nusenu/who-controls-tors-dns-traffic-a74a7632e8ca 234 | 235 | tor_dnsresolver_blacklist: 236 | - 8\.8\.8\.8 237 | - 8\.8\.4\.4 238 | - 2001:4860:4860:.*:8888 239 | - 2001:4860:4860:.*:8844 240 | - 208\.67\.222\.123 241 | - 208\.67\.220\.123 242 | - 208\.67\.222\.222 243 | - 208\.67\.220\.220 244 | - 2620:119:35:.*:35 245 | - 2620:119:53:.*:53 246 | - 1\.1\.1\.1 247 | - 1\.0\.0\.1 248 | - 2606:4700:4700:.*:1111 249 | - 2606:4700:4700:.*:1001 250 | - 9\.9\.9\.9 251 | - 9\.9\.9\.10 252 | - 149\.112\.112\.10 253 | - 149\.112\.112\.112 254 | - 2620:fe:.*:fe 255 | - 2620:fe:.*:9 256 | - 2620:fe:.*:10 257 | - 2620:fe:.*:fe:10 258 | - 4\.2\.2\.[1-6] 259 | 260 | # Construction of grep command to 261 | # match blacklisted DNS resolvers 262 | tor_resolvconf_path: /etc/resolv.conf 263 | tor_dns_resolver_blacklist_pattern: "{% for IP in tor_dnsresolver_blacklist %}{{ '-e \"^nameserver ' + IP + '[[:space:]]*$\" ' }}{% endfor %}" 264 | tor_grep_blacklisted_dnsresolvers: "{{ [ 'grep -qE', tor_dns_resolver_blacklist_pattern, tor_resolvconf_path ] | join(' ') }}" 265 | 266 | # Default exit policy if the user wants to be an exit but doesn't specify one 267 | # taken from https://trac.torproject.org/projects/tor/wiki/doc/ReducedExitPolicy 268 | tor_ExitPolicy: 269 | - accept *:20-22 270 | - accept *:43 271 | - accept *:53 272 | - accept *:79-81 273 | - accept *:194 274 | - accept *:220 275 | - accept *:389 276 | - accept *:443 277 | - accept *:465 278 | - accept *:531 279 | - accept *:543-544 280 | - accept *:554 281 | - accept *:563 282 | - accept *:587 283 | - accept *:636 284 | - accept *:706 285 | - accept *:853 286 | - accept *:873 287 | - accept *:902-904 288 | - accept *:981 289 | - accept *:989-995 290 | - accept *:1194 291 | - accept *:1220 292 | - accept *:1293 293 | - accept *:1500 294 | - accept *:1533 295 | - accept *:1677 296 | - accept *:1723 297 | - accept *:1755 298 | - accept *:1863 299 | - accept *:2082 300 | - accept *:2083 301 | - accept *:2086-2087 302 | - accept *:2095-2096 303 | - accept *:2102-2104 304 | - accept *:3128 305 | - accept *:3690 306 | - accept *:4321 307 | - accept *:4643 308 | - accept *:5050 309 | - accept *:5190 310 | - accept *:5222-5223 311 | - accept *:5228 312 | - accept *:5900 313 | - accept *:6660-6669 314 | - accept *:6679 315 | - accept *:6697 316 | - accept *:8000 317 | - accept *:8008 318 | - accept *:8074 319 | - accept *:8080 320 | - accept *:8082 321 | - accept *:8087-8088 322 | - accept *:8332-8333 323 | - accept *:8443 324 | - accept *:8888 325 | - accept *:9418 326 | - accept *:9999 327 | - accept *:10000 328 | - accept *:11371 329 | - accept *:19294 330 | - accept *:19638 331 | - accept *:50002 332 | - accept *:64738 333 | - reject *:* 334 | -------------------------------------------------------------------------------- /files/local.conf: -------------------------------------------------------------------------------- 1 | [Service] 2 | ReadWriteDirectories=/var/lib/tor-instances /run/tor-instances 3 | -------------------------------------------------------------------------------- /files/tor-generator: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # systemd generator to create dependency symlink to start 4 | # all tor instances from /etc/tor/instances/ 5 | 6 | set -eu 7 | 8 | if [ $# -lt 1 ]; then 9 | echo >&2 "Usage: $0 [...]" 10 | exit 1 11 | fi 12 | 13 | GENDIR="$1" 14 | WANTDIR="$1/tor.service.wants" 15 | SERVICEFILE="/lib/systemd/system/tor@.service" 16 | DEFAULTTOR="/lib/systemd/system/tor@default.service" 17 | BASEETC="/etc/tor/instances" 18 | 19 | mkdir -p "$WANTDIR" 20 | 21 | [ -e "/etc/tor/torrc" ] && ln -s "$DEFAULTTOR" "$WANTDIR/" 22 | for name in $( find "$BASEETC" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' ); do 23 | if echo "x$name" | grep -q '[^a-zA-Z0-9_.]' || 24 | [ "$name" = "default" ] ; then 25 | continue 26 | fi 27 | [ -e "$BASEETC/$name/torrc" ] && ln -s "$SERVICEFILE" "$WANTDIR/tor@$name.service" 28 | done 29 | 30 | exit 0 31 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: stop-and-mask default tor instance 3 | become: true 4 | ansible.builtin.systemd: 5 | name: tor@default 6 | state: stopped 7 | enabled: false 8 | masked: true 9 | when: ansible_pkg_mgr == 'apt' 10 | 11 | - name: restart apparmor 12 | become: true 13 | ansible.builtin.service: 14 | name: apparmor 15 | state: restarted 16 | 17 | - name: systemctl daemon-reload 18 | become: true 19 | ansible.builtin.systemd: 20 | daemon_reload: true 21 | 22 | - name: re-gather facts 23 | ansible.builtin.setup: 24 | 25 | - name: disable default tor instance FreeBSD 26 | become: true 27 | ansible.builtin.lineinfile: 28 | dest: /etc/rc.conf 29 | line: tor_disable_default_instance="YES" 30 | create: true 31 | mode: "0644" 32 | when: ansible_system == 'FreeBSD' 33 | 34 | # TODO: this restarts all instances on a FreeBSD host even if just one torrc changed 35 | - name: Ensure Tor instances are restarted if its torrc changed (FreeBSD) 36 | become: true 37 | ansible.builtin.service: 38 | name: tor 39 | state: restarted 40 | when: ansible_system == 'FreeBSD' 41 | 42 | - name: Ensure Tor instances are restarted if its torrc changed (Linux) 43 | become: true 44 | ansible.builtin.service: 45 | name: tor@{{ item.item.0.ipv4 }}_{{ item.item.1.orport }}.service 46 | state: restarted 47 | with_items: "{{ tor_instances_tmp.results }}" 48 | when: item.changed and ansible_system == 'Linux' 49 | 50 | - name: Ensure Tor instances are restarted if its torrc changed (OpenBSD) 51 | become: true 52 | ansible.builtin.service: 53 | name: tor{{ item.item.0.ipv4|replace('.','_') }}_{{ item.item.1.orport }} 54 | state: restarted 55 | with_items: "{{ tor_instances_tmp.results }}" 56 | when: item.changed and ansible_system == 'OpenBSD' 57 | tags: 58 | - reconfigure 59 | 60 | - name: Ensure Tor instances are reloaded if tor-exit-notice.html changed (FreeBSD) 61 | become: true 62 | ansible.builtin.service: 63 | name: tor 64 | state: reloaded 65 | when: ansible_system == 'FreeBSD' 66 | 67 | - name: Ensure Tor instances are reloaded if tor-exit-notice.html changed (Linux) 68 | become: true 69 | ansible.builtin.service: 70 | name: tor@{{ item.item.0.ipv4 }}_{{ item.item.1.orport }}.service 71 | state: reloaded 72 | with_items: "{{ tor_instances_tmp.results }}" 73 | when: ansible_system == 'Linux' 74 | 75 | - name: Ensure Tor instances are reloaded if tor-exit-notice.html changed (OpenBSD) 76 | become: true 77 | ansible.builtin.service: 78 | name: tor{{ item.item.0.ipv4|replace('.','_') }}_{{ item.item.1.orport }} 79 | state: reloaded 80 | with_items: "{{ tor_instances_tmp.results }}" 81 | when: ansible_system == 'OpenBSD' 82 | 83 | - name: reload prometheus 84 | become: true 85 | ansible.builtin.service: 86 | name: prometheus 87 | state: reloaded 88 | delegate_to: "{{ tor_prometheus_host }}" 89 | 90 | - name: reload nginx 91 | become: true 92 | ansible.builtin.service: 93 | name: nginx 94 | state: reloaded 95 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | role_name: relayor 4 | author: nusenu 5 | description: An Ansible role for Tor Relay Operators 6 | license: GPLv3 7 | platforms: 8 | - name: Debian 9 | versions: 10 | - bookworm 11 | - name: FreeBSD 12 | versions: 13 | - all 14 | - name: Ubuntu 15 | versions: 16 | - noble 17 | galaxy_tags: 18 | - tor 19 | - ipv6 20 | - anonymity 21 | - networking 22 | min_ansible_version: 2.16.14 23 | dependencies: [] 24 | -------------------------------------------------------------------------------- /playbook-examples/exit-relays/10_4_instances_per_IP.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: relays 4 | vars: 5 | #tor_ContactInfo: relayor-example@example.com 6 | tor_ExitRelay: true 7 | tor_ports: 8 | - { orport: 1, dirport: 0} 9 | - { orport: 2, dirport: 0} 10 | - { orport: 3, dirport: 0} 11 | - { orport: 4, dirport: 0} 12 | roles: 13 | - nusenu.relayor 14 | -------------------------------------------------------------------------------- /playbook-examples/exit-relays/11_4_instances_per_IP_prometheus.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: relays 4 | vars: 5 | #tor_ContactInfo: relayor-example@example.com 6 | tor_ExitRelay: true 7 | tor_enableMetricsPort: true 8 | tor_prometheus_host: 127.0.0.1 9 | tor_ports: 10 | - { orport: 1, dirport: 0} 11 | - { orport: 2, dirport: 0} 12 | - { orport: 3, dirport: 0} 13 | - { orport: 4, dirport: 0} 14 | roles: 15 | - nusenu.relayor 16 | -------------------------------------------------------------------------------- /playbook-examples/exit-relays/12_4_instances_per_IP_prometheus-alertrules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: relays 4 | vars: 5 | #tor_ContactInfo: relayor-example@example.com 6 | tor_ExitRelay: true 7 | tor_enableMetricsPort: true 8 | tor_prometheus_host: 127.0.0.1 9 | tor_gen_prometheus_alert_rules: true 10 | tor_ports: 11 | - { orport: 1, dirport: 0} 12 | - { orport: 2, dirport: 0} 13 | - { orport: 3, dirport: 0} 14 | - { orport: 4, dirport: 0} 15 | roles: 16 | - nusenu.relayor 17 | -------------------------------------------------------------------------------- /playbook-examples/exit-relays/8_reduced_exitpolicy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: relays 4 | vars: 5 | #tor_ContactInfo: relayor-example@example.com 6 | tor_signingkeylifetime_days: 90 7 | tor_nickname: "{{ ansible_hostname }}" 8 | tor_alpha: true 9 | tor_ExitRelay: true 10 | roles: 11 | - nusenu.relayor 12 | -------------------------------------------------------------------------------- /playbook-examples/exit-relays/9_custom_exit_policy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: relays 4 | vars: 5 | #tor_ContactInfo: relayor-example@example.com 6 | tor_signingkeylifetime_days: 90 7 | tor_nickname: "{{ ansible_hostname }}" 8 | tor_alpha: true 9 | tor_ExitRelay: true 10 | tor_ExitPolicy: 11 | - accept *:80 12 | - accept *:443 13 | - reject *:* 14 | roles: 15 | - nusenu.relayor 16 | -------------------------------------------------------------------------------- /playbook-examples/non-exit-relays/1_contactInfo.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: relays 4 | vars: 5 | #tor_ContactInfo: relayor-example@example.com 6 | roles: 7 | - nusenu.relayor 8 | -------------------------------------------------------------------------------- /playbook-examples/non-exit-relays/2_keyexpiry_90days.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: relays 4 | vars: 5 | #tor_ContactInfo: relayor-example@example.com 6 | tor_signingkeylifetime_days: 90 7 | roles: 8 | - nusenu.relayor 9 | -------------------------------------------------------------------------------- /playbook-examples/non-exit-relays/3_nickname.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: relays 4 | vars: 5 | #tor_ContactInfo: relayor-example@example.com 6 | tor_signingkeylifetime_days: 90 7 | tor_nickname: "{{ ansible_hostname }}" 8 | roles: 9 | - nusenu.relayor 10 | -------------------------------------------------------------------------------- /playbook-examples/non-exit-relays/4_custom_ports.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: relays 4 | vars: 5 | #tor_ContactInfo: relayor-example@example.com 6 | tor_signingkeylifetime_days: 90 7 | tor_nickname: "{{ ansible_hostname }}" 8 | tor_ports: 9 | - { orport: 443, dirport: 80} 10 | - { orport: 444, dirport: 8080} 11 | roles: 12 | - nusenu.relayor 13 | -------------------------------------------------------------------------------- /playbook-examples/non-exit-relays/5_single_instance.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: relays 4 | vars: 5 | #tor_ContactInfo: relayor-example@example.com 6 | tor_signingkeylifetime_days: 90 7 | tor_nickname: "{{ ansible_hostname }}" 8 | tor_ports: 9 | - { orport: 443, dirport: 80} 10 | roles: 11 | - nusenu.relayor 12 | -------------------------------------------------------------------------------- /playbook-examples/non-exit-relays/6_four_instances.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: relays 4 | vars: 5 | #tor_ContactInfo: relayor-example@example.com 6 | tor_signingkeylifetime_days: 90 7 | tor_nickname: "{{ ansible_hostname }}" 8 | tor_maxPublicIPs: 2 9 | roles: 10 | - nusenu.relayor 11 | -------------------------------------------------------------------------------- /playbook-examples/non-exit-relays/7_alpha-apt-repo.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: relays 4 | vars: 5 | #tor_ContactInfo: relayor-example@example.com 6 | tor_signingkeylifetime_days: 90 7 | tor_nickname: "{{ ansible_hostname }}" 8 | tor_maxPublicIPs: 2 9 | tor_alpha: true 10 | roles: 11 | - nusenu.relayor 12 | -------------------------------------------------------------------------------- /tasks/apt_prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure lsb-release, apt-transport-https and gpg packages are installed 3 | become: true 4 | ansible.builtin.apt: 5 | name: lsb-release,apt-transport-https,gpg 6 | update_cache: "{{ tor_apt_update_cache }}" 7 | cache_valid_time: 86400 8 | notify: 9 | - re-gather facts 10 | 11 | - ansible.builtin.meta: flush_handlers 12 | 13 | - name: Ensure torproject gpg key is installed (A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89) 14 | become: true 15 | ansible.builtin.apt_key: 16 | url: https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc 17 | id: A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89 18 | state: present 19 | 20 | - name: Ensure torproject.org repository is present (APT) 21 | become: true 22 | ansible.builtin.apt_repository: 23 | repo: deb https://deb.torproject.org/torproject.org {{ tor_distribution_release }} main 24 | state: present 25 | update_cache: true 26 | 27 | - name: Override tor_alpha_version if nightly builds repo is enabled (APT) 28 | ansible.builtin.set_fact: 29 | tor_alpha_version: nightly-main 30 | tor_alpha: true 31 | when: tor_nightly_builds 32 | 33 | - name: Ensure torproject.org alpha/nightly repo is present if enabled (APT) 34 | become: true 35 | ansible.builtin.apt_repository: 36 | repo: deb https://deb.torproject.org/torproject.org tor-{{ tor_alpha_version }}-{{ tor_distribution_release }} main 37 | state: present 38 | update_cache: true 39 | when: tor_alpha 40 | 41 | # Background: 42 | # https://github.com/nusenu/ansible-relayor/issues/72 43 | - name: Ensure systemd generator folder exists (Debian/Ubuntu) 44 | become: true 45 | ansible.builtin.file: 46 | path: /etc/systemd/system-generators 47 | state: directory 48 | mode: "0755" 49 | 50 | - name: Ensure custom systemd generator is in place (Debian/Ubuntu only) 51 | become: true 52 | ansible.builtin.copy: 53 | src: tor-generator 54 | dest: /etc/systemd/system-generators/tor-generator 55 | owner: root 56 | mode: "0755" 57 | -------------------------------------------------------------------------------- /tasks/configure.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure local DataDir folders exist (LOCAL) 3 | become: false 4 | ansible.builtin.file: 5 | path: "{{ tor_offline_masterkey_dir }}/{{ inventory_hostname }}-{{ item.0.ipv4 }}_{{ item.1.orport }}" 6 | state: directory 7 | mode: "0700" 8 | delegate_to: 127.0.0.1 9 | with_nested: 10 | - "{{ tor_ips }}" 11 | - "{{ tor_ports }}" 12 | tags: 13 | - createdir 14 | 15 | - name: Check happy family key (LOCAL) 16 | become: false 17 | ansible.builtin.stat: 18 | path: "{{ tor_local_happy_family_folder }}/{{ tor_happy_family_basename }}.secret_family_key" 19 | delegate_to: 127.0.0.1 20 | run_once: true 21 | when: tor_happy_family 22 | register: tor_happy_family_stat 23 | 24 | - name: Ensure happy family key exists (LOCAL) 25 | become: false 26 | ansible.builtin.command: > 27 | "{{ tor_binary }}" --quiet --keygen-family "{{ tor_local_happy_family_folder }}/{{ tor_happy_family_basename }}" 28 | delegate_to: 127.0.0.1 29 | run_once: true 30 | when: tor_happy_family and not tor_happy_family_stat.stat.exists 31 | 32 | - name: Check FamilyId presence (LOCAL) 33 | become: false 34 | ansible.builtin.stat: 35 | path: "{{ tor_local_happy_family_folder }}/{{ tor_happy_family_basename }}.public_family_id" 36 | delegate_to: 127.0.0.1 37 | run_once: true 38 | when: tor_happy_family 39 | register: tor_happy_family_publicid_stat 40 | 41 | - name: Ensure FamilyId public id file exists (LOCAL) 42 | become: false 43 | ansible.builtin.assert: 44 | that: 45 | - tor_happy_family_publicid_stat.stat.exists 46 | msg: public_family_id file is missing, aborting! 47 | run_once: true 48 | delegate_to: 127.0.0.1 49 | when: tor_happy_family 50 | 51 | - name: Ensure all relay keys exist (LOCAL) 52 | become: false 53 | ansible.builtin.command: > 54 | "{{ tor_binary }}" --list-fingerprint --DisableNetwork 1 --orport auto --PublishServerDescriptor 0 --ExitRelay 0 55 | --ignore-missing-torrc -f /dev/null --defaults-torrc /dev/null --Log "err stdout" 56 | --datadirectory "{{ tor_offline_masterkey_dir }}/{{ inventory_hostname }}-{{ item.0.ipv4 }}_{{ item.1.orport }}" 57 | delegate_to: 127.0.0.1 58 | with_nested: 59 | - "{{ tor_ips }}" 60 | - "{{ tor_ports }}" 61 | changed_when: false 62 | 63 | - name: Generate new Ed25519 signing keys (LOCAL) 64 | become: false 65 | ansible.builtin.command: > 66 | "{{ tor_binary }}" --keygen --SigningKeyLifetime {{ tor_signingkeylifetime_days }}\ days --ignore-missing-torrc -f /dev/null 67 | --defaults-torrc /dev/null --Log "err stdout" 68 | --datadirectory "{{ tor_offline_masterkey_dir }}/{{ inventory_hostname }}-{{ item.0.ipv4 }}_{{ item.1.orport }}" 69 | delegate_to: 127.0.0.1 70 | with_nested: 71 | - "{{ tor_ips }}" 72 | - "{{ tor_ports }}" 73 | tags: 74 | - renewkey 75 | changed_when: false 76 | 77 | - name: Detect duplicate relay keys across relays (LOCAL) 78 | become: false 79 | ansible.builtin.shell: > 80 | set -o pipefail && openssl sha256 -r 81 | {{ tor_offline_masterkey_dir }}/*/keys/secret_id_key {{ tor_offline_masterkey_dir }}/*/keys/ed25519_master_id_secret_key | 82 | cut -d' ' -f1|sort|uniq -d|wc -l 83 | args: 84 | executable: /bin/bash 85 | delegate_to: 127.0.0.1 86 | run_once: true 87 | register: tor_dupkeycount 88 | changed_when: false 89 | 90 | - name: Abort on duplicate relay keys 91 | ansible.builtin.fail: 92 | msg: Duplicate relay key detected! Aborting. 93 | run_once: true 94 | when: tor_dupkeycount.stdout|int(1) != 0 95 | 96 | - name: Detect if Ed25519 master keys are on the relay 97 | become: true 98 | ansible.builtin.stat: 99 | path: "{{ tor_DataDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}/keys/ed25519_master_id_secret_key" 100 | register: tor_masterkeyonline 101 | with_nested: 102 | - "{{ tor_ips }}" 103 | - "{{ tor_ports }}" 104 | 105 | - name: Abort if Ed25519 master keys are on the relay 106 | ansible.builtin.fail: 107 | msg: Ed25519 MASTER KEY detected on the relay - it is NOT supposed to be there! Aborting. 108 | when: item.stat.exists 109 | with_items: "{{ tor_masterkeyonline.results }}" 110 | 111 | # not relying on the datadir/fingerprint file is more robust 112 | - name: Collect fingerprints for MyFamily (LOCAL) 113 | become: false 114 | ansible.builtin.shell: > 115 | set -o pipefail && for key in {{ tor_offline_masterkey_dir }}/*/keys/secret_id_key; 116 | do openssl rsa -in $key -outform DER -RSAPublicKey_out 2> /dev/null| openssl sha1 -r; 117 | done|cut -d" " -f1|sort|xargs|sed -e 's/ /,/g' 118 | args: 119 | executable: /bin/bash 120 | delegate_to: 127.0.0.1 121 | run_once: true 122 | register: tor_family 123 | tags: 124 | - reconfigure 125 | changed_when: false 126 | 127 | - name: Ensure per-instance tor users exist 128 | become: true 129 | ansible.builtin.user: 130 | name: _tor-{{ item.0.ipv4 }}_{{ item.1.orport }} 131 | system: true 132 | shell: /bin/false 133 | create_home: true 134 | home: "{{ tor_DataDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}" 135 | with_nested: 136 | - "{{ tor_ips }}" 137 | - "{{ tor_ports }}" 138 | 139 | # We only need to create this folder (used for ControlSocket and on FreeBSD for the pidfile) on: 140 | # - FreeBSD (regardless of tor_enableControlSocket) 141 | # - CentOS/Fedora/OpenBSD when tor_enableControlSocket is True 142 | # we never create it on Debian since the systemd service file creates it there (with different permissions) 143 | - name: Ensure PID/ControlSocket directory exists 144 | become: true 145 | ansible.builtin.file: 146 | path: "{{ tor_PidDir }}" 147 | state: directory 148 | owner: root 149 | mode: "0755" 150 | when: ansible_system == 'FreeBSD' or (tor_enableControlSocket and (ansible_system == 'OpenBSD' or ansible_os_family == 'RedHat')) 151 | 152 | - name: Ensure PID/ControlSocket directory is owned by per-instance tor user 153 | become: true 154 | ansible.builtin.file: 155 | path: "{{ tor_PidDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}" 156 | state: directory 157 | owner: _tor-{{ item.0.ipv4 }}_{{ item.1.orport }} 158 | group: _tor-{{ item.0.ipv4 }}_{{ item.1.orport }} 159 | mode: "0750" 160 | with_nested: 161 | - "{{ tor_ips }}" 162 | - "{{ tor_ports }}" 163 | when: ansible_system == 'FreeBSD' or (tor_enableControlSocket and (ansible_system == 'OpenBSD' or ansible_os_family == 'RedHat')) 164 | 165 | - name: Ensure per-instance config folders exist (Debian only) 166 | become: true 167 | ansible.builtin.file: 168 | path: "{{ tor_ConfDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}" 169 | state: directory 170 | mode: "0755" 171 | with_nested: 172 | - "{{ tor_ips }}" 173 | - "{{ tor_ports }}" 174 | when: ansible_pkg_mgr == 'apt' 175 | 176 | - name: Ensure DataDir exists 177 | become: true 178 | ansible.builtin.file: 179 | path: "{{ tor_DataDir }}" 180 | state: directory 181 | owner: root 182 | mode: "0755" 183 | 184 | - name: Ensure "keys" subfolder exists 185 | become: true 186 | ansible.builtin.file: 187 | path: "{{ tor_DataDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}/keys" 188 | state: directory 189 | owner: _tor-{{ item.0.ipv4 }}_{{ item.1.orport }} 190 | group: _tor-{{ item.0.ipv4 }}_{{ item.1.orport }} 191 | mode: u=rwX,g=,o= 192 | recurse: true 193 | with_nested: 194 | - "{{ tor_ips }}" 195 | - "{{ tor_ports }}" 196 | 197 | - name: Ensure happy family key is in place 198 | become: true 199 | ansible.builtin.copy: 200 | src: "{{ tor_local_happy_family_folder }}/{{ tor_happy_family_basename }}.secret_family_key" 201 | dest: "{{ tor_DataDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}/keys/{{ tor_happy_family_basename }}.secret_family_key" 202 | owner: _tor-{{ item.0.ipv4 }}_{{ item.1.orport }} 203 | mode: "0600" 204 | with_nested: 205 | - "{{ tor_ips }}" 206 | - "{{ tor_ports }}" 207 | when: tor_happy_family 208 | 209 | - name: Ensure RSA key is in place (without overriding existing keys) 210 | become: true 211 | ansible.builtin.copy: 212 | src: "{{ tor_offline_masterkey_dir }}/{{ inventory_hostname }}-{{ item.0.ipv4 }}_{{ item.1.orport }}/keys/{{ item[2] }}" 213 | dest: "{{ tor_DataDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}/keys/{{ item[2] }}" 214 | owner: _tor-{{ item.0.ipv4 }}_{{ item.1.orport }} 215 | mode: "0600" 216 | force: false 217 | with_nested: 218 | - "{{ tor_ips }}" 219 | - "{{ tor_ports }}" 220 | - [secret_id_key] 221 | 222 | - name: Fetch RSA key for comparison 223 | become: true 224 | ansible.builtin.fetch: 225 | src: "{{ tor_DataDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}/keys/{{ item[2] }}" 226 | dest: "{{ tor_offline_masterkey_dir }}/{{ inventory_hostname }}-{{ item.0.ipv4 }}_{{ item.1.orport }}/keys/{{ item[2] }}.untrustedremotekey" 227 | flat: true 228 | with_nested: 229 | - "{{ tor_ips }}" 230 | - "{{ tor_ports }}" 231 | - [secret_id_key] 232 | 233 | - name: Compare local vs. remote RSA key (secret_id_key) 234 | become: false 235 | ansible.builtin.shell: > 236 | set -o pipefail && openssl sha256 -r 237 | {{ tor_offline_masterkey_dir }}/{{ inventory_hostname }}-"{{ item.0.ipv4 }}_{{ item.1.orport }}"/keys/secret_id_key* 238 | | cut -d' ' -f1|uniq -d|wc -l 239 | args: 240 | executable: /bin/bash 241 | delegate_to: 127.0.0.1 242 | with_nested: 243 | - "{{ tor_ips }}" 244 | - "{{ tor_ports }}" 245 | register: tor_rsakey_match 246 | changed_when: false 247 | 248 | - name: Abort if local and remote RSA keys do not match 249 | ansible.builtin.assert: 250 | that: 251 | - item.stdout|int == 1 252 | msg: > 253 | "Key mismatch detected! Solution: http://bit.ly/2j6wc70 Affected instance: 254 | {{ tor_offline_masterkey_dir }}/{{ inventory_hostname }}-{{ item.item.0.ipv4 }}_{{ item.item.1.orport }}/keys" 255 | with_items: "{{ tor_rsakey_match.results }}" 256 | 257 | # this task is separated from the task named "Ensure RSA key is in place" because it is not run with 'force=no' 258 | - name: Transmit new Ed25519 signing keys 259 | become: true 260 | ansible.builtin.copy: 261 | src: "{{ tor_offline_masterkey_dir }}/{{ inventory_hostname }}-{{ item.0.ipv4 }}_{{ item.1.orport }}/keys/{{ item[2] }}" 262 | dest: "{{ tor_DataDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}/keys/{{ item[2] }}" 263 | owner: _tor-{{ item.0.ipv4 }}_{{ item.1.orport }} 264 | mode: "0600" 265 | setype: tor_var_lib_t 266 | with_nested: 267 | - "{{ tor_ips }}" 268 | - "{{ tor_ports }}" 269 | - [ed25519_signing_cert, ed25519_signing_secret_key] 270 | changed_when: false 271 | tags: 272 | - renewkey 273 | 274 | # This needs to be at the end to fix SELinux contexts recursively 275 | - name: Ensure per-instance DataDir have proper permissions 276 | become: true 277 | ansible.builtin.file: 278 | path: "{{ tor_DataDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}" 279 | state: directory 280 | owner: _tor-{{ item.0.ipv4 }}_{{ item.1.orport }} 281 | group: _tor-{{ item.0.ipv4 }}_{{ item.1.orport }} 282 | mode: u=rwX,g=,o= 283 | recurse: true 284 | setype: tor_var_lib_t 285 | with_nested: 286 | - "{{ tor_ips }}" 287 | - "{{ tor_ports }}" 288 | 289 | - name: Ensure Tor config directory exists 290 | become: true 291 | ansible.builtin.file: 292 | path: "{{ tor_ConfDir }}" 293 | state: directory 294 | owner: root 295 | group: "{{ tor_user }}" 296 | mode: "0755" 297 | 298 | - name: Ensure tor-exit-notice.html is present (if we are an exit) 299 | become: true 300 | ansible.builtin.template: 301 | src: "{{ tor_exit_notice_file }}" 302 | dest: "{{ tor_ConfDir }}/tor-exit-notice.html" 303 | mode: "0444" 304 | when: tor_ExitRelay and tor_ExitNoticePage 305 | notify: 306 | - Ensure Tor instances are reloaded if tor-exit-notice.html changed (FreeBSD) 307 | - Ensure Tor instances are reloaded if tor-exit-notice.html changed (Linux) 308 | - Ensure Tor instances are reloaded if tor-exit-notice.html changed (OpenBSD) 309 | 310 | - name: Ensure torrc configuration file(s) are in place 311 | become: true 312 | ansible.builtin.template: 313 | src: torrc 314 | dest: "{{ (ansible_pkg_mgr != 'apt')| ternary(tor_ConfDir ~ '/' ~ item.0.ipv4 ~ '_' ~ item.1.orport~ '.torrc', tor_ConfDir ~ '/' ~ item.0.ipv4 ~ '_' ~ item.1.orport 315 | ~ '/torrc') }}" 316 | owner: root 317 | mode: "0644" 318 | backup: "{{ tor_backup_torrc }}" 319 | validate: tor --verify-config -f %s 320 | with_nested: 321 | - "{{ tor_ips }}" 322 | - "{{ tor_ports }}" 323 | register: tor_instances_tmp 324 | loop_control: 325 | index_var: loop_idx 326 | notify: 327 | - Ensure Tor instances are restarted if its torrc changed (FreeBSD) 328 | - Ensure Tor instances are restarted if its torrc changed (Linux) 329 | - Ensure Tor instances are restarted if its torrc changed (OpenBSD) 330 | tags: 331 | - reconfigure 332 | 333 | - name: Ensure prometheus per host scrape configs are in place 334 | become: true 335 | ansible.builtin.template: 336 | src: prometheus-scrape-configs 337 | dest: "{{ tor_prometheus_confd_folder}}/{{ tor_prometheus_scrape_file }}" 338 | owner: root 339 | group: "{{ tor_prometheus_group }}" 340 | mode: "0640" 341 | delegate_to: "{{ tor_prometheus_host }}" 342 | when: (tor_enableMetricsPort or tor_blackbox_exporter_host is defined) and tor_prometheus_host is defined 343 | notify: 344 | - reload prometheus 345 | tags: 346 | - reconfigure 347 | - promconfig 348 | 349 | - name: Ensure prometheus alert rules are in place 350 | become: true 351 | ansible.builtin.template: 352 | src: prometheus-alert-rules 353 | dest: "{{ tor_prometheus_rules_file }}" 354 | owner: root 355 | group: "{{ tor_prometheus_group }}" 356 | mode: "0640" 357 | validate: promtool check rules %s 358 | delegate_to: "{{ tor_prometheus_host }}" 359 | run_once: true 360 | when: tor_enableMetricsPort and tor_gen_prometheus_alert_rules and tor_prometheus_rules_file is defined and tor_prometheus_host is defined 361 | notify: 362 | - reload prometheus 363 | tags: 364 | - reconfigure 365 | - promconfig 366 | 367 | - name: Ensure nginx reverse proxy configuration for MetricsPort is in place 368 | become: true 369 | ansible.builtin.template: 370 | src: nginx-reverse-proxy-for-metricsport 371 | dest: "{{ tor_metricsport_nginx_config_file }}" 372 | mode: "0644" 373 | owner: root 374 | when: tor_enableMetricsPort and tor_prometheus_host is defined 375 | notify: 376 | - reload nginx 377 | tags: 378 | - reconfigure 379 | - promconfig 380 | 381 | # The htpasswd ansible module requires python passlib to be on the remote host 382 | - name: Ensure htpasswd Ansible dependencies are installed 383 | become: true 384 | ansible.builtin.package: 385 | name: "{{ tor_htpasswd_dependency }}" 386 | state: present 387 | when: tor_gen_metricsport_htpasswd and tor_enableMetricsPort 388 | 389 | - name: Ensure htpasswd file for MetricsPort protection is in place 390 | become: true 391 | community.general.htpasswd: 392 | create: true 393 | path: "{{ tor_metricsport_htpasswd_file }}" 394 | name: "{{ tor_metricsport_user }}" 395 | password: "{{ lookup('password', tor_prometheus_scrape_password_folder+inventory_hostname ) }}" 396 | mode: "0640" 397 | group: "{{ tor_metricsport_htpasswd_file_owner }}" 398 | when: tor_gen_metricsport_htpasswd and tor_enableMetricsPort 399 | tags: 400 | - reconfigure 401 | - promconfig 402 | -------------------------------------------------------------------------------- /tasks/freebsd_prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Choose tor alpha version package (FreeBSD) 3 | ansible.builtin.set_fact: 4 | tor_packages: tor-devel 5 | when: tor_alpha 6 | 7 | - name: Ensure sequential IP IDs are avoided (FreeBSD) 8 | become: true 9 | ansible.posix.sysctl: 10 | name: net.inet.ip.random_id 11 | value: 1 12 | reload: false 13 | sysctl_set: true 14 | 15 | - name: Gather current kern.ipc.somaxconn setting (FreeBSD) 16 | ansible.builtin.command: /sbin/sysctl -n kern.ipc.somaxconn 17 | become: false 18 | register: tor_currentsomaxconn 19 | changed_when: false 20 | 21 | - name: Ensure somaxconn setting is reasonable (FreeBSD) 22 | become: true 23 | ansible.posix.sysctl: 24 | name: kern.ipc.somaxconn 25 | value: "{{ tor_freebsd_somaxconn }}" 26 | reload: false 27 | sysctl_set: true 28 | when: tor_currentsomaxconn.stdout|int < tor_freebsd_somaxconn 29 | 30 | - name: Gather current kern.ipc.nmbclusters setting (FreeBSD) 31 | become: false 32 | ansible.builtin.command: /sbin/sysctl -n kern.ipc.nmbclusters 33 | register: tor_currentnmbc 34 | changed_when: false 35 | 36 | - name: Ensure nmbclusters setting is reasonable (FreeBSD) 37 | become: true 38 | ansible.posix.sysctl: 39 | name: kern.ipc.nmbclusters 40 | value: "{{ tor_freebsd_nmbclusters }}" 41 | reload: false 42 | sysctl_set: true 43 | when: tor_currentnmbc.stdout|int < tor_freebsd_nmbclusters 44 | -------------------------------------------------------------------------------- /tasks/freebsd_service.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tor_intances defines the number and configurations of instances 3 | # an instance is defined with the following fields: 4 | # inst_name:configfile:username:groupname:pidfile:data_dir 5 | # username/groupname is set to root to be able to bind to <1024 ports 6 | # but privileges are dropped afterwards (torrc User parameter) 7 | - name: Ensure Tor multi-instance configuration is present (FreeBSD) 8 | become: true 9 | ansible.builtin.lineinfile: 10 | dest: /etc/rc.conf 11 | line: tor_instances="${tor_instances} {{ item.0.ipv4 }}_{{ item.1.orport }}:{{ tor_ConfDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}.torrc:root:root:{{ tor_PidDir 12 | }}/{{ item.0.ipv4 }}_{{ item.1.orport }}/pid:{{ tor_DataDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}" 13 | create: true 14 | mode: "0644" 15 | with_nested: 16 | - "{{ tor_ips }}" 17 | - "{{ tor_ports }}" 18 | 19 | # this task is a workaround, because 'service tor status' 20 | # fails if this line is not present (which in turn fails the ansible service module) 21 | - name: Ensure tor is enabled in /etc/rc.conf (FreeBSD) 22 | become: true 23 | ansible.builtin.lineinfile: 24 | dest: /etc/rc.conf 25 | line: tor_enable="YES" 26 | create: true 27 | mode: "0644" 28 | 29 | # this affects all instances 30 | - name: Ensure Tor instances are running and enabled (FreeBSD) 31 | become: true 32 | ansible.builtin.service: 33 | name: tor 34 | enabled: true 35 | state: started 36 | -------------------------------------------------------------------------------- /tasks/ip-list.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Use a single private IPv4 address if we have no public IPv4 address 3 | ansible.builtin.include_vars: private_IPv4_only.yml 4 | when: tor_v4ips == [] 5 | 6 | - name: Setup IP list (1/2) 7 | ansible.builtin.set_fact: 8 | ips: 9 | ipv4: "{{ item.0 }}" 10 | ipv6: "{{ item.1 }}" 11 | with_together: 12 | - "{{ tor_v4ips }}" 13 | - "{{ tor_v6ips }}" 14 | register: tor_ipsinterm 15 | 16 | - name: Setup IP list (2/2) 17 | ansible.builtin.set_fact: 18 | tor_ips: "{{ tor_ipsinterm.results | map(attribute='ansible_facts.ips') | list }}" 19 | -------------------------------------------------------------------------------- /tasks/linux_service.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Linux/systemd section (uses service module) 3 | # =========================================== 4 | 5 | - name: Ensure Tor instances are enabled and started (Linux/systemd) 6 | become: true 7 | ansible.builtin.service: 8 | name: tor@{{ item.0.ipv4 }}_{{ item.1.orport }}.service 9 | enabled: true 10 | state: started 11 | with_nested: 12 | - "{{ tor_ips }}" 13 | - "{{ tor_ports }}" 14 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check for min. ansible version requirement 3 | ansible.builtin.assert: 4 | that: 5 | - ansible_version.full is version_compare('2.16.14', '>=') 6 | msg: Your ansible version is too old, please upgrade to v2.16.14 or newer. Exiting. 7 | run_once: true 8 | delegate_to: 127.0.0.1 9 | tags: 10 | - always 11 | - ansible-version-check 12 | 13 | - name: Check for local requirements 14 | ansible.builtin.shell: > 15 | command -V /bin/bash && command -V "{{ tor_binary }}" && command -V openssl && command -V sort 16 | && command -V uniq && command -V wc && command -V cut && command -V xargs && command -V sed 17 | run_once: true 18 | become: false 19 | delegate_to: 127.0.0.1 20 | tags: 21 | - always 22 | changed_when: false 23 | 24 | - name: Ensure tor_ContactInfo is set 25 | ansible.builtin.assert: 26 | that: 27 | - tor_ContactInfo is defined 28 | msg: tor_ContactInfo is not configured but mandatory, please set it. 29 | tags: 30 | - always 31 | 32 | - name: Ensure we do not create more than 8 instances per IP 33 | ansible.builtin.assert: 34 | that: 35 | - tor_ports | length > 0 36 | - tor_ports | length < 9 37 | msg: You can not run more than 8 instances per IP address, please update your tor_ports configuration. 38 | tags: 39 | - always 40 | 41 | - name: > 42 | Abort if we use a centralized/common DNS resolver (Google, Quad9, CloudFlare, OpenDNS, Level3). 43 | See https://torproject.org/relay-guide#DNSonExitRelays (Exits only) 44 | ansible.builtin.command: "{{ tor_grep_blacklisted_dnsresolvers }}" 45 | register: tor_dns_check 46 | failed_when: tor_dns_check.rc == 0 47 | changed_when: false 48 | when: tor_ExitRelay 49 | tags: 50 | - always 51 | 52 | - name: Set OS specific variables 53 | ansible.builtin.include_vars: os_{{ ansible_os_family }}.yml 54 | tags: 55 | - always 56 | 57 | - ansible.builtin.import_tasks: ip-list.yml 58 | tags: 59 | - always 60 | 61 | - name: Preparation for Debian-based systems 62 | ansible.builtin.include_tasks: apt_prepare.yml 63 | when: ansible_pkg_mgr == 'apt' 64 | tags: 65 | - debian 66 | - install 67 | 68 | - name: Preparation for RPM based systems 69 | ansible.builtin.include_tasks: rpm_prepare.yml 70 | when: ansible_os_family == 'RedHat' 71 | tags: 72 | - centos 73 | - fedora 74 | - install 75 | 76 | - name: Preparation for OpenBSD systems 77 | ansible.builtin.include_tasks: openbsd_prepare.yml 78 | when: ansible_system == 'OpenBSD' 79 | tags: 80 | - openbsd 81 | 82 | - name: Preparation for FreeBSD based systems 83 | ansible.builtin.include_tasks: freebsd_prepare.yml 84 | when: ansible_system == 'FreeBSD' 85 | tags: 86 | - freebsd 87 | 88 | # we specifically opt for present over latest to improve performance 89 | - name: Ensure tor is installed 90 | become: true 91 | ansible.builtin.package: 92 | name: "{{ item }}" 93 | state: "{{ tor_package_state }}" 94 | with_items: "{{ tor_packages }}" 95 | # apt starts a tor client instance by default after installing the package 96 | # we do not need that 97 | notify: 98 | - stop-and-mask default tor instance 99 | - disable default tor instance FreeBSD 100 | tags: 101 | - openbsd 102 | - freebsd 103 | - debian 104 | - centos 105 | - fedora 106 | - install 107 | 108 | - ansible.builtin.meta: flush_handlers 109 | - ansible.builtin.import_tasks: configure.yml 110 | tags: 111 | - debian 112 | - centos 113 | - fedora 114 | - openbsd 115 | - freebsd 116 | 117 | - name: Linux service configuration 118 | ansible.builtin.include_tasks: linux_service.yml 119 | when: ansible_system == 'Linux' 120 | tags: 121 | - debian 122 | - centos 123 | - fedora 124 | 125 | - name: OpenBSD service configuration 126 | ansible.builtin.include_tasks: openbsd_service.yml 127 | when: ansible_system == 'OpenBSD' 128 | tags: 129 | - openbsd 130 | 131 | - name: FreeBSD service configuration 132 | ansible.builtin.include_tasks: freebsd_service.yml 133 | when: ansible_system == 'FreeBSD' 134 | tags: 135 | - freebsd 136 | 137 | - name: Generate CIISS rsa-fingerprint.txt proof file 138 | become: false 139 | ansible.builtin.shell: > 140 | set -o pipefail && for key in {{ tor_offline_masterkey_dir }}/*/keys/secret_id_key; 141 | do openssl rsa -in $key -outform DER -RSAPublicKey_out 2> /dev/null| openssl sha1 -r; 142 | done|cut -d" " -f1|sort > {{ tor_ciiss_proof_folder }}/rsa-fingerprint.txt 143 | args: 144 | executable: /bin/bash 145 | delegate_to: 127.0.0.1 146 | run_once: true 147 | changed_when: false 148 | when: tor_gen_ciiss_proof_files 149 | tags: 150 | - always 151 | 152 | - name: Generate CIISS ed25519-master-pubkey.txt proof file 153 | become: false 154 | ansible.builtin.shell: > 155 | set -o pipefail && cut -d" " -f2 {{ tor_offline_masterkey_dir }}/*/fingerprint-ed25519| 156 | sort > {{ tor_ciiss_proof_folder }}/ed25519-master-pubkey.txt 157 | args: 158 | executable: /bin/bash 159 | delegate_to: 127.0.0.1 160 | run_once: true 161 | changed_when: false 162 | when: tor_gen_ciiss_proof_files 163 | tags: 164 | - always 165 | -------------------------------------------------------------------------------- /tasks/openbsd_prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Gather current system-wide file descriptor limits (OpenBSD) 3 | ansible.builtin.command: sysctl -n kern.maxfiles 4 | become: false 5 | register: tor_openbsd_maxfiles 6 | changed_when: false 7 | 8 | - name: Ensure system-wide runtime file descriptor limits are reasonable (OpenBSD) 9 | become: true 10 | ansible.builtin.command: sysctl kern.maxfiles=20000 11 | when: tor_openbsd_maxfiles.stdout|int < 20000 12 | 13 | - name: Ensure system-wide persistent file descriptor limits are reasonable (OpenBSD) 14 | become: true 15 | ansible.builtin.lineinfile: 16 | dest: /etc/sysctl.conf 17 | regexp: ^kern.maxfiles 18 | line: kern.maxfiles=20000 19 | create: true 20 | when: tor_openbsd_maxfiles.stdout|int < 20000 21 | 22 | # We rise openfiles limits for every tor instance separately. 23 | # An instance is identified by its rc.d file name. 24 | - name: Ensure Tor process file descriptor limits are reasonable (OpenBSD) 25 | become: true 26 | ansible.builtin.lineinfile: 27 | dest: /etc/login.conf 28 | line: "tor{{ item.0.ipv4| replace('.','_') }}_{{ item.1.orport }}::openfiles-max=13500::tc=daemon:" 29 | with_nested: 30 | - "{{ tor_ips }}" 31 | - "{{ tor_ports }}" 32 | -------------------------------------------------------------------------------- /tasks/openbsd_service.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # OpenBSD section (uses service module) 3 | # This is basically a copy from the Linux 4 | # section, but it requires different service 5 | # names and additional arguments. 6 | # ===================================== 7 | 8 | # OpenBSD does not support multi-instance rc.d 9 | # # so we link as many pseudo rc scripts as we need. 10 | # # OpenBSD does not like dots in rc filenames so 11 | # # we replace them with underscores. 12 | - name: Create links to the service files (OpenBSD) 13 | become: true 14 | ansible.builtin.file: 15 | src: /etc/rc.d/tor 16 | state: link 17 | path: /etc/rc.d/tor{{ item.0.ipv4| replace('.','_') }}_{{ item.1.orport }} 18 | with_nested: 19 | - "{{ tor_ips }}" 20 | - "{{ tor_ports }}" 21 | 22 | - name: Ensure Tor instances are enabled and started (OpenBSD) 23 | become: true 24 | ansible.builtin.service: 25 | name: tor{{ item.0.ipv4|replace('.','_') }}_{{ item.1.orport }} 26 | arguments: -f {{ tor_ConfDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}.torrc 27 | enabled: true 28 | state: started 29 | with_nested: 30 | - "{{ tor_ips }}" 31 | - "{{ tor_ports }}" 32 | -------------------------------------------------------------------------------- /tasks/rpm_prepare.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure EPEL repo is installed (yum) 3 | become: true 4 | ansible.builtin.yum: 5 | name: epel-release 6 | when: ansible_pkg_mgr == 'yum' 7 | 8 | - name: Ensure SELinux dependencies are installed 9 | become: true 10 | ansible.builtin.package: 11 | name: libselinux-python,libsemanage-python 12 | state: present 13 | notify: re-gather facts 14 | 15 | # re-gathering facts after installing ansible SELinux dependencies (libselinux-python) 16 | - ansible.builtin.meta: flush_handlers 17 | - name: Ensure SELinux boolean (tor_can_network_relay) is set appropriately 18 | become: true 19 | ansible.posix.seboolean: 20 | name: tor_can_network_relay 21 | state: true 22 | persistent: true 23 | when: ansible_selinux.status == 'enabled' 24 | 25 | - name: Ensure systemd drop-in folder is present (RPM) 26 | become: true 27 | ansible.builtin.file: 28 | path: /etc/systemd/system/tor@.service.d 29 | state: directory 30 | owner: root 31 | mode: "0755" 32 | 33 | # this is needed for a small service file modification (allow it to write to /var/lib/tor-instances) 34 | # without replacing the maintainer's file, for details see 35 | # http://www.freedesktop.org/software/systemd/man/systemd.unit.html#id-1.11.3 36 | - name: Ensure service file drop-in is present (RPM) 37 | become: true 38 | ansible.builtin.copy: 39 | src: local.conf 40 | dest: /etc/systemd/system/tor@.service.d/local.conf 41 | owner: root 42 | mode: "0644" 43 | notify: systemctl daemon-reload 44 | 45 | - ansible.builtin.meta: flush_handlers 46 | -------------------------------------------------------------------------------- /templates/nginx-reverse-proxy-for-metricsport: -------------------------------------------------------------------------------- 1 | location /{{ lookup('password', '~/.tor/prometheus/metrics_path/' + inventory_hostname + ' length=10 chars=ascii_lowercase') }} { 2 | auth_basic ""; 3 | auth_basic_user_file {{ tor_metricsport_htpasswd_file }}; 4 | {% set c = namespace(i=0) %} 5 | {% for ip in tor_ips %} 6 | {% for port in tor_ports %} 7 | location /{{ lookup('password', '~/.tor/prometheus/metrics_path/' + inventory_hostname + ' length=10 chars=ascii_lowercase') }}/{{c.i}} { 8 | proxy_pass http://127.0.0.1:{{tor_MetricsPort_offset + c.i}}/metrics; 9 | } 10 | {% set c.i = c.i + 1 %} 11 | {% endfor %} 12 | {% endfor %} 13 | } 14 | -------------------------------------------------------------------------------- /templates/prometheus-alert-rules: -------------------------------------------------------------------------------- 1 | 2 | groups: 3 | - name: ansible-relayor managed alert rules 4 | rules: 5 | {{ tor_prometheus_alert_rules | to_nice_yaml(indent=2,sort_keys=False) | indent(2,False) }} 6 | {% if tor_prometheus_custom_alert_rules is defined %} 7 | - name: user defined tor alert rules 8 | rules: 9 | {{ tor_prometheus_custom_alert_rules | to_nice_yaml(indent=2,sort_keys=False) | indent(2,False) }} 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /templates/prometheus-scrape-configs: -------------------------------------------------------------------------------- 1 | {% if tor_enableMetricsPort %} 2 | {% set c = namespace(i=0) %} 3 | {% for ip in tor_ips %} 4 | {% for port in tor_ports %} 5 | - job_name: 'tor-{{ansible_fqdn}}-{{c.i}}' 6 | metrics_path: '/{{ lookup('password', '~/.tor/prometheus/metrics_path/' + inventory_hostname + ' length=10 chars=ascii_lowercase') }}/{{c.i}}' 7 | scheme: 'https' 8 | basic_auth: 9 | username: "{{ tor_metricsport_user }}" 10 | password: "{{ lookup('password', tor_prometheus_scrape_password_folder + inventory_hostname ) }}" 11 | static_configs: 12 | - targets: 13 | - "{{ansible_fqdn}}:{{ tor_prometheus_scrape_port }}" 14 | labels: 15 | service: "torrelay" 16 | id: "{{ ip.ipv4 }}_{{ port.orport }}" 17 | {% if tor_nicknamefile is defined %} 18 | tor_nickname: "{{ lookup('csvfile', inventory_hostname~'-'~ip.ipv4~'_'~port.orport~' file='~tor_nicknamefile~' delimiter=,') |regex_replace('[^a-zA-Z0-9]', '') |truncate(19, True, '')}}" 19 | {% elif tor_nickname is defined %} 20 | tor_nickname: "{{ tor_nickname |regex_replace('[^a-zA-Z0-9]', '') |truncate(19, True, '') }}" 21 | {% endif %} 22 | {% if ((tor_ExitRelay == True and tor_ExitRelaySetting_file is not defined) or (tor_ExitRelay == True and tor_ExitRelaySetting_file is defined and (lookup('csvfile', inventory_hostname~'-'~item.0.ipv4~'_'~item.1.orport~' file='~tor_ExitRelaySetting_file~' delimiter=,') == "exit"))) %} 23 | relaytype: "exit" 24 | {% else %} 25 | relaytype: "nonexit" 26 | {% endif %} 27 | {% for label, value in tor_prom_labels.items() %} 28 | {{ label }}: "{{ value }}" 29 | {% endfor %} 30 | {% set c.i = c.i + 1 %} 31 | {% endfor %} 32 | {% endfor %} 33 | {% endif %} 34 | {% if tor_blackbox_exporter_host is defined %} 35 | - job_name: 'tor-blackbox_tcp_connect_{{ansible_fqdn}}' 36 | metrics_path: /probe 37 | scheme: '{{ tor_blackbox_exporter_scheme }}' 38 | {% if tor_blackbox_exporter_username is defined %} 39 | basic_auth: 40 | username: "{{ tor_blackbox_exporter_username }}" 41 | password: "{{ tor_blackbox_exporter_password }}" 42 | {% endif %} 43 | params: 44 | module: [tcp_connect] 45 | static_configs: 46 | - targets: 47 | {% for ip in tor_ips %} 48 | {% for port in tor_ports %} 49 | - {{ ip.ipv4 }}:{{ port.orport }} 50 | {% if port.dirport != 0 %} 51 | - {{ ip.ipv4 }}:{{ port.dirport }} 52 | {% endif %} 53 | {% if ip.ipv6 != '' and tor_IPv6 %} 54 | - "[{{ ip.ipv6 }}]:{{ port.orport }}" 55 | {% if port.dirport != 0 %} 56 | - "[{{ ip.ipv6 }}]:{{ port.dirport }}" 57 | {% endif %} 58 | {% endif %} 59 | {% endfor %} 60 | {% endfor %} 61 | relabel_configs: 62 | - source_labels: [__address__] 63 | target_label: __param_target 64 | - source_labels: [__param_target] 65 | target_label: instance 66 | - target_label: __address__ 67 | replacement: {{tor_blackbox_exporter_host}} 68 | {% endif %} 69 | -------------------------------------------------------------------------------- /templates/tor-exit-notice.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | This is a Tor Exit Router 8 | 9 | 24 | 25 | 26 | 27 | 28 |

This is a 29 | Tor Exit Router

30 | 31 |

32 | Most likely you are accessing this website because you had some issue with 33 | the traffic coming from this IP. This router is part of the Tor Anonymity Network, which is 35 | dedicated to providing 36 | privacy to people who need it most: average computer users. This 37 | router IP should be generating no other traffic, unless it has been 38 | compromised.

39 | 40 | 41 | 43 | 44 |

45 | 46 | How Tor works 47 |

48 | 49 |

50 | Tor sees use by many 51 | important segments of the population, including whistle blowers, 52 | journalists, Chinese dissidents skirting the Great Firewall and oppressive 53 | censorship, abuse victims, stalker targets, the US military, and law 54 | enforcement, just to name a few. While Tor is not designed for malicious 55 | computer users, it is true that they can use the network for malicious ends. 56 | In reality however, the actual amount of abuse is quite low. This 58 | is largely because criminals and hackers have significantly better access to 59 | privacy and anonymity than do the regular users whom they prey upon. Criminals 60 | can and do build, 62 | sell, and trade far larger and more 64 | powerful networks than Tor on a daily basis. Thus, in the mind of this 65 | operator, the social need for easily accessible censorship-resistant private, 66 | anonymous communication trumps the risk of unskilled bad actors, who are 67 | almost always more easily uncovered by traditional police work than by 68 | extensive monitoring and surveillance anyway.

69 | 70 |

71 | In terms of applicable law, the best way to understand Tor is to consider it a 72 | network of routers operating as common carriers, much like the Internet 73 | backbone. However, unlike the Internet backbone routers, Tor routers 74 | explicitly do not contain identifiable routing information about the source of 75 | a packet, and no single Tor node can determine both the origin and destination 76 | of a given transmission.

77 | 78 |

79 | As such, there is little the operator of this router can do to help you track 80 | the connection further. This router maintains no logs of any of the Tor 81 | traffic, so there is little that can be done to trace either legitimate or 82 | illegitimate traffic (or to filter one from the other). Attempts to 83 | seize this router will accomplish nothing.

84 | 85 | 86 | 87 |

88 | Furthermore, this machine also serves as a carrier of email, which means that 89 | its contents are further protected under the ECPA. 18 91 | USC 2707 explicitly allows for civil remedies ($1000/account 92 | plus legal fees) 93 | in the event of a seizure executed without good faith or probable cause (it 94 | should be clear at this point that traffic originating from this IP address 95 | should not constitute probable cause to seize the 96 | machine). Similar considerations exist for 1st amendment content on this 97 | machine.

98 | 99 | 101 | 102 |

103 | If you are a representative of a company who feels that this router is being 104 | used to violate the DMCA, please be aware that this machine does not host or 105 | contain any illegal content. Also be aware that network infrastructure 106 | maintainers are not liable for the type of content that passes over their 107 | equipment, in accordance with DMCA 109 | "safe harbor" provisions. In other words, you will have just as much luck 110 | sending a takedown notice to the Internet backbone providers. Please consult 111 | EFF's prepared 112 | response for more information on this matter.

113 | 114 |

For more information, please consult the following documentation:

115 | 116 |
    117 |
  1. Tor Overview
  2. 118 |
  3. Tor Abuse FAQ
  4. 119 |
  5. Tor Legal FAQ
  6. 120 |
121 | 122 | {% if tor_AbuseEmailAddress is defined %} 123 |

124 | That being said, if you still have a complaint about the router, you may 125 | email the maintainer. If 126 | complaints are related to a particular service that is being abused, I will 127 | consider removing that service from my exit policy, which would prevent my 128 | router from allowing that traffic to exit through it. I can only do this on an 129 | IP+destination port basis, however. Common P2P ports are 130 | already blocked.

131 | {% endif %} 132 | 133 |

134 | You also have the option of blocking this IP address and others on 135 | the Tor network if you so desire. The Tor project provides a web service 137 | to fetch a list of all IP addresses of Tor exit nodes that allow exiting to a 138 | specified IP:port combination. 139 | Please be considerate when using this option. It would be unfortunate to deny all Tor users access 140 | to your site indefinitely simply because of a few bad apples.

141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /templates/torrc: -------------------------------------------------------------------------------- 1 | # ansible-relayor generated torrc configuration file 2 | # Note: manual changes will be OVERWRITTEN on the next ansible-playbook run 3 | 4 | OfflineMasterKey 1 5 | RunAsDaemon {{ tor_RunAsDaemon }} 6 | Log {{ tor_LogLevel }} syslog 7 | OutboundBindAddress {{ item.0.ipv4 }} 8 | SocksPort 0 9 | User _tor-{{ item.0.ipv4 }}_{{ item.1.orport }} 10 | DataDirectory {{ tor_DataDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }} 11 | ORPort {{ item.0.ipv4 }}:{{ item.1.orport }} 12 | {% if item.0.ipv6 != "" and item.0.ipv6 != "False" and tor_IPv6 == True %} 13 | ORPort [{{item.0.ipv6}}]:{{item.1.orport}} 14 | OutboundBindAddress [{{item.0.ipv6}}] 15 | {% endif %} 16 | 17 | {% if item.1.dirport != 0 %} 18 | DirPort {{ item.0.ipv4 }}:{{ item.1.dirport }} 19 | {% endif %} 20 | {% if tor_Address is defined %} 21 | Address {{ tor_Address }} 22 | {% elif tor_maxPublicIPs > 1 and tor_available_public_ipv4s|length > 1 %} 23 | Address {{ item.0.ipv4 }} 24 | {% endif %} 25 | 26 | SyslogIdentityTag {{ item.0.ipv4 }}_{{ item.1.orport }} 27 | {% if ansible_os_family == 'FreeBSD' %} 28 | PidFile {{ tor_PidDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}/pid 29 | {% endif %} 30 | 31 | {% if tor_enableMetricsPort %} 32 | MetricsPort 127.0.0.1:{{tor_MetricsPort_offset + loop_idx}} 33 | MetricsPortPolicy accept 127.0.0.1 34 | {% if tor_config.OverloadStatistics is not defined %} 35 | OverloadStatistics 0 36 | {% endif %} 37 | {% endif %} 38 | 39 | {% if tor_enableControlSocket %} 40 | ControlSocket {{ tor_PidDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}/control GroupWritable RelaxDirModeCheck 41 | {% elif tor_enableControlSocket == False and ansible_pkg_mgr == 'apt' %} 42 | ControlSocket 0 43 | CookieAuthentication 0 44 | {% endif %} 45 | 46 | {% if tor_nicknamefile is defined %} 47 | Nickname {{ lookup('csvfile', inventory_hostname~'-'~item.0.ipv4~'_'~item.1.orport~' file='~tor_nicknamefile~' delimiter=,') |regex_replace('[^a-zA-Z0-9]', '') |truncate(19, True, '')}} 48 | {% elif tor_nickname is defined %} 49 | Nickname {{ tor_nickname |regex_replace('[^a-zA-Z0-9]', '') |truncate(19, True, '') }} 50 | {% endif %} 51 | {% if tor_ContactInfo is defined %} 52 | ContactInfo {{ tor_ContactInfo }} 53 | {% endif %} 54 | 55 | {% if ((tor_ExitRelay == True and tor_ExitRelaySetting_file is not defined) or (tor_ExitRelay == True and tor_ExitRelaySetting_file is defined and (lookup('csvfile', inventory_hostname~'-'~item.0.ipv4~'_'~item.1.orport~' file='~tor_ExitRelaySetting_file~' delimiter=,') == "exit"))) %} 56 | # we are an exit relay! 57 | ExitRelay 1 58 | {% if item.0.ipv6 != "" and item.0.ipv6 != "False" and tor_IPv6 == True and tor_IPv6Exit == True %} 59 | IPv6Exit 1 60 | {% if item.1.dirport != 0 %} 61 | DirPort [{{ item.0.ipv6 }}]:{{ item.1.dirport }} NoAdvertise 62 | {% endif %} 63 | {% endif %} 64 | {% if tor_ExitNoticePage == True and tor_config.DirPortFrontPage is not defined %} 65 | DirPortFrontPage {{ tor_ConfDir }}/tor-exit-notice.html 66 | {% endif %} 67 | 68 | {% if tor_ExitPolicy_file is defined and (lookup('csvfile', inventory_hostname~'-'~item.0.ipv4~'_'~item.1.orport~' file='~tor_ExitPolicy_file~' delimiter=;') != []) %} 69 | ExitPolicy {{ lookup('csvfile', inventory_hostname~'-'~item.0.ipv4~'_'~item.1.orport~' file='~tor_ExitPolicy_file~' delimiter=;') }} 70 | {% else %} 71 | {% for entry in tor_ExitPolicy %} 72 | ExitPolicy {{entry}} 73 | {% endfor %} 74 | {% endif %} 75 | {% else %} 76 | ExitRelay 0 77 | ExitPolicy reject *:* 78 | {% endif %} 79 | 80 | {% if tor_RelayBandwidthRate_file is defined and (lookup('csvfile', inventory_hostname~'-'~item.0.ipv4~'_'~item.1.orport~' file='~tor_RelayBandwidthRate_file~' delimiter=,') != "") %} 81 | RelayBandwidthRate {{ lookup('csvfile', inventory_hostname~'-'~item.0.ipv4~'_'~item.1.orport~' file='~tor_RelayBandwidthRate_file~' delimiter=,') }} 82 | {% elif tor_RelayBandwidthRate is defined %} 83 | RelayBandwidthRate {{ tor_RelayBandwidthRate }} 84 | {% endif %} 85 | {% if tor_RelayBandwidthBurst_file is defined and (lookup('csvfile', inventory_hostname~'-'~item.0.ipv4~'_'~item.1.orport~' file='~tor_RelayBandwidthBurst_file~' delimiter=,') != "") %} 86 | RelayBandwidthBurst {{ lookup('csvfile', inventory_hostname~'-'~item.0.ipv4~'_'~item.1.orport~' file='~tor_RelayBandwidthBurst_file~' delimiter=,') }} 87 | {% elif tor_RelayBandwidthBurst is defined %} 88 | RelayBandwidthBurst {{ tor_RelayBandwidthBurst }} 89 | {% endif %} 90 | 91 | {% if ansible_os_family == 'Debian' and tor_config.Sandbox is not defined %} 92 | Sandbox 1 93 | {% endif %} 94 | {% if tor_config.NoExec is not defined %} 95 | NoExec 1 96 | {% endif %} 97 | 98 | {% for config_item, value in tor_config.items() | sort() %} 99 | {% if config_item not in ["ContactInfo","OfflineMasterKey","RunAsDaemon","Log","SocksPort","OutboundBindAddress","User","DataDirectory","ORPort","OutboundBindAddress","DirPort","SyslogIdentityTag","PidFile","MetricsPort","MetricsPortPolicy","ControlSocket","CookieAuthentication","Nickname","ExitRelay","IPv6Exit","ExitPolicy","RelayBandwidthRate","RelayBandwidthBurst","SigningKeyLifetime"] %} 100 | {{ config_item }} {{ value }} 101 | {% endif %} 102 | {% endfor %} 103 | 104 | MyFamily {{ tor_family.stdout }} 105 | {% if tor_happy_family %} 106 | FamilyId {{ lookup('ansible.builtin.file', tor_local_happy_family_folder~'/'~tor_happy_family_basename~'.public_family_id') }} 107 | {% endif %} 108 | # end of torrc 109 | -------------------------------------------------------------------------------- /test/integration/default/2publicIPs-exit-node.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | - tor_maxPublicIPs: 2 7 | - tor_ExitRelay: true 8 | roles: 9 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 10 | -------------------------------------------------------------------------------- /test/integration/default/2publicIPs-guard-metricsport.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | - tor_maxPublicIPs: 2 7 | - tor_enableMetricsPort: true 8 | - tor_metricsport_nginx_config_file: /tmp/web-reverse-proxy-{{ inventory_hostname }} 9 | - tor_blackbox_exporter_host: 127.0.0.1:9115 10 | - tor_prometheus_host: 127.0.0.1 11 | roles: 12 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 13 | -------------------------------------------------------------------------------- /test/integration/default/2publicIPs-guard-node.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | - tor_maxPublicIPs: 2 7 | roles: 8 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 9 | -------------------------------------------------------------------------------- /test/integration/default/2publicIPs-guard4-metricsport.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | tor_maxPublicIPs: 2 7 | tor_enableMetricsPort: true 8 | tor_metricsport_nginx_config_file: /tmp/web-reverse-proxy-{{ inventory_hostname }} 9 | tor_blackbox_exporter_host: 127.0.0.1:9115 10 | tor_prometheus_host: 127.0.0.1 11 | tor_ports: 12 | - orport: 9000 13 | dirport: 9001 14 | - orport: 9100 15 | dirport: 9101 16 | - orport: 9200 17 | dirport: 9201 18 | - orport: 9300 19 | dirport: 9301 20 | roles: 21 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 22 | -------------------------------------------------------------------------------- /test/integration/default/exit-node.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | tor_ExitRelay: true 7 | roles: 8 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 9 | -------------------------------------------------------------------------------- /test/integration/default/exit-node4.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | tor_ExitRelay: true 7 | tor_ports: 8 | - orport: 9000 9 | dirport: 9001 10 | - orport: 9100 11 | dirport: 9101 12 | - orport: 9200 13 | dirport: 9201 14 | - orport: 9300 15 | dirport: 9301 16 | roles: 17 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 18 | -------------------------------------------------------------------------------- /test/integration/default/exit-per-instance-exitpolicy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | - tor_ExitRelay: true 7 | - tor_ExitPolicy_file: vars/per-instance-exitpolicy 8 | roles: 9 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 10 | -------------------------------------------------------------------------------- /test/integration/default/guard-alpha-node.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | - tor_alpha: true 7 | roles: 8 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 9 | -------------------------------------------------------------------------------- /test/integration/default/guard-blackbox-exporter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | - tor_blackbox_exporter_username: testuser 7 | - tor_blackbox_exporter_host: 127.0.0.1:9115 8 | - tor_prometheus_host: 127.0.0.1 9 | roles: 10 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 11 | -------------------------------------------------------------------------------- /test/integration/default/guard-metricsport-blackbox-alertrules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | - tor_enableMetricsPort: true 7 | - tor_metricsport_nginx_config_file: /tmp/web-reverse-proxy-{{ inventory_hostname }} 8 | - tor_gen_prometheus_alert_rules: true 9 | - tor_blackbox_exporter_host: 127.0.0.1:9115 10 | - tor_prometheus_host: 127.0.0.1 11 | - tor_prom_labels: 12 | label1: "value1" 13 | label2: "value2" 14 | roles: 15 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 16 | -------------------------------------------------------------------------------- /test/integration/default/guard-metricsport-blackbox-customalertrules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | - tor_enableMetricsPort: true 7 | - tor_metricsport_nginx_config_file: /tmp/web-reverse-proxy-{{ inventory_hostname }} 8 | - tor_gen_prometheus_alert_rules: true 9 | - tor_blackbox_exporter_host: 127.0.0.1:9115 10 | - tor_prometheus_host: 127.0.0.1 11 | - tor_prom_labels: 12 | label1: "value1" 13 | label2: "value2" 14 | - tor_prometheus_custom_alert_rules: 15 | - alert: TorOnionskinsDropped 16 | expr: 'rate(tor_relay_load_onionskins_total{action="dropped"}[15m]) > 0' 17 | for: 15m 18 | labels: 19 | severity: warning 20 | roles: 21 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 22 | -------------------------------------------------------------------------------- /test/integration/default/guard-metricsport-blackbox.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | - tor_enableMetricsPort: true 7 | - tor_metricsport_nginx_config_file: /tmp/web-reverse-proxy-{{ inventory_hostname }} 8 | - tor_blackbox_exporter_host: 127.0.0.1:9115 9 | - tor_prometheus_host: 127.0.0.1 10 | - tor_prom_labels: 11 | label1: "value1" 12 | label2: "value2" 13 | roles: 14 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 15 | -------------------------------------------------------------------------------- /test/integration/default/guard-nightly-happy-families.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | - tor_nightly_builds: true 7 | - tor_happy_family: true 8 | roles: 9 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 10 | -------------------------------------------------------------------------------- /test/integration/default/guard-nightly-metricsport-blackbox.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | - tor_nightly_builds: true 7 | - tor_enableMetricsPort: true 8 | - tor_metricsport_nginx_config_file: /tmp/web-reverse-proxy-{{ inventory_hostname }} 9 | - tor_blackbox_exporter_host: 127.0.0.1:9115 10 | - tor_prometheus_host: 127.0.0.1 11 | roles: 12 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 13 | -------------------------------------------------------------------------------- /test/integration/default/guard-nightly-node.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | - tor_nightly_builds: true 7 | roles: 8 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 9 | -------------------------------------------------------------------------------- /test/integration/default/guard-node.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | roles: 6 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 7 | -------------------------------------------------------------------------------- /test/integration/default/guard-node4.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | tor_ports: 7 | - orport: 9000 8 | dirport: 9001 9 | - orport: 9100 10 | dirport: 9101 11 | - orport: 9200 12 | dirport: 9201 13 | - orport: 9300 14 | dirport: 9301 15 | roles: 16 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 17 | -------------------------------------------------------------------------------- /test/integration/default/mixed-node.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars_files: 4 | - vars/dry-run-vars.yml 5 | vars: 6 | tor_ExitRelay: true 7 | tor_ExitRelaySetting_file: vars/exit-conf 8 | roles: 9 | - "{{ playbook_dir | regex_replace('test/integration/default$') }}" 10 | -------------------------------------------------------------------------------- /test/integration/default/vars/dry-run-vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | tor_offline_masterkey_dir: ~/.tor/test/offlinemasterkeys/{{ inventory_hostname }} 3 | tor_apt_update_cache: false 4 | tor_ContactInfo: "ansible-relayor test-kitchen (you should never see this on a public relay) https://github.com/nusenu/ansible-relayor" 5 | tor_config: 6 | DisableNetwork: 1 7 | PublishServerDescriptor: 0 8 | ShutdownWaitLength: 0 9 | -------------------------------------------------------------------------------- /test/integration/default/vars/exit-conf: -------------------------------------------------------------------------------- 1 | mixed-freebsd-10.0.2.15_9000,exit 2 | mixed-freebsd-10.0.2.15_9100, 3 | mixed-debian-stable-10.0.2.15_9000,exit 4 | mixed-debian-stable-10.0.2.15_9100, 5 | mixed-ubuntu-lts-10.0.2.15_9000,exit 6 | mixed-ubuntu-lts-10.0.2.15_9100, 7 | -------------------------------------------------------------------------------- /test/integration/default/vars/per-instance-exitpolicy: -------------------------------------------------------------------------------- 1 | exit-per-instance-exitpolicy-debian-stable-10.0.2.15_9100;reject *4:25,reject *6:12 2 | exit-per-instance-exitpolicy-ubuntu-lts-10.0.2.15_9100;reject *4:25,reject *6:123 3 | exit-per-instance-exitpolicy-freebsd-10.0.2.15_9100;reject *4:25,reject *6:123 4 | -------------------------------------------------------------------------------- /test/vagrant_provisions/freebsd_vagrant_provision.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.provision "shell", inline: <<-SHELL 3 | echo 'FreeBSD: { url: "https://pkg.FreeBSD.org/${ABI}/quarterly" , signature_type: "fingerprints", fingerprints: "/usr/share/keys/pkg", enabled: yes }' > /etc/pkg/FreeBSD.conf 4 | sudo pkg install -y python 5 | SHELL 6 | end 7 | -------------------------------------------------------------------------------- /test/vagrant_provisions/openbsd_vagrant_provision.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.provision "shell", inline: <<-SHELL 3 | sudo pkg_add python3 4 | sudo ln -s /usr/local/bin/python3 /usr/bin/python 5 | SHELL 6 | end 7 | -------------------------------------------------------------------------------- /vars/os_Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | tor_user: debian-tor 4 | tor_DataDir: /var/lib/tor-instances 5 | tor_ConfDir: /etc/tor/instances 6 | tor_RunAsDaemon: 0 7 | tor_packages: 8 | - deb.torproject.org-keyring 9 | - tor 10 | - tor-geoipdb 11 | tor_htpasswd_dependency: python3-passlib 12 | -------------------------------------------------------------------------------- /vars/os_FreeBSD.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | tor_DataDir: /var/db/tor-instances 4 | tor_ConfDir: /usr/local/etc/tor/enabled 5 | tor_htpasswd_dependency: py39-passlib 6 | tor_metricsport_htpasswd_file_owner: www 7 | -------------------------------------------------------------------------------- /vars/os_OpenBSD.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | tor_DataDir: /var/tor-instances 4 | tor_ConfDir: /etc/tor/enabled 5 | tor_htpasswd_dependency: py3-passlib 6 | tor_metricsport_htpasswd_file_owner: www 7 | -------------------------------------------------------------------------------- /vars/os_RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | tor_user: toranon 4 | tor_ConfDir: /etc/tor 5 | tor_RunAsDaemon: 0 6 | tor_DataDir: /var/lib/tor-instances 7 | -------------------------------------------------------------------------------- /vars/private_IPv4_only.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | tor_v4ips: "{{ ansible_all_ipv4_addresses[0:tor_maxPrivateIPs] | ansible.utils.ipv4('address') }}" 4 | tor_ipv4_count: "{{ tor_v4ips | length | int }}" 5 | --------------------------------------------------------------------------------