From 4b397f3b27b29ed93a83d3fb497ebb25b0de1b5f Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 30 Jan 2024 13:08:35 -0800 Subject: [PATCH] Enable stronger cryptnono protections for public hubs - Bring in last 6 months of work on cryptnono for mybinder.org into this repo, and document it heavily. Read the documentation for more info! - Enable stronger cryptnono protections for HHMI spyglass ephemeral - Move HHMI spyglass ephemeral hub to using tmpauthenticator open to the world instead of CILogon - Document that ephemeral hubs should have this extra protection be enabled Ref https://github.com/2i2c-org/infrastructure/issues/3643 --- config/clusters/hhmi/spyglass.values.yaml | 15 +- config/clusters/hhmi/support.values.yaml | 6 + deployer/__main__.py | 1 + .../generate/cryptnono_config/__init__.py | 33 ++++ .../enc-blocklist-generator.secret.py | 21 +++ deployer/infra_components/cluster.py | 5 +- docs/howto/features/cryptnono.md | 176 ++++++++++++++++++ docs/howto/features/ephemeral.md | 87 +++++---- docs/howto/features/index.md | 1 + helm-charts/support/values.yaml | 6 + 10 files changed, 301 insertions(+), 50 deletions(-) create mode 100644 deployer/commands/generate/cryptnono_config/__init__.py create mode 100644 deployer/commands/generate/cryptnono_config/enc-blocklist-generator.secret.py create mode 100644 docs/howto/features/cryptnono.md diff --git a/config/clusters/hhmi/spyglass.values.yaml b/config/clusters/hhmi/spyglass.values.yaml index 2edf0f10ce..1765f1bb42 100644 --- a/config/clusters/hhmi/spyglass.values.yaml +++ b/config/clusters/hhmi/spyglass.values.yaml @@ -72,12 +72,9 @@ jupyterhub: hub: config: JupyterHub: - authenticator_class: cilogon - CILogonOAuthenticator: - oauth_callback_url: https://spyglass.hhmi.2i2c.cloud/hub/oauth_callback - allowed_idps: - http://github.com/login/oauth/authorize: - default: true - username_derivation: - username_claim: "preferred_username" - allow_all: true + authenticator_class: tmpauthenticator.TmpAuthenticator + TmpAuthenticator: + # This allows users to go to the hub URL directly again to + # get a new server, instead of being plopped back into their + # older, existing user with a 'start server' button. + force_new_server: true diff --git a/config/clusters/hhmi/support.values.yaml b/config/clusters/hhmi/support.values.yaml index d8980db70e..e27e6edfee 100644 --- a/config/clusters/hhmi/support.values.yaml +++ b/config/clusters/hhmi/support.values.yaml @@ -26,3 +26,9 @@ grafana: - secretName: grafana-tls hosts: - grafana.hhmi.2i2c.cloud + +cryptnono: + detectors: + # Enable execwhacker, as this cluster has a hub that is widely open to the public + execwhacker: + enabled: true diff --git a/deployer/__main__.py b/deployer/__main__.py index 782f047f34..bffe455a0f 100644 --- a/deployer/__main__.py +++ b/deployer/__main__.py @@ -6,6 +6,7 @@ import deployer.commands.exec.cloud # noqa: F401 import deployer.commands.exec.infra_components # noqa: F401 import deployer.commands.generate.billing.cost_table # noqa: F401 +import deployer.commands.generate.cryptnono_config # noqa: F401 import deployer.commands.generate.dedicated_cluster.aws # noqa: F401 import deployer.commands.generate.dedicated_cluster.gcp # noqa: F401 import deployer.commands.generate.helm_upgrade.jobs # noqa: F401 diff --git a/deployer/commands/generate/cryptnono_config/__init__.py b/deployer/commands/generate/cryptnono_config/__init__.py new file mode 100644 index 0000000000..ee871b52dd --- /dev/null +++ b/deployer/commands/generate/cryptnono_config/__init__.py @@ -0,0 +1,33 @@ +from deployer.cli_app import generate_app +from pathlib import Path +import subprocess +import shutil + +HERE = Path(__file__).parent +REPO_ROOT_PATH = HERE.parent.parent.parent.parent + + +@generate_app.command() +def cryptnono_secret_config(): + """ + Update the secret blocklist for cryptnono + """ + unencrypted_path = HERE / "unencrypted_secret_blocklist.py" + + try: + # The code to generate this blocklist is small but encrypted. + # We temporarily decrypt it before importing the file via regular means, + # and then delete the imported file. + shutil.copyfile(HERE / "enc-blocklist-generator.secret.py", unencrypted_path) + subprocess.check_call( + ["sops", "--decrypt", "--in-place", str(unencrypted_path)] + ) + + from .unencrypted_secret_blocklist import write_encrypted_cryptnono_config + + secret_config_path = ( + REPO_ROOT_PATH / "helm-charts/support/enc-cryptnono.secret.values.yaml" + ) + write_encrypted_cryptnono_config(secret_config_path) + finally: + unencrypted_path.unlink() diff --git a/deployer/commands/generate/cryptnono_config/enc-blocklist-generator.secret.py b/deployer/commands/generate/cryptnono_config/enc-blocklist-generator.secret.py new file mode 100644 index 0000000000..fcf397b04b --- /dev/null +++ b/deployer/commands/generate/cryptnono_config/enc-blocklist-generator.secret.py @@ -0,0 +1,21 @@ +{ + "data": "ENC[AES256_GCM,data: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,iv:TZ8YGS+cfxUUdYDE5GS+f5REAkp8rRUsHbllvkAIPQE=,tag:wwi8RPqEI3AwKKIrl3Vg3Q==,type:str]", + "sops": { + "kms": null, + "gcp_kms": [ + { + "resource_id": "projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs", + "created_at": "2024-01-30T21:02:48Z", + "enc": "CiUA4OM7eCxHPiBqy6mOWR2S3PJbLSWYZXzFOQnZJ8lrTylqCTNlEkgAjTWv+o7jO3nOhYDxVi96kSIKuPF6qw/LbnuwkQhW1Q2KPYvEqWxnuSlGmdbgaB3ue6zs963VkoviKthuemqMELyCzwFLdR8=" + } + ], + "azure_kv": null, + "hc_vault": null, + "age": null, + "lastmodified": "2024-01-30T21:02:49Z", + "mac": "ENC[AES256_GCM,data:y1DPJnP1DLtDdLpCHbtq15gg9G7ZxDz7AB2++fAsrwml+vnEUpsE03Mv7IWP0glVpoY5MEvANTz5QmGtVzLIwyL2iVRlP2qYeZFW54z40pO69p5foUH4wfS+D7G2oKCIOJ4F29MziTVkCkINHb9PVM/6ePvZyh1EGIXXTn5Hs74=,iv:SBgSWs19yRmSxbJ2l3Ddpc4/EZpNaMYnnOBIUEM6gxs=,tag:zGt6bSNusiSF4Q8QN/nc6w==,type:str]", + "pgp": null, + "unencrypted_suffix": "_unencrypted", + "version": "3.7.3" + } +} \ No newline at end of file diff --git a/deployer/infra_components/cluster.py b/deployer/infra_components/cluster.py index db6fe71d4c..4af2a6840c 100644 --- a/deployer/infra_components/cluster.py +++ b/deployer/infra_components/cluster.py @@ -71,7 +71,10 @@ def deploy_support(self, cert_manager_version): subprocess.check_call(["helm", "dep", "up", support_dir]) # contains both encrypted and unencrypted values files - values_file_paths = [support_dir.joinpath("enc-support.secret.values.yaml")] + [ + values_file_paths = [ + support_dir.joinpath("enc-support.secret.values.yaml"), + support_dir.joinpath("enc-cryptnono.secret.values.yaml"), + ] + [ self.config_path.joinpath(p) for p in self.support["helm_chart_values_files"] ] diff --git a/docs/howto/features/cryptnono.md b/docs/howto/features/cryptnono.md new file mode 100644 index 0000000000..d407c80b29 --- /dev/null +++ b/docs/howto/features/cryptnono.md @@ -0,0 +1,176 @@ +(howto:features:cryptnono)= +# Enable stronger anti-crypto abuse features for a hub + +For JupyterHubs and BinderHubs that are broadly open to the public, cryptomining +attacks are the most common security threat. They take up resources and rack up +cloud costs. While most hubs can get away with gating access to them via some +kind of login restriction, for a subset of hubs this is not ideal (for equity and +access reasons). + +For those cases, we enable a stronger deployment of [cryptnono](https://github.com/cryptnono/cryptnono). +The cryptnono project (in use on mybinder.org as well) helps detect and kill cryptominers via +various 'Detectors'. + +## Detectors + +Cryptnono currently has two primary detectors: + +1. A detector for the [monero](https://www.getmonero.org/) cryptocurrency, that is based on + official guidance [from the monero project](https://blog.px.dev/detect-monero-miners/) on + how to detect it. This is fairly safe and has a very low false positive rate, and requires + no configuration. So by default, **this detector is enabled on all hubs**. + +2. A detector (`execWhacker`) based on heuristics, where the full commandline used to execute a process ( + regardless of how it is started) is used to detect if a process is likely crypto mining, + and if so, immediately kill it. This relies on a tweakable config of banned strings to + look for in the commandline of a process, and is [constantly being tweaked](https://github.com/jupyterhub/mybinder.org-deploy/security/advisories/GHSA-j42g-x8qw-jjfh). + Since making the list of banned strings public would make ban evasion easy, the list of + strings (and the method used to generate them) is encrypted. You can read more details + about the specific method used by looking in the encrypted code file (`deployer/commands/generate/cryptnono_config/encrypted_secret_blocklist.py`) + in this repository. + + Since this detector may have a non-0 false positive rate, it is currently *not* enabled by + default. However, eventually, once the config matures enough (and we have tested it enough), + this would also be enabled by default everywhere. In the meantime, we only enable it for + *clusters* with any hub that allows unfettered external access. + +## Enabling the `execWhacker` detector + +The `execWhacker` detector can be enabled with the following configuration in the appropriate +`support.values.yaml` for the cluster: + +```yaml +cryptnono: + detectors: + # Enable execwhacker, as this cluster has a hub that is widely open to the public + execwhacker: + enabled: true +``` + +Upon deployment of this change, you can verify the detector is enabled by looking for a container +named `execwhacker` in the `cryptnono` daemonset in the `support` namespace. + +```yaml +kubectl -n support get daemonset support-cryptnono -o yaml +``` + +## Testing the `execWhacker` detector + +To test that the detector is actually working, you can login to a hub on the cluster and +try to execute the following command: + +```bash +ls phahPaiWie2aeluax4Of7tiwiekujeaF7aquuPeexeiT7jieJailaKai7haiB0raetib9ue8Ai2daeTaehaemohJeeyaifeip6nevae5Safeir9iep8Baic3nohn9zoa +``` + +It should immediately die, with a message saying `Killed`. This is a randomly generated test string, set up +in an unencrypted fashion in `helm-charts/support/values.yaml` under `cryptnono.detectors.execwhacker.configs`, +to enable testing by engineers and others. + +## Looking at logs to understand why a process was killed by `execwhacker` + +Cryptnono is deployed as a [daemonset](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/), +so there should be one pod per node deployed. It will log each process it kills, why it kills them, and if it +intentionally spares a process, why as well. So if a process is being killed due to this, you can look at logs +to understand why - it may also lead to more tweaking of the generator config. + +1. Find the *node* in which the user server is running. + + ```bash + kubectl -n get pod -o wide + ``` + + The `-o wide` will add an additional column, `NODE`, showing which node the pods are running in. Find the + node of the user pod you care about. + +2. Find the appropriate `cryptnono` pod for this node. + + ```bash + kubectl -n support get pod \ + --field-selector spec.nodeName=\ + -l app.kubernetes.io/name=cryptnono + ``` + + This should show *just* the cryptnono pod running on the node in which the user server in question was running. + +3. Look at the logs on that pod with `kubectl logs`: + + ```bash + kubectl -n support logs -c execwhacker + ``` + + The logs will be structured as `json`, and will look like this: + + ```json + {"pid": 13704, "cmdline": "/usr/bin/ls --color=auto phahpaiwie2aeluax4of7tiwiekujeaf7aquupeexeit7jiejailakai7haib0raetib9ue8ai2daetaehaemohjeeyaifeip6nevae5safeir9iep8baic3nohn9zoa", "matched": "phahpaiwie2aeluax4of7tiwiekujeaf7aquupeexeit7jiejailakai7haib0raetib9ue8ai2daetaehaemohjeeyaifeip6nevae5safeir9iep8baic3nohn9zoa", "source": "execwhacker.bpf", "action": "killed", "event": "Killed process", "level": "info", "timestamp": "2024-01-30T19:35:36.610659Z"} + ``` + + This tells us that the name of the process, as well as why it was killed. + + ```{note} + We will eventually be able to see the [pod & namespace](https://github.com/cryptnono/cryptnono/issues/30) information + of killed processes as well. + ``` + + ```{tip} + To make the JSON easier to read, you can pipe the logs command to `jq`. + ``` + +## Regenerating list of banned strings + +Periodically, we will have to regenerate the list of banned strings to tune `cryptnono`, +by running the following command: + +```bash +deployer generate cryptnono-secret-config +``` + +This will update the file `helm-charts/support/enc-cryptnono.secret.values.yaml` and re-encrypt it. + +## Working on the banned strings generator + +The banned strings generator is a fairly simple python script, present in `deployer/commands/generate/cryptnono_config/enc-blocklist-generator.secret.py`. +It's unencrypted and loaded by code in `deployer/commands/generate/cryptnono_config/__init__.py`. There is inline +documentation in `encrypted_secret_blocklist.py`, but how does one hack on it? + +1. Unencrypt the file with `sops` + + ```bash + sops --decrypt deployer/commands/generate/cryptnono_config/enc-blocklist-generator.secret.py > deployer/commands/generate/cryptnono_config/blocklist-generator.secret.py + ``` + + This will create `deployer/commands/generate/cryptnono_config/blocklist-generator.secret.py` (which is in `.gitignore` and hence + can not be committed accidentally) with the unencrypted code. + + ```{note} + + Because this file is in `.gitignore`, your IDE may not show it to when you search for files by default! + ``` + +2. Work on the code as you wish - it's just a regular python file. + +3. Re-encrypt it with `sops` + + ```bash + sops --encrypt deployer/commands/generate/cryptnono_config/blocklist-generator.secret.py > deployer/commands/generate/cryptnono_config/enc-blocklist-generator.secret.py + ``` + +4. Run `deployer generate cryptnono-secret-config` to test. + +## Right to Replicate considerations + +2i2c's [Right to Replicate](https://2i2c.org/right-to-replicate/) guarantees that communities can leave +with their hubs whenever they please, without any secret sauce. Cryptnono has two pieces of secret info +here: + +1. The script to generate the secret config. +2. The secret config itself. + +If a community wishes to leave and needs the config, we can make sure they know what the config is and +how to keep it updated - very similar to how we would handle passing on CILogon credentials or similar to +them. Since none of the code for cryptnono itself is secret, this does not conflict with right to replicate. + +## Future work + +`cryptnono` also exposes prometheus metrics about processes it has killed. We currently do not collect these, +but we should enable collection so we can diff --git a/docs/howto/features/ephemeral.md b/docs/howto/features/ephemeral.md index 417772ed98..50ef00f954 100644 --- a/docs/howto/features/ephemeral.md +++ b/docs/howto/features/ephemeral.md @@ -45,46 +45,6 @@ jupyterhub: force_new_server: true ``` -## Shared systemwide password setup - -Unfortunately, a shared password is required to guard against random cryptobros -abusing using our systems for 'free' compute. This password is same for all -users, and can be shared by the community champions of the hub in whatever -form they wish. - -This requires two bits of config - one in the `.values.yaml` file for the hub, -and another in the `enc-.secret.values.yaml` file. - -In `.values.yaml`: - -```yaml -ingressBasicAuth: - enabled: true - -jupyterhub: - ingress: - annotations: - # We protect our entire hub from cryptobros by putting it all - # behind a single shared basicauth - nginx.ingress.kubernetes.io/auth-type: basic - nginx.ingress.kubernetes.io/auth-secret: ingress-basic-auth - nginx.ingress.kubernetes.io/auth-realm: "Authentication Required" -``` - -In `enc-.secret.values.yaml`: -```yaml -ingressBasicAuth: - username: - password: -``` - -```{tip} -Pick a *passphrase* for the password, like [the holy book says](https://xkcd.com/936/). -``` - -```{note} -Make sure the `enc-.secret.values.yaml` is encrypted via [sops](tools:sops) -``` ## No persistent storage @@ -199,3 +159,50 @@ style links, but for use with *ephemeral hubs*, just use the regular 'JupyterHub link generator. [Firefox](https://addons.mozilla.org/en-US/firefox/addon/nbgitpuller-link-generator/) and [Google Chrome](https://chrome.google.com/webstore/detail/nbgitpuller-link-generato/hpdbdpklpmppnoibabdkkhnfhkkehgnc) extensions are also available. + +## Enable stronger anti-crypto abuse features for a hub + +Ephemeral hubs with public access must have [stronger anti crypto abuse features +enabled](howto:features:cryptnono) before going live. + +## (Optional) Shared systemwide password setup + +Optionally, in addition to the cryptnono setup documented above, a shared +password may be used as additional protection to guard against random cryptobros +abusing using our systems for 'free' compute. This password is same for all +users, and can be shared by the community champions of the hub in whatever form +they wish. + +This requires two bits of config - one in the `.values.yaml` file for the hub, +and another in the `enc-.secret.values.yaml` file. + +In `.values.yaml`: + +```yaml +ingressBasicAuth: + enabled: true + +jupyterhub: + ingress: + annotations: + # We protect our entire hub from cryptobros by putting it all + # behind a single shared basicauth + nginx.ingress.kubernetes.io/auth-type: basic + nginx.ingress.kubernetes.io/auth-secret: ingress-basic-auth + nginx.ingress.kubernetes.io/auth-realm: "Authentication Required" +``` + +In `enc-.secret.values.yaml`: +```yaml +ingressBasicAuth: + username: + password: +``` + +```{tip} +Pick a *passphrase* for the password, like [the holy book says](https://xkcd.com/936/). +``` + +```{note} +Make sure the `enc-.secret.values.yaml` is encrypted via [sops](tools:sops) +``` \ No newline at end of file diff --git a/docs/howto/features/index.md b/docs/howto/features/index.md index 3e42dfc921..5045b7d8f3 100644 --- a/docs/howto/features/index.md +++ b/docs/howto/features/index.md @@ -53,6 +53,7 @@ See the sections below for more details. 4. (default enabled) Configurator 5. (dedicated clusters only) Grafana <../..//topic/monitoring-alerting/grafana> 6. Using JupyterHub as an identity provider +7. Stronger anti-crypto abuse features for a hub ``` ### Performance Layer diff --git a/helm-charts/support/values.yaml b/helm-charts/support/values.yaml index 39996dc6ad..e4d2b95f8c 100644 --- a/helm-charts/support/values.yaml +++ b/helm-charts/support/values.yaml @@ -422,6 +422,12 @@ cryptnono: # Disable the execwhacker detector for now, as it matures by being deployed on mybinder.org execwhacker: enabled: false + configs: + unencrypted-test-01: + bannedCommandStrings: + # Provide an unencrypted, randomly generated test string. All processes that have this in their commandline will be killed. + # This is helpful for testing by engineers + - phahPaiWie2aeluax4Of7tiwiekujeaF7aquuPeexeiT7jieJailaKai7haiB0raetib9ue8Ai2daeTaehaemohJeeyaifeip6nevae5Safeir9iep8Baic3nohn9zoa monero: enabled: true resources: