Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions app/controlplane/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ import (
"github.com/go-kratos/kratos/v2/config/file"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/transport"
"github.com/go-kratos/kratos/v2/transport/grpc"
"github.com/go-kratos/kratos/v2/transport/http"
)

var (
Expand All @@ -61,11 +59,16 @@ func init() {
flag.StringVar(&flagconf, "conf", "../configs", "config path, eg: -conf config.yaml")
}

func newApp(logger log.Logger, gs *grpc.Server, hs *http.Server, ms *server.HTTPMetricsServer, profilerSvc *server.HTTPProfilerServer,
func newApp(logger log.Logger, gs *server.GRPCServers, api *server.APIServer, ms *server.HTTPMetricsServer, profilerSvc *server.HTTPProfilerServer,
expirer *biz.WorkflowRunExpirerUseCase, plugins sdk.AvailablePlugins,
userAccessSyncer *biz.UserAccessSyncerUseCase, casBackendChecker *biz.CASBackendChecker,
apiTokenStaleRevoker *biz.APITokenStaleRevoker, loginHandoffs *biz.LoginHandoffUseCase, cfg *conf.Bootstrap) *app {
servers := []transport.Server{gs, hs, ms}
servers := []transport.Server{api, ms}
// APIServer owns the gRPC handler's lifecycle. Only the legacy server
// starts a native gRPC listener.
if gs.Legacy != nil {
servers = append(servers, gs.Legacy)
}
if cfg.EnableProfiler {
servers = append(servers, profilerSvc)
}
Expand Down
6 changes: 3 additions & 3 deletions app/controlplane/cmd/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 49 additions & 17 deletions app/controlplane/internal/conf/controlplane/config/v1/conf.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions app/controlplane/internal/conf/controlplane/config/v1/conf.proto
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ message Server {
// Optional
string external_url = 4;
google.protobuf.Duration timeout = 5;
// TLS for the single port, which serves gRPC, gRPC-Web and HTTP: the
// listener negotiates HTTP/2 and HTTP/1.1 through ALPN. A partial
// configuration (only one of certificate/private_key) is a startup
// error. Note: server.http_metrics reuses this message type but does
// not support TLS.
TLS tls_config = 6;
}

message TLS {
Expand All @@ -247,6 +253,12 @@ message Server {
GRPC grpc = 2;
// HTTPMetrics defines the HTTP server that exposes prometheus metrics
HTTP http_metrics = 3;
// Legacy gRPC-only listener that serves native gRPC on its own port, next
// to the single port that already serves gRPC, gRPC-Web and HTTP. It exists
// for the deprecation period of docs/specs/issue-3628-single-endpoint.md.
// Defaults to true when unset. Set it to false to listen on the single port
// only, plus the internal metrics and profiler ports.
optional bool legacy_grpc_listener = 4;
}

message Data {
Expand Down
88 changes: 68 additions & 20 deletions app/controlplane/internal/server/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"crypto/tls"
"fmt"
"net/url"
"regexp"

v1 "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
Expand Down Expand Up @@ -109,29 +110,56 @@ var (
allButOrganizationOperationsSkipRegexp = regexp.MustCompile("/controlplane.v1.OrganizationService/Create|/controlplane.v1.UserService/ListMemberships|/controlplane.v1.ContextService/Current|/controlplane.v1.AuthService/DeleteAccount")
)

// NewGRPCServer new a gRPC server.
func NewGRPCServer(opts *Opts) (*grpc.Server, error) {
// Opt-in histogram metrics for the interceptor
grpc_prometheus.EnableHandlingTimeHistogram()
// GRPCServers separates the HTTP handler from the optional native listener.
// Only Legacy must be started as a transport server.
type GRPCServers struct {
Handler *grpc.Server
Legacy *grpc.Server
}

// NOTE: kratos middlewares will always be run before plain grpc unary interceptors
// If you want to change the behavior, please refer to wrappers.go and the Prometheus wrapper as example
var serverOpts = []grpc.ServerOption{
grpc.Middleware(craftMiddleware(opts)...),
grpc.UnaryInterceptor(
protovalidateMiddleware.UnaryServerInterceptor(opts.Validator),
),
// NewGRPCServers assembles the HTTP handler and optional native listener.
func NewGRPCServers(opts *Opts) (*GRPCServers, error) {
servers := &GRPCServers{Handler: newGRPCHandler(opts)}
if legacyGRPCListenerEnabled(opts.ServerConfig) {
// Ignore unused legacy certificates when this listener is disabled.
legacy, err := NewGRPCServer(opts)
if err != nil {
return nil, err
}
servers.Legacy = legacy
}
return servers, nil
}

// newGRPCHandler builds the handler served on the shared HTTP endpoint.
func newGRPCHandler(opts *Opts) *grpc.Server {
// Kratos' stream interceptor needs a non-nil endpoint even when the gRPC
// handler never starts a native listener. Supply it explicitly rather than
// calling Endpoint(), which would bind an otherwise unused socket.
scheme := "grpc"
if opts.ServerConfig.GetHttp().GetTlsConfig().GetCertificate() != "" {
Comment thread
migmartri marked this conversation as resolved.
scheme = "grpcs"
}
return newGRPCServer(opts, grpc.Endpoint(&url.URL{
Scheme: scheme,
Host: opts.ServerConfig.GetHttp().GetAddr(),
}))
}

func legacyGRPCListenerEnabled(c *conf.Server) bool {
return c.LegacyGrpcListener == nil || *c.LegacyGrpcListener
}

// NewGRPCServer builds the native gRPC listener with its existing configuration.
// A partial TLS configuration keeps this listener plaintext, as it always did.
func NewGRPCServer(opts *Opts) (*grpc.Server, error) {
var serverOpts []grpc.ServerOption
if v := opts.ServerConfig.Grpc.Network; v != "" {
serverOpts = append(serverOpts, grpc.Network(v))
}
if v := opts.ServerConfig.Grpc.Addr; v != "" {
serverOpts = append(serverOpts, grpc.Address(v))
}
if v := opts.ServerConfig.Grpc.Timeout; v != nil {
serverOpts = append(serverOpts, grpc.Timeout(v.AsDuration()))
}
if tlsConf := opts.ServerConfig.Grpc.GetTlsConfig(); tlsConf != nil {
cert := tlsConf.GetCertificate()
privKey := tlsConf.GetPrivateKey()
Expand All @@ -146,6 +174,26 @@ func NewGRPCServer(opts *Opts) (*grpc.Server, error) {
}))
}
}
return newGRPCServer(opts, serverOpts...), nil
}

// newGRPCServer builds one gRPC server with the shared middleware chain,
// interceptors and service registrations, bound through the given extra
// options.
func newGRPCServer(opts *Opts, extraOpts ...grpc.ServerOption) *grpc.Server {
// Opt-in histogram metrics for the interceptor.
grpc_prometheus.EnableHandlingTimeHistogram()

var serverOpts = append([]grpc.ServerOption{
grpc.Middleware(craftMiddleware(opts)...),
grpc.UnaryInterceptor(
protovalidateMiddleware.UnaryServerInterceptor(opts.Validator),
),
}, extraOpts...)

if v := opts.ServerConfig.Grpc.Timeout; v != nil {
serverOpts = append(serverOpts, grpc.Timeout(v.AsDuration()))
}

// Raw grpc-go server options must be passed in a single grpc.Options call:
// kratos' grpc.Options assigns s.grpcOpts instead of appending, so a second
Expand Down Expand Up @@ -182,7 +230,7 @@ func NewGRPCServer(opts *Opts) (*grpc.Server, error) {
// Register Prometheus metrics
grpc_prometheus.Register(srv.Server)

return srv, nil
return srv
}

func craftMiddleware(opts *Opts) []middleware.Middleware {
Expand Down Expand Up @@ -288,13 +336,13 @@ func craftMiddleware(opts *Opts) []middleware.Middleware {

// If we should load the user
func requireCurrentUserMatcher() selector.MatchFunc {
return func(ctx context.Context, operation string) bool {
return func(_ context.Context, operation string) bool {
return !currentUserSkipRegexp.MatchString(operation)
}
}

func requireFullyConfiguredOrgMatcher() selector.MatchFunc {
return func(ctx context.Context, operation string) bool {
return func(_ context.Context, operation string) bool {
return !fullyConfiguredOrgSkipRegexp.MatchString(operation)
}
}
Expand All @@ -306,20 +354,20 @@ func requireFullyConfiguredCASBackendMatcher() selector.MatchFunc {
}

func allowListEnabled() selector.MatchFunc {
return func(ctx context.Context, operation string) bool {
return func(_ context.Context, operation string) bool {
return !allowListSkipRegexp.MatchString(operation)
}
}

func requireRobotAccountMatcher() selector.MatchFunc {
return func(ctx context.Context, operation string) bool {
return func(_ context.Context, operation string) bool {
return robotAccountRequireRegexp.MatchString(operation)
}
}

// Matches all operations that require to have a current organization
func requireAllButOrganizationOperationsMatcher() selector.MatchFunc {
return func(ctx context.Context, operation string) bool {
return func(_ context.Context, operation string) bool {
return !allButOrganizationOperationsSkipRegexp.MatchString(operation)
}
}
Loading
Loading