| #pragma once |
| |
| #include <functional> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/base/no_destructor.h" |
| #include "absl/base/thread_annotations.h" |
| #include "absl/functional/any_invocable.h" |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/synchronization/mutex.h" |
| #include "absl/time/time.h" |
| #include "absl/types/span.h" |
| #include "redfish_query_engine/file/uds.h" |
| #include "grpcpp/completion_queue.h" |
| #include "grpcpp/impl/service_type.h" |
| #include "grpcpp/security/auth_context.h" |
| #include "grpcpp/security/authorization_policy_provider.h" |
| #include "grpcpp/security/server_credentials.h" |
| #include "grpcpp/server.h" |
| #include "grpcpp/server_builder.h" |
| #include "grpcpp/support/status.h" |
| #include "proxy_config.pb.h" |
| #include "redfish_plugin.h" |
| #include "request_response.h" |
| #include "resource_authz.h" |
| #include "ssh_client.h" |
| #include "voyager/deferrable_priority_queue.hpp" |
| #include "voyager/priority_queue.hpp" |
| |
| namespace milotic { |
| |
| /* |
| * A `Proxy` implements a common interface to proxy HTTP requests from a gRPC |
| * service to an HTTP service. The proxy may include 'plugins' that intercept |
| * specific requests and handle them differently. These are implemented using |
| * the `RedfishPlugin` class, and are added to the proxy in the constructor. |
| * |
| * A proxy may have multiple gRPC services sending requests. These should be |
| * owned by the Proxy object. The proxy implementation will handle starting the |
| * services. e.g.: |
| * ``` |
| * class MyService : public some::grpc::Service { |
| * public: |
| * MyService(Proxy& proxy){...} |
| * }; |
| * Proxy proxy(...); |
| * proxy.AddService<MyService>(); |
| * ... |
| * proxy.ConfigureGrpc(...); |
| * ... |
| * ``` |
| */ |
| |
| class Proxy { |
| public: |
| using RequestPriority = voyager::TelemetryPriorityQueue::QueuePriority; |
| |
| class RequestJob : public voyager::Job { |
| public: |
| using Handler = |
| absl::AnyInvocable<void(const absl::StatusOr<ProxyResponse>&)>; |
| RequestJob(Proxy& proxy, RedfishPlugin::RequestVerb verb, |
| std::unique_ptr<ProxyRequest> request) |
| : proxy_(proxy), verb_(verb), request_(std::move(request)) {} |
| RequestJob(RequestJob&&) = delete; |
| RequestJob(const RequestJob&) = delete; |
| static absl::Status Run(voyager::Job& generic_job) { |
| return static_cast<RequestJob&>(generic_job).RunImpl(); |
| } |
| void Handle(Handler handler) ABSL_LOCKS_EXCLUDED(handler_mutex_); |
| absl::StatusOr<ProxyResponse>& response() { return response_; } |
| |
| private: |
| absl::Status RunImpl() ABSL_LOCKS_EXCLUDED(handler_mutex_); |
| |
| Proxy& proxy_; |
| RedfishPlugin::RequestVerb verb_; |
| std::unique_ptr<ProxyRequest> request_; |
| absl::StatusOr<ProxyResponse> response_; |
| absl::Mutex handler_mutex_; |
| Handler handler_ ABSL_GUARDED_BY(handler_mutex_); |
| bool has_run_ ABSL_GUARDED_BY(handler_mutex_) = false; |
| }; |
| |
| struct GrpcCredentials { |
| std::shared_ptr<grpc::ServerCredentials> net_creds; |
| std::shared_ptr<grpc::ServerCredentials> uds_creds; |
| }; |
| |
| struct Resources { |
| std::unique_ptr<SshClient> ssh_client; |
| }; |
| |
| // network_endpoint is the endpoint that is proxied. |
| // handlers is a list of additional `RedfishPlugin`s that will run before the |
| // default plugin. |
| explicit Proxy( |
| std::vector<Host> hosts, |
| absl::Span<std::unique_ptr<RedfishPlugin>> handlers = {}, |
| voyager::DeferrablePriorityQueue* queue = nullptr, |
| Resources resources = {}, |
| milotic_grpc_proxy::AuthorizationPolicy authorization_policy = {}, |
| std::unique_ptr<PermissionChecker> permission_checker = nullptr, |
| std::string name = ""); |
| Proxy(const Proxy&) = delete; |
| Proxy& operator=(const Proxy&) = delete; |
| Proxy(Proxy&&) = delete; |
| Proxy& operator=(Proxy&&) = delete; |
| ~Proxy(); |
| |
| const Resources& GetResources() const { return resources_; } |
| std::unique_ptr<ProxyRequest> CreateRequest( |
| absl::string_view redfish_id) const; |
| |
| absl::StatusOr<std::unique_ptr<ProxyRequest>> CreateAuthorizedRequest( |
| RedfishPlugin::RequestVerb verb, absl::string_view redfish_id, |
| const grpc::AuthContext& auth_context) const; |
| |
| // GetRequestAction can be used by plugins to handle a request without |
| // modifying it. paths_to_handle is a list of (verb, path) pairs that the |
| // plugin should handle. |
| // If the path matches but verb does not match, the request will be dropped. |
| // If the request path does not match any path, the request will be passed to |
| // the next plugin. |
| static RedfishPlugin::RequestAction GetRequestAction( |
| RedfishPlugin::RequestVerb verb, const ProxyRequest& request, |
| const std::vector<std::pair<RedfishPlugin::RequestVerb, |
| absl::string_view>>& paths_to_handle); |
| |
| absl::StatusOr<ProxyResponse> DispatchRequest( |
| RedfishPlugin::RequestVerb verb, |
| std::unique_ptr<ProxyRequest> http_request); |
| |
| absl::StatusOr<std::unique_ptr<RequestJob>> DispatchRequestToQueue( |
| RedfishPlugin::RequestVerb verb, |
| std::unique_ptr<ProxyRequest> http_request, |
| RequestPriority priority = RequestPriority::kLow); |
| |
| std::unique_ptr<RequestJob> CreateRequestJob( |
| RedfishPlugin::RequestVerb verb, |
| std::unique_ptr<ProxyRequest> http_request) { |
| return std::make_unique<RequestJob>(*this, verb, std::move(http_request)); |
| } |
| |
| absl::Status DispatchRequestToQueue( |
| std::unique_ptr<RequestJob> request_job, |
| RequestPriority priority = RequestPriority::kLow, |
| std::optional<absl::Time> after = std::nullopt); |
| |
| // TODO(jainrish): This should be removed. |
| absl::string_view GetNetworkEndpoint() const { |
| if (hosts_.empty()) { |
| return ""; |
| } |
| return hosts_[0].GetEndpoint(); |
| } |
| |
| absl::Status Subscribe(std::unique_ptr<ProxyRequest> http_request, |
| RedfishPlugin::EventHandler* handler); |
| |
| absl::StatusOr<std::unique_ptr<RedfishPlugin::EventHandler>> |
| BidirectionalStream(std::unique_ptr<ProxyRequest> http_request, |
| RedfishPlugin::EventHandler* handler); |
| |
| absl::Status ConfigGrpcAndStart( |
| const milotic_grpc_proxy::GrpcConfiguration& config, |
| const std::function<bool(const std::string&)>& is_safe_uds_root = |
| ecclesia::IsSafeUnixDomainSocketRoot, |
| const GrpcCredentials& credentials = |
| {.net_creds = grpc::InsecureServerCredentials(), |
| .uds_creds = grpc::InsecureServerCredentials()}, |
| const std::shared_ptr< |
| grpc::experimental::AuthorizationPolicyProviderInterface>& provider = |
| nullptr); |
| |
| // There may be multiple gRPC services that implement the upstream side of |
| // the proxy. These should always have a constructor that takes a reference to |
| // this `Proxy` object and a config proto as an argument, and should only be |
| // created using this method to ensure correct ownership. This function is |
| // templated to allow the legacy config protos to be used as arguments. |
| template <typename Config> |
| absl::Status AddService(const Config& config) { |
| google::protobuf::Any any; |
| any.PackFrom(config); |
| return AddService(any); |
| } |
| |
| absl::Status AddService(const google::protobuf::Any& config); |
| |
| // In order to register a service, create an instance of this struct with the |
| // service's config proto and service class as template parameters. This |
| // instance may be static, which will register the service when the binary |
| // starts. Example: |
| // ``` |
| // static Proxy::RegisterService<milotic_grpc_proxy::RedfishV1Options, |
| // ProxyRedfishV1Impl> |
| // redfish_v1_service_reg; |
| // ``` |
| template <typename Config, typename Service> |
| struct RegisterService { |
| RegisterService() { |
| service_creators().push_back( |
| [](Proxy& proxy, const google::protobuf::Any& config) |
| -> std::unique_ptr<grpc::Service> { |
| Config service_config; |
| if (!config.UnpackTo(&service_config)) { |
| return nullptr; |
| } |
| return std::make_unique<Service>(proxy, service_config); |
| }); |
| } |
| }; |
| |
| bool IsInitialized() const { return handler_initialized_; } |
| |
| grpc::Server* grpc_server() const { return server_.get(); } |
| |
| absl::string_view name() const { return name_; } |
| |
| voyager::DeferrablePriorityQueue* queue() const { return queue_; } |
| |
| private: |
| std::unique_ptr<grpc::Service> CreateService( |
| const google::protobuf::Any& config); |
| |
| static std::vector<absl::AnyInvocable<std::unique_ptr<grpc::Service>( |
| Proxy&, const google::protobuf::Any&) const>>& |
| service_creators() { |
| static absl::NoDestructor<std::vector<absl::AnyInvocable<std::unique_ptr< |
| grpc::Service>(Proxy&, const google::protobuf::Any&) const>>> |
| creators; |
| return *creators; |
| } |
| |
| absl::StatusOr<RedfishPlugin*> GetRequestHandler( |
| RedfishPlugin::RequestVerb verb, ProxyRequest& request); |
| |
| std::vector<Host> hosts_; |
| voyager::DeferrablePriorityQueue* queue_; |
| std::vector<std::unique_ptr<RedfishPlugin>> handlers_; |
| std::vector<std::unique_ptr<::grpc::Service>> services_; |
| bool handler_initialized_ = false; |
| Resources resources_; |
| milotic_grpc_proxy::AuthorizationPolicy authorization_policy_; |
| std::unique_ptr<PermissionChecker> permission_checker_; |
| std::unique_ptr<grpc::Server> server_; |
| std::string name_; |
| }; |
| |
| // Converts an absl::Status to a grpc::Status. |
| grpc::Status ConvertStatus(const absl::Status& status); |
| |
| } // namespace milotic |