package getsvc import ( "context" "crypto/ecdsa" "errors" "fmt" "sync" objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/pkg/tracing" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc" rpcclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/client" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/internal" frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) type headRequestForwarder struct { Request *objectV2.HeadRequest Response *objectV2.HeadResponse OnceResign *sync.Once ObjectAddr oid.Address Key *ecdsa.PrivateKey } func (f *headRequestForwarder) forwardRequestToNode(ctx context.Context, addr network.Address, c client.MultiAddressClient, pubkey []byte) (*object.Object, error) { ctx, span := tracing.StartSpanFromContext(ctx, "headRequestForwarder.forwardRequestToNode", trace.WithAttributes(attribute.String("address", addr.String())), ) defer span.End() var err error // once compose and resign forwarding request f.OnceResign.Do(func() { // compose meta header of the local server metaHdr := new(session.RequestMetaHeader) metaHdr.SetTTL(f.Request.GetMetaHeader().GetTTL() - 1) // TODO: #1165 think how to set the other fields metaHdr.SetOrigin(f.Request.GetMetaHeader()) writeCurrentVersion(metaHdr) f.Request.SetMetaHeader(metaHdr) err = signature.SignServiceMessage(f.Key, f.Request) }) if err != nil { return nil, err } headResp, err := f.sendHeadRequest(ctx, addr, c) if err != nil { return nil, err } if err := f.verifyResponse(headResp, pubkey); err != nil { return nil, err } var ( hdr *objectV2.Header idSig *refs.Signature ) switch v := headResp.GetBody().GetHeaderPart().(type) { case nil: return nil, fmt.Errorf("unexpected header type %T", v) case *objectV2.ShortHeader: if hdr, err = f.getHeaderFromShortHeader(v); err != nil { return nil, err } case *objectV2.HeaderWithSignature: if hdr, idSig, err = f.getHeaderAndSignature(v); err != nil { return nil, err } case *objectV2.SplitInfo: si := object.NewSplitInfoFromV2(v) return nil, object.NewSplitInfoError(si) } objv2 := new(objectV2.Object) objv2.SetHeader(hdr) objv2.SetSignature(idSig) obj := object.NewFromV2(objv2) obj.SetID(f.ObjectAddr.Object()) return obj, nil } func (f *headRequestForwarder) getHeaderFromShortHeader(sh *objectV2.ShortHeader) (*objectV2.Header, error) { if !f.Request.GetBody().GetMainOnly() { return nil, fmt.Errorf("wrong header part type: expected %T, received %T", (*objectV2.ShortHeader)(nil), (*objectV2.HeaderWithSignature)(nil), ) } hdr := new(objectV2.Header) hdr.SetPayloadLength(sh.GetPayloadLength()) hdr.SetVersion(sh.GetVersion()) hdr.SetOwnerID(sh.GetOwnerID()) hdr.SetObjectType(sh.GetObjectType()) hdr.SetCreationEpoch(sh.GetCreationEpoch()) hdr.SetPayloadHash(sh.GetPayloadHash()) hdr.SetHomomorphicHash(sh.GetHomomorphicHash()) return hdr, nil } func (f *headRequestForwarder) getHeaderAndSignature(hdrWithSig *objectV2.HeaderWithSignature) (*objectV2.Header, *refs.Signature, error) { if f.Request.GetBody().GetMainOnly() { return nil, nil, fmt.Errorf("wrong header part type: expected %T, received %T", (*objectV2.HeaderWithSignature)(nil), (*objectV2.ShortHeader)(nil), ) } if hdrWithSig == nil { return nil, nil, errors.New("nil object part") } hdr := hdrWithSig.GetHeader() idSig := hdrWithSig.GetSignature() if idSig == nil { // TODO(@cthulhu-rider): #1387 use "const" error return nil, nil, errors.New("missing signature") } binID, err := f.ObjectAddr.Object().Marshal() if err != nil { return nil, nil, fmt.Errorf("marshal ID: %w", err) } var sig frostfscrypto.Signature if err := sig.ReadFromV2(*idSig); err != nil { return nil, nil, fmt.Errorf("can't read signature: %w", err) } if !sig.Verify(binID) { return nil, nil, errors.New("invalid object ID signature") } return hdr, idSig, nil } func (f *headRequestForwarder) sendHeadRequest(ctx context.Context, addr network.Address, c client.MultiAddressClient) (*objectV2.HeadResponse, error) { var headResp *objectV2.HeadResponse err := c.RawForAddress(addr, func(cli *rpcclient.Client) error { var e error headResp, e = rpc.HeadObject(cli, f.Request, rpcclient.WithContext(ctx)) return e }) if err != nil { return nil, fmt.Errorf("sending the request failed: %w", err) } return headResp, nil } func (f *headRequestForwarder) verifyResponse(headResp *objectV2.HeadResponse, pubkey []byte) error { // verify response key if err := internal.VerifyResponseKeyV2(pubkey, headResp); err != nil { return err } // verify response structure if err := signature.VerifyServiceMessage(headResp); err != nil { return fmt.Errorf("response verification failed: %w", err) } if err := checkStatus(f.Response.GetMetaHeader().GetStatus()); err != nil { return err } return nil }