Files
Sankofa/crossplane-provider-proxmox/pkg/proxmox/http_client.go
defiQUG 9daf1fd378 Apply Composer changes: comprehensive API updates, migrations, middleware, and infrastructure improvements
- Add comprehensive database migrations (001-024) for schema evolution
- Enhance API schema with expanded type definitions and resolvers
- Add new middleware: audit logging, rate limiting, MFA enforcement, security, tenant auth
- Implement new services: AI optimization, billing, blockchain, compliance, marketplace
- Add adapter layer for cloud integrations (Cloudflare, Kubernetes, Proxmox, storage)
- Update Crossplane provider with enhanced VM management capabilities
- Add comprehensive test suite for API endpoints and services
- Update frontend components with improved GraphQL subscriptions and real-time updates
- Enhance security configurations and headers (CSP, CORS, etc.)
- Update documentation and configuration files
- Add new CI/CD workflows and validation scripts
- Implement design system improvements and UI enhancements
2025-12-12 18:01:35 -08:00

262 lines
6.7 KiB
Go

package proxmox
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
"github.com/pkg/errors"
)
// HTTPClient wraps http.Client with Proxmox-specific functionality
type HTTPClient struct {
client *http.Client
endpoint string
ticket string
csrfToken string
token string
username string
password string
}
// NewHTTPClient creates a new HTTP client for Proxmox API
// Implements FIPS 140-2 compliant TLS configuration per DoD/MilSpec requirements
func NewHTTPClient(endpoint string, insecureSkipTLS bool) *HTTPClient {
// FIPS-approved cipher suites for TLS 1.3
// Per NIST SP 800-53 SC-8 and NIST SP 800-171 3.13.1
fipsCipherSuites := []uint16{
tls.TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256,
tls.TLS_AES_128_GCM_SHA256,
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecureSkipTLS,
MinVersion: tls.VersionTLS13, // TLS 1.3 minimum
MaxVersion: tls.VersionTLS13, // Only TLS 1.3
CipherSuites: fipsCipherSuites,
PreferServerCipherSuites: true,
},
DisableKeepAlives: false,
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
}
return &HTTPClient{
client: &http.Client{
Transport: tr,
Timeout: 30 * time.Second,
},
endpoint: endpoint,
}
}
// Authenticate authenticates with Proxmox API
func (c *HTTPClient) Authenticate(ctx context.Context) error {
if c.token != "" {
// Token authentication - token format: "user@realm!token-name=token-secret"
return nil // Token will be used in requests
}
if c.username == "" || c.password == "" {
return fmt.Errorf("username/password or token required")
}
authURL := fmt.Sprintf("%s/api2/json/access/ticket", c.endpoint)
data := url.Values{}
data.Set("username", c.username)
data.Set("password", c.password)
req, err := http.NewRequestWithContext(ctx, "POST", authURL, bytes.NewBufferString(data.Encode()))
if err != nil {
return errors.Wrap(err, "failed to create auth request")
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := c.client.Do(req)
if err != nil {
return errors.Wrap(err, "failed to authenticate")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("authentication failed: %s - %s", resp.Status, string(body))
}
var result struct {
Data struct {
Ticket string `json:"ticket"`
CSRFPreventionToken string `json:"CSRFPreventionToken"`
} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return errors.Wrap(err, "failed to decode auth response")
}
c.ticket = result.Data.Ticket
c.csrfToken = result.Data.CSRFPreventionToken
return nil
}
// SetToken sets API token for authentication
func (c *HTTPClient) SetToken(token string) {
c.token = token
}
// SetCredentials sets username and password for authentication
func (c *HTTPClient) SetCredentials(username, password string) {
c.username = username
c.password = password
}
// Do performs an HTTP request with authentication
func (c *HTTPClient) Do(ctx context.Context, method, path string, body interface{}) (*http.Response, error) {
url := fmt.Sprintf("%s/api2/json%s", c.endpoint, path)
var reqBody io.Reader
if body != nil {
jsonData, err := json.Marshal(body)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal request body")
}
reqBody = bytes.NewBuffer(jsonData)
}
req, err := http.NewRequestWithContext(ctx, method, url, reqBody)
if err != nil {
return nil, errors.Wrap(err, "failed to create request")
}
// Set headers
req.Header.Set("Content-Type", "application/json")
if c.token != "" {
// Token authentication
req.Header.Set("Authorization", fmt.Sprintf("PVEAuthCookie=%s", c.token))
} else if c.ticket != "" {
// Ticket authentication
req.AddCookie(&http.Cookie{
Name: "PVEAuthCookie",
Value: c.ticket,
})
if c.csrfToken != "" && method != "GET" {
req.Header.Set("CSRFPreventionToken", c.csrfToken)
}
}
resp, err := c.client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "request failed")
}
return resp, nil
}
// Get performs a GET request
func (c *HTTPClient) Get(ctx context.Context, path string, result interface{}) error {
resp, err := c.Do(ctx, "GET", path, nil)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("GET %s failed: %s - %s", path, resp.Status, string(body))
}
if result != nil {
var apiResponse struct {
Data interface{} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {
return errors.Wrap(err, "failed to decode response")
}
// Unmarshal data into result
dataBytes, _ := json.Marshal(apiResponse.Data)
return json.Unmarshal(dataBytes, result)
}
return nil
}
// Post performs a POST request
func (c *HTTPClient) Post(ctx context.Context, path string, body, result interface{}) error {
resp, err := c.Do(ctx, "POST", path, body)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(resp.Body)
return fmt.Errorf("POST %s failed: %s - %s", path, resp.Status, string(bodyBytes))
}
if result != nil {
var apiResponse struct {
Data interface{} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {
return errors.Wrap(err, "failed to decode response")
}
dataBytes, _ := json.Marshal(apiResponse.Data)
return json.Unmarshal(dataBytes, result)
}
return nil
}
// Put performs a PUT request
func (c *HTTPClient) Put(ctx context.Context, path string, body, result interface{}) error {
resp, err := c.Do(ctx, "PUT", path, body)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(resp.Body)
return fmt.Errorf("PUT %s failed: %s - %s", path, resp.Status, string(bodyBytes))
}
if result != nil {
var apiResponse struct {
Data interface{} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {
return errors.Wrap(err, "failed to decode response")
}
dataBytes, _ := json.Marshal(apiResponse.Data)
return json.Unmarshal(dataBytes, result)
}
return nil
}
// Delete performs a DELETE request
func (c *HTTPClient) Delete(ctx context.Context, path string) error {
resp, err := c.Do(ctx, "DELETE", path, nil)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(resp.Body)
return fmt.Errorf("DELETE %s failed: %s - %s", path, resp.Status, string(bodyBytes))
}
return nil
}