Merge branch 'main' into dry
This commit is contained in:
commit
5505a33557
2
.gitignore
vendored
2
.gitignore
vendored
@ -20,3 +20,5 @@
|
|||||||
|
|
||||||
|
|
||||||
config.yaml
|
config.yaml
|
||||||
|
|
||||||
|
polemos
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
mtd:
|
mtd:
|
||||||
services: []
|
services: {}
|
||||||
|
management_port: 14000
|
||||||
aws:
|
aws:
|
||||||
regions: []
|
regions: []
|
||||||
credentials_path: ./mtdaws/.credentials
|
credentials_path: ./mtdaws/.credentials
|
||||||
|
113
main.go
113
main.go
@ -3,9 +3,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/thefeli73/polemos/mtdaws"
|
"github.com/thefeli73/polemos/mtdaws"
|
||||||
|
"github.com/thefeli73/polemos/pcsdk"
|
||||||
"github.com/thefeli73/polemos/state"
|
"github.com/thefeli73/polemos/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,45 +19,120 @@ func main() {
|
|||||||
|
|
||||||
ConfigPath = "config.yaml"
|
ConfigPath = "config.yaml"
|
||||||
|
|
||||||
config := state.LoadConf(ConfigPath)
|
// Initialize the config.Services map
|
||||||
|
var config state.Config
|
||||||
|
config.MTD.Services = make(map[state.CustomUUID]state.Service)
|
||||||
|
|
||||||
|
config = state.LoadConf(ConfigPath)
|
||||||
state.SaveConf(ConfigPath, config)
|
state.SaveConf(ConfigPath, config)
|
||||||
|
|
||||||
config = indexInstances(config)
|
|
||||||
|
|
||||||
|
config = indexAllInstances(config)
|
||||||
|
state.SaveConf(ConfigPath, config)
|
||||||
|
|
||||||
|
// CREATE TUNNELS
|
||||||
|
createTunnels(config)
|
||||||
|
|
||||||
|
// START DOING MTD
|
||||||
|
mtdLoop(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexInstances(config state.Config) state.Config {
|
func mtdLoop(config state.Config) {
|
||||||
|
for true {
|
||||||
|
//TODO: figure out migration (MTD)
|
||||||
|
config = movingTargetDefense(config)
|
||||||
|
state.SaveConf(ConfigPath, config)
|
||||||
|
|
||||||
|
fmt.Println("Sleeping for 1 minute")
|
||||||
|
time.Sleep(1*time.Minute)
|
||||||
|
|
||||||
|
//TODO: proxy commands
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func movingTargetDefense(config state.Config) state.Config{
|
||||||
|
|
||||||
|
mtdaws.AWSMoveInstance(config)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexAllInstances(config state.Config) state.Config {
|
||||||
fmt.Println("Indexing instances")
|
fmt.Println("Indexing instances")
|
||||||
|
t := time.Now()
|
||||||
|
|
||||||
|
for _, service := range config.MTD.Services {
|
||||||
|
service.Active = false
|
||||||
|
}
|
||||||
|
|
||||||
//index AWS instances
|
//index AWS instances
|
||||||
|
awsNewInstanceCounter := 0
|
||||||
|
awsInactiveInstanceCounter := len(config.MTD.Services)
|
||||||
|
awsInstanceCounter := 0
|
||||||
awsInstances := mtdaws.GetInstances(config)
|
awsInstances := mtdaws.GetInstances(config)
|
||||||
for _, instance := range awsInstances {
|
for _, instance := range awsInstances {
|
||||||
cloudID := mtdaws.GetCloudID(instance)
|
cloudID := mtdaws.GetCloudID(instance)
|
||||||
ip, err := netip.ParseAddr(instance.PublicIP)
|
ip, err := netip.ParseAddr(instance.PublicIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error converting ip:", err)
|
fmt.Println("Error converting ip:\t", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
newService, found := indexInstance(config, cloudID, ip)
|
var found bool
|
||||||
|
config, found = indexInstance(config, cloudID, ip)
|
||||||
if !found {
|
if !found {
|
||||||
config.MTD.Services = append(config.MTD.Services, newService)
|
awsNewInstanceCounter++
|
||||||
state.SaveConf(ConfigPath, config)
|
} else {
|
||||||
|
awsInactiveInstanceCounter--
|
||||||
}
|
}
|
||||||
|
awsInstanceCounter++
|
||||||
}
|
}
|
||||||
|
// TODO: Purge instances in config that are not found in the cloud
|
||||||
|
fmt.Printf("Found %d active AWS instances (%d newly added, %d inactive) (took %s)\n",
|
||||||
|
awsInstanceCounter, awsNewInstanceCounter, awsInactiveInstanceCounter, time.Since(t).Round(100*time.Millisecond).String())
|
||||||
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexInstance(config state.Config, cloudID string, serviceIP netip.Addr) (state.Service, bool) {
|
func createTunnels(config state.Config) {
|
||||||
found := false
|
for serviceUUID, service := range config.MTD.Services {
|
||||||
for _, service := range config.MTD.Services {
|
if service.AdminEnabled && service.Active {
|
||||||
if service.CloudID == cloudID {
|
s := pcsdk.NewCommandStatus()
|
||||||
found = true
|
err := s.Execute(netip.AddrPortFrom(service.EntryIP, config.MTD.ManagementPort))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Reconfigure Proxy to new instance
|
||||||
|
c := pcsdk.NewCommandCreate(service.EntryPort, service.ServicePort, service.ServiceIP, serviceUUID)
|
||||||
|
err = c.Execute(netip.AddrPortFrom(service.EntryIP, config.MTD.ManagementPort))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
u := uuid.New()
|
}
|
||||||
newService := state.Service{
|
|
||||||
ID: state.CustomUUID(u),
|
func indexInstance(config state.Config, cloudID string, serviceIP netip.Addr) (state.Config, bool) {
|
||||||
CloudID: cloudID,
|
found := false
|
||||||
ServiceIP: serviceIP}
|
var foundUUID state.CustomUUID
|
||||||
return newService, found
|
for u, service := range config.MTD.Services {
|
||||||
|
if service.CloudID == cloudID {
|
||||||
|
found = true
|
||||||
|
foundUUID = u
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
fmt.Println("New instance found:\t", cloudID)
|
||||||
|
u := uuid.New()
|
||||||
|
config.MTD.Services[state.CustomUUID(u)] = state.Service{CloudID: cloudID, ServiceIP: serviceIP, Active: true, AdminEnabled: true}
|
||||||
|
state.SaveConf(ConfigPath, config)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
s := config.MTD.Services[foundUUID]
|
||||||
|
s.Active = true
|
||||||
|
config.MTD.Services[foundUUID] = s
|
||||||
|
state.SaveConf(ConfigPath, config)
|
||||||
|
}
|
||||||
|
return config, found
|
||||||
}
|
}
|
||||||
|
181
mtdaws/mtd.go
Normal file
181
mtdaws/mtd.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
package mtdaws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/thefeli73/polemos/pcsdk"
|
||||||
|
"github.com/thefeli73/polemos/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AWSMoveInstance moves a specified instance to a new availability region
|
||||||
|
func AWSMoveInstance(config state.Config) (state.Config) {
|
||||||
|
|
||||||
|
// pseudorandom instance from all services for testing
|
||||||
|
var serviceUUID state.CustomUUID
|
||||||
|
var instance state.Service
|
||||||
|
for key, service := range config.MTD.Services {
|
||||||
|
serviceUUID = key
|
||||||
|
instance = service
|
||||||
|
if !instance.AdminEnabled {continue}
|
||||||
|
if !instance.Active {continue}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("MTD move service:\t", uuid.UUID.String(uuid.UUID(serviceUUID)))
|
||||||
|
|
||||||
|
// Test Proxy Connection
|
||||||
|
t := time.Now()
|
||||||
|
s := pcsdk.NewCommandStatus()
|
||||||
|
err := s.Execute(netip.AddrPortFrom(instance.EntryIP, config.MTD.ManagementPort))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error executing test command: %s\n", err)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
fmt.Printf("Proxy Tested. (took %s)\n", time.Since(t).Round(100*time.Millisecond).String())
|
||||||
|
region, instanceID := DecodeCloudID(instance.CloudID)
|
||||||
|
awsConfig := NewConfig(region, config.AWS.CredentialsPath)
|
||||||
|
svc := ec2.NewFromConfig(awsConfig)
|
||||||
|
|
||||||
|
realInstance, err := getInstanceDetailsFromString(svc, instanceID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error getting instance details:\t", err)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
if !isInstanceRunning(realInstance) {
|
||||||
|
fmt.Println("Error, Instance is not running!")
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create image
|
||||||
|
t = time.Now()
|
||||||
|
imageName, err := createImage(svc, instanceID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error creating image:\t", err)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
fmt.Printf("Created image:\t\t%s (took %s)\n", imageName, time.Since(t).Round(100*time.Millisecond).String())
|
||||||
|
|
||||||
|
// Wait for image
|
||||||
|
t = time.Now()
|
||||||
|
err = waitForImageReady(svc, imageName, 5*time.Minute)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error waiting for image to be ready:\t", err)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
fmt.Printf("Image is ready:\t\t%s (took %s)\n", imageName, time.Since(t).Round(100*time.Millisecond).String())
|
||||||
|
|
||||||
|
// Launch new instance
|
||||||
|
t = time.Now()
|
||||||
|
newInstanceID, err := launchInstance(svc, realInstance, imageName, region)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error launching instance:\t", err)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
fmt.Printf("Launched new instance:\t%s (took %s)\n", newInstanceID, time.Since(t).Round(100*time.Millisecond).String())
|
||||||
|
|
||||||
|
// Wait for instance
|
||||||
|
t = time.Now()
|
||||||
|
err = waitForInstanceReady(svc, newInstanceID, 5*time.Minute)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error waiting for instance to be ready:\t", err)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
fmt.Printf("instance is ready:\t\t%s (took %s)\n", newInstanceID, time.Since(t).Round(100*time.Millisecond).String())
|
||||||
|
|
||||||
|
// update local config to match new instance
|
||||||
|
config = AWSUpdateService(config, region, serviceUUID, newInstanceID)
|
||||||
|
|
||||||
|
// Reconfigure Proxy to new instance
|
||||||
|
t = time.Now()
|
||||||
|
m := pcsdk.NewCommandModify(config.MTD.Services[serviceUUID].ServicePort, config.MTD.Services[serviceUUID].ServiceIP, serviceUUID)
|
||||||
|
err = m.Execute(netip.AddrPortFrom(config.MTD.Services[serviceUUID].EntryIP, config.MTD.ManagementPort))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error executing modify command: %s\n", err)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
fmt.Printf("Proxy modified. (took %s)\n", time.Since(t).Round(100*time.Millisecond).String())
|
||||||
|
|
||||||
|
// take care of old instance, deregister image and delete snapshot
|
||||||
|
cleanupAWS(svc, config, instanceID, imageName)
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// AWSUpdateService updates a specified service config to match a newly moved instance
|
||||||
|
func AWSUpdateService(config state.Config, region string, service state.CustomUUID, newInstanceID string) (state.Config) {
|
||||||
|
awsConfig := NewConfig(region, config.AWS.CredentialsPath)
|
||||||
|
svc := ec2.NewFromConfig(awsConfig)
|
||||||
|
instance, err := getInstanceDetailsFromString(svc, newInstanceID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error getting instance details:\t", err)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
var publicAddr string
|
||||||
|
if instance.PublicIpAddress != nil {
|
||||||
|
publicAddr = aws.ToString(instance.PublicIpAddress)
|
||||||
|
}
|
||||||
|
formattedinstance := AwsInstance{
|
||||||
|
InstanceID: aws.ToString(instance.InstanceId),
|
||||||
|
Region: region,
|
||||||
|
PublicIP: publicAddr,
|
||||||
|
PrivateIP: aws.ToString(instance.PrivateIpAddress),
|
||||||
|
}
|
||||||
|
cloudid := GetCloudID(formattedinstance)
|
||||||
|
serviceip := netip.MustParseAddr(publicAddr)
|
||||||
|
s := config.MTD.Services[service]
|
||||||
|
s.CloudID = cloudid
|
||||||
|
s.ServiceIP = serviceip
|
||||||
|
config.MTD.Services[service] = s
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// isInstanceRunning returns if an instance is running (true=running)
|
||||||
|
func isInstanceRunning(instance *types.Instance) bool {
|
||||||
|
return instance.State.Name == types.InstanceStateNameRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanupAWS terminates the old instance, deregisters the image and deletes the old snapshot
|
||||||
|
func cleanupAWS(svc *ec2.Client, config state.Config, instanceID string, imageName string) state.Config {
|
||||||
|
// Terminate old instance
|
||||||
|
t := time.Now()
|
||||||
|
err := terminateInstance(svc, instanceID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error terminating instance:\t", err)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
fmt.Printf("Killed old instance:\t%s (took %s)\n", instanceID, time.Since(t).Round(100*time.Millisecond).String())
|
||||||
|
|
||||||
|
// Deregister old image
|
||||||
|
t = time.Now()
|
||||||
|
image, err := describeImage(svc, imageName)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error describing image:\t", err)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
err = deregisterImage(svc, imageName)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error deregistering image:\t", err)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
fmt.Printf("Deregistered image:\t%s (took %s)\n", imageName, time.Since(t).Round(100*time.Millisecond).String())
|
||||||
|
|
||||||
|
// Delete old snapshot
|
||||||
|
t = time.Now()
|
||||||
|
if len(image.BlockDeviceMappings) > 0 {
|
||||||
|
snapshotID := aws.ToString(image.BlockDeviceMappings[0].Ebs.SnapshotId)
|
||||||
|
err = deleteSnapshot(svc, snapshotID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error deleting snapshot:\t", err)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
fmt.Printf("Deleted snapshot:\t%s (took %s)\n", snapshotID, time.Since(t).Round(100*time.Millisecond).String())
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
248
mtdaws/utils.go
248
mtdaws/utils.go
@ -2,8 +2,12 @@ package mtdaws
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/aws/aws-sdk-go-v2/config"
|
"github.com/aws/aws-sdk-go-v2/config"
|
||||||
@ -36,6 +40,17 @@ func GetCloudID(instance AwsInstance) string {
|
|||||||
return "aws_" + instance.Region + "_" + instance.InstanceID
|
return "aws_" + instance.Region + "_" + instance.InstanceID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeCloudID returns information to locate instance in aws
|
||||||
|
func DecodeCloudID(cloudID string) (string, string) {
|
||||||
|
split := strings.Split(cloudID, "_")
|
||||||
|
if len(split) != 3 {
|
||||||
|
panic(cloudID + " does not decode as AWS CloudID")
|
||||||
|
}
|
||||||
|
region := split[1]
|
||||||
|
instanceID := split[2]
|
||||||
|
return region, instanceID
|
||||||
|
}
|
||||||
|
|
||||||
// GetInstances scans all configured regions for instances and add them to services
|
// GetInstances scans all configured regions for instances and add them to services
|
||||||
func GetInstances(config state.Config) []AwsInstance {
|
func GetInstances(config state.Config) []AwsInstance {
|
||||||
awsInstances := []AwsInstance{}
|
awsInstances := []AwsInstance{}
|
||||||
@ -46,8 +61,6 @@ func GetInstances(config state.Config) []AwsInstance {
|
|||||||
fmt.Println("Error listing instances:", err)
|
fmt.Println("Error listing instances:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
//fmt.Println("Listing instances in region:", region)
|
|
||||||
for _, instance := range instances {
|
for _, instance := range instances {
|
||||||
var publicAddr string
|
var publicAddr string
|
||||||
if instance.PublicIpAddress != nil {
|
if instance.PublicIpAddress != nil {
|
||||||
@ -63,25 +76,12 @@ func GetInstances(config state.Config) []AwsInstance {
|
|||||||
return awsInstances
|
return awsInstances
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintInstanceInfo prints info about a specific instance in a region
|
|
||||||
func PrintInstanceInfo(instance *types.Instance) {
|
|
||||||
fmt.Println("\tInstance ID:", aws.ToString(instance.InstanceId))
|
|
||||||
fmt.Println("\t\tInstance Type:", string(instance.InstanceType))
|
|
||||||
fmt.Println("\t\tAMI ID:", aws.ToString(instance.ImageId))
|
|
||||||
fmt.Println("\t\tState:", string(instance.State.Name))
|
|
||||||
fmt.Println("\t\tAvailability Zone:", aws.ToString(instance.Placement.AvailabilityZone))
|
|
||||||
if instance.PublicIpAddress != nil {
|
|
||||||
fmt.Println("\t\tPublic IP Address:", aws.ToString(instance.PublicIpAddress))
|
|
||||||
}
|
|
||||||
fmt.Println("\t\tPrivate IP Address:", aws.ToString(instance.PrivateIpAddress))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instances returns all instances for a config i.e. a region
|
// Instances returns all instances for a config i.e. a region
|
||||||
func Instances(config aws.Config) ([]*types.Instance, error) {
|
func Instances(config aws.Config) ([]types.Instance, error) {
|
||||||
svc := ec2.NewFromConfig(config)
|
svc := ec2.NewFromConfig(config)
|
||||||
|
|
||||||
input := &ec2.DescribeInstancesInput{}
|
input := &ec2.DescribeInstancesInput{}
|
||||||
var instances []*types.Instance
|
var instances []types.Instance
|
||||||
|
|
||||||
paginator := ec2.NewDescribeInstancesPaginator(svc, input)
|
paginator := ec2.NewDescribeInstancesPaginator(svc, input)
|
||||||
|
|
||||||
@ -93,10 +93,220 @@ func Instances(config aws.Config) ([]*types.Instance, error) {
|
|||||||
|
|
||||||
for _, reservation := range page.Reservations {
|
for _, reservation := range page.Reservations {
|
||||||
for _, instance := range reservation.Instances {
|
for _, instance := range reservation.Instances {
|
||||||
instances = append(instances, &instance)
|
instances = append(instances, instance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return instances, nil
|
return instances, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createImage will create an AMI (amazon machine image) of a given instance
|
||||||
|
func createImage(svc *ec2.Client, instanceID string) (string, error) {
|
||||||
|
input := &ec2.CreateImageInput{
|
||||||
|
InstanceId: aws.String(instanceID),
|
||||||
|
Name: aws.String(fmt.Sprintf("backup-%s-%d", instanceID, time.Now().Unix())),
|
||||||
|
Description: aws.String("Migration backup"),
|
||||||
|
NoReboot: aws.Bool(true),
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := svc.CreateImage(context.TODO(), input)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return aws.ToString(output.ImageId), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForImageReady polls every second to see if the image is ready
|
||||||
|
func waitForImageReady(svc *ec2.Client, imageID string, timeout time.Duration) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return errors.New("timed out waiting for image to be ready")
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
input := &ec2.DescribeImagesInput{
|
||||||
|
ImageIds: []string{imageID},
|
||||||
|
}
|
||||||
|
output, err := svc.DescribeImages(ctx, input)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(output.Images) > 0 && output.Images[0].State == types.ImageStateAvailable {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForInstanceReady waits for the newly launched instance to be running and ready
|
||||||
|
func waitForInstanceReady(svc *ec2.Client, newInstanceID string, timeout time.Duration) error {
|
||||||
|
// Wait for the instance to be running
|
||||||
|
waitInput := &ec2.DescribeInstancesInput{
|
||||||
|
InstanceIds: []string{newInstanceID},
|
||||||
|
}
|
||||||
|
waiter := ec2.NewInstanceRunningWaiter(svc)
|
||||||
|
err := waiter.Wait(context.TODO(), waitInput, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// launchInstance launches a instance IN RANDOM AVAILABILITY ZONE within the same region, based on an oldInstance and AMI (duplicating the instance)
|
||||||
|
func launchInstance(svc *ec2.Client, oldInstance *types.Instance, imageID string, region string) (string, error) {
|
||||||
|
securityGroupIds := make([]string, len(oldInstance.SecurityGroups))
|
||||||
|
for i, sg := range oldInstance.SecurityGroups {
|
||||||
|
securityGroupIds[i] = aws.ToString(sg.GroupId)
|
||||||
|
}
|
||||||
|
availabilityZone, err := getRandomDifferentAvailabilityZone(svc, oldInstance, region)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var nameTag string
|
||||||
|
for _, tag := range oldInstance.Tags {
|
||||||
|
if aws.ToString(tag.Key) == "Name" {
|
||||||
|
nameTag = aws.ToString(tag.Value)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input := &ec2.RunInstancesInput{
|
||||||
|
ImageId: aws.String(imageID),
|
||||||
|
InstanceType: oldInstance.InstanceType,
|
||||||
|
MinCount: aws.Int32(1),
|
||||||
|
MaxCount: aws.Int32(1),
|
||||||
|
KeyName: oldInstance.KeyName,
|
||||||
|
SecurityGroupIds: securityGroupIds,
|
||||||
|
Placement: &types.Placement{
|
||||||
|
AvailabilityZone: aws.String(availabilityZone),
|
||||||
|
},
|
||||||
|
TagSpecifications: []types.TagSpecification{
|
||||||
|
{
|
||||||
|
ResourceType: types.ResourceTypeInstance,
|
||||||
|
Tags: []types.Tag{
|
||||||
|
{
|
||||||
|
Key: aws.String("Name"),
|
||||||
|
Value: aws.String(nameTag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := svc.RunInstances(context.TODO(), input)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: save/index config for the new instance
|
||||||
|
|
||||||
|
return aws.ToString(output.Instances[0].InstanceId), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRandomDifferentAvailabilityZone fetches all AZ from the same region as the instance and returns a random AZ that is not equal to the one used by the instance
|
||||||
|
func getRandomDifferentAvailabilityZone(svc *ec2.Client, instance *types.Instance, region string) (string, error) {
|
||||||
|
// Seed the random generator
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
|
// Get the current availability zone of the instance
|
||||||
|
currentAZ := aws.ToString(instance.Placement.AvailabilityZone)
|
||||||
|
|
||||||
|
// Describe availability zones in the region
|
||||||
|
input := &ec2.DescribeAvailabilityZonesInput{
|
||||||
|
Filters: []types.Filter{
|
||||||
|
{
|
||||||
|
Name: aws.String("region-name"),
|
||||||
|
Values: []string{region},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := svc.DescribeAvailabilityZones(context.TODO(), input)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out the current availability zone
|
||||||
|
availableAZs := []string{}
|
||||||
|
for _, az := range output.AvailabilityZones {
|
||||||
|
if aws.ToString(az.ZoneName) != currentAZ {
|
||||||
|
availableAZs = append(availableAZs, aws.ToString(az.ZoneName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no other availability zones are available, return an error
|
||||||
|
if len(availableAZs) == 0 {
|
||||||
|
return "", errors.New("no other availability zones available")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select a random availability zone from the remaining ones
|
||||||
|
randomIndex := rand.Intn(len(availableAZs))
|
||||||
|
randomAZ := availableAZs[randomIndex]
|
||||||
|
return randomAZ, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// terminateInstance kills an instance by id
|
||||||
|
func terminateInstance(svc *ec2.Client, instanceID string) error {
|
||||||
|
input := &ec2.TerminateInstancesInput{
|
||||||
|
InstanceIds: []string{instanceID},
|
||||||
|
}
|
||||||
|
_, err := svc.TerminateInstances(context.TODO(), input)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// describeImage gets info about an image from string
|
||||||
|
func describeImage(svc *ec2.Client, imageID string) (*types.Image, error) {
|
||||||
|
input := &ec2.DescribeImagesInput{
|
||||||
|
ImageIds: []string{imageID},
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := svc.DescribeImages(context.TODO(), input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(output.Images) == 0 {
|
||||||
|
return nil, errors.New("image not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &output.Images[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deregisterImage deletes the AMI passed as string
|
||||||
|
func deregisterImage(svc *ec2.Client, imageID string) error {
|
||||||
|
input := &ec2.DeregisterImageInput{
|
||||||
|
ImageId: aws.String(imageID),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := svc.DeregisterImage(context.TODO(), input)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteSnapshot deletes the snapshot passed as string
|
||||||
|
func deleteSnapshot(svc *ec2.Client, snapshotID string) error {
|
||||||
|
input := &ec2.DeleteSnapshotInput{
|
||||||
|
SnapshotId: aws.String(snapshotID),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := svc.DeleteSnapshot(context.TODO(), input)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInstanceDetailsFromString does what the name says
|
||||||
|
func getInstanceDetailsFromString(svc *ec2.Client, instanceID string) (*types.Instance, error) {
|
||||||
|
input := &ec2.DescribeInstancesInput{
|
||||||
|
InstanceIds: []string{instanceID},
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := svc.DescribeInstances(context.TODO(), input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &output.Reservations[0].Instances[0], nil
|
||||||
|
}
|
||||||
|
@ -57,11 +57,7 @@ func (p Proxy) execute(c command) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New(fmt.Sprintf("error making http request: %s\n", err))
|
return "", errors.New(fmt.Sprintf("error making http request: %s\n", err))
|
||||||
}
|
}
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
fmt.Println(res)
|
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
|
||||||
fmt.Println(string(body))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New(fmt.Sprintf("error reading response: %s\n", err))
|
return "", errors.New(fmt.Sprintf("error reading response: %s\n", err))
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,16 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type mtdconf struct {
|
type mtdconf struct {
|
||||||
Services []Service `yaml:"services"`
|
Services map[CustomUUID]Service `yaml:"services"`
|
||||||
|
ManagementPort uint16 `yaml:"management_port"`
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service contains all necessary information about a service to identify it in the cloud as well as configuring a proxy for it
|
// Service contains all necessary information about a service to identify it in the cloud as well as configuring a proxy for it
|
||||||
type Service struct {
|
type Service struct {
|
||||||
ID CustomUUID `yaml:"id"`
|
|
||||||
CloudID string `yaml:"cloud_id"`
|
CloudID string `yaml:"cloud_id"`
|
||||||
|
AdminEnabled bool `yaml:"admin_enabled"`
|
||||||
|
Active bool `yaml:"active"`
|
||||||
EntryIP netip.Addr `yaml:"entry_ip"`
|
EntryIP netip.Addr `yaml:"entry_ip"`
|
||||||
EntryPort uint16 `yaml:"entry_port"`
|
EntryPort uint16 `yaml:"entry_port"`
|
||||||
ServiceIP netip.Addr `yaml:"service_ip"`
|
ServiceIP netip.Addr `yaml:"service_ip"`
|
||||||
|
Loading…
Reference in New Issue
Block a user