Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add v3 onion support to tor-hidden-services ansible role #4652

Merged
merged 46 commits into from
Aug 22, 2019

Conversation

kushaldas
Copy link
Contributor

@kushaldas kushaldas commented Jul 30, 2019

Status

Ready for review

Description of Changes

Fixes #4628
Fixes #4667

Started adding related Ansible changes to have v3 onion services (including the authenticated ones).

Testing

v3 only

  • Fresh install
  • Rerun ./securedrop-admin install on a already installed system.

For both the options above, make the following two changes in install_files/ansible-base/group_vars/all/site-specific

v2_onion_services: false
v3_onion_services: true

For this case:

v2/v3 alongside

v2_onion_services: true
v3_onion_services: true

For this:

v2 only

There is also the third point to test which is not adding v2_onion_services or v3_onion_services to site-specific and instead the default values (v2 on, v3 off).

This should keep the current v2 systems as it is, and no v3 details should be generated in the servers.

Deployment

Any special considerations for deployment? Consider both:

  1. Upgrading existing production instances.
  2. New installs.

Checklist

If you made changes to the server application code:

  • Linting (make lint) and tests (make -C securedrop test) pass in the development container

If you made changes to securedrop-admin:

  • Linting and tests (make -C admin test) pass in the admin development container

If you made changes to the system configuration:

If you made non-trivial code changes:

  • I have written a test plan and validated it for this PR

If you made changes to documentation:

  • Doc linting (make docs-lint) passed locally

@eloquence eloquence changed the title [DO NOT MERGE] WIP [DO NOT MERGE] WIP: Add v3 onion support to tor-hidden-services ansible role Jul 30, 2019
@eloquence eloquence changed the title [DO NOT MERGE] WIP: Add v3 onion support to tor-hidden-services ansible role [WIP] Add v3 onion support to tor-hidden-services ansible role Jul 30, 2019
@eloquence
Copy link
Member

(Since it's a draft PR and cannot be merged anyway, I've removed "DO NOT MERGE" from the PR title)

@kushaldas
Copy link
Contributor Author

While working on tailsconfig I figured one issue with the current logic.

In v3, we have to create at least 2 different set of public/private keys, one for the journalist UI, and the other for ssh service if required.

@redshiftzero Is it okay if I use same set of keys for both app and mon servers, or should we create 2 different sets for both the servers?

@redshiftzero
Copy link
Contributor

@kushaldas I think we should use separate keys where possible, so separate keys for the SSH services on app and mon

@kushaldas kushaldas force-pushed the onion_onion_and_more_onions branch from 0e920a0 to 07fed0c Compare August 7, 2019 19:35
@kushaldas kushaldas marked this pull request as ready for review August 7, 2019 20:00
@kushaldas kushaldas changed the title [WIP] Add v3 onion support to tor-hidden-services ansible role Add v3 onion support to tor-hidden-services ansible role Aug 7, 2019
Copy link
Contributor

@conorsch conorsch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you provide a test plan, @kushaldas? I haven't yet tested in VMs, only provided a visual review as a first pass. Take a look at the duplication and see if you can reduce it, based on the previously implemented v2 logic. Happy to lend a hand or collaborate directly on this branch if you'd prefer.

admin/requirements.in Outdated Show resolved Hide resolved
public/private key pair.
"""
secret_key_path = os.path.join(args.ansible_path,
"tor_v3_keys.json")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as we currently have two discrete files on the Admin Workstation for the v2 auth info, it'd be great to have the same for the v3 info. Doing so would simplify the lookup logic required in the dynamic-inventory script (not yet implemented in this PR), as well. Perhaps the function should receive hostname=None, and select the files that way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel that one file for admin workstation with all 3 different key pairs (journalist, ssh app, and ssh mon) and a file for journalist workstations (only with the journalist keypair) is simpler. Also, this is only for the key information, if we want, we can also remove the ths files all together and just the .onion address from the JSON files iteself (we have to add them there).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having taken a closer look at the keygen logic, we're still going to need separate files for the dynamic-inventory support (OK to defer to #4629). With the v2 logic, if-and-only-if the -ths files are present on the Admin Workstation, then Onion URLs are used for the SSH connection. Since those -ths files are created on the Admin Workstation at the end of the install process, the logic is sound. For the v3 keygen logic, however, we need a way to denote whether the corresponding Onion URLs should be used for the SSH connection.

I suggest moving the keygen logic into Ansible via a script, so that the JSON file does not exist when Ansible builds the SSH connection, meaning local IPv4 addresses (or v2 Onion URLs) will be used. At the end of the run, we can sprinkle in appropriate -v3-ths files back onto the Admin Workstation.

We cannot relegate the v3 keygen logic solely to the securedrop-admin init, because that's only relevant for Tails environments, which means developer staging VMs will not have v3 connectivity.

@@ -20,6 +20,17 @@ tor_instances:
- service: journalist
filename: app-journalist-aths

tor_instances_v3:
- "{{ {'service': 'sshv3', 'filename': 'app-sshv3-aths'} if enable_ssh_over_tor else [] }}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The else [] causes an empty list here—is that intentional? The other elements of tor_instances_v3 are dicts, not lists. Haven't tested functionally yet to observe an error here, but it seems inconsistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

admin/securedrop_admin/__init__.py Outdated Show resolved Hide resolved
- "v3_onion_services|default(False)"
- inventory_hostname == 'app'
tags:
- tor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key-management tasks here are largely duplicated, with conditional checks for determining whether app or mon is the current host. Since the tasks will by default run against both hosts, and the facts will be set uniquely per-host, we don't need the duplication.

@@ -0,0 +1 @@
descriptor:x25519:{{ tor_v3_app["app_ssh_key"] }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, these templates don't need to be unique per-host—see for reference the existing v2 logic:

- name: Write Tor hidden service hostname files to Admin Workstation.
local_action:
module: template
dest: "{{ role_path }}/../../{{ item.item.filename }}"
src: ths_config.j2
# Local action, so we don't want elevated privileges
become: no
with_items: "{{ tor_hidden_service_hostname_lookup.results }}"
tags:
- tor
- admin

@conorsch
Copy link
Contributor

conorsch commented Aug 8, 2019

Appended two commits, aiming to simplify the keypair-management logic within the Ansible roles. Please take a look, @kushaldas, and see if you agree that the terser version is suitable.

While working on this, I noticed that the branch here is outdated; it doesn't include the related Tor logic introduced in #4648:

$ git checkout develop
Switched to branch 'develop'
Your branch is up to date with 'origin/develop'.
$ grep -rF update_onion_version_config
admin/securedrop_admin/__init__.py:        self.update_onion_version_config()
admin/securedrop_admin/__init__.py:    def update_onion_version_config(self):
admin/tests/test_securedrop-admin.py:        site_config.update_onion_version_config()
admin/tests/test_securedrop-admin.py:        site_config.update_onion_version_config()
admin/tests/test_securedrop-admin.py:        site_config.update_onion_version_config()
[user@sd-dev:~/securedrop] [sd] develop+* ±
$ git checkout onion_onion_and_more_onions
Switched to branch 'onion_onion_and_more_onions'
Your branch is up to date with 'origin/onion_onion_and_more_onions'.
$ grep -rF update_onion_version_config
$ # nothing found

If you agree the added commits are work keeping, please rebase on top of latest develop, so we're testing with the most recently merged work.

The most important outstanding changes in my view are:

  • justify version bump to "cryptography" dependency
  • move v3 keygen logic out of securedrop-admin, otherwise v3 won't work for devs

Based on the discussion in #4628 (comment), it looks like you've already tried to the script-based approach. Let's try to get that working again. See example below for a slightly modified version of your script. If we can adjust the calls to get it working with the currently used version of cryptography, great, otherwise let's make sure to bump the requirement in the same contexts Ansible is used (i.e. also dev envs).

Example script generate-tor-v3-keypairs
diff --git a/install_files/ansible-base/roles/tor-hidden-services/files/generate-tor-v3-keypairs b/install_files/ansible-base/roles/tor-hidden-services/files/generate-tor-v3-keypairs
new file mode 100644
index 000000000..666eed601
--- /dev/null
+++ b/install_files/ansible-base/roles/tor-hidden-services/files/generate-tor-v3-keypairs
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import x25519
+
+
+def generate_x25519_keypair():
+    """This function generate new keys and returns them as tuple.
+
+    :returns: Tuple(public_key, private_key)
+    """
+
+    private_key = x25519.X25519PrivateKey.generate()
+    private_bytes = private_key.private_bytes(
+        encoding=serialization.Encoding.Raw,
+        format=serialization.PrivateFormat.Raw,
+        encryption_algorithm=serialization.NoEncryption())
+    public_key = private_key.public_key()
+    public_bytes = public_key.public_bytes(
+        encoding=serialization.Encoding.Raw,
+        format=serialization.PublicFormat.Raw)
+
+    public = base64.b32encode(public_bytes)[:-4].decode("utf-8")
+    private = base64.b32encode(private_bytes)[:-4].decode("utf-8")
+    return public, private
+
+
+def generate_new_tor_v3_keypairs():
+    """
+    This method will either read the old keys or generate a new
+    public/private key pair.
+    """
+    # No old keys, generate and store them first
+    app_journalist_public_key, app_journalist_private_key = generate_x25519_keypair()
+    # For app ssh service
+    app_ssh_public_key, app_ssh_private_key = generate_x25519_keypair()
+    # For mon ssh service
+    mon_ssh__public_key, mon_ssh_private_key = generate_x25519_keypair()
+    tor_v3_service_info = {
+            "app_journalist_public_key": app_journalist_public_key,
+            "app_journalist_private_key": app_journalist_private_key,
+            "app_ssh_public_key": app_ssh_public_key,
+            "app_ssh_private_key": app_ssh_private_key,
+            "mon_ssh_public_key": mon_public_ssh_key,
+            "mon_ssh_private_key": mon_ssh_private_key,
+    }
+    # Send results to stdout
+    print(json.dumps(tor_v3_service_info))
+
+
+if __name__ == "__main__":
+    generate_new_tor_v3_keypairs()

Once we have passable keypair-and-service generation in this PR, we can discuss specifics on the Tails-specific integration in #4629. Let me know your thoughts on the above, @kushaldas.

@kushaldas
Copy link
Contributor Author

@conorsch I am okay with the approach you mentioned here. What should be the next step?

@conorsch
Copy link
Contributor

conorsch commented Aug 9, 2019

Appended two commits

Oops, my mistake: I hadn't actually pushed! The two new commits are now present on the branch, see fea52e0 & 92e0949.

What should be the next step?

@kushaldas Recapping my comment above, the next steps should be:

  • review the two commits I added
  • rebase this branch on develop, to include changes from Fixes #4627 Adds v2 and v3 onion service variables #4648 and others
  • explain why a version bump to the "cryptography" dependency is necessary; it's hard to understand in the correct branch why this change was made
  • move v3 keygen logic out of securedrop-admin, into a script runnable by Ansible (see example above), otherwise v3 URLs won't work for devs

Please handle those tasks, and ping me when you feel the branch is ready for another look!

@kushaldas kushaldas force-pushed the onion_onion_and_more_onions branch from 6143233 to 8f64cf4 Compare August 12, 2019 15:14
@kushaldas
Copy link
Contributor Author

move v3 keygen logic out of securedrop-admin, into a script runnable by Ansible (see example above), otherwise v3 URLs won't work for devs

@conorsch Last time when I tried, I could not do that the above in a nice Ansible way. I will need your help in that too.

@msheiny
Copy link
Contributor

msheiny commented Aug 12, 2019

move v3 keygen logic out of securedrop-admin, into a script runnable by Ansible (see example above), otherwise v3 URLs won't work for devs

What about tweaking the dev initialization entry-points to call a subset of securedrop admin scripts first? I don't think ansible is the right answer to every scriptable problem. Ansible is really good at running a bunch of tasks remotely. This task is run locally and would benefit from more in-depth unit testing. Recall that in the past at one point, the admin scripting was in ansible but that was deemed unmaintainable and was moved to python.

@conorsch
Copy link
Contributor

The currently implemented securedrop-admin strategy cannot configure v3 URLs when testing against VMs, so the invocation must change. It needn't be pure Ansible—simply calling a script makes sense—but at no point other than Tails-based QA testing are devs regularly invoking the securedrop-admin logic. The current script logic uses python cryptography, which means required libraries will be present within the virtualenv that executes both securedrop-admin and Ansible, on the Admin Workstation.

If we moved to a leaner method like bash, we might wind up with more portable keygen logic, but that doesn't seem worthwhile—we'd have to be careful about system deps like openssl versions. As far as what calls the keygen logic script, the most important attributes are 1) it's automatic, so devs don't forget; 2) the same process is used on dev/admin workstations, as far as possible.

@kushaldas
Copy link
Contributor Author

The currently implemented securedrop-admin strategy cannot configure v3 URLs when testing against VMs, so the invocation must change.

Current means? The branch I pushed last week, did it amazingly :)

@kushaldas
Copy link
Contributor Author

Also, we can just make the dynamic inventory to call the similar python function to get key pairs while doing from molecule.

@kushaldas kushaldas force-pushed the onion_onion_and_more_onions branch from 8f64cf4 to 6fc3763 Compare August 12, 2019 17:14
@kushaldas
Copy link
Contributor Author

@conorsch Added the changes requested. When I tried the scrip way, I could not find a way to get the correct cryptography into this script's environment.

@conorsch
Copy link
Contributor

The currently implemented securedrop-admin strategy cannot configure v3 URLs when testing against VMs, so the invocation must change.

Current means? The branch I pushed last week, did it amazingly :)

I could have been more clear: the current strategy (accurate as of today on this branch) fails to configure v3 URLs on staging VMs unless a Tails VM is also used. That means that make staging will not configure v3 URLs, nor will the v3 service config be tested in CI. We'll need config tests for the v3 tor changes; consider writing those tests sooner rather than later to guide development!

When I tried the scrip way, I could not find a way to get the correct cryptography into this script's environment.

That sounds like a consequence of the crytography version bump. Although you added the new cryptography version to admin/requirements-ansible.in, you must also run make update-python{2,3}-requirements for that dependency to propagate to the relevant requirements.txt files. Once you do that, the script should work just fine for you, modulo linting errors from the copy/paste version currently in place. I already pushed a small commit fixing yamllint errors on the new Ansible logic.

Testing locally, I was able to invoke the keygen script successfully via Ansible using the following command:

ansible -c local -i localhost, all -m script -a 'install_files/ansible-base/roles/tor-hidden-services/files/generate-tor-v3-keypairs'

After cleaning up the aforementioned linting errors, that script executes well, and returns JSON (albeit escaped in the interactive terminal, since Ansible renders task results as JSON by default). So, for next steps, please do the following:

  1. Rebase this branch on top of latest develop, to include changes from Fixes #4627 Adds v2 and v3 onion service variables #4648 and others.
  2. Expand comment justifying cryptography version bump. (Remember, this is for future maintainers: which calls were not present in the old API? Why are those calls necessary?)
  3. Update *.txt requirements files via the Makefile targets, then update your local venv to use the new reqs.
  4. Modify the generate-tor-v3-keypairs script to execute successfully (has a few missing imports right now).
  5. Run the script inside the Ansible tasks, if-and-only-if the JSON file doesn't exist locally. That will ensure the JSON info is present, and we can read it in for the host config, as the branch already does.
  6. Write testinfra tests to validate to the tor v3 service state. These may need to wait until v3 is "on-by-default"; still, we should define "successful" service behavior in the tests, even if we temporarily disable them.

Based on previous discussion, there may be outstanding questions regarding the implementation of step 5. Please set aside some time to give it your best shot. If you get stuck and it makes sense for us to pair on Ansible work for a bit, we can find a time to do so.

@kushaldas kushaldas force-pushed the onion_onion_and_more_onions branch from fbe7c7c to 78e524e Compare August 13, 2019 14:56
redshiftzero and others added 22 commits August 21, 2019 17:29
app-staging.yml is what is used in the test_tor_hidden_services tests,
the variables removed in this diff seem to be just unnecessary
duplicated/confusing
The securedrop-admin virtualenv is still py2, but the rest of the
project, including developer env, uses py3. Let's modify just the keygen
logic for v3 URLs to work under both Python 2 & Python 3.
Two changes here. First, we remove the sys.exit() calls inside the v3
keygen logic. Those worked well enough when the script is invoked
directly via the subcommand, but causes the `./securedrop-admin install`
exit to quit before calling out to Ansible. We replace the sys.exit
calls with `return 0`, since the parent function expects an exit code
to be returned, and passes this to the shell.

Second, we stop calling the keygen logic automatically as part of the
install action. If anything, it'd be more appropriate to configure those
values as part of `sdconfig`, but since the generation depends on the
site config already being evaluted (i.e. v3_onion_services=True), we
shouldn't automatically generate keys before inspecting the config.
The service name is different for v2/v3 Onion services; the v3 config
was missing the "v3" suffix on the end of the service name, causing the
install to fail when writing.
Implementing suggestion by @zenmonkeykstop, to format the files directly
on the Admin Workstation in the format required for ingestion by the
`tailsconfig` logic in securedrop-admin.

Modified the filename for the journalistv3 service, so that the existing
"if filename ends with '-aths'" logic will work, as with the SSH
services.

h/t to @redshiftzero for an assist on the Jinja.
this seems to be timing out a bunch lately in CI, giving it
another 5 minutes
Let's name the v3 client auth info with a compatible filename for use
with the tor services on the Admin Workstation. Simplifies the
tailsconfig logic to move the files, rather than rename them according
to a map.
If an instance has multiple Administrators, and one chooses to enable
v3, we should detect that scenario and fail early to avoid destroying
the previously configured client auth info.
We're aiming to fail early if an Admin runs the install action
against a server with v3 configured, but without having run
sdconfig to enable v3 (indicates multiple Admins).

Updated the monstrous filter line to pull out the booleans, then inspect
that list in an assert statement. Seems to be working as expected, will
test with multiple configuration paths prior to squashing.

Better support for "staging" scenario, which has v3_onion_services=true,
but enable_ssh_over_tor=false, so mon-staging was failing the check.
Updated to accommodate.

If no onions exist, don't fail: we're about to set them up!
Checks that BOTH of the following conditions are NOT true:

  * v3_onion_services=false
  * v3 services exist on remote host

If both are true, that's likely the result of a misconfiguration.
This logic handles the prod use case of v2 enabled, also enabling
v3. It hasn't yet been tested with ssh-over-LAN, we'll likely need to
modify the restrict-direct-access role to accommodate.
The dynamic logic to accommodate for SSH-over-LAN includes the
"restrict-direct-access" role, which assumes that Onion Services already
exist on the remote host. That's not the case when migrating from v2 to
v3 Onion services, so let's override the dynamic include logic to
instruct the role *not* to wait for the hostname files to be created.

Later in the playbook, the restrict-direct-access role will run without
the override, fetching back the client auth config that now exists,
since the tor role will also have run by this point.
@conorsch conorsch force-pushed the onion_onion_and_more_onions branch from 3d86cd2 to f24ffc0 Compare August 22, 2019 00:35
@conorsch
Copy link
Contributor

Rebased on top of latest develop (a9087a2), to accommodate for changes from #4665. Specifically, these were minor discrepancies in the pip deps:

  • setuptools, 41.1.0 -> 41.2.0
  • requests, 2.20.0 -> 2.22.0

Now that conflicts are resolved, let's make sure CI passes, then we're good to go.

@conorsch conorsch dismissed zenmonkeykstop’s stale review August 22, 2019 01:29

Changes requested by @zenmonkeykstop have been addressed

@conorsch conorsch self-requested a review August 22, 2019 01:29
Copy link
Contributor

@conorsch conorsch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving. Satisfied with behavior in the following scenarios (all using Tails VM against prod VMs):

Scenario 1

  1. Clean install with v2-only.
  2. Enable v3 (in addition to v2), install again.

Scenario 2

  1. Clean install with v2-only and ssh-over-lan
  2. Enable v3 (in addition to v2), preserve ssh-over-lan, install again.

Scenario 3

  1. Clean install with v3-only
  2. [did not run a second install, since connection depends on Adds support for v3 Onion urls to ./securedrop-admin tailsconfig` #4675]

CI is passing, as well, so calling this one a wrap. Thanks for all your hard work on this one, @kushaldas & @redshiftzero!

@conorsch conorsch merged commit 0c0ed47 into develop Aug 22, 2019
@conorsch conorsch deleted the onion_onion_and_more_onions branch June 18, 2020 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
7 participants