-
-
Notifications
You must be signed in to change notification settings - Fork 277
Proposal: Auth #1883
|
To facilitate the use of Labgrid in production environments we propose adding flexible authentication and authorisation functionality. This functionality will be backwards compatible so that Labgrid can continue to run in an insecure way for deployments that do not have security requirements (eg. local usage). This proposal is dependant on Implementing authentication and authorisation requires the introduction of the following:
Identity contextWe propose adding a client_identity_context: contextvars.ContextVar[Identity] = contextvars.ContextVar("client_identity") We propose # labgrid/auth/auth.py from typing import Collection, Protocol from .capability import Capability class Auth(Protocol): @property def id(self) -> str: """A unique identity of the user.""" ... @property def capabilities(self) -> Collection[Capability]: """Capabilities this authenticated user can perform.""" ... Capability modelWe propose the following capabilities:
The capabilities could be represented as an enumerable: # labgrid/auth/capability.py from enum import StrEnum, auto class Capability(StrEnum): client_stream = auto() exporter_stream = auto() add_place = auto() delete_place = auto() get_places = auto() add_place_alias = auto() delete_place_alias = auto() set_place_tags = auto() set_place_comment = auto() add_place_match = auto() delete_place_match = auto() acquire_place = auto() release_place_owned = auto() release_place_any = auto() allow_place_owned = auto() allow_place_any = auto() create_reservation = auto() cancel_reservation_owned = auto() cancel_reservation_any = auto() poll_reservation = auto() get_reservations = auto() Client/Exporter and Coordinator-side plugins for injecting and parsing authentication metadatagRPC metadata is the channel for transmitting authentication metadata. gRPC metadata is comprised of K/V pairs. We propose a plugin architecture where, for a given identity service:
We propose Python Client/Exporter-sidefrom typing import Mapping, Protocol class ClientAuthPlugin(Protocol): def __call__(self) -> Mapping[str, List[str]]: """Produces the gRPC metadata pairs to add to each RPC call's metadata. """ ... Coordinator-sidefrom typing import Mapping, Protocol class IdentityError(Exception): """Exception to be raised when the provided metadata cannot be resolved to a valid identity.""" pass class ServerAuthPlugin(Protocol): def __call__(self, metadata: Mapping[str, List[str]]) -> Identity: """Consumes the gRPC metadata, performs identity provider-specific validation and, if successful, emits a populated Identity.""" ... Default pluginsWe propose the implementation of default plugin implementations that replicate the current functionality of Labgrid. For the Client/Exporter-side plugin this will involve the injection of the hostname (and username, for the Client) into the metadata. For the Coordinator-side plugin, this will involve generating an Refactoring of existing RPCs to include capabilities checksFor RPC methods that have a 1:1 mapping with a Capability, we suggest a method decorator that specifies the Capability (or potentially multiple) required to call the method: @capability(Capability.add_place) async def AddPlace(self, request, context): ... For RPC methods that require more granular access, a decorator is not sufficient and the Capability-checking logic will need to be embedded into the method logic: async def ReleasePlace(self, request, context): ... principal_id = client_identity_context.get().id principal_capabilities = client_identity_context.get().capabilities if Capability.release_place_any in principal_capabilities or \ (Capability.release_place_owned in principal_capabilities and place.owned == principal_id): # Release place allowed ... ... |
All reactions
Replies: 4 comments 9 replies
Hi @Emantor, on behalf of @pamolloy;
The changes above are welcome, but are not clear how the identity->capability look-up would be implemented; if there is a token in the grpc metadata, the encryption of the grpc channel needs to be handled, too.
To resolve that, I did a PoF at commit afd2d0f master...gastmaier:labgrid:tls
For the client|exporter-side, a new argument --ssl is added to enable TLS, where the system certificates are used, including SSL_CERT_FILE env var, or explicitly by passing a path. If --ssl is set, grpc.aio.secure_channel is used instead grpc.aio.insecure_channel, straight forward, already implemented by the lib.
For the coordinator, no changes are necessary, the user adds the coordinator behind a reverse proxy with TLS, like nginx. This is preferable to having labgrid|grpc managing the secret keys directly.
The second security concern from my side is ssh key-management, but https://github.com/openpubkey/opkssh seem to provide both OIDC for CI/CD workflows and and SSO for users; so changes to labgrid are also not necessary.
We still need to test opkssh + client|exporter ssl to confirm it fully solves the security+encryption needs.
All reactions
-
❤️ 1
There is #1885 for TLS secured gRPC.
All reactions
-
🎉 1
Regarding the identity -> capability lookup, the intention is to have loadable policies on the coordinator which are written for your infrastructure to cover the lookup. You can than do static mappings, database lookups or mappings from key/values inside of JWT tokens depending on how the identity is constructed.
@asher-pem-arm please correct me if I am wrong.
All reactions
I've got a few questions for understanding:
- Are the credentials per gRPC channel or per call? What are the benefits for either approach?
- How is the relationship between the new
Place.ownedand the existingPlace.acquired? - How would you permit a user to only acquire a restricted set of places? As far as I can see, the capabilities are not Place/Resource-specific?
All reactions
Hi @jluebbe,
In the old API, several unary RPCs effectively depend on a prior stream-side identity registration. That makes auth awkward to review and implement cleanly, because authorization for a unary RPC partly depends on unrelated stream state. With the refactor, identity is available on the RPC being authorized, and the coordinator can make the authorization decision locally in that handler.
Keeping auth on top of the refactor avoids designing two auth paths, one for legacy stream/session identity and another for the new unary API. We planned to keep the legacy StartupDone fallback for backwards compatibility, but we do not want the new auth model to be constrained by that.
So I think the split we are aiming for is:
- Land the gRPC/API refactor that detaches unary RPCs from ClientStream identity.
- Add auth/capability checks on the refactored identity model.
All reactions
with response to the previous questions:
- Are the credentials per gRPC channel or per call? What are the benefits for either approach?
that's per call as the only way you could do channel-level auth would be to move exclusively to mTLS.
With our design we retain the flexibility to use any method we want. - How is the relationship between the new Place.owned and the existing Place.acquired?
They are intended to be identical. It made more sense to have "owners" and "shared_with" - see the bottom of the gRPC refactoring proposal: Proposal: gRPC Refactoring #1881 - How would you permit a user to only acquire a restricted set of places? As far as I can see, the capabilities are not Place/Resource-specific?
The proposed capabilities are not Place/Resource-specific. That was a deliberate simplification where principals would have roles/relationships to specific assets such as Places or Reservations. The current proposal aims to cover the common/global cases first but open to discussions on how it could be extended
All reactions
In the old API, several unary RPCs effectively depend on a prior stream-side identity registration. That makes auth awkward to review and implement cleanly, because authorization for a unary RPC partly depends on unrelated stream state. With the refactor, identity is available on the RPC being authorized, and the coordinator can make the authorization decision locally in that handler.
Keeping auth on top of the refactor avoids designing two auth paths, one for legacy stream/session identity and another for the new unary API. We planned to keep the legacy StartupDone fallback for backwards compatibility, but we do not want the new auth model to be constrained by that.
Hmm. I've discussed the gRPC refactor (unary RPCs, StartupDone) with @Emantor and @Bastian-Krause for most of the day and I'm not yet convinced that adding new RPCs for listing requests is the right way forward. There are several open questions which I'd prefer not to block adding authentication, as e.g. #1888 is also working in that direction.
It seems to me that the first two commits of #1918 basically contain the machinery so that individual RPC could use information provided by infer_peer_identity and pass it into a (to be added) policy mechanism/plugin.
We'll need to keep sending the StartupDone message for a while to support new clients on old coordinators, but this also allows us to keep creating the ClientSession in a single place (either from the peer identity if provided or from the StartupDone contents otherwise). This way, we have separate commits/PRs for metadata-based identity, adding authrization, then requiring authentication in the coordinator and finally dropping the StartupDone message in the client.
All reactions
Hi @jluebbe, we could certainly split down #1918 into the first two commits if that helps get that part in. We have split #1918 into that and #1919, so if we the first set of refactors are agreed on we could get those in and do #1919 after we start auth.
For the fallback identity path, do we need to support old clients when auth is enabled?
New clients should keep sending StartupDone for compatibility with old coordinators. But for a coordinator running in an auth-required mode, it seems reasonable to require new clients from release onwards? This would simplify the code and the possible test combos (i.e. saving integration/unti testing old clients with auth etc)
All reactions
We should not support old clients with auth enabled. We still support StartupDone for old clients when auth is disabled, but a coordinator requiring Auth will require the metadata based identity.
All reactions
-
👍 1
Building on top of auth, there could then be an optional credentials dict on Resources which is only send from the exporter to clients which have acquired a place with that resource.
All reactions
think that should be kept separate from this first proposal on auth/capabilities. But we will have a follow up proposal that will cover sensitive fields in resources
All reactions
-
👍 2