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 local keymanager API #151

Closed
wants to merge 29 commits into from

Conversation

dapplion
Copy link
Collaborator

@dapplion dapplion commented Jun 4, 2021

Why?

The main goal of this effort is to increase client diversity. UIs are very important to some users so this standard should ensure all clients have at least one.

The secondary goal is to offload UI work from core dev teams to other parties that can prioritize this work over Altair, the merge, etc.

WIP: This standard must be cheap to implement to be successful. Core devs are encouraged to voice concerns around complexity to ensure a timely implementation.


EDIT: Note - this proposal is initially motivated for a specific type of validator operator: the solo home DIY staker without good technical knowledge. This API is a simplification of the existing Prysm's validator API, which is also designed for that type of staker


Minimal validator API to enable a basic validator manager workflow. Supports:

  • Import, delete keystores
  • Import, export slashing db

Does not support:

  • Metrics visualization or plotting any type of chain data. Instead, should be done with metrics tooling such as Grafana or block explorers
  • VoluntaryExit. Instead, should be done with the deposit cli tooling.
  • Backup accounts. When migrating validator clients stakers should re-generate the keystores from the mnemonic or retrieve them from a self-managed backup.
  • Displaying logs.

All routes SHOULD be disabled by default and only be enabled with a CLI flag such as --enable-validator-api.

All sensitive routes MUST be authenticated. There SHOULD exist a single password per validator client binary such that multiple users are not supported.

The validator API MUST allow to add and delete accounts but SHOULD not allow to retrieve them. Users are encouraged to use alternative locations to backup their keystores and use them to perform validator client migration.


See the hackmd document for more context https://hackmd.io/@dapplion/eth2-validator-api

@rolfyone
Copy link
Collaborator

rolfyone commented Jun 7, 2021

I'm a little concerned about this. Allowing remote access to this kind of functionality may make it very easy to get slashed...

Having CLI access implies a level of admin access, and I guess with that a level of responsibility, and it'd be potentially less open to error with export/import processes...

If a validator client with a password doesn't stop their validator from where ever its running and imports it to another client, that'll result in a slashing event, so we need to think really carefully whether this actually needs to be accessible from the standard rest api...

If we were to allow it, I think you'd want the key/slashing data in a single payload, so that you can't load a key without having slashing protection data, or do something like without the slashing protection enforce a 2 epoch delay or something...

We might be able to do something like verify it the specified validator hasn't attested or something currently, i'm not sure what validations could be worked in for altair - potentially there's a way to see the validator isn't active...

We also would need to be very careful about stipulations on how this is secured. I'm not sure we want to stipulate that a single password is used, as it should be up to the person running the client and in line with security requirements that the company has. If they want to go and do one-time-passwords or 2FA, all power to them IMHO.

We can definitely suggest that it's disabled by default without providing a flag etc, and that they need to be authenticated routes, but ultimately that's really the level we can stipulate to, and maybe that at least basic auth should be supported or something like that.

The response of deleting keystores may need to contain more context about slashing summary of the keys removed...

If we do go this way, it would probably be pertinent to not require a login/logout structure, but also to be able to just issue an authenticated api call that has no real 'session'. Partially I say this because sessions can be particularly painful to manage in balanced environments.

Create/list accounts is something that needs more discussion. It's a definite attack vector to start creating millions of random accounts, and list accounts is something that non admin users shouldn't really be able to do (get half of the user/password problem, then brute force passwords)

Potentially this should be a separate repo, given its dealing purely with keys etc, and won't be dealing with any of the structures in eth2.0-api? It's a whole bunch of infrastructure to confuse the existing apis if we add it all here, and fairly unrelated in real terms... Just a thought...

@mcdee
Copy link
Contributor

mcdee commented Jun 7, 2021

I would like to echo the concerns of @rolfyone and add a few of my own.

First, and most importantly, this locks in a specific way of thinking about a validator, specifically that there is an endpoint that is responsible for both validating and signing. This is becoming more commonly not the case, with multiple remote signer implementations available and being used. This is not only a conceptual issue but a practical one: which process runs the API? This repo builds an API that is run on the beacon node, and that's no use for these endpoints in many cases. Ditto the validator client, as it may not have ownership of the keys (but does need to know the pubkeys for which it is validating). The signer part is the most likely place to put this, or some of this, but they are generally built to be highly secure and allowing creation and deletion of keys seems to be an unlikely fit wit with their existing security model.

Second, the general idea of accessing slashing protection data from an active endpoint is a Bad Thing. By definition, fetching this data from a running system means that it is out of date as soon as it has been retrieved and so cannot be trusted to be a complete picture when stored or imported elsewhere.

More of an implementation "detail", but the lack of HTTPS is something that seems to be massively insecure. The idea that the keystore, plus the passphrase to access its private data, are both sent in the same request and in plaintext is a very concerning.

Regarding the goals of this PR: I think that both are laudable, however I suspect that this would be very low impact. Client diversity, from what I have seen with those that install the software, is much more a question of ease of installation (and, long-term, maintenance) of the beacon node software. The operations to import validators, although not standard between implementations, is relatively simple and so would not benefit massively from having an API rather than its existing CLI. As for UI, the majority of any such project would be around the monitoring of an active beacon chain rather than its setup, in which I include the creation of validators.

@dapplion
Copy link
Collaborator Author

dapplion commented Jun 7, 2021

You are right, in that this proposal is initially motivated for a specific type of validator operator: the solo home DIY staker without good technical knowledge. This API is a simplification of the existing Prysm's validator API, which is also designed for that type of staker. This proposal is definitely not for professional staking pools or institutional stakers.

@rolfyone

Potentially this should be a separate repo, given its dealing purely with keys etc, and won't be dealing with any of the structures in eth2.0-api? It's a whole bunch of infrastructure to confuse the existing apis if we add it all here, and fairly unrelated in real terms... Just a thought...

Moving this to a different repo is great idea since I agree this functionality doesn't have any resemblance with existing routes. The server for the validator manager API should be served by the a different process, the validator client. Happy to move this PR to a new repo once it exists.

If we were to allow it, I think you'd want the key/slashing data in a single payload, so that you can't load a key without having slashing protection data, or do something like without the slashing protection enforce a 2 epoch delay or something...

I agree, that's a great point. The counter-argument there is to not allow to extract the keys in anyway with this API, to contain the damage if auth fails.

We also would need to be very careful about stipulations on how this is secured. I'm not sure we want to stipulate that a single password is used, as it should be up to the person running the client and in line with security requirements that the company has. If they want to go and do one-time-passwords or 2FA, all power to them IMHO.

This simple auth scheme is meant to be served and used locally for solo stakers. In a company setting a more complex setup would have to be used.

The response of deleting keystores may need to contain more context about slashing summary of the keys removed...

That's a great point, I will add it.

@mcdee

This is not only a conceptual issue but a practical one: which process runs the API? This repo builds an API that is run on the beacon node, and that's no use for these endpoints in many cases.

This API should not be run by the beacon node, it should be run by the validator client unless using remote signing.

First, and most importantly, this locks in a specific way of thinking about a validator, specifically that there is an endpoint that is responsible for both validating and signing. This is becoming more commonly not the case, with multiple remote signer implementations available and being used.

That's a great point. I noted this API is motivated for the solo home staker. However I will research how new approaches to validating can influence this.

Second, the general idea of accessing slashing protection data from an active endpoint is a Bad Thing. By definition, fetching this data from a running system means that it is out of date as soon as it has been retrieved and so cannot be trusted to be a complete picture when stored or imported elsewhere.

@rolfyone idea of fetching slashing protection data only after deleting the keystores from the validator is good step in that direction.

More of an implementation "detail", but the lack of HTTPS is something that seems to be massively insecure. The idea that the keystore, plus the passphrase to access its private data, are both sent in the same request and in plaintext is a very concerning.

If users are able to get SSL certificates they should, or just do self-signed certs for local networks. Should the openapi doc reflect that?

Client diversity, from what I have seen with those that install the software, is much more a question of ease of installation (and, long-term, maintenance) of the beacon node software

Given that all projects publish to docker registries it's quite easy to build systems around that. The goal of this is to allow staking without forcing CLI interaction.

As for UI, the majority of any such project would be around the monitoring of an active beacon chain rather than its setup, in which I include the creation of validators.

We have the metrics standardization effort for that front.

@dapplion dapplion changed the title Add validator manager API Add local validator manager API Jun 7, 2021
Copy link
Contributor

@paulhauner paulhauner left a comment

Choose a reason for hiding this comment

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

Hey, this is just a quick fly-by of a few comments I raised during a discussion out of band.

Here is the link to the endpoints we currently have on the Lighthouse API now: https://lighthouse-book.sigmaprime.io/api-vc-endpoints.html This API needs some work before it can become GUI-ready. Among other things, we need to add some endpoints around importing/exporting slashing protection data.

I'll need to come back for a more detailed review, especially around the authentication method. Presently Lighthouse protects the API by requiring an authorization token.

For the record, I generally support the standardization of the VC API.

apis/accounts/auth/change_password.yaml Outdated Show resolved Hide resolved
apis/accounts/auth/signup.yaml Outdated Show resolved Hide resolved
apis/accounts/index.yaml Outdated Show resolved Hide resolved
"200":
description: |
Successfully authenticated. The session ID is returned in a cookie named `SESSION`. You need to include this cookie in subsequent requests.
- HttpOnly: MUST be included
Copy link

@cheatfate cheatfate Jul 1, 2021

Choose a reason for hiding this comment

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

Is it possible to avoid cookies? Because as soon as you introduce cookies for authentication server become stateful. Also because Max-Age is not specified its possible to create cookies which will never be deleted, and so can be used if cookie become stolen.
What if you use token authentication instead?

Copy link
Collaborator Author

@dapplion dapplion Jul 2, 2021

Choose a reason for hiding this comment

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

Could you describe in more detail what you refer to with "token authentication"?

Given that each node will likely implement different flavours of authentication, using HttpOnly cookies would simplify the UI implementation. The browser will just re-send whatever that specific node has sent in the Set-Cookie header.

  • You can make the cookie stateless by signing it with a known key and adding data to it. Cookies fit 4096 bytes, so it should be enough for whatever session data a validator UI may need to know.
  • You can also add a timestamp to the cookie's content and use that to invalidate it in the backend, irrespective of the Max-Age directive

Choose a reason for hiding this comment

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

You can check for Json Web Token about token authentication which could be passed via headers or request string. The biggest difference is that tokens will not be managed by browsers.
This feature protects validators from loosing cookies through malicious browser extensions or malware.
As soon as communication finishes, tokens become not valid anymore.

Copy link
Contributor

@paulhauner paulhauner Jul 6, 2021

Choose a reason for hiding this comment

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

Lighthouse has an auth scheme in its existing VC API:

Here's an example of a curl request using this scheme:

curl -v localhost:5062/lighthouse/version -H "Authorization: Basic api-token-0x03eace4c98e8f77477bb99efb74f9af10d800bd3318f92c33b719a4644254d4123"

This approach is simpler for the VC, but it doesn't allow for user specified passwords. Our design ethos is to keep the VC HTTP server as simple as possible and to handle all the user-facing authentication in the GUI. Here's a design doc we drew up a while ago: https://hackmd.io/zS3h-Kk_TTmYhvyoHap6DQ

Basically, we expect the VC and BN to be hiding behind a reverse-proxy provided by the GUI web app. This avoids needing to provide HTTPS certificates for the BN, VC and web application.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@paulhauner This Signature Header is very cool idea! Is this like a roll-your-own TLS strategy to guarantee integrity of the responses? If so, could we take it a step further and make the client encrypt the requests with the pubkey so that the keystores + password are not sent in plain text? https://lighthouse-book.sigmaprime.io/api-vc-sig-header.html

Copy link

Choose a reason for hiding this comment

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

The beacon client should not provide extensive user authentication & authorization. A simple API key mechanism is enough for putting a UI-based user authenticatoin & authorization on top. In the case of implementing authentication & authorization on the beacon client, where do you draw the line with features? What about 2FA? Different storage techniques for sensible data? Etc.

Handling a JWT (Json Web Token) only makes sense when complex user management is needed.

When HTTPS is needed because access is provided on an unsecure network, a simple SSL termination service (e. g. nginx, haproxy, ...) would suffice and is more secure than handling SSL on the beacon client itself.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Here's a new tilt at an authentication scheme:

  1. Add an endpoint /eth/v1/keystores/auth that is not protected by any form of authentication. It returns a response with an absolute path to a file containing an auth token. The assumption here is that the user of the VC API is a privileged entity with access to the machine the VC is running on, and permission to read this file.
{
    "token_path": "/home/michael/.lighthouse/mainnet/validators/api-token.txt"
}
  1. All other endpoints require an Authorization: Bearer $token header with the value of the token loaded from token_path. The intention is that the token remains the same across multiple VC restarts, unless the user specifically regenerates a new token (this can happen out of band, e.g. by deleting the file and restarting the VC, or using a non-standard API).

Open questions:

On the JWT issue I tend to agree with @stefa2k that it's overkill for the VC. The main thing that JWT adds is the ability for the client to verify that the token was issued by the VC's public key... which does not seem useful. We could take a JWT-agnostic approach by permitting the VC to return extra metadata in the /auth response if it wishes (such as its JWT-signing public key). JWT-aware clients could use this to verify if they wanted to, while JWT-oblivious implementations could just use the JWT as an arbitrary token without parsing or verifying it.

I also think not requiring signed responses is fine.

Addendum:

The path that the VC stores the token in, and its permissions could be configurable via the CLI. This would enable things like running the VC and the UI as different users if desired, but I think we can get a long way even before those config options are added, and they needn't be standardised (the CLIs are already non-standard).

Copy link
Contributor

Choose a reason for hiding this comment

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

hmm so on the Prysm side we are switching to something like this already though there's no API to expose the path...

this is what happens ( feature in develop prysm/ master prysm-web-ui)

you can generate the jwt either by running ./validator web generate-auth-token which will write to a file called auth-token in the prysm wallet directory and return a custom URL in the CLI (i.e. localhost:7500/initialize?token=) they can also change the path using --wallet-dir though it's for the whole wallet.

they should get the same thing when running ./validator --web in the console as well as that same file location but there is a cli command to regenerate

the frontend stores jwt this in local storage and injects it throughout the front end if it's designed with an initialize page 🤔

wondering if we can do something similar in this case

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@michaelsproul So providing the path through an API route would be for the front-end to tell the user to "Go to $path to grab your authenticating token" ? Maybe that's a very specific implementation of auth, not sure if it should be part of the standard. Maybe as a complimentary note or suggestion for same fs setups auth?

Copy link
Collaborator

@michaelsproul michaelsproul Oct 28, 2021

Choose a reason for hiding this comment

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

@james-prysm Perfect! I think if Prysm can accept the JWT in an Authorization: Bearer <JWT> header on requests to the key manager API then that would be compatible. Not all programs (other clients, tools, etc) interacting with the JWT need to understand that it's a JWT, they can just treat it as an opaque API token if they wish, i.e. we are JWT-agnostic 😅

@dapplion I was thinking that the backend for the GUI could fetch the token automatically if it's running on the same machine, although you're right that it wouldn't work with the GUI and VC running on different hosts. It would be nice if we could provide a "no touch" set up that's client agnostic for the API token for the majority of use cases, and this was the only way I could think of achieving that. The alternative seems to be to leave the step where the user obtains the token client-specific, in which case the GUI has to hard-code a bunch of token-access heuristics, or up to the user, which is worse for UX (although should exist as a fallback option in case the fetch-from-fs fails).

apis/accounts/index.yaml Outdated Show resolved Hide resolved
apis/accounts/index.yaml Outdated Show resolved Hide resolved
@dapplion
Copy link
Collaborator Author

dapplion commented Oct 4, 2021

I've updated and simplified the routes to do a simple atomic keymanager flow:

  1. POST / Import keystores + slashing protection data
  2. GET / List current known keys
  3. DELETE / Atomically stop validating and export slashing protection data

I've removed the login, logout routes and added notes about the need to protect this routes. I left the suggestion of using an auth token in all requests that should be distributed in some way.

key-manager-oapi.yaml Outdated Show resolved Hide resolved
zah pushed a commit to status-im/nimbus-eth2 that referenced this pull request Oct 4, 2021
Copy link
Collaborator

@michaelsproul michaelsproul left a comment

Choose a reason for hiding this comment

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

I'm a big fan of the new simplified API @dapplion, thank you for putting it together!

Some observations, which I'm not sure are actionable:

  1. We're pushing the responsibility for keystore storage on to the user of this API. There's no way to get a keystore out of the API, which means if one wanted to migrate between VCs one would need an independent copy of the keystore files (and password). This is probably desirable from a security point of view, because it means in the worst case, unauthorized access to this API can only cause a liveness fault, and not a slashing. The current Lighthouse VC API also has this property.
  2. Deleting keystores increases the risk of data loss slightly compared to just disabling them. On the other hand, given (1) the user should have copies of their keystores handy anyway, and should definitely have a back up of their mnemonic to restore from. Deleting is also safer for preventing slashing (compared to disabling, where the key itself is still present and we rely on the absence of bugs to prevent it being used).

Aside from that I've raised two items which might be actionable in the code itself.

key-manager-oapi.yaml Outdated Show resolved Hide resolved
key-manager-oapi.yaml Outdated Show resolved Hide resolved
@dapplion dapplion changed the title Add local validator manager API Add local keymanager API Oct 5, 2021
key-manager-oapi.yaml Outdated Show resolved Hide resolved
key-manager-oapi.yaml Outdated Show resolved Hide resolved
$ref: "#/components/responses/InternalError"

delete:
operationId: DeleteKeys
Copy link
Contributor

Choose a reason for hiding this comment

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

question on intended behavior, if you send an empty request body is the intention to return the current state of slashing protection data? Also is the slashing protection data only on keys that are inactive or returning the data on the active ones as well 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

probably never mind on the second part, it doesn't hurt to have more data right 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

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

No, it's very risky to return slashing protection data for keys that haven't been deleted, as it represents stale information that could get you slashed.

Slashing protection data should only be returned for keys from the request that were successfully deleted (status == deleted). We should clarify that in the description

key-manager-oapi.yaml Outdated Show resolved Hide resolved
key-manager-oapi.yaml Outdated Show resolved Hide resolved
key-manager-oapi.yaml Outdated Show resolved Hide resolved
Comment on lines +97 to +100
slashing_protection:
type: object
required: false
description: Slashing protection of the imported keys data in EIP-3076 JSON format.
Copy link
Collaborator

Choose a reason for hiding this comment

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

we're going to have to define this here if we want it listed in the schema...

Copy link
Collaborator

Choose a reason for hiding this comment

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

key-manager-oapi.yaml Outdated Show resolved Hide resolved
Comment on lines +216 to +220
securitySchemes:
cookieAuth: # arbitrary name for the security scheme; will be used in the "security" key later
type: apiKey
in: cookie
name: SESSION # cookie name
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the rough consensus around cookies vs tokens fell in favour of tokens (see #151 (comment)). Prysm have implemented a JWT, and Lighthouse has a bearer token that's compatible.

We can specify token auth using the template here: https://swagger.io/docs/specification/authentication/bearer-authentication/

Copy link
Contributor

Choose a reason for hiding this comment

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

yup in favor of a token for now as it keeps it more simple.

@dapplion
Copy link
Collaborator Author

dapplion commented Nov 10, 2021

This code has been moved to this repo https://github.com/ethereum/keymanager-APIs/blob/master/keymanager-oapi.yaml

Thank you so much everyone for engaging in this initiative ❤️ To continue the discussion please open an issue to new repo https://github.com/ethereum/keymanager-APIs.

@mpetrunic mpetrunic closed this Nov 16, 2021
bors bot pushed a commit to sigp/lighthouse that referenced this pull request Jan 30, 2022
## Issue Addressed

Implements the standard key manager API from https://ethereum.github.io/keymanager-APIs/, formerly ethereum/beacon-APIs#151
Related to #2557

## Proposed Changes

- [x] Add all of the new endpoints from the standard API: GET, POST and DELETE.
- [x] Add a `validators.enabled` column to the slashing protection database to support atomic disable + export.
- [x] Add tests for all the common sequential accesses of the API
- [x] Add tests for interactions with remote signer validators
- [x] Add end-to-end tests for migration of validators from one VC to another
- [x] Implement the authentication scheme from the standard (token bearer auth)

## Additional Info

The `enabled` column in the validators SQL database is necessary to prevent a race condition when exporting slashing protection data. Without the slashing protection database having a way of knowing that a key has been disabled, a concurrent request to sign a message could insert a new record into the database. The `delete_concurrent_with_signing` test exercises this code path, and was indeed failing before the `enabled` column was added.

The validator client authentication has been modified from basic auth to bearer auth, with basic auth preserved for backwards compatibility.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.