2023-10-12 12:29:21 -07:00
package ccq
import (
"crypto/ecdsa"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"sort"
2023-10-13 15:04:43 -07:00
"strings"
2023-10-12 12:29:21 -07:00
"time"
"github.com/certusone/wormhole/node/pkg/common"
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
"github.com/certusone/wormhole/node/pkg/query"
"github.com/gorilla/mux"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"go.uber.org/zap"
"google.golang.org/protobuf/proto"
)
2023-10-13 15:04:43 -07:00
const MAX_BODY_SIZE = 5 * 1024 * 1024
2023-10-12 12:29:21 -07:00
type queryRequest struct {
Bytes string ` json:"bytes" `
Signature string ` json:"signature" `
}
type queryResponse struct {
Bytes string ` json:"bytes" `
Signatures [ ] string ` json:"signatures" `
}
type httpServer struct {
topic * pubsub . Topic
logger * zap . Logger
env common . Environment
2023-12-11 12:44:48 -08:00
permissions * Permissions
2023-10-12 12:29:21 -07:00
signerKey * ecdsa . PrivateKey
pendingResponses * PendingResponses
}
func ( s * httpServer ) handleQuery ( w http . ResponseWriter , r * http . Request ) {
// Set CORS headers for all requests.
w . Header ( ) . Set ( "Access-Control-Allow-Origin" , "*" )
// Set CORS headers for the preflight request
if r . Method == http . MethodOptions {
w . Header ( ) . Set ( "Access-Control-Allow-Methods" , "PUT, POST" )
w . Header ( ) . Set ( "Access-Control-Allow-Headers" , "Content-Type, X-Api-Key" )
w . Header ( ) . Set ( "Access-Control-Max-Age" , "3600" )
w . WriteHeader ( http . StatusNoContent )
return
}
2023-11-08 09:22:34 -08:00
start := time . Now ( )
allQueryRequestsReceived . Inc ( )
2023-10-19 08:32:23 -07:00
// Decode the body first. This is because the library seems to hang if we receive a large body and return without decoding it.
// This could be a slight waste of resources, but should not be a DoS risk because we cap the max body size.
var q queryRequest
err := json . NewDecoder ( http . MaxBytesReader ( w , r . Body , MAX_BODY_SIZE ) ) . Decode ( & q )
if err != nil {
2023-11-08 17:47:28 -08:00
s . logger . Error ( "failed to decode body" , zap . Error ( err ) )
2023-10-19 08:32:23 -07:00
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "failed_to_decode_body" ) . Inc ( )
2023-10-19 08:32:23 -07:00
return
}
2023-10-12 12:29:21 -07:00
// There should be one and only one API key in the header.
2023-10-13 15:04:43 -07:00
apiKeys , exists := r . Header [ "X-Api-Key" ]
if ! exists || len ( apiKeys ) != 1 {
2023-11-08 17:47:28 -08:00
s . logger . Error ( "received a request with the wrong number of api keys" , zap . Stringer ( "url" , r . URL ) , zap . Int ( "numApiKeys" , len ( apiKeys ) ) )
2023-10-12 12:29:21 -07:00
http . Error ( w , "api key is missing" , http . StatusUnauthorized )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "missing_api_key" ) . Inc ( )
2023-10-12 12:29:21 -07:00
return
}
2023-10-19 08:32:23 -07:00
apiKey := strings . ToLower ( apiKeys [ 0 ] )
2023-10-13 15:04:43 -07:00
// Make sure the user is authorized before we go any farther.
2023-12-11 12:44:48 -08:00
permEntry , exists := s . permissions . GetUserEntry ( apiKey )
2023-10-13 15:04:43 -07:00
if ! exists {
2023-11-08 17:47:28 -08:00
s . logger . Error ( "invalid api key" , zap . String ( "apiKey" , apiKey ) )
2023-10-13 15:04:43 -07:00
http . Error ( w , "invalid api key" , http . StatusForbidden )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "invalid_api_key" ) . Inc ( )
2023-10-13 15:04:43 -07:00
return
}
2023-12-11 09:24:05 -08:00
totalRequestsByUser . WithLabelValues ( permEntry . userName ) . Inc ( )
2023-10-13 15:04:43 -07:00
2023-10-12 12:29:21 -07:00
queryRequestBytes , err := hex . DecodeString ( q . Bytes )
if err != nil {
2023-11-08 17:47:28 -08:00
s . logger . Error ( "failed to decode request bytes" , zap . String ( "userId" , permEntry . userName ) , zap . Error ( err ) )
2023-10-12 12:29:21 -07:00
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "failed_to_decode_request" ) . Inc ( )
2023-12-11 09:24:05 -08:00
invalidRequestsByUser . WithLabelValues ( permEntry . userName ) . Inc ( )
2023-10-12 12:29:21 -07:00
return
}
signature , err := hex . DecodeString ( q . Signature )
if err != nil {
2023-11-08 17:47:28 -08:00
s . logger . Error ( "failed to decode signature bytes" , zap . String ( "userId" , permEntry . userName ) , zap . Error ( err ) )
2023-10-12 12:29:21 -07:00
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "failed_to_decode_signature" ) . Inc ( )
2023-12-11 09:24:05 -08:00
invalidRequestsByUser . WithLabelValues ( permEntry . userName ) . Inc ( )
2023-10-12 12:29:21 -07:00
return
}
signedQueryRequest := & gossipv1 . SignedQueryRequest {
QueryRequest : queryRequestBytes ,
Signature : signature ,
}
2023-11-08 17:47:28 -08:00
requestId := hex . EncodeToString ( signedQueryRequest . Signature )
s . logger . Info ( "received request from client" , zap . String ( "userId" , permEntry . userName ) , zap . String ( "requestId" , requestId ) )
2023-10-13 15:04:43 -07:00
if status , err := validateRequest ( s . logger , s . env , s . permissions , s . signerKey , apiKey , signedQueryRequest ) ; err != nil {
2023-11-08 17:47:28 -08:00
s . logger . Error ( "failed to validate request" , zap . String ( "userId" , permEntry . userName ) , zap . String ( "requestId" , requestId ) , zap . Int ( "status" , status ) , zap . Error ( err ) )
2023-10-12 12:29:21 -07:00
http . Error ( w , err . Error ( ) , status )
2023-12-11 09:24:05 -08:00
// Error specific metric has already been pegged.
invalidRequestsByUser . WithLabelValues ( permEntry . userName ) . Inc ( )
2023-10-12 12:29:21 -07:00
return
}
m := gossipv1 . GossipMessage {
Message : & gossipv1 . GossipMessage_SignedQueryRequest {
SignedQueryRequest : signedQueryRequest ,
} ,
}
b , err := proto . Marshal ( & m )
if err != nil {
2023-11-08 17:47:28 -08:00
s . logger . Error ( "failed to marshal gossip message" , zap . String ( "userId" , permEntry . userName ) , zap . String ( "requestId" , requestId ) , zap . Error ( err ) )
2023-10-12 12:29:21 -07:00
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "failed_to_marshal_gossip_msg" ) . Inc ( )
2023-12-11 09:24:05 -08:00
invalidRequestsByUser . WithLabelValues ( permEntry . userName ) . Inc ( )
2023-10-12 12:29:21 -07:00
return
}
pendingResponse := NewPendingResponse ( signedQueryRequest )
added := s . pendingResponses . Add ( pendingResponse )
if ! added {
2023-11-08 17:47:28 -08:00
s . logger . Info ( "duplicate request" , zap . String ( "userId" , permEntry . userName ) , zap . String ( "requestId" , requestId ) )
2023-10-12 12:29:21 -07:00
http . Error ( w , "Duplicate request" , http . StatusBadRequest )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "duplicate_request" ) . Inc ( )
2023-12-11 09:24:05 -08:00
invalidRequestsByUser . WithLabelValues ( permEntry . userName ) . Inc ( )
2023-10-12 12:29:21 -07:00
return
}
2023-11-08 17:47:28 -08:00
s . logger . Info ( "posting request to gossip" , zap . String ( "userId" , permEntry . userName ) , zap . String ( "requestId" , requestId ) )
2023-10-12 12:29:21 -07:00
err = s . topic . Publish ( r . Context ( ) , b )
if err != nil {
2023-11-08 17:47:28 -08:00
s . logger . Error ( "failed to publish gossip message" , zap . String ( "userId" , permEntry . userName ) , zap . String ( "requestId" , requestId ) , zap . Error ( err ) )
2023-10-12 12:29:21 -07:00
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "failed_to_publish_gossip_msg" ) . Inc ( )
2023-12-11 09:24:05 -08:00
invalidRequestsByUser . WithLabelValues ( permEntry . userName ) . Inc ( )
2023-10-12 12:29:21 -07:00
s . pendingResponses . Remove ( pendingResponse )
return
}
// Wait for the response or timeout
select {
case <- time . After ( query . RequestTimeout + 5 * time . Second ) :
2023-11-08 17:47:28 -08:00
s . logger . Info ( "publishing time out to client" , zap . String ( "userId" , permEntry . userName ) , zap . String ( "requestId" , requestId ) )
2023-10-12 12:29:21 -07:00
http . Error ( w , "Timed out waiting for response" , http . StatusGatewayTimeout )
case res := <- pendingResponse . ch :
2023-11-08 17:47:28 -08:00
s . logger . Info ( "publishing response to client" , zap . String ( "userId" , permEntry . userName ) , zap . String ( "requestId" , requestId ) )
2023-10-12 12:29:21 -07:00
resBytes , err := res . Response . Marshal ( )
if err != nil {
2023-11-08 17:47:28 -08:00
s . logger . Error ( "failed to marshal response" , zap . String ( "userId" , permEntry . userName ) , zap . String ( "requestId" , requestId ) , zap . Error ( err ) )
2023-10-12 12:29:21 -07:00
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "failed_to_marshal_response" ) . Inc ( )
2023-12-11 09:24:05 -08:00
invalidRequestsByUser . WithLabelValues ( permEntry . userName ) . Inc ( )
2023-10-12 12:29:21 -07:00
break
}
// Signature indices must be ascending for on-chain verification
sort . Slice ( res . Signatures , func ( i , j int ) bool {
return res . Signatures [ i ] . Index < res . Signatures [ j ] . Index
} )
signatures := make ( [ ] string , 0 , len ( res . Signatures ) )
for _ , s := range res . Signatures {
// ECDSA signature + a byte for the index of the guardian in the guardian set
signature := fmt . Sprintf ( "%s%02x" , s . Signature , uint8 ( s . Index ) )
signatures = append ( signatures , signature )
}
w . Header ( ) . Add ( "Content-Type" , "application/json" )
err = json . NewEncoder ( w ) . Encode ( & queryResponse {
Signatures : signatures ,
Bytes : hex . EncodeToString ( resBytes ) ,
} )
if err != nil {
2023-11-08 17:47:28 -08:00
s . logger . Error ( "failed to encode response" , zap . String ( "userId" , permEntry . userName ) , zap . String ( "requestId" , requestId ) , zap . Error ( err ) )
2023-10-12 12:29:21 -07:00
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
2023-11-08 09:22:34 -08:00
invalidQueryRequestReceived . WithLabelValues ( "failed_to_encode_response" ) . Inc ( )
2023-12-11 09:24:05 -08:00
invalidRequestsByUser . WithLabelValues ( permEntry . userName ) . Inc ( )
2023-11-08 17:47:28 -08:00
break
2023-10-12 12:29:21 -07:00
}
}
2023-11-08 09:22:34 -08:00
totalQueryTime . Observe ( float64 ( time . Since ( start ) . Milliseconds ( ) ) )
validQueryRequestsReceived . Inc ( )
2023-10-12 12:29:21 -07:00
s . pendingResponses . Remove ( pendingResponse )
}
2023-12-11 12:44:48 -08:00
func NewHTTPServer ( addr string , t * pubsub . Topic , permissions * Permissions , signerKey * ecdsa . PrivateKey , p * PendingResponses , logger * zap . Logger , env common . Environment ) * http . Server {
2023-10-12 12:29:21 -07:00
s := & httpServer {
topic : t ,
permissions : permissions ,
signerKey : signerKey ,
pendingResponses : p ,
logger : logger ,
env : env ,
}
r := mux . NewRouter ( )
r . HandleFunc ( "/v1/query" , s . handleQuery ) . Methods ( "PUT" , "POST" , "OPTIONS" )
return & http . Server {
Addr : addr ,
Handler : r ,
ReadHeaderTimeout : 5 * time . Second ,
}
}