mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 02:52:53 +00:00
refactor: Update Rust version to 1.95, add SSRF protection to ES2 client, and fix CLI API endpoint paths
- Change rust-version from "1.94" to "1.95" in workspace Cargo.toml - Add validateURL method to ES2Client with private IP blocking and domain whitelisting - Add SSRF validation to DownloadProfile and GetProfileStatus endpoints - Add slices, errors, net, and url imports to es2/client.go - Update CLI charging API endpoints from /v1 to /api/v1 prefix - Fix DeductCredit response handling to use nil
This commit is contained in:
+1
-1
@@ -9,7 +9,7 @@ resolver = "2"
|
||||
version = "1.0.0"
|
||||
authors = ["TaaS Team <[email protected]>"]
|
||||
edition = "2024"
|
||||
rust-version = "1.94"
|
||||
rust-version = "1.95"
|
||||
license = "MIT"
|
||||
|
||||
[workspace.metadata.cargo-audit]
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package es2
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/config"
|
||||
@@ -38,6 +42,32 @@ func NewES2Client(cfg *config.ES2Config) *ES2Client {
|
||||
}
|
||||
}
|
||||
|
||||
// validateURL prevents SSRF by checking if URL is safe to access
|
||||
func (c *ES2Client) validateURL(urlStr string) error {
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid URL: %w", err)
|
||||
}
|
||||
|
||||
host := u.Hostname()
|
||||
ip := net.ParseIP(host)
|
||||
|
||||
// Block private IPs, loopback, and link-local addresses
|
||||
if ip != nil && (ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast()) {
|
||||
return errors.New("access to private IP addresses is not allowed")
|
||||
}
|
||||
|
||||
// Whitelist allowed ES2/SMDP domains (should be production SM-DP+ servers)
|
||||
allowedDomains := []string{"smdp.example.com", "es2plus.example.com", "smdp.carrier.com"}
|
||||
allowed := slices.Contains(allowedDomains, host)
|
||||
|
||||
if !allowed {
|
||||
return fmt.Errorf("domain %s is not in allowed ES2 domains list", host)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// retryableError checks if an error is retryable
|
||||
func (c *ES2Client) retryableError(err error, statusCode int) bool {
|
||||
if err != nil {
|
||||
@@ -96,6 +126,11 @@ func (c *ES2Client) executeWithRetry(ctx context.Context, req *http.Request) (*h
|
||||
func (c *ES2Client) DownloadProfile(ctx context.Context, req *DownloadProfileRequest) (*DownloadProfileResponse, error) {
|
||||
url := fmt.Sprintf("%s/es2plus/downloadProfile", c.baseURL)
|
||||
|
||||
// Validate URL to prevent SSRF
|
||||
if err := c.validateURL(url); err != nil {
|
||||
return nil, fmt.Errorf("SSRF protection: %w", err)
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||
@@ -129,6 +164,11 @@ func (c *ES2Client) DownloadProfile(ctx context.Context, req *DownloadProfileReq
|
||||
func (c *ES2Client) GetProfileStatus(ctx context.Context, req *GetProfileStatusRequest) (*GetProfileStatusResponse, error) {
|
||||
url := fmt.Sprintf("%s/es2plus/getProfileStatus", c.baseURL)
|
||||
|
||||
// Validate URL to prevent SSRF
|
||||
if err := c.validateURL(url); err != nil {
|
||||
return nil, fmt.Errorf("SSRF protection: %w", err)
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||
|
||||
@@ -28,7 +28,7 @@ type CreditDeductRequest struct {
|
||||
func (c *Client) CheckCredit(ip string, bytesRequested uint64) (*CreditCheckResponse, error) {
|
||||
var resp CreditCheckResponse
|
||||
req := CreditCheckRequest{BytesRequested: bytesRequested}
|
||||
if err := c.doPostJSON("/v1/charging/credit/"+ip+"/check", req, &resp); err != nil {
|
||||
if err := c.doPostJSON("/api/v1/charging/credit/"+ip+"/check", req, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
@@ -37,7 +37,7 @@ func (c *Client) CheckCredit(ip string, bytesRequested uint64) (*CreditCheckResp
|
||||
// GetBalance gets the current credit balance for a subscriber
|
||||
func (c *Client) GetBalance(ip string) (*CreditBalanceResponse, error) {
|
||||
var resp CreditBalanceResponse
|
||||
if err := c.doGetJSON("/v1/charging/credit/"+ip+"/balance", &resp); err != nil {
|
||||
if err := c.doGetJSON("/api/v1/charging/credit/"+ip+"/balance", &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
@@ -47,7 +47,7 @@ func (c *Client) GetBalance(ip string) (*CreditBalanceResponse, error) {
|
||||
func (c *Client) AddCredit(ip string, bytesToAdd uint64) (*CreditBalanceResponse, error) {
|
||||
var resp CreditBalanceResponse
|
||||
req := CreditAddRequest{BytesToAdd: bytesToAdd}
|
||||
if err := c.doPostJSON("/v1/charging/credit/"+ip+"/add", req, &resp); err != nil {
|
||||
if err := c.doPostJSON("/api/v1/charging/credit/"+ip+"/add", req, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
@@ -57,7 +57,7 @@ func (c *Client) AddCredit(ip string, bytesToAdd uint64) (*CreditBalanceResponse
|
||||
func (c *Client) DeductCredit(ip string, bytesUsed uint64) (*CreditBalanceResponse, error) {
|
||||
var resp CreditBalanceResponse
|
||||
req := CreditDeductRequest{BytesUsed: bytesUsed}
|
||||
if err := c.doPostJSON("/v1/charging/credit/"+ip+"/deduct", req, &resp); err != nil {
|
||||
if err := c.doPostJSON("/api/v1/charging/credit/"+ip+"/deduct", req, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
|
||||
Reference in New Issue
Block a user