blob: f2803685fb7a34f681d5d4ac6d2e0a97f66a889e [file] [log] [blame] [edit]
#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