From 377dc47eace8ef71853b250e979363aa34fbe534 Mon Sep 17 00:00:00 2001 From: schulze Date: Wed, 22 Mar 2023 15:46:49 +0100 Subject: [PATCH 01/25] pointer bug fix --- mtdaws/utils.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mtdaws/utils.go b/mtdaws/utils.go index b59805b..8f7a2ae 100644 --- a/mtdaws/utils.go +++ b/mtdaws/utils.go @@ -77,11 +77,11 @@ func PrintInstanceInfo(instance *types.Instance) { } // 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) input := &ec2.DescribeInstancesInput{} - var instances []*types.Instance + var instances []types.Instance paginator := ec2.NewDescribeInstancesPaginator(svc, input) @@ -93,10 +93,9 @@ func Instances(config aws.Config) ([]*types.Instance, error) { for _, reservation := range page.Reservations { for _, instance := range reservation.Instances { - instances = append(instances, &instance) + instances = append(instances, instance) } } } - return instances, nil } From 83383a41568c245e4d7889307d0bc29785586175 Mon Sep 17 00:00:00 2001 From: schulze Date: Wed, 22 Mar 2023 15:46:58 +0100 Subject: [PATCH 02/25] prints --- main.go | 1 + mtdaws/utils.go | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 3db5399..423a9a9 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ func indexInstances(config state.Config) state.Config { } newService, found := indexInstance(config, cloudID, ip) if !found { + fmt.Println("New instance found:", newService.CloudID) config.MTD.Services = append(config.MTD.Services, newService) state.SaveConf(ConfigPath, config) } diff --git a/mtdaws/utils.go b/mtdaws/utils.go index 8f7a2ae..41356a7 100644 --- a/mtdaws/utils.go +++ b/mtdaws/utils.go @@ -46,8 +46,6 @@ func GetInstances(config state.Config) []AwsInstance { fmt.Println("Error listing instances:", err) continue } - - //fmt.Println("Listing instances in region:", region) for _, instance := range instances { var publicAddr string if instance.PublicIpAddress != nil { @@ -66,6 +64,7 @@ func GetInstances(config state.Config) []AwsInstance { // 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 DNS name:", aws.ToString(instance.PublicDnsName)) 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)) From cb7ac73acb3906785dbe2491889ea777563b1daf Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 28 Mar 2023 16:08:42 +0200 Subject: [PATCH 03/25] utils functions that *should* enable MTD --- mtdaws/utils.go | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/mtdaws/utils.go b/mtdaws/utils.go index 41356a7..2f8b440 100644 --- a/mtdaws/utils.go +++ b/mtdaws/utils.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "os" + "strings" + "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" @@ -36,6 +38,17 @@ func GetCloudID(instance AwsInstance) string { 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 func GetInstances(config state.Config) []AwsInstance { awsInstances := []AwsInstance{} @@ -98,3 +111,69 @@ func Instances(config aws.Config) ([]types.Instance, error) { } 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 +} + +// launchInstance launches a instance specified by id with parameters +func launchInstance(svc *ec2.Client, instance *types.Instance, imageID string) (string, error) { + securityGroupIds := make([]string, len(instance.SecurityGroups)) + for i, sg := range instance.SecurityGroups { + securityGroupIds[i] = aws.ToString(sg.GroupId) + } + + input := &ec2.RunInstancesInput{ + ImageId: aws.String(imageID), + InstanceType: instance.InstanceType, + MinCount: aws.Int32(1), + MaxCount: aws.Int32(1), + KeyName: instance.KeyName, + SubnetId: instance.SubnetId, + SecurityGroupIds: securityGroupIds, + } + + output, err := svc.RunInstances(context.TODO(), input) + if err != nil { + return "", err + } + + return aws.ToString(output.Instances[0].InstanceId), 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 +} + +// 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 +} From d9aa192cd1a35c9c566765404e1c5cd26ad1c35f Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 28 Mar 2023 16:09:14 +0200 Subject: [PATCH 04/25] move a hardcoded instance (not working) --- mtdaws/mtd.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 mtdaws/mtd.go diff --git a/mtdaws/mtd.go b/mtdaws/mtd.go new file mode 100644 index 0000000..cbd1d4c --- /dev/null +++ b/mtdaws/mtd.go @@ -0,0 +1,46 @@ +package mtdaws + +import ( + "fmt" + + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/thefeli73/polemos/state" +) + +// AWSMoveInstance moves a specified instance to a new availability region +func AWSMoveInstance(config state.Config) (state.Config) { + instance := config.MTD.Services[0] // for testing use first instance + 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:", err) + return config + } + + imageName, err := createImage(svc, instanceID) + if err != nil { + fmt.Println("Error creating image:", err) + return config + } + fmt.Println("Created image: ", imageName) + + newInstanceID, err := launchInstance(svc, realInstance, imageName) + if err != nil { + fmt.Println("Error launching instance:", err) + return config + } + fmt.Println("Launched new instance:", newInstanceID) + + err = terminateInstance(svc, instanceID) + if err != nil { + fmt.Println("Error terminating instance:", err) + return config + } + fmt.Println("Terminated original instance:", instanceID) + + + return config +} From d0cd4340a5fd121fa1a15c2e5cb086a59b29fda2 Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 28 Mar 2023 16:09:30 +0200 Subject: [PATCH 05/25] stuff --- main.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 423a9a9..a185c29 100644 --- a/main.go +++ b/main.go @@ -20,14 +20,26 @@ func main() { config := state.LoadConf(ConfigPath) state.SaveConf(ConfigPath, config) - config = indexInstances(config) + config = indexAllInstances(config) + //TODO: figure out migration (MTD) + config = movingTargetDefense(config) + + //TODO: proxy commands } -func indexInstances(config state.Config) state.Config { +func movingTargetDefense(config state.Config) state.Config{ + + mtdaws.AWSMoveInstance(config) + return config +} + +func indexAllInstances(config state.Config) state.Config { fmt.Println("Indexing instances") //index AWS instances + awsNewInstanceCounter := 0 + awsInstanceCounter := 0 awsInstances := mtdaws.GetInstances(config) for _, instance := range awsInstances { cloudID := mtdaws.GetCloudID(instance) @@ -41,8 +53,13 @@ func indexInstances(config state.Config) state.Config { fmt.Println("New instance found:", newService.CloudID) config.MTD.Services = append(config.MTD.Services, newService) state.SaveConf(ConfigPath, config) + awsNewInstanceCounter++ } + awsInstanceCounter++ } + fmt.Printf("Found %d AWS instances (%d newly added)\n", awsInstanceCounter, awsNewInstanceCounter) + + return config } From c6c26bd9146d205ca661e681d6c81d125f34fc44 Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 11 Apr 2023 12:55:38 +0200 Subject: [PATCH 06/25] remove UUIDs --- main.go | 8 ++++---- state/config.go | 20 -------------------- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/main.go b/main.go index a185c29..4ca5190 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,6 @@ import ( "fmt" "net/netip" - "github.com/google/uuid" "github.com/thefeli73/polemos/mtdaws" "github.com/thefeli73/polemos/state" ) @@ -39,6 +38,7 @@ func indexAllInstances(config state.Config) state.Config { //index AWS instances awsNewInstanceCounter := 0 + awsRemovedInstanceCounter := 0 awsInstanceCounter := 0 awsInstances := mtdaws.GetInstances(config) for _, instance := range awsInstances { @@ -57,7 +57,8 @@ func indexAllInstances(config state.Config) state.Config { } awsInstanceCounter++ } - fmt.Printf("Found %d AWS instances (%d newly added)\n", awsInstanceCounter, awsNewInstanceCounter) + // TODO: Purge instances in config that are not found in the cloud + fmt.Printf("Found %d AWS instances (%d newly added, %d removed)\n", awsInstanceCounter, awsNewInstanceCounter, awsRemovedInstanceCounter) return config @@ -68,11 +69,10 @@ func indexInstance(config state.Config, cloudID string, serviceIP netip.Addr) (s for _, service := range config.MTD.Services { if service.CloudID == cloudID { found = true + break; } } - u := uuid.New() newService := state.Service{ - ID: state.CustomUUID(u), CloudID: cloudID, ServiceIP: serviceIP} return newService, found diff --git a/state/config.go b/state/config.go index a74c59e..628df3f 100644 --- a/state/config.go +++ b/state/config.go @@ -6,7 +6,6 @@ import ( "net/netip" "os" - "github.com/google/uuid" "gopkg.in/yaml.v3" ) @@ -22,7 +21,6 @@ type mtdconf struct { // 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 { - ID CustomUUID `yaml:"id"` CloudID string `yaml:"cloud_id"` EntryIP netip.Addr `yaml:"entry_ip"` EntryPort uint16 `yaml:"entry_port"` @@ -30,29 +28,11 @@ type Service struct { ServicePort uint16 `yaml:"service_port"` } -// CustomUUID is an alias for uuid.UUID to enable custom unmarshal function -type CustomUUID uuid.UUID - type aws struct { Regions []string `yaml:"regions"` CredentialsPath string `yaml:"credentials_path"` } -// UnmarshalYAML parses uuid in yaml to CustomUUID type -func (u *CustomUUID) UnmarshalYAML(value *yaml.Node) error { - id, err := uuid.Parse(value.Value) - if err != nil { - return err - } - *u = CustomUUID(id) - return nil -} - -// MarshalYAML parses CustomUUID type to uuid string for yaml -func (u CustomUUID) MarshalYAML() (interface{}, error) { - return uuid.UUID(u).String(), nil -} - // LoadConf loads config from a yaml file func LoadConf(filename string) (Config) { var config Config From 7bd42bc569dfd8073a951cb8974b129d90551b41 Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 11 Apr 2023 12:56:07 +0200 Subject: [PATCH 07/25] Wait for image ready (migration working --- mtdaws/mtd.go | 8 ++++++++ mtdaws/utils.go | 49 +++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/mtdaws/mtd.go b/mtdaws/mtd.go index cbd1d4c..ab60d50 100644 --- a/mtdaws/mtd.go +++ b/mtdaws/mtd.go @@ -2,6 +2,7 @@ package mtdaws import ( "fmt" + "time" "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/thefeli73/polemos/state" @@ -27,6 +28,13 @@ func AWSMoveInstance(config state.Config) (state.Config) { } fmt.Println("Created image: ", imageName) + err = waitForImageReady(svc, imageName, 5*time.Minute) + if err != nil { + fmt.Println("Error waiting for image to be ready:", err) + return config + } + fmt.Println("Image is ready:", imageName) + newInstanceID, err := launchInstance(svc, realInstance, imageName) if err != nil { fmt.Println("Error launching instance:", err) diff --git a/mtdaws/utils.go b/mtdaws/utils.go index 2f8b440..429ffb3 100644 --- a/mtdaws/utils.go +++ b/mtdaws/utils.go @@ -2,6 +2,7 @@ package mtdaws import ( "context" + "errors" "fmt" "os" "strings" @@ -129,21 +130,49 @@ func createImage(svc *ec2.Client, instanceID string) (string, error) { return aws.ToString(output.ImageId), nil } -// launchInstance launches a instance specified by id with parameters -func launchInstance(svc *ec2.Client, instance *types.Instance, imageID string) (string, error) { - securityGroupIds := make([]string, len(instance.SecurityGroups)) - for i, sg := range instance.SecurityGroups { +// 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 + } + } + } +} + +// launchInstance launches a instance based on an oldInstance and AMI (duplicating the instance) +func launchInstance(svc *ec2.Client, oldInstance *types.Instance, imageID string) (string, error) { + securityGroupIds := make([]string, len(oldInstance.SecurityGroups)) + for i, sg := range oldInstance.SecurityGroups { securityGroupIds[i] = aws.ToString(sg.GroupId) } - + // TODO: select random zone that is not the current one. + availabilityZone := "us-east-1d" input := &ec2.RunInstancesInput{ ImageId: aws.String(imageID), - InstanceType: instance.InstanceType, + InstanceType: oldInstance.InstanceType, MinCount: aws.Int32(1), MaxCount: aws.Int32(1), - KeyName: instance.KeyName, - SubnetId: instance.SubnetId, + KeyName: oldInstance.KeyName, SecurityGroupIds: securityGroupIds, + Placement: &types.Placement{ + AvailabilityZone: aws.String(availabilityZone), + }, } output, err := svc.RunInstances(context.TODO(), input) @@ -151,6 +180,8 @@ func launchInstance(svc *ec2.Client, instance *types.Instance, imageID string) ( return "", err } + // TODO: save/index config for the new instance + return aws.ToString(output.Instances[0].InstanceId), nil } @@ -161,6 +192,8 @@ func terminateInstance(svc *ec2.Client, instanceID string) error { } _, err := svc.TerminateInstances(context.TODO(), input) + + // TODO: remove config for old instance return err } From 65d815aa8b80836ed9ca334814d7540f18e7934c Mon Sep 17 00:00:00 2001 From: schulze Date: Wed, 12 Apr 2023 10:16:22 +0200 Subject: [PATCH 08/25] remove old image and snapshot, formatting --- main.go | 4 ++-- mtdaws/mtd.go | 41 +++++++++++++++++++++++++++++--------- mtdaws/utils.go | 52 ++++++++++++++++++++++++++++++++++++------------- 3 files changed, 72 insertions(+), 25 deletions(-) diff --git a/main.go b/main.go index 4ca5190..f7a5ef4 100644 --- a/main.go +++ b/main.go @@ -45,12 +45,12 @@ func indexAllInstances(config state.Config) state.Config { cloudID := mtdaws.GetCloudID(instance) ip, err := netip.ParseAddr(instance.PublicIP) if err != nil { - fmt.Println("Error converting ip:", err) + fmt.Println("Error converting ip:\t", err) continue } newService, found := indexInstance(config, cloudID, ip) if !found { - fmt.Println("New instance found:", newService.CloudID) + fmt.Println("New instance found:\t", newService.CloudID) config.MTD.Services = append(config.MTD.Services, newService) state.SaveConf(ConfigPath, config) awsNewInstanceCounter++ diff --git a/mtdaws/mtd.go b/mtdaws/mtd.go index ab60d50..c307f50 100644 --- a/mtdaws/mtd.go +++ b/mtdaws/mtd.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/thefeli73/polemos/state" ) @@ -17,38 +18,60 @@ func AWSMoveInstance(config state.Config) (state.Config) { realInstance, err := getInstanceDetailsFromString(svc, instanceID) if err != nil { - fmt.Println("Error getting instance details:", err) + fmt.Println("Error getting instance details:\t", err) return config } imageName, err := createImage(svc, instanceID) if err != nil { - fmt.Println("Error creating image:", err) + fmt.Println("Error creating image:\t", err) return config } - fmt.Println("Created image: ", imageName) + fmt.Println("Created image:\t", imageName) err = waitForImageReady(svc, imageName, 5*time.Minute) if err != nil { - fmt.Println("Error waiting for image to be ready:", err) + fmt.Println("Error waiting for image to be ready:\t", err) return config } - fmt.Println("Image is ready:", imageName) + fmt.Println("Image is ready:\t", imageName) newInstanceID, err := launchInstance(svc, realInstance, imageName) if err != nil { - fmt.Println("Error launching instance:", err) + fmt.Println("Error launching instance:\t", err) return config } - fmt.Println("Launched new instance:", newInstanceID) + fmt.Println("Launched new instance:\t", newInstanceID) err = terminateInstance(svc, instanceID) if err != nil { - fmt.Println("Error terminating instance:", err) + fmt.Println("Error terminating instance:\t", err) return config } - fmt.Println("Terminated original instance:", instanceID) + fmt.Println("Terminated old instance:\t", instanceID) + 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.Println("Deregistered image:\t", imageName) + + 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.Println("Deleted snapshot:\t", snapshotID) + } return config } diff --git a/mtdaws/utils.go b/mtdaws/utils.go index 429ffb3..32e87ed 100644 --- a/mtdaws/utils.go +++ b/mtdaws/utils.go @@ -75,20 +75,6 @@ func GetInstances(config state.Config) []AwsInstance { 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 DNS name:", aws.ToString(instance.PublicDnsName)) - 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 func Instances(config aws.Config) ([]types.Instance, error) { svc := ec2.NewFromConfig(config) @@ -197,6 +183,44 @@ func terminateInstance(svc *ec2.Client, instanceID string) error { 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{ From c6465ec429265e0d31ef68bfe457eefa4414e76d Mon Sep 17 00:00:00 2001 From: schulze Date: Wed, 12 Apr 2023 11:13:53 +0200 Subject: [PATCH 09/25] Revert UUID purge --- main.go | 3 +++ state/config.go | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/main.go b/main.go index f7a5ef4..73e4e72 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "fmt" "net/netip" + "github.com/google/uuid" "github.com/thefeli73/polemos/mtdaws" "github.com/thefeli73/polemos/state" ) @@ -72,7 +73,9 @@ func indexInstance(config state.Config, cloudID string, serviceIP netip.Addr) (s break; } } + u := uuid.New() newService := state.Service{ + ID: state.CustomUUID(u), CloudID: cloudID, ServiceIP: serviceIP} return newService, found diff --git a/state/config.go b/state/config.go index 628df3f..a74c59e 100644 --- a/state/config.go +++ b/state/config.go @@ -6,6 +6,7 @@ import ( "net/netip" "os" + "github.com/google/uuid" "gopkg.in/yaml.v3" ) @@ -21,6 +22,7 @@ type mtdconf struct { // 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 { + ID CustomUUID `yaml:"id"` CloudID string `yaml:"cloud_id"` EntryIP netip.Addr `yaml:"entry_ip"` EntryPort uint16 `yaml:"entry_port"` @@ -28,11 +30,29 @@ type Service struct { ServicePort uint16 `yaml:"service_port"` } +// CustomUUID is an alias for uuid.UUID to enable custom unmarshal function +type CustomUUID uuid.UUID + type aws struct { Regions []string `yaml:"regions"` CredentialsPath string `yaml:"credentials_path"` } +// UnmarshalYAML parses uuid in yaml to CustomUUID type +func (u *CustomUUID) UnmarshalYAML(value *yaml.Node) error { + id, err := uuid.Parse(value.Value) + if err != nil { + return err + } + *u = CustomUUID(id) + return nil +} + +// MarshalYAML parses CustomUUID type to uuid string for yaml +func (u CustomUUID) MarshalYAML() (interface{}, error) { + return uuid.UUID(u).String(), nil +} + // LoadConf loads config from a yaml file func LoadConf(filename string) (Config) { var config Config From 03eff229c6045c2adce33a10b84274aabfc22f88 Mon Sep 17 00:00:00 2001 From: schulze Date: Wed, 12 Apr 2023 15:31:11 +0200 Subject: [PATCH 10/25] switch from list (slice) to dict (map) --- main.go | 7 ++++++- mtdaws/mtd.go | 18 ++++++++++++++---- state/config.go | 4 ++-- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/main.go b/main.go index 73e4e72..473b76e 100644 --- a/main.go +++ b/main.go @@ -17,9 +17,14 @@ func main() { 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) + config = indexAllInstances(config) //TODO: figure out migration (MTD) diff --git a/mtdaws/mtd.go b/mtdaws/mtd.go index c307f50..e45e4ca 100644 --- a/mtdaws/mtd.go +++ b/mtdaws/mtd.go @@ -11,7 +11,17 @@ import ( // AWSMoveInstance moves a specified instance to a new availability region func AWSMoveInstance(config state.Config) (state.Config) { - instance := config.MTD.Services[0] // for testing use first instance + // 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 + break + } + + fmt.Println("MTD move service:\t", serviceUUID) + region, instanceID := DecodeCloudID(instance.CloudID) awsConfig := NewConfig(region, config.AWS.CredentialsPath) svc := ec2.NewFromConfig(awsConfig) @@ -27,14 +37,14 @@ func AWSMoveInstance(config state.Config) (state.Config) { fmt.Println("Error creating image:\t", err) return config } - fmt.Println("Created image:\t", imageName) + fmt.Println("Created image:\t\t", imageName) err = waitForImageReady(svc, imageName, 5*time.Minute) if err != nil { fmt.Println("Error waiting for image to be ready:\t", err) return config } - fmt.Println("Image is ready:\t", imageName) + fmt.Println("Image is ready:\t\t", imageName) newInstanceID, err := launchInstance(svc, realInstance, imageName) if err != nil { @@ -48,7 +58,7 @@ func AWSMoveInstance(config state.Config) (state.Config) { fmt.Println("Error terminating instance:\t", err) return config } - fmt.Println("Terminated old instance:\t", instanceID) + fmt.Println("Killed old instance:\t", instanceID) image, err := describeImage(svc, imageName) if err != nil { diff --git a/state/config.go b/state/config.go index a74c59e..395f9a0 100644 --- a/state/config.go +++ b/state/config.go @@ -17,12 +17,12 @@ type Config struct { } type mtdconf struct { - Services []Service `yaml:"services"` + Services map[CustomUUID]Service `yaml:"services"` + } // 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 { - ID CustomUUID `yaml:"id"` CloudID string `yaml:"cloud_id"` EntryIP netip.Addr `yaml:"entry_ip"` EntryPort uint16 `yaml:"entry_port"` From 61175acc67f8118125d53348c858d1488f0937fb Mon Sep 17 00:00:00 2001 From: schulze Date: Mon, 17 Apr 2023 15:20:40 +0200 Subject: [PATCH 11/25] update services config after MTD --- mtdaws/mtd.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/mtdaws/mtd.go b/mtdaws/mtd.go index e45e4ca..3f81795 100644 --- a/mtdaws/mtd.go +++ b/mtdaws/mtd.go @@ -2,6 +2,7 @@ package mtdaws import ( "fmt" + "net/netip" "time" "github.com/aws/aws-sdk-go-v2/aws" @@ -9,6 +10,32 @@ import ( "github.com/thefeli73/polemos/state" ) +// 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) + config.MTD.Services[service] = state.Service{CloudID: cloudid, ServiceIP: serviceip} + return config +} + // AWSMoveInstance moves a specified instance to a new availability region func AWSMoveInstance(config state.Config) (state.Config) { // pseudorandom instance from all services for testing @@ -83,5 +110,7 @@ func AWSMoveInstance(config state.Config) (state.Config) { fmt.Println("Deleted snapshot:\t", snapshotID) } + AWSUpdateService(config, region, serviceUUID, newInstanceID) + return config } From 3fb51d8f84dba015524d671581c7362e3152fbd9 Mon Sep 17 00:00:00 2001 From: schulze Date: Mon, 17 Apr 2023 15:21:06 +0200 Subject: [PATCH 12/25] misc --- config.default.yaml | 2 +- main.go | 29 +++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/config.default.yaml b/config.default.yaml index 4420f39..77240ff 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -1,5 +1,5 @@ mtd: - services: [] + services: {} aws: regions: [] credentials_path: ./mtdaws/.credentials diff --git a/main.go b/main.go index 473b76e..774efbd 100644 --- a/main.go +++ b/main.go @@ -26,9 +26,11 @@ func main() { config = indexAllInstances(config) + state.SaveConf(ConfigPath, config) //TODO: figure out migration (MTD) config = movingTargetDefense(config) + state.SaveConf(ConfigPath, config) //TODO: proxy commands } @@ -54,13 +56,9 @@ func indexAllInstances(config state.Config) state.Config { fmt.Println("Error converting ip:\t", err) continue } - newService, found := indexInstance(config, cloudID, ip) - if !found { - fmt.Println("New instance found:\t", newService.CloudID) - config.MTD.Services = append(config.MTD.Services, newService) - state.SaveConf(ConfigPath, config) - awsNewInstanceCounter++ - } + var found bool + config, found = indexInstance(config, cloudID, ip) + if !found {awsNewInstanceCounter++} awsInstanceCounter++ } // TODO: Purge instances in config that are not found in the cloud @@ -70,7 +68,7 @@ func indexAllInstances(config state.Config) state.Config { return config } -func indexInstance(config state.Config, cloudID string, serviceIP netip.Addr) (state.Service, bool) { +func indexInstance(config state.Config, cloudID string, serviceIP netip.Addr) (state.Config, bool) { found := false for _, service := range config.MTD.Services { if service.CloudID == cloudID { @@ -78,10 +76,13 @@ func indexInstance(config state.Config, cloudID string, serviceIP netip.Addr) (s break; } } - u := uuid.New() - newService := state.Service{ - ID: state.CustomUUID(u), - CloudID: cloudID, - ServiceIP: serviceIP} - return newService, found + + if !found { + fmt.Println("New instance found:\t", cloudID) + u := uuid.New() + config.MTD.Services[state.CustomUUID(u)] = state.Service{CloudID: cloudID, ServiceIP: serviceIP} + state.SaveConf(ConfigPath, config) + + } + return config, found } From e8962c3cba8c47c73b7bce93466d7bd6e44231fe Mon Sep 17 00:00:00 2001 From: schulze Date: Mon, 17 Apr 2023 15:54:42 +0200 Subject: [PATCH 13/25] Availability zone randomisation --- mtdaws/mtd.go | 6 ++++-- mtdaws/utils.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/mtdaws/mtd.go b/mtdaws/mtd.go index 3f81795..8e428ae 100644 --- a/mtdaws/mtd.go +++ b/mtdaws/mtd.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/google/uuid" "github.com/thefeli73/polemos/state" ) @@ -38,6 +39,7 @@ func AWSUpdateService(config state.Config, region string, service state.CustomUU // 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 @@ -47,7 +49,7 @@ func AWSMoveInstance(config state.Config) (state.Config) { break } - fmt.Println("MTD move service:\t", serviceUUID) + fmt.Println("MTD move service:\t", uuid.UUID.String(uuid.UUID(serviceUUID))) region, instanceID := DecodeCloudID(instance.CloudID) awsConfig := NewConfig(region, config.AWS.CredentialsPath) @@ -73,7 +75,7 @@ func AWSMoveInstance(config state.Config) (state.Config) { } fmt.Println("Image is ready:\t\t", imageName) - newInstanceID, err := launchInstance(svc, realInstance, imageName) + newInstanceID, err := launchInstance(svc, realInstance, imageName, region) if err != nil { fmt.Println("Error launching instance:\t", err) return config diff --git a/mtdaws/utils.go b/mtdaws/utils.go index 32e87ed..2d4d5a4 100644 --- a/mtdaws/utils.go +++ b/mtdaws/utils.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math/rand" "os" "strings" "time" @@ -141,14 +142,18 @@ func waitForImageReady(svc *ec2.Client, imageID string, timeout time.Duration) e } } -// launchInstance launches a instance based on an oldInstance and AMI (duplicating the instance) -func launchInstance(svc *ec2.Client, oldInstance *types.Instance, imageID string) (string, error) { +// 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) } // TODO: select random zone that is not the current one. - availabilityZone := "us-east-1d" + availabilityZone, err := getRandomDifferentAvailabilityZone(svc, oldInstance, region) + if err != nil { + return "", err + } + input := &ec2.RunInstancesInput{ ImageId: aws.String(imageID), InstanceType: oldInstance.InstanceType, @@ -171,6 +176,49 @@ func launchInstance(svc *ec2.Client, oldInstance *types.Instance, imageID string 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{ From 143e839e78118ca07cc1b19f6210e4aea5e70546 Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 18 Apr 2023 11:08:31 +0200 Subject: [PATCH 14/25] loop MTD, checks before doing mtd to service --- main.go | 15 +++++++++++---- mtdaws/mtd.go | 20 ++++++++++++++++++++ state/config.go | 2 ++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 774efbd..f4f9c15 100644 --- a/main.go +++ b/main.go @@ -28,11 +28,18 @@ func main() { config = indexAllInstances(config) state.SaveConf(ConfigPath, config) - //TODO: figure out migration (MTD) - config = movingTargetDefense(config) - state.SaveConf(ConfigPath, config) + // START DOING MTD + mtdLoop(config) +} - //TODO: proxy commands +func mtdLoop(config state.Config) { + for true { + //TODO: figure out migration (MTD) + config = movingTargetDefense(config) + state.SaveConf(ConfigPath, config) + + //TODO: proxy commands + } } func movingTargetDefense(config state.Config) state.Config{ diff --git a/mtdaws/mtd.go b/mtdaws/mtd.go index 8e428ae..7811a34 100644 --- a/mtdaws/mtd.go +++ b/mtdaws/mtd.go @@ -7,6 +7,7 @@ import ( "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/state" ) @@ -37,6 +38,12 @@ func AWSUpdateService(config state.Config, region string, service state.CustomUU return config } + +// isInstanceRunning returns if an instance is running (true=running) +func isInstanceRunning(instance *types.Instance) bool { + return instance.State.Name == types.InstanceStateNameRunning +} + // AWSMoveInstance moves a specified instance to a new availability region func AWSMoveInstance(config state.Config) (state.Config) { @@ -61,6 +68,19 @@ func AWSMoveInstance(config state.Config) (state.Config) { return config } + if !isInstanceRunning(realInstance) { + fmt.Println("Error, Instance is not running!") + return config + } + if instance.AdminDisabled { + fmt.Println("Error, Service is Disabled!") + return config + } + if instance.Inactive { + fmt.Println("Error, Service is Inactive!") + return config + } + imageName, err := createImage(svc, instanceID) if err != nil { fmt.Println("Error creating image:\t", err) diff --git a/state/config.go b/state/config.go index 395f9a0..e96bfdb 100644 --- a/state/config.go +++ b/state/config.go @@ -24,6 +24,8 @@ type mtdconf struct { // 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 { CloudID string `yaml:"cloud_id"` + AdminDisabled bool `yaml:"admin_disabled"` + Inactive bool `yaml:"inactive"` EntryIP netip.Addr `yaml:"entry_ip"` EntryPort uint16 `yaml:"entry_port"` ServiceIP netip.Addr `yaml:"service_ip"` From 5571b66d52f7d569aa29d312e4b28a6594be961a Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 18 Apr 2023 15:03:34 +0200 Subject: [PATCH 15/25] disregard inactive instances --- main.go | 13 +++++++++++-- mtdaws/mtd.go | 14 +++++++------- state/config.go | 4 ++-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/main.go b/main.go index f4f9c15..3e0cfc5 100644 --- a/main.go +++ b/main.go @@ -51,6 +51,10 @@ func movingTargetDefense(config state.Config) state.Config{ func indexAllInstances(config state.Config) state.Config { fmt.Println("Indexing instances") + for _, service := range config.MTD.Services { + service.Active = false + } + //index AWS instances awsNewInstanceCounter := 0 awsRemovedInstanceCounter := 0 @@ -77,9 +81,11 @@ func indexAllInstances(config state.Config) state.Config { func indexInstance(config state.Config, cloudID string, serviceIP netip.Addr) (state.Config, bool) { found := false - for _, service := range config.MTD.Services { + var foundUUID state.CustomUUID + for u, service := range config.MTD.Services { if service.CloudID == cloudID { found = true + foundUUID = u break; } } @@ -87,9 +93,12 @@ func indexInstance(config state.Config, cloudID string, serviceIP netip.Addr) (s if !found { fmt.Println("New instance found:\t", cloudID) u := uuid.New() - config.MTD.Services[state.CustomUUID(u)] = state.Service{CloudID: cloudID, ServiceIP: serviceIP} + config.MTD.Services[state.CustomUUID(u)] = state.Service{CloudID: cloudID, ServiceIP: serviceIP, Active: true, AdminEnabled: true} state.SaveConf(ConfigPath, config) + } else { + config.MTD.Services[foundUUID] = state.Service{Active: true} + state.SaveConf(ConfigPath, config) } return config, found } diff --git a/mtdaws/mtd.go b/mtdaws/mtd.go index 7811a34..57633ca 100644 --- a/mtdaws/mtd.go +++ b/mtdaws/mtd.go @@ -68,16 +68,16 @@ func AWSMoveInstance(config state.Config) (state.Config) { return config } - if !isInstanceRunning(realInstance) { - fmt.Println("Error, Instance is not running!") - return config - } - if instance.AdminDisabled { + if !instance.AdminEnabled { fmt.Println("Error, Service is Disabled!") return config } - if instance.Inactive { - fmt.Println("Error, Service is Inactive!") + if !instance.Active { + fmt.Println("Error, Service is not active!") + return config + } + if !isInstanceRunning(realInstance) { + fmt.Println("Error, Instance is not running!") return config } diff --git a/state/config.go b/state/config.go index e96bfdb..67e3bee 100644 --- a/state/config.go +++ b/state/config.go @@ -24,8 +24,8 @@ type mtdconf struct { // 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 { CloudID string `yaml:"cloud_id"` - AdminDisabled bool `yaml:"admin_disabled"` - Inactive bool `yaml:"inactive"` + AdminEnabled bool `yaml:"admin_enabled"` + Active bool `yaml:"active"` EntryIP netip.Addr `yaml:"entry_ip"` EntryPort uint16 `yaml:"entry_port"` ServiceIP netip.Addr `yaml:"service_ip"` From e8bb5506f5b4e1a4ac97084def9b17834bd84dd0 Mon Sep 17 00:00:00 2001 From: schulze Date: Wed, 19 Apr 2023 11:39:11 +0200 Subject: [PATCH 16/25] timers, counters, misc --- main.go | 20 ++++++++++++++++---- mtdaws/mtd.go | 36 ++++++++++++++++++++---------------- mtdaws/utils.go | 3 --- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/main.go b/main.go index 3e0cfc5..0387f45 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "net/netip" + "time" "github.com/google/uuid" "github.com/thefeli73/polemos/mtdaws" @@ -38,6 +39,9 @@ func mtdLoop(config state.Config) { config = movingTargetDefense(config) state.SaveConf(ConfigPath, config) + fmt.Println("Sleeping for 5 seconds") + time.Sleep(5*time.Second) + //TODO: proxy commands } } @@ -50,6 +54,7 @@ func movingTargetDefense(config state.Config) state.Config{ func indexAllInstances(config state.Config) state.Config { fmt.Println("Indexing instances") + t := time.Now() for _, service := range config.MTD.Services { service.Active = false @@ -57,7 +62,7 @@ func indexAllInstances(config state.Config) state.Config { //index AWS instances awsNewInstanceCounter := 0 - awsRemovedInstanceCounter := 0 + awsInactiveInstanceCounter := len(config.MTD.Services) awsInstanceCounter := 0 awsInstances := mtdaws.GetInstances(config) for _, instance := range awsInstances { @@ -69,11 +74,16 @@ func indexAllInstances(config state.Config) state.Config { } var found bool config, found = indexInstance(config, cloudID, ip) - if !found {awsNewInstanceCounter++} + if !found { + awsNewInstanceCounter++ + } else { + awsInactiveInstanceCounter-- + } awsInstanceCounter++ } // TODO: Purge instances in config that are not found in the cloud - fmt.Printf("Found %d AWS instances (%d newly added, %d removed)\n", awsInstanceCounter, awsNewInstanceCounter, awsRemovedInstanceCounter) + 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 @@ -97,7 +107,9 @@ func indexInstance(config state.Config, cloudID string, serviceIP netip.Addr) (s state.SaveConf(ConfigPath, config) } else { - config.MTD.Services[foundUUID] = state.Service{Active: true} + s := config.MTD.Services[foundUUID] + s.Active = true + config.MTD.Services[foundUUID] = s state.SaveConf(ConfigPath, config) } return config, found diff --git a/mtdaws/mtd.go b/mtdaws/mtd.go index 57633ca..ab69354 100644 --- a/mtdaws/mtd.go +++ b/mtdaws/mtd.go @@ -53,6 +53,8 @@ func AWSMoveInstance(config state.Config) (state.Config) { for key, service := range config.MTD.Services { serviceUUID = key instance = service + if !instance.AdminEnabled {continue} + if !instance.Active {continue} break } @@ -67,61 +69,63 @@ func AWSMoveInstance(config state.Config) (state.Config) { fmt.Println("Error getting instance details:\t", err) return config } - - if !instance.AdminEnabled { - fmt.Println("Error, Service is Disabled!") - return config - } - if !instance.Active { - fmt.Println("Error, Service is not active!") - 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.Println("Created image:\t\t", imageName) + 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.Println("Image is ready:\t\t", imageName) + 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.Println("Launched new instance:\t", newInstanceID) + fmt.Printf("Launched new instance:\t%s (took %s)\n", newInstanceID, time.Since(t).Round(100*time.Millisecond).String()) + // Terminate old instance + t = time.Now() err = terminateInstance(svc, instanceID) if err != nil { fmt.Println("Error terminating instance:\t", err) return config } - fmt.Println("Killed old instance:\t", instanceID) + 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.Println("Deregistered image:\t", imageName) + 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) @@ -129,7 +133,7 @@ func AWSMoveInstance(config state.Config) (state.Config) { fmt.Println("Error deleting snapshot:\t", err) return config } - fmt.Println("Deleted snapshot:\t", snapshotID) + fmt.Printf("Deleted snapshot:\t%s (took %s)\n", snapshotID, time.Since(t).Round(100*time.Millisecond).String()) } AWSUpdateService(config, region, serviceUUID, newInstanceID) diff --git a/mtdaws/utils.go b/mtdaws/utils.go index 2d4d5a4..f81850b 100644 --- a/mtdaws/utils.go +++ b/mtdaws/utils.go @@ -224,10 +224,7 @@ func terminateInstance(svc *ec2.Client, instanceID string) error { input := &ec2.TerminateInstancesInput{ InstanceIds: []string{instanceID}, } - _, err := svc.TerminateInstances(context.TODO(), input) - - // TODO: remove config for old instance return err } From d06620ca741ecb10c15e19ab7f379f9af99bb903 Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 25 Apr 2023 15:50:45 +0200 Subject: [PATCH 17/25] default port for proxima centauri --- config.default.yaml | 1 + state/config.go | 1 + 2 files changed, 2 insertions(+) diff --git a/config.default.yaml b/config.default.yaml index 77240ff..147aecd 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -1,5 +1,6 @@ mtd: services: {} + management_port: 14000 aws: regions: [] credentials_path: ./mtdaws/.credentials diff --git a/state/config.go b/state/config.go index 67e3bee..88ea825 100644 --- a/state/config.go +++ b/state/config.go @@ -18,6 +18,7 @@ type Config struct { type mtdconf struct { Services map[CustomUUID]Service `yaml:"services"` + ManagementPort uint16 `yaml:"management_port"` } From 91132d0e22b21509b1fe9abb8cd6ebf0aaac36de Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 25 Apr 2023 15:52:34 +0200 Subject: [PATCH 18/25] wait for instance and send command with pcsdk --- mtdaws/mtd.go | 101 ++++++++++++++++++++++++++++++------------------ mtdaws/utils.go | 14 +++++++ 2 files changed, 78 insertions(+), 37 deletions(-) diff --git a/mtdaws/mtd.go b/mtdaws/mtd.go index ab69354..69e3d3b 100644 --- a/mtdaws/mtd.go +++ b/mtdaws/mtd.go @@ -9,41 +9,10 @@ import ( "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" ) -// 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) - config.MTD.Services[service] = state.Service{CloudID: cloudid, ServiceIP: serviceip} - return config -} - - -// isInstanceRunning returns if an instance is running (true=running) -func isInstanceRunning(instance *types.Instance) bool { - return instance.State.Name == types.InstanceStateNameRunning -} - // AWSMoveInstance moves a specified instance to a new availability region func AWSMoveInstance(config state.Config) (state.Config) { @@ -101,9 +70,70 @@ func AWSMoveInstance(config state.Config) (state.Config) { } fmt.Printf("Launched new instance:\t%s (took %s)\n", newInstanceID, time.Since(t).Round(100*time.Millisecond).String()) - // Terminate old instance + // Wait for instance t = time.Now() - err = terminateInstance(svc, instanceID) + 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()) + + // Reconfigure Proxy to new instance + t = time.Now() + m := pcsdk.NewCommandModify(instance.ServicePort, instance.ServiceIP, serviceUUID) + err = m.Execute(netip.AddrPortFrom(instance.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()) + + + AWSUpdateService(config, region, serviceUUID, newInstanceID) + + 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) + config.MTD.Services[service] = state.Service{CloudID: cloudid, ServiceIP: serviceip} + 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 @@ -135,8 +165,5 @@ func AWSMoveInstance(config state.Config) (state.Config) { } fmt.Printf("Deleted snapshot:\t%s (took %s)\n", snapshotID, time.Since(t).Round(100*time.Millisecond).String()) } - - AWSUpdateService(config, region, serviceUUID, newInstanceID) - return config } diff --git a/mtdaws/utils.go b/mtdaws/utils.go index f81850b..1726fca 100644 --- a/mtdaws/utils.go +++ b/mtdaws/utils.go @@ -142,6 +142,20 @@ func waitForImageReady(svc *ec2.Client, imageID string, timeout time.Duration) e } } +// 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)) From 0da207fd887635a05b0a85ebec5f2e97f5b9afe0 Mon Sep 17 00:00:00 2001 From: schulze Date: Wed, 26 Apr 2023 14:20:56 +0200 Subject: [PATCH 19/25] Test Proxy connection works --- mtdaws/mtd.go | 12 +++++++++++- pcsdk/commands.go | 20 +------------------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/mtdaws/mtd.go b/mtdaws/mtd.go index 69e3d3b..b5b6b5e 100644 --- a/mtdaws/mtd.go +++ b/mtdaws/mtd.go @@ -29,6 +29,16 @@ func AWSMoveInstance(config state.Config) (state.Config) { 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()) + return config region, instanceID := DecodeCloudID(instance.CloudID) awsConfig := NewConfig(region, config.AWS.CredentialsPath) svc := ec2.NewFromConfig(awsConfig) @@ -44,7 +54,7 @@ func AWSMoveInstance(config state.Config) (state.Config) { } //Create image - t := time.Now() + t = time.Now() imageName, err := createImage(svc, instanceID) if err != nil { fmt.Println("Error creating image:\t", err) diff --git a/pcsdk/commands.go b/pcsdk/commands.go index ad1d288..7aaa123 100644 --- a/pcsdk/commands.go +++ b/pcsdk/commands.go @@ -39,23 +39,18 @@ func (c ProxyCommandStatus) Execute(url netip.AddrPort) error { } requestURL := fmt.Sprintf("http://%s:%d/command", url.Addr().String(), url.Port()) - fmt.Println(requestURL) bodyReader := bytes.NewReader(data) res, err := http.DefaultClient.Post(requestURL, "application/json", bodyReader) if err != nil { return errors.New(fmt.Sprintf("error making http request: %s\n", err)) } - - fmt.Println(res) - body, err := ioutil.ReadAll(res.Body) - fmt.Println(string(body)) if err != nil { return errors.New(fmt.Sprintf("error reading response: %s\n", err)) } - if res.StatusCode != 202 { + if res.StatusCode != 200 { return errors.New(fmt.Sprintf("error processing command: (%d) %s\n", res.StatusCode, body)) } else { return nil @@ -90,18 +85,13 @@ func (c ProxyCommandCreate) Execute(url netip.AddrPort) error { } requestURL := fmt.Sprintf("http://%s:%d/command", url.Addr().String(), url.Port()) - fmt.Println(requestURL) bodyReader := bytes.NewReader(data) res, err := http.DefaultClient.Post(requestURL, "application/json", bodyReader) if err != nil { return errors.New(fmt.Sprintf("error making http request: %s\n", err)) } - - fmt.Println(res) - body, err := ioutil.ReadAll(res.Body) - fmt.Println(string(body)) if err != nil { return errors.New(fmt.Sprintf("error reading response: %s\n", err)) } @@ -146,11 +136,7 @@ func (c ProxyCommandModify) Execute(url netip.AddrPort) error { if err != nil { return errors.New(fmt.Sprintf("error making http request: %s\n", err)) } - - fmt.Println(res) - body, err := ioutil.ReadAll(res.Body) - fmt.Println(string(body)) if err != nil { return errors.New(fmt.Sprintf("error reading response: %s\n", err)) } @@ -193,11 +179,7 @@ func (c ProxyCommandDelete) Execute(url netip.AddrPort) error { if err != nil { return errors.New(fmt.Sprintf("error making http request: %s\n", err)) } - - fmt.Println(res) - body, err := ioutil.ReadAll(res.Body) - fmt.Println(string(body)) if err != nil { return errors.New(fmt.Sprintf("error reading response: %s\n", err)) } From 9c84a74a146a70002e9631f873692ed52ff80a4a Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 2 May 2023 13:31:23 +0200 Subject: [PATCH 20/25] ignore binary --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 13da3e6..73f2b83 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ config.yaml + +polemos From 126c0dbebb77b2558c9ad128a4051c74671112a9 Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 2 May 2023 13:32:39 +0200 Subject: [PATCH 21/25] . --- mtdaws/mtd.go | 1 - 1 file changed, 1 deletion(-) diff --git a/mtdaws/mtd.go b/mtdaws/mtd.go index b5b6b5e..cd8abd9 100644 --- a/mtdaws/mtd.go +++ b/mtdaws/mtd.go @@ -38,7 +38,6 @@ func AWSMoveInstance(config state.Config) (state.Config) { return config } fmt.Printf("Proxy Tested. (took %s)\n", time.Since(t).Round(100*time.Millisecond).String()) - return config region, instanceID := DecodeCloudID(instance.CloudID) awsConfig := NewConfig(region, config.AWS.CredentialsPath) svc := ec2.NewFromConfig(awsConfig) From 8cb672a8ea45139fa72fdcc4255f90fdf855b0d1 Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 2 May 2023 13:33:33 +0200 Subject: [PATCH 22/25] Keep instance name when migrating --- mtdaws/utils.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/mtdaws/utils.go b/mtdaws/utils.go index 1726fca..a080125 100644 --- a/mtdaws/utils.go +++ b/mtdaws/utils.go @@ -162,11 +162,17 @@ func launchInstance(svc *ec2.Client, oldInstance *types.Instance, imageID string for i, sg := range oldInstance.SecurityGroups { securityGroupIds[i] = aws.ToString(sg.GroupId) } - // TODO: select random zone that is not the current one. 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), @@ -178,6 +184,17 @@ func launchInstance(svc *ec2.Client, oldInstance *types.Instance, imageID string 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) From e95f77d203233d305941034bba795ccc7b114eb6 Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 2 May 2023 13:59:33 +0200 Subject: [PATCH 23/25] create all tunnels on startup (no mtd right now) --- main.go | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 0387f45..d73946e 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "github.com/google/uuid" "github.com/thefeli73/polemos/mtdaws" + "github.com/thefeli73/polemos/pcsdk" "github.com/thefeli73/polemos/state" ) @@ -29,8 +30,11 @@ func main() { config = indexAllInstances(config) state.SaveConf(ConfigPath, config) + // CREATE TUNNELS + createTunnels(config) + // START DOING MTD - mtdLoop(config) + //mtdLoop(config) } func mtdLoop(config state.Config) { @@ -39,8 +43,8 @@ func mtdLoop(config state.Config) { config = movingTargetDefense(config) state.SaveConf(ConfigPath, config) - fmt.Println("Sleeping for 5 seconds") - time.Sleep(5*time.Second) + fmt.Println("Sleeping for 1 minute") + time.Sleep(1*time.Minute) //TODO: proxy commands } @@ -89,6 +93,24 @@ func indexAllInstances(config state.Config) state.Config { return config } +func createTunnels(config state.Config) { + for serviceUUID, service := range config.MTD.Services { + if service.AdminEnabled && service.Active { + s := pcsdk.NewCommandStatus() + err := s.Execute(netip.AddrPortFrom(service.EntryIP, config.MTD.ManagementPort)) + if err != nil { + continue + } + // Reconfigure Proxy to new instance + c := pcsdk.NewCommandCreate(service.ServicePort, service.ServicePort, service.ServiceIP, serviceUUID) + err = c.Execute(netip.AddrPortFrom(service.EntryIP, config.MTD.ManagementPort)) + if err != nil { + continue + } + } + } +} + func indexInstance(config state.Config, cloudID string, serviceIP netip.Addr) (state.Config, bool) { found := false var foundUUID state.CustomUUID From f5e01ba3c2f81e6ec34324066327af92182d811f Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 2 May 2023 14:50:22 +0200 Subject: [PATCH 24/25] do moving target defense, fix wrong port, --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index d73946e..7118300 100644 --- a/main.go +++ b/main.go @@ -34,7 +34,7 @@ func main() { createTunnels(config) // START DOING MTD - //mtdLoop(config) + mtdLoop(config) } func mtdLoop(config state.Config) { @@ -102,7 +102,7 @@ func createTunnels(config state.Config) { continue } // Reconfigure Proxy to new instance - c := pcsdk.NewCommandCreate(service.ServicePort, service.ServicePort, service.ServiceIP, serviceUUID) + c := pcsdk.NewCommandCreate(service.EntryPort, service.ServicePort, service.ServiceIP, serviceUUID) err = c.Execute(netip.AddrPortFrom(service.EntryIP, config.MTD.ManagementPort)) if err != nil { continue From 288b39c2e736232652d340f375de761f5b877bed Mon Sep 17 00:00:00 2001 From: schulze Date: Tue, 2 May 2023 14:53:45 +0200 Subject: [PATCH 25/25] modify proxy tunnel with NEW IP etc, update local config to match --- mtdaws/mtd.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/mtdaws/mtd.go b/mtdaws/mtd.go index cd8abd9..016a3f0 100644 --- a/mtdaws/mtd.go +++ b/mtdaws/mtd.go @@ -87,23 +87,23 @@ func AWSMoveInstance(config state.Config) (state.Config) { 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(instance.ServicePort, instance.ServiceIP, serviceUUID) - err = m.Execute(netip.AddrPortFrom(instance.EntryIP, config.MTD.ManagementPort)) + 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()) - - - AWSUpdateService(config, region, serviceUUID, newInstanceID) + // take care of old instance, deregister image and delete snapshot cleanupAWS(svc, config, instanceID, imageName) - return config } @@ -129,7 +129,10 @@ func AWSUpdateService(config state.Config, region string, service state.CustomUU } cloudid := GetCloudID(formattedinstance) serviceip := netip.MustParseAddr(publicAddr) - config.MTD.Services[service] = state.Service{CloudID: cloudid, ServiceIP: serviceip} + s := config.MTD.Services[service] + s.CloudID = cloudid + s.ServiceIP = serviceip + config.MTD.Services[service] = s return config }