From 07a5b22d0843612fe464e8951c7757f71b829c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 3 Oct 2024 17:18:05 +0200 Subject: [PATCH] pdp: server and tool ping functionality --- cmd/pdptool/main.go | 160 ++++++++++++++++++++++++++++++++------------ pdp/auth.go | 7 +- pdp/handlers.go | 16 +++++ 3 files changed, 136 insertions(+), 47 deletions(-) diff --git a/cmd/pdptool/main.go b/cmd/pdptool/main.go index 6c7797a51..89f8e6993 100644 --- a/cmd/pdptool/main.go +++ b/cmd/pdptool/main.go @@ -30,6 +30,8 @@ func main() { authCreateServiceSecretCmd, // generates pdpservice.json, outputs pubkey authCreateJWTTokenCmd, // generates jwt token from a secret + pingCmd, + piecePrepareCmd, // hash a piece to get a piece cid pieceUploadCmd, // upload a piece to a pdp service }, @@ -97,72 +99,144 @@ var authCreateServiceSecretCmd = &cli.Command{ var authCreateJWTTokenCmd = &cli.Command{ Name: "create-jwt-token", Usage: "Generate a JWT token using the service secret", - ArgsUsage: "[service_id]", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "service-id", - Aliases: []string{"serviceId"}, - Usage: "Service ID to include in the JWT token", - Required: true, - }, - }, + ArgsUsage: "[service_name]", Action: func(cctx *cli.Context) error { // Read the private key from pdpservice.json - file, err := os.Open("pdpservice.json") + privKey, err := loadPrivateKey() if err != nil { - return fmt.Errorf("failed to open pdpservice.json: %v", err) + return err } - defer file.Close() - var serviceSecret map[string]string - decoder := json.NewDecoder(file) - if err := decoder.Decode(&serviceSecret); err != nil { - return fmt.Errorf("failed to read pdpservice.json: %v", err) + + // Get the service name + serviceName := cctx.Args().First() + if serviceName == "" { + return fmt.Errorf("service_name argument is required") } - privPEM := serviceSecret["private_key"] - block, _ := pem.Decode([]byte(privPEM)) - if block == nil { - return fmt.Errorf("failed to parse private key PEM") + // Create JWT token using the common function + tokenString, err := createJWTToken(serviceName, privKey) + if err != nil { + return err } - // Parse the private key - privKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) + // Output the token + fmt.Printf("JWT Token:\n%s\n", tokenString) + + return nil + }, +} + +var pingCmd = &cli.Command{ + Name: "ping", + Usage: "Ping the /pdp/ping endpoint of a PDP service", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "service-url", + Usage: "URL of the PDP service", + Required: true, + }, + &cli.StringFlag{ + Name: "service-name", + Usage: "Service Name to include in the JWT token", + }, + }, + Action: func(cctx *cli.Context) error { + serviceURL := cctx.String("service-url") + serviceName := cctx.String("service-name") + + if serviceName == "" { + return fmt.Errorf("either --jwt-token or --service-name must be provided") + } + privKey, err := loadPrivateKey() if err != nil { - return fmt.Errorf("failed to parse private key: %v", err) + return err } - ecdsaPrivKey, ok := privKey.(*ecdsa.PrivateKey) - if !ok { - return fmt.Errorf("private key is not ECDSA") + var errCreateToken error + jwtToken, errCreateToken := createJWTToken(serviceName, privKey) + if errCreateToken != nil { + return errCreateToken } - // Get the service ID - serviceID := cctx.String("service-id") - if serviceID == "" { - return fmt.Errorf("service-id is required") - } + // Append /pdp/ping to the service URL + pingURL := serviceURL + "/pdp/ping" - // Create JWT claims - claims := jwt.MapClaims{ - "service_id": serviceID, - "exp": time.Now().Add(time.Hour * 24).Unix(), + // Create the GET request + req, err := http.NewRequest("GET", pingURL, nil) + if err != nil { + return fmt.Errorf("failed to create request: %v", err) } + req.Header.Set("Authorization", "Bearer "+jwtToken) - // Create the token - token := jwt.NewWithClaims(jwt.SigningMethodES256, claims) - - // Sign the token - tokenString, err := token.SignedString(ecdsaPrivKey) + // Send the request + client := &http.Client{} + resp, err := client.Do(req) if err != nil { - return fmt.Errorf("failed to sign token: %v", err) + return fmt.Errorf("failed to send request: %v", err) } + defer resp.Body.Close() - // Output the token - fmt.Printf("JWT Token:\n%s\n", tokenString) + // Check the response + if resp.StatusCode == http.StatusOK { + fmt.Println("Ping successful: Service is reachable and JWT token is valid.") + } else { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("ping failed with status code %d: %s", resp.StatusCode, string(body)) + } return nil }, } +func createJWTToken(serviceName string, privateKey *ecdsa.PrivateKey) (string, error) { + // Create JWT claims + claims := jwt.MapClaims{ + "service_name": serviceName, + "exp": time.Now().Add(time.Hour * 24).Unix(), + } + + // Create the token + token := jwt.NewWithClaims(jwt.SigningMethodES256, claims) + + // Sign the token + tokenString, err := token.SignedString(privateKey) + if err != nil { + return "", fmt.Errorf("failed to sign token: %v", err) + } + + return tokenString, nil +} + +func loadPrivateKey() (*ecdsa.PrivateKey, error) { + file, err := os.Open("pdpservice.json") + if err != nil { + return nil, fmt.Errorf("failed to open pdpservice.json: %v", err) + } + defer file.Close() + var serviceSecret map[string]string + decoder := json.NewDecoder(file) + if err := decoder.Decode(&serviceSecret); err != nil { + return nil, fmt.Errorf("failed to read pdpservice.json: %v", err) + } + + privPEM := serviceSecret["private_key"] + block, _ := pem.Decode([]byte(privPEM)) + if block == nil { + return nil, fmt.Errorf("failed to parse private key PEM") + } + + // Parse the private key + privKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse private key: %v", err) + } + ecdsaPrivKey, ok := privKey.(*ecdsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("private key is not ECDSA") + } + + return ecdsaPrivKey, nil +} + var piecePrepareCmd = &cli.Command{ Name: "prepare-piece", Usage: "Compute the PieceCID of a file", diff --git a/pdp/auth.go b/pdp/auth.go index 58120f9ed..50842a8f6 100644 --- a/pdp/auth.go +++ b/pdp/auth.go @@ -45,17 +45,16 @@ func (p *PDPService) verifyJWTToken(r *http.Request) (int64, error) { } // Extract service_id from claims - serviceIDFloat, ok := claims["service_id"].(float64) + serviceID, ok := claims["service_name"] if !ok { - return nil, fmt.Errorf("service_id not found in token claims") + return nil, fmt.Errorf("missing service_name claim") } - serviceID = int64(serviceIDFloat) // Query the database for the public key using serviceID var pubKeyBytes []byte ctx := r.Context() err := p.db.QueryRow(ctx, ` - SELECT pubkey FROM pdp_services WHERE id=? + SELECT pubkey FROM pdp_services WHERE service_label=$1 `, serviceID).Scan(&pubKeyBytes) if err != nil { return nil, fmt.Errorf("failed to retrieve public key for service_id %d: %v", serviceID, err) diff --git a/pdp/handlers.go b/pdp/handlers.go index 743ff4a70..8d06baa40 100644 --- a/pdp/handlers.go +++ b/pdp/handlers.go @@ -73,6 +73,8 @@ func Routes(r *chi.Mux, p *PDPService) { }) }) + r.Get(path.Join(PDPRoutePath, "/ping"), p.handlePing) + // Routes for piece storage and retrieval // POST /pdp/piece r.Post(path.Join(PDPRoutePath, "/piece"), p.handlePiecePost) @@ -83,6 +85,20 @@ func Routes(r *chi.Mux, p *PDPService) { // Handler functions +func (p *PDPService) handlePing(w http.ResponseWriter, r *http.Request) { + // Verify that the request is authorized using ECDSA JWT + _, err := p.verifyJWTToken(r) + if err != nil { + http.Error(w, "Unauthorized: "+err.Error(), http.StatusUnauthorized) + return + } + + // Return 200 OK + w.WriteHeader(http.StatusOK) +} + +// TODO STUFF BELOW IS JUST A SKELETON + func (p *PDPService) handleCreateProofSet(w http.ResponseWriter, r *http.Request) { // Spec snippet: // ### POST /proof-sets