diff --git a/control/cmd/control/BUILD.bazel b/control/cmd/control/BUILD.bazel index 7386db661a..a37b41e50f 100644 --- a/control/cmd/control/BUILD.bazel +++ b/control/cmd/control/BUILD.bazel @@ -29,6 +29,7 @@ go_library( "//control/segreq/connect:go_default_library", "//control/segreq/grpc:go_default_library", "//control/trust:go_default_library", + "//control/trust/connect:go_default_library", "//control/trust/grpc:go_default_library", "//control/trust/metrics:go_default_library", "//pkg/addr:go_default_library", @@ -75,7 +76,9 @@ go_library( "//private/topology:go_default_library", "//private/trust:go_default_library", "//private/trust/compat:go_default_library", + "//private/trust/connect:go_default_library", "//private/trust/grpc:go_default_library", + "//private/trust/happy:go_default_library", "//private/trust/metrics:go_default_library", "@com_github_go_chi_chi_v5//:go_default_library", "@com_github_go_chi_cors//:go_default_library", diff --git a/control/cmd/control/main.go b/control/cmd/control/main.go index f84c377e70..ab45373f97 100644 --- a/control/cmd/control/main.go +++ b/control/cmd/control/main.go @@ -62,6 +62,7 @@ import ( segreqconnect "github.com/scionproto/scion/control/segreq/connect" segreqgrpc "github.com/scionproto/scion/control/segreq/grpc" cstrust "github.com/scionproto/scion/control/trust" + cstrustconnect "github.com/scionproto/scion/control/trust/connect" cstrustgrpc "github.com/scionproto/scion/control/trust/grpc" cstrustmetrics "github.com/scionproto/scion/control/trust/metrics" "github.com/scionproto/scion/pkg/addr" @@ -108,7 +109,9 @@ import ( "github.com/scionproto/scion/private/topology" "github.com/scionproto/scion/private/trust" "github.com/scionproto/scion/private/trust/compat" + trustconnect "github.com/scionproto/scion/private/trust/connect" trustgrpc "github.com/scionproto/scion/private/trust/grpc" + trusthappy "github.com/scionproto/scion/private/trust/happy" trustmetrics "github.com/scionproto/scion/private/trust/metrics" ) @@ -305,10 +308,24 @@ func realMain(ctx context.Context) error { } provider := trust.FetchingProvider{ DB: trustDB, - Fetcher: trustgrpc.Fetcher{ - IA: topo.IA(), - Dialer: dialer, - Requests: libmetrics.NewPromCounter(trustmetrics.RPC.Fetches), + Fetcher: trusthappy.Fetcher{ + Connect: trustconnect.Fetcher{ + IA: topo.IA(), + Dialer: (&squic.EarlyDialerFactory{ + Transport: quicStack.InsecureDialer.Transport, + TLSConfig: func() *tls.Config { + cfg := quicStack.InsecureDialer.TLSConfig.Clone() + cfg.NextProtos = []string{"h3", "SCION"} + return cfg + }(), + Rewriter: dialer.Rewriter, + }).NewDialer, + }, + Grpc: trustgrpc.Fetcher{ + IA: topo.IA(), + Dialer: dialer, + Requests: libmetrics.NewPromCounter(trustmetrics.RPC.Fetches), + }, }, Recurser: trust.ASLocalRecurser{IA: topo.IA()}, // XXX(roosd): cyclic dependency on router. It is set below. @@ -375,6 +392,7 @@ func realMain(ctx context.Context) error { //connectMux.Handle(cpconnect.NewTrustMaterialServiceHandler(nil)) cppb.RegisterTrustMaterialServiceServer(quicServer, trustServer) cppb.RegisterTrustMaterialServiceServer(tcpServer, trustServer) + connectMux.Handle(cpconnect.NewTrustMaterialServiceHandler(cstrustconnect.MaterialServer{MaterialServer: trustServer})) // Handle beaconing. segmentCreationServer := &beaconinggrpc.SegmentCreationServer{ diff --git a/control/trust/connect/material.go b/control/trust/connect/material.go index 34b33768db..cb54648196 100644 --- a/control/trust/connect/material.go +++ b/control/trust/connect/material.go @@ -12,7 +12,7 @@ import ( var _ control_planeconnect.TrustMaterialServiceHandler = MaterialServer{} type MaterialServer struct { - grpc.MaterialServer + *grpc.MaterialServer } func (m MaterialServer) Chains(ctx context.Context, req *connect.Request[control_plane.ChainsRequest]) (*connect.Response[control_plane.ChainsResponse], error) { diff --git a/private/trust/connect/BUILD.bazel b/private/trust/connect/BUILD.bazel new file mode 100644 index 0000000000..146e6c2988 --- /dev/null +++ b/private/trust/connect/BUILD.bazel @@ -0,0 +1,20 @@ +load("//tools/lint:go.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["fetcher.go"], + importpath = "github.com/scionproto/scion/private/trust/connect", + visibility = ["//visibility:public"], + deps = [ + "//bufgen/proto/control_plane/v1/control_planeconnect:go_default_library", + "//pkg/addr:go_default_library", + "//pkg/connect:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/scrypto/cppki:go_default_library", + "//pkg/snet/squic:go_default_library", + "//private/trust:go_default_library", + "//private/trust/grpc:go_default_library", + "@com_connectrpc_connect//:go_default_library", + "@com_github_quic_go_quic_go//http3:go_default_library", + ], +) diff --git a/private/trust/connect/fetcher.go b/private/trust/connect/fetcher.go new file mode 100644 index 0000000000..46b9b09bb2 --- /dev/null +++ b/private/trust/connect/fetcher.go @@ -0,0 +1,79 @@ +package connect + +import ( + "context" + "crypto/x509" + "net" + + "connectrpc.com/connect" + "github.com/quic-go/quic-go/http3" + "github.com/scionproto/scion/bufgen/proto/control_plane/v1/control_planeconnect" + "github.com/scionproto/scion/pkg/addr" + libconnect "github.com/scionproto/scion/pkg/connect" + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/scrypto/cppki" + "github.com/scionproto/scion/pkg/snet/squic" + "github.com/scionproto/scion/private/trust" + "github.com/scionproto/scion/private/trust/grpc" +) + +type Fetcher struct { + // IA is the local ISD-AS. + IA addr.IA + // Dialer dials a new gRPC connection. + Dialer func(net.Addr, ...squic.EarlyDialerOption) squic.EarlyDialer +} + +// Chains fetches certificate chains over the network +func (f Fetcher) Chains(ctx context.Context, query trust.ChainQuery, + server net.Addr) ([][]*x509.Certificate, error) { + + dialer := f.Dialer(server) + client := control_planeconnect.NewTrustMaterialServiceClient( + libconnect.HTTPClient{ + RoundTripper: &http3.RoundTripper{ + Dial: dialer.DialEarly, + }, + }, + libconnect.BaseUrl(server), + ) + rep, err := client.Chains(ctx, connect.NewRequest(grpc.ChainQueryToReq(query))) + if err != nil { + return nil, serrors.WrapStr("fetching chains over connect", err) + } + chains, _, err := grpc.RepToChains(rep.Msg.Chains) + if err != nil { + return nil, serrors.WrapStr("parsing chains", err) + } + if err := grpc.CheckChainsMatchQuery(query, chains); err != nil { + return nil, serrors.WrapStr("chains do not match query", err) + } + return chains, nil +} + +func (f Fetcher) TRC(ctx context.Context, id cppki.TRCID, + server net.Addr) (cppki.SignedTRC, error) { + + dialer := f.Dialer(server) + client := control_planeconnect.NewTrustMaterialServiceClient( + libconnect.HTTPClient{ + RoundTripper: &http3.RoundTripper{ + Dial: dialer.DialEarly, + }, + }, + libconnect.BaseUrl(server), + ) + rep, err := client.TRC(ctx, connect.NewRequest(grpc.IDToReq(id))) + if err != nil { + return cppki.SignedTRC{}, serrors.WrapStr("fetching chains over connect", err) + } + trc, err := cppki.DecodeSignedTRC(rep.Msg.Trc) + if err != nil { + return cppki.SignedTRC{}, serrors.WrapStr("parse TRC reply", err) + } + if trc.TRC.ID != id { + return cppki.SignedTRC{}, serrors.New("received wrong TRC", "expected", id, + "actual", trc.TRC.ID) + } + return trc, nil +} diff --git a/private/trust/grpc/BUILD.bazel b/private/trust/grpc/BUILD.bazel index a306375b1d..18ff19e4ac 100644 --- a/private/trust/grpc/BUILD.bazel +++ b/private/trust/grpc/BUILD.bazel @@ -29,14 +29,13 @@ go_library( go_test( name = "go_default_test", srcs = [ - "export_test.go", "fetcher_test.go", "main_test.go", "proto_test.go", ], data = glob(["testdata/**"]), - embed = [":go_default_library"], deps = [ + ":go_default_library", "//pkg/private/serrors:go_default_library", "//pkg/private/xtest:go_default_library", "//pkg/proto/control_plane:go_default_library", diff --git a/private/trust/grpc/export_test.go b/private/trust/grpc/export_test.go deleted file mode 100644 index 0bb09962e2..0000000000 --- a/private/trust/grpc/export_test.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020 Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package grpc - -var ( - ChainQueryToReq = chainQueryToReq - RepToChains = repToChains - IDToReq = idToReq -) diff --git a/private/trust/grpc/fetcher.go b/private/trust/grpc/fetcher.go index ff852bcd91..bc58dbc190 100644 --- a/private/trust/grpc/fetcher.go +++ b/private/trust/grpc/fetcher.go @@ -77,13 +77,13 @@ func (f Fetcher) Chains(ctx context.Context, query trust.ChainQuery, } defer conn.Close() client := cppb.NewTrustMaterialServiceClient(conn) - rep, err := client.Chains(ctx, chainQueryToReq(query), grpc.RetryProfile...) + rep, err := client.Chains(ctx, ChainQueryToReq(query), grpc.RetryProfile...) if err != nil { f.updateMetric(span, labels.WithResult(trustmetrics.ErrTransmit), err) return nil, serrors.WrapStr("receiving chains", err) } - chains, res, err := repToChains(rep.Chains) + chains, res, err := RepToChains(rep.Chains) if err != nil { f.updateMetric(span, labels.WithResult(res), err) return nil, err @@ -93,7 +93,7 @@ func (f Fetcher) Chains(ctx context.Context, query trust.ChainQuery, "chains", len(chains), ) - if err := checkChainsMatchQuery(query, chains); err != nil { + if err := CheckChainsMatchQuery(query, chains); err != nil { f.updateMetric(span, labels.WithResult(trustmetrics.ErrMismatch), err) return nil, serrors.WrapStr("chains do not match query", err) } @@ -123,7 +123,7 @@ func (f Fetcher) TRC(ctx context.Context, id cppki.TRCID, } defer conn.Close() client := cppb.NewTrustMaterialServiceClient(conn) - rep, err := client.TRC(ctx, idToReq(id), grpc.RetryProfile...) + rep, err := client.TRC(ctx, IDToReq(id), grpc.RetryProfile...) if err != nil { f.updateMetric(span, labels.WithResult(trustmetrics.ErrTransmit), err) return cppki.SignedTRC{}, serrors.WrapStr("receiving TRC", err) @@ -176,7 +176,7 @@ func addTRCSpan(ctx context.Context, return span, ctx } -func checkChainsMatchQuery(query trust.ChainQuery, chains [][]*x509.Certificate) error { +func CheckChainsMatchQuery(query trust.ChainQuery, chains [][]*x509.Certificate) error { for i, chain := range chains { ia, err := cppki.ExtractIA(chain[0].Subject) if err != nil { diff --git a/private/trust/grpc/proto.go b/private/trust/grpc/proto.go index 9902932d1b..fd506b16e6 100644 --- a/private/trust/grpc/proto.go +++ b/private/trust/grpc/proto.go @@ -26,7 +26,7 @@ import ( trustmetrics "github.com/scionproto/scion/private/trust/internal/metrics" ) -func chainQueryToReq(query trust.ChainQuery) *cppb.ChainsRequest { +func ChainQueryToReq(query trust.ChainQuery) *cppb.ChainsRequest { return &cppb.ChainsRequest{ IsdAs: uint64(query.IA), SubjectKeyId: query.SubjectKeyID, @@ -34,7 +34,7 @@ func chainQueryToReq(query trust.ChainQuery) *cppb.ChainsRequest { } } -func repToChains(pbChains []*cppb.Chain) ([][]*x509.Certificate, string, error) { +func RepToChains(pbChains []*cppb.Chain) ([][]*x509.Certificate, string, error) { chains := make([][]*x509.Certificate, 0, len(pbChains)) for _, c := range pbChains { var err error @@ -53,7 +53,7 @@ func repToChains(pbChains []*cppb.Chain) ([][]*x509.Certificate, string, error) return chains, "", nil } -func idToReq(id cppki.TRCID) *cppb.TRCRequest { +func IDToReq(id cppki.TRCID) *cppb.TRCRequest { return &cppb.TRCRequest{ Isd: uint32(id.ISD), Base: uint64(id.Base), diff --git a/private/trust/happy/BUILD.bazel b/private/trust/happy/BUILD.bazel new file mode 100644 index 0000000000..ea8d7bd598 --- /dev/null +++ b/private/trust/happy/BUILD.bazel @@ -0,0 +1,14 @@ +load("//tools/lint:go.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["fetcher.go"], + importpath = "github.com/scionproto/scion/private/trust/happy", + visibility = ["//visibility:public"], + deps = [ + "//pkg/log:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/scrypto/cppki:go_default_library", + "//private/trust:go_default_library", + ], +) diff --git a/private/trust/happy/fetcher.go b/private/trust/happy/fetcher.go new file mode 100644 index 0000000000..19e650dc66 --- /dev/null +++ b/private/trust/happy/fetcher.go @@ -0,0 +1,130 @@ +package happy + +import ( + "context" + "crypto/x509" + "net" + "sync" + "time" + + "github.com/scionproto/scion/pkg/log" + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/scrypto/cppki" + "github.com/scionproto/scion/private/trust" +) + +// BeaconSenderFactory can be used to create beacon senders. +type Fetcher struct { + Connect trust.Fetcher + Grpc trust.Fetcher +} + +func (f Fetcher) Chains(ctx context.Context, query trust.ChainQuery, + server net.Addr) ([][]*x509.Certificate, error) { + + abortCtx, cancel := context.WithCancel(ctx) + var wg sync.WaitGroup + wg.Add(2) + + reps := [2][][]*x509.Certificate{} + errs := [2]error{} + + go func() { + defer log.HandlePanic() + defer wg.Done() + rep, err := f.Connect.Chains(abortCtx, query, server) + if err == nil { + reps[0] = rep + log.Info("Received chains via connect") + cancel() + } else { + log.Info("Failed to fetch chains via connect", "err", err) + } + errs[0] = err + }() + + go func() { + defer log.HandlePanic() + defer wg.Done() + select { + case <-abortCtx.Done(): + return + case <-time.After(500 * time.Millisecond): + } + rep, err := f.Grpc.Chains(abortCtx, query, server) + if err == nil { + reps[0] = rep + log.Info("Received chains via gRPC") + cancel() + } else { + log.Info("Failed to fetch chains via gRPC", "err", err) + } + errs[1] = err + }() + + wg.Wait() + var combinedErrs serrors.List + for i := range reps { + if errs[i] != nil { + combinedErrs = append(combinedErrs, errs[i]) + continue + } + return reps[i], nil + } + return nil, combinedErrs.ToError() +} + +func (f Fetcher) TRC(ctx context.Context, id cppki.TRCID, + server net.Addr) (cppki.SignedTRC, error) { + + abortCtx, cancel := context.WithCancel(ctx) + var wg sync.WaitGroup + wg.Add(2) + + reps := [2]cppki.SignedTRC{} + errs := [2]error{} + + go func() { + defer log.HandlePanic() + defer wg.Done() + rep, err := f.Connect.TRC(abortCtx, id, server) + if err == nil { + reps[0] = rep + log.Info("Received TRC via connect") + cancel() + } else { + log.Info("Failed to fetch TRC via connect", "err", err) + } + errs[0] = err + }() + + go func() { + defer log.HandlePanic() + defer wg.Done() + select { + case <-abortCtx.Done(): + return + case <-time.After(500 * time.Millisecond): + } + rep, err := f.Grpc.TRC(abortCtx, id, server) + if err == nil { + reps[0] = rep + log.Info("Received TRC via gRPC") + cancel() + } else { + log.Info("Failed to fetch TRC via gRPC", "err", err) + } + errs[1] = err + }() + + wg.Wait() + var combinedErrs serrors.List + for i := range reps { + if errs[i] != nil { + combinedErrs = append(combinedErrs, errs[i]) + continue + } + return reps[i], nil + } + return cppki.SignedTRC{}, combinedErrs.ToError() +}