mirror of
https://github.com/j3ssie/osmedeus.git
synced 2026-09-29 21:15:00 +02:00
- Implement cloud provider infrastructure (DigitalOcean, AWS, GCP, Linode, Azure) with Pulumi integration for distributed scanning - Add nmap and tmux utility functions for port scanning results processing and long-running background session management - Introduce webhook-triggered run execution with unique UUID and authentication key support for external integrations
193 lines
5.8 KiB
Go
193 lines
5.8 KiB
Go
package cloud
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/digitalocean/godo"
|
|
"github.com/pulumi/pulumi-digitalocean/sdk/v4/go/digitalocean"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
// DigitalOceanProvider implements the Provider interface for DigitalOcean
|
|
type DigitalOceanProvider struct {
|
|
token string
|
|
region string
|
|
size string
|
|
snapshotID string
|
|
sshKeyFingerprint string
|
|
client *godo.Client
|
|
}
|
|
|
|
// NewDigitalOceanProvider creates a new DigitalOcean provider
|
|
func NewDigitalOceanProvider(token, region, size, snapshotID, sshKeyFingerprint string) (*DigitalOceanProvider, error) {
|
|
if token == "" {
|
|
return nil, fmt.Errorf("DigitalOcean token is required")
|
|
}
|
|
|
|
// Create DigitalOcean client
|
|
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
|
|
oauthClient := oauth2.NewClient(context.Background(), tokenSource)
|
|
client := godo.NewClient(oauthClient)
|
|
|
|
return &DigitalOceanProvider{
|
|
token: token,
|
|
region: region,
|
|
size: size,
|
|
snapshotID: snapshotID,
|
|
sshKeyFingerprint: sshKeyFingerprint,
|
|
client: client,
|
|
}, nil
|
|
}
|
|
|
|
// Validate checks if the provider configuration is valid
|
|
func (p *DigitalOceanProvider) Validate(ctx context.Context) error {
|
|
// Test API access by getting account info
|
|
_, _, err := p.client.Account.Get(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to validate DigitalOcean credentials: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// EstimateCost estimates the cost for the given configuration
|
|
func (p *DigitalOceanProvider) EstimateCost(mode ExecutionMode, instanceCount int) (*CostEstimate, error) {
|
|
if mode != ModeVM {
|
|
return nil, fmt.Errorf("only VM mode is supported for DigitalOcean")
|
|
}
|
|
|
|
// Default pricing for common sizes (USD per hour)
|
|
pricing := map[string]float64{
|
|
"s-1vcpu-1gb": 0.00744, // $5/month
|
|
"s-1vcpu-2gb": 0.01116, // $7.5/month
|
|
"s-2vcpu-2gb": 0.01488, // $10/month
|
|
"s-2vcpu-4gb": 0.02232, // $15/month
|
|
"s-4vcpu-8gb": 0.04464, // $30/month
|
|
"s-8vcpu-16gb": 0.08928, // $60/month
|
|
"s-16vcpu-32gb": 0.17856, // $120/month
|
|
}
|
|
|
|
hourlyRate, ok := pricing[p.size]
|
|
if !ok {
|
|
// Default to s-2vcpu-4gb pricing if unknown
|
|
hourlyRate = 0.02232
|
|
}
|
|
|
|
totalHourlyRate := hourlyRate * float64(instanceCount)
|
|
|
|
return &CostEstimate{
|
|
HourlyCost: totalHourlyRate,
|
|
DailyCost: totalHourlyRate * 24,
|
|
Currency: "USD",
|
|
Breakdown: map[string]float64{
|
|
"compute": totalHourlyRate,
|
|
},
|
|
Notes: []string{
|
|
fmt.Sprintf("%d x %s droplets @ $%.4f/hr each", instanceCount, p.size, hourlyRate),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// CreateInfrastructure provisions DigitalOcean droplets
|
|
func (p *DigitalOceanProvider) CreateInfrastructure(ctx context.Context, opts *CreateOptions) (*Infrastructure, error) {
|
|
// Generate unique infrastructure ID
|
|
infraID := fmt.Sprintf("cloud-do-%d", time.Now().Unix())
|
|
|
|
// Placeholder implementation - actual Pulumi logic will be added
|
|
// This demonstrates the structure
|
|
infra := &Infrastructure{
|
|
ID: infraID,
|
|
Provider: ProviderDigitalOcean,
|
|
Mode: opts.Mode,
|
|
CreatedAt: time.Now(),
|
|
PulumiStackID: fmt.Sprintf("osmedeus-cloud/%s", infraID),
|
|
Resources: []Resource{},
|
|
Metadata: map[string]interface{}{},
|
|
}
|
|
|
|
return infra, fmt.Errorf("DigitalOcean infrastructure creation not yet fully implemented")
|
|
}
|
|
|
|
// DestroyInfrastructure tears down DigitalOcean resources
|
|
func (p *DigitalOceanProvider) DestroyInfrastructure(ctx context.Context, infra *Infrastructure) error {
|
|
// Placeholder implementation
|
|
return fmt.Errorf("DigitalOcean infrastructure destruction not yet fully implemented")
|
|
}
|
|
|
|
// GetStatus retrieves the current status of infrastructure
|
|
func (p *DigitalOceanProvider) GetStatus(ctx context.Context, infra *Infrastructure) (*InfraStatus, error) {
|
|
// Placeholder implementation
|
|
return &InfraStatus{
|
|
Status: "unknown",
|
|
ReadyCount: 0,
|
|
TotalCount: len(infra.Resources),
|
|
WorkersRegistered: 0,
|
|
Details: []ResourceStatus{},
|
|
}, nil
|
|
}
|
|
|
|
// Type returns the provider type
|
|
func (p *DigitalOceanProvider) Type() ProviderType {
|
|
return ProviderDigitalOcean
|
|
}
|
|
|
|
// generateCloudInit generates the cloud-init user data script
|
|
// TODO: This method will be used when the Create() method is fully implemented
|
|
//
|
|
//nolint:unused // Reserved for future implementation
|
|
func (p *DigitalOceanProvider) generateCloudInit(redisURL, sshPublicKey string, setupCommands []string) string {
|
|
script := `#!/bin/bash
|
|
set -e
|
|
|
|
# Install osmedeus
|
|
curl -fsSL https://www.osmedeus.org/install.sh | bash
|
|
|
|
# Setup SSH keys
|
|
mkdir -p ~/.ssh
|
|
echo "` + sshPublicKey + `" >> ~/.ssh/authorized_keys
|
|
chmod 700 ~/.ssh
|
|
chmod 600 ~/.ssh/authorized_keys
|
|
|
|
# Join as worker
|
|
osmedeus worker join --redis-url ` + redisURL + ` --get-public-ip
|
|
|
|
`
|
|
// Add custom setup commands
|
|
for _, cmd := range setupCommands {
|
|
script += cmd + "\n"
|
|
}
|
|
|
|
return script
|
|
}
|
|
|
|
// createDropletProgram creates a Pulumi program for DigitalOcean droplets
|
|
// TODO: This method will be used when the Create() method is fully implemented
|
|
//
|
|
//nolint:unused // Reserved for future implementation
|
|
func (p *DigitalOceanProvider) createDropletProgram(opts *CreateOptions) pulumi.RunFunc {
|
|
return func(ctx *pulumi.Context) error {
|
|
// This will be implemented with actual Pulumi DigitalOcean resource creation
|
|
// For now, just a placeholder to demonstrate the pattern
|
|
|
|
userData := p.generateCloudInit(opts.RedisURL, opts.SSHPublicKey, opts.SetupCommands)
|
|
|
|
for i := 0; i < opts.InstanceCount; i++ {
|
|
dropletName := fmt.Sprintf("osmedeus-worker-%d", i)
|
|
|
|
_, err := digitalocean.NewDroplet(ctx, dropletName, &digitalocean.DropletArgs{
|
|
Image: pulumi.String(opts.ImageID),
|
|
Region: pulumi.String(p.region),
|
|
Size: pulumi.String(p.size),
|
|
UserData: pulumi.String(userData),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|