SolarEdgeTech / pyctuator

Monitor Python applications using Spring Boot Admin
Apache License 2.0
172 stars 17 forks source link

How do I secure pyctuator endpoints with basic auth (FastAPI)? #67

Closed thurse93 closed 2 years ago

thurse93 commented 2 years ago

In the API of the Pyctuator object I did not find a way to secure the provided endpoints with BasicAuth. What would be the most straightforward way of doing this using FastAPI?

Also, how can i communicate the credentials to Spring Boot Admin?

If I understood https://codecentric.github.io/spring-boot-admin/current/#spring-boot-admin-client right, I can use something like:

    metadata={
        "user.name": "foo",
        "user.password": "bar"
    }

In the metadata arg of Pyctuator. Is that correct?

michaelyaakoby commented 2 years ago

Your reference is for the java spring-boot-admin-client. In Pyctuator, see https://github.com/SolarEdgeTech/pyctuator#spring-boot-admin-using-basic-authentication

thurse93 commented 2 years ago

Your reference is for the java spring-boot-admin-client. In Pyctuator, see https://github.com/SolarEdgeTech/pyctuator#spring-boot-admin-using-basic-authentication

Oh, I think there was a missunderstanding. I do not want to authenticate against a protected Spring Boot Admin Instance. Instead I want to secure the Endpoints of the App itself. Custom health providers could leak sensitive information, so I want some kind of BasicAuth-Mechanism for every route under /pyctuator.

michaelyaakoby commented 2 years ago

Hi @thurse93,

In the case of protecting the /pyctuator paths, I think it should be the responsibility of the app developer to protect the app's API. I mean, there so many ways to protect an API (starting with basic-auth, oauth2, client-certificate, adding all kinds of RBAC etc), that it doesn't make sense for Pyctuator to support all of them.

However, after you figure out how to protect your web app using FastAPI, it would be great if you could share an example here or even submit a pull-request with the example added to the examples folder.

Thanks

thurse93 commented 2 years ago

Hey @michaelyaakoby ,

100% agree with that. But atm securing the FastAPI endpoints normally happens on a per-endpoint / per-router basis, see: https://fastapi.tiangolo.com/advanced/security/http-basic-auth/ Since the Routing definition is taking place in pyctuator, the should be some kind of API to add at least the most basic kind of authentication.

More importantly, again referring to the spring boot admin docs: https://codecentric.github.io/spring-boot-admin/current/#_securing_client_actuator_endpoints If I understand this section correctly SBA has builtin support for BasicAuth against the clients Endpoints. So on registration with SBA the client application can provide the metadata credentials (see https://codecentric.github.io/spring-boot-admin/current/#_sba_client / initial comment) needed for SBA to access its endpoints. If this is correct I think pyctuator should provide some kind of interface for this functionality.

michaelyaakoby commented 2 years ago

Oh, now I see the issue - reopening and lets try to find a way to configure FastAPI security using a "path" and not a specific function.

michaelyaakoby commented 2 years ago

Might be possible to inject the auth dependency to the router, see https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter. I'll try this later.

thurse93 commented 2 years ago

I made a proof of concept, just for FastAPI: https://github.com/thurse93/pyctuator/commit/146d2ba6fde36b31ab39f728e1c33486ed9e1f48

(Since I changed the signature of your core classes, updated FastAPI and did not adjust the tests its far from a PR)

Minimal working example would be as follows (assuming SBA runs on http://localhost:8090):

import uvicorn
from fastapi import FastAPI

from pyctuator.auth import BasicAuth
from pyctuator.pyctuator import Pyctuator

app = FastAPI()

pyctuator = Pyctuator(
    app,
    app.title,
    "http://localhost:8080",
    "http://localhost:8080/pyctuator",
    "http://localhost:8090/instances",
    pyctuator_endpoint_auth=BasicAuth(username="foo", password="bar")
)

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8080)

SBA gets the credentials via the registration metadata (fields are user.name and user.password literal, no nesting) and successfully authenticated itself against the pyctuator endpoints.

I think its quite straightforward to do. Unfortunately I dont know much about the other web frameworks you're using so I can't provide much help there.

michaelyaakoby commented 2 years ago

Thanks @thurse93, its a good start.

I see you were using the approach of including the credentials in the registration request as described here.

Somehow it feels wrong that Pyctuator would be responsible for securing its own routes - I think its better if we can find a way to do this from outside Pyctuator and only have Pyctuator include the credentials in the metadata of the registration.

I'm trying to see if its possible with FastAPI to secure a path instead of using the dependency parameter - if we figure this out, than all you need to do is to add the credentials to the metadata which is already supported.

Anyways, yours is a good direction - i'll be digging too, and please do write if you find a way of enabling authentication "from outside".

thurse93 commented 2 years ago

@michaelyaakoby Alright, I'll get your point. Unfortunately I didn't find a clean way to do this.

One option of course would be to add a middleware for the whole application which seems kind of overkill.

Another one may be to crawl the routes attribute of the FastAPI Object after registration of the APIRouter (FastAPI uses starlette and its routing system under the hood). But even if you follow this rather hacky path I don't see a way of alternating the routing information afterwards.

I think the problem with FastAPI is that it was designed as a very easy way of providing well-documented Restful APIs for various backends. So it makes a lot of assumptions how APIs should be built. Everything source I found expects you to define your security mechanisms at the endpoint or routing level.

michaelyaakoby commented 2 years ago

Was a busy week, starting to dig now, will update.

michaelyaakoby commented 2 years ago

@thurse93, i've chaned Pyctuator to accept an optional customizer function which in case of FastAPI is receiving the router as a parameter.

See #70

WDYT?

Of course I need to document this.

michaelyaakoby commented 2 years ago

I've tidied up and completed the example in #70. Once this will be reviewed, i'll merge and release.

thurse93 commented 2 years ago

@michaelyaakoby That looks great! Sorry for the late reply, I was on vacation. Thank you very much for the realization :)

michaelyaakoby commented 2 years ago

Done. 0.17.0