diff --git a/CHANGELOG.md b/CHANGELOG.md index 90f1a1f..8fb5fc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 2.1.0 + +- Authorize encryption of already encrypted volumes (key migration) + +## 2.0.3 + +- Adding a security check on KMS alias + ## 2.0.2 - Adding option for choosing to restart or not an instance after encryption diff --git a/README.md b/README.md index dd1b4e5..ff9bff5 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Encrypt EBS volumes from AWS EC2 instances This tool let you : - Encrypt all the EBS volumes for an instance +- If volumes already encrypted, re-encrypt these with the given key - Duplicate all the source tags to the target - Apply DeleteOnTermination flag if needs - Preserve the original volume or not as an option (thank to @cobaltjacket) diff --git a/cmd/root.go b/cmd/root.go index 845590c..3061904 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -51,9 +51,6 @@ func Execute() { func init() { cobra.OnInitialize(initConfig) - // TODO: Not yet implemented - //rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ec2cryptomatic.yaml)") - } // initConfig reads in config file and ENV variables if set. @@ -63,8 +60,7 @@ func initConfig() { viper.SetConfigFile(cfgFile) } else { // Find home directory. - home, err := homedir.Dir() - if err != nil { + home, err := homedir.Dir(); if err != nil { fmt.Println(err) os.Exit(1) } diff --git a/cmd/run.go b/cmd/run.go index efd2fa0..5f88852 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -48,24 +48,21 @@ var runCmd = &cobra.Command{ fmt.Print("\t\t-=[ EC2Cryptomatic ]=-\n") - awsSession, err := session.NewSession(&aws.Config{Region: aws.String(region)}) - if err != nil { + awsSession, err := session.NewSession(&aws.Config{Region: aws.String(region)}); if err != nil { log.Fatalln("Cannot create an AWS awsSession object: " + err.Error()) } kmsService := kms.New(awsSession) kmsInput := &kms.DescribeKeyInput{KeyId: aws.String(kmsAlias)} - _, errorKmsKey := kmsService.DescribeKey(kmsInput); if errorKmsKey != nil { + if _, errorKmsKey := kmsService.DescribeKey(kmsInput); errorKmsKey != nil { log.Fatalln("Error with this key: " + errorKmsKey.Error()) } - ec2Instance, instanceError := ec2instance.New(awsSession, instanceID) - if instanceError != nil { + ec2Instance, instanceError := ec2instance.New(awsSession, instanceID); if instanceError != nil { log.Fatalln(instanceError) } - errorAlgorithm := algorithm.EncryptInstance(ec2Instance, kmsAlias, discard, startInstance) - if errorAlgorithm != nil { + if errorAlgorithm := algorithm.EncryptInstance(ec2Instance, kmsAlias, discard, startInstance); errorAlgorithm != nil { log.Fatalln("/!\\ " + errorAlgorithm.Error()) } }, diff --git a/constants/constants.go b/constants/constants.go index 2c3d9ff..3071f2a 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -2,7 +2,7 @@ package constants const ( // VERSION is the overall version of the program - VERSION string = "2.0.2" + VERSION string = "2.1.0" // VolumeMaxAttempts how many attempts EC2 EBS waiters will used between SDK actions (snapshot ebsvolume) VolumeMaxAttempts int = 10000 // InstanceMaxAttempts how many attempts EC2 waiters will used between SDK actions (attach/detach ebsvolume) diff --git a/internal/algorithm/encrypt_algorithm.go b/internal/algorithm/encrypt_algorithm.go index 6a8c850..ba89c48 100644 --- a/internal/algorithm/encrypt_algorithm.go +++ b/internal/algorithm/encrypt_algorithm.go @@ -7,52 +7,43 @@ import ( "github.com/jbrt/ec2cryptomatic/internal/ec2instance" ) +var nonEligibleForEncryptionError = errors.New("instance must be stopped and compatible with EBS encryption") + // EncryptInstance will takes an instanceID and encrypt all the related EBS volumes -func EncryptInstance(ec2 *ec2instance.Ec2Instance, kmsKeyAlias string, sourceDiscard bool, startInstance bool) error { +func EncryptInstance(ec2 *ec2instance.Ec2Instance, kmsKeyAlias string, discardSource bool, startInstance bool) error { if !ec2.IsStopped() || !ec2.IsSupportsEncryptedVolumes() { - return errors.New("instance must be stopped and compatible with EBS encryption") + return nonEligibleForEncryptionError } actionDone := false for _, ebsVolume := range ec2.GetEBSMappedVolumes() { log.Println("-- Beginning work on EBS volume " + *ebsVolume.Ebs.VolumeId) - sourceVolume, volumeError := ec2.GetEBSVolume(*ebsVolume.Ebs.VolumeId) - if volumeError != nil { + sourceVolume, volumeError := ec2.GetEBSVolume(*ebsVolume.Ebs.VolumeId); if volumeError != nil { return errors.New("Problem with volume initialization: " + volumeError.Error()) } - if sourceVolume.IsEncrypted() { - log.Println("-- This volume is already encrypted nothing to do with this one") - continue - } - - encryptedVolume, encryptedVolumeError := sourceVolume.EncryptVolume(kmsKeyAlias) - if encryptedVolumeError != nil { + encryptedVolume, encryptedVolumeError := sourceVolume.EncryptVolume(kmsKeyAlias); if encryptedVolumeError != nil { log.Printf("Problem while encrypting volume: %s (%s)\n", *ebsVolume.Ebs.VolumeId, encryptedVolumeError.Error()) continue } - swappingError := ec2.SwapBlockDevice(ebsVolume, encryptedVolume) - if swappingError != nil { + if swappingError := ec2.SwapBlockDevice(ebsVolume, encryptedVolume); swappingError != nil { log.Println("Problem while trying to swap volumes: " + swappingError.Error()) continue } - if sourceDiscard { + if discardSource { _ = sourceVolume.DeleteVolume() } actionDone = true } - if actionDone { - if startInstance { - log.Println("Let's starts instance " + *ec2.InstanceID) - startError := ec2.StartInstance() - if startError != nil { - return startError - } + if actionDone && startInstance { + log.Println("Let's starts instance " + *ec2.InstanceID) + if startError := ec2.StartInstance(); startError != nil { + return startError } } diff --git a/internal/ebsvolume/ebsvolume.go b/internal/ebsvolume/ebsvolume.go index 2bbbead..da423c1 100644 --- a/internal/ebsvolume/ebsvolume.go +++ b/internal/ebsvolume/ebsvolume.go @@ -43,8 +43,7 @@ func (v VolumeToEncrypt) takeSnapshot() (*ec2.Snapshot, error) { VolumeId: v.describe.VolumeId, } - snapshot, errSnapshot := v.client.CreateSnapshot(snapShotInput) - if errSnapshot != nil { + snapshot, errSnapshot := v.client.CreateSnapshot(snapShotInput); if errSnapshot != nil { return nil, errSnapshot } @@ -62,9 +61,8 @@ func (v VolumeToEncrypt) takeSnapshot() (*ec2.Snapshot, error) { // DeleteVolume will delete the given EBS volume func (v VolumeToEncrypt) DeleteVolume() error { - log.Println("--- Delete volume " + *v.volumeID) - _, errDelete := v.client.DeleteVolume(&ec2.DeleteVolumeInput{VolumeId: v.volumeID}) - if errDelete != nil { + log.Println("---> Delete volume " + *v.volumeID) + if _, errDelete := v.client.DeleteVolume(&ec2.DeleteVolumeInput{VolumeId: v.volumeID}); errDelete != nil { return errDelete } return nil @@ -72,10 +70,9 @@ func (v VolumeToEncrypt) DeleteVolume() error { // EncryptVolume will produce an encrypted version of the EBS volume func (v VolumeToEncrypt) EncryptVolume(kmsKeyID string) (*ec2.Volume, error) { - log.Println("--- Start encryption process for volume " + *v.volumeID) + log.Println("---> Start encryption process for volume " + *v.volumeID) encrypted := true - snapshot, errSnapshot := v.takeSnapshot() - if errSnapshot != nil { + snapshot, errSnapshot := v.takeSnapshot(); if errSnapshot != nil { return nil, errSnapshot } @@ -88,19 +85,17 @@ func (v VolumeToEncrypt) EncryptVolume(kmsKeyID string) (*ec2.Volume, error) { } // Adding tags if needed - tagsWithoutAwsDedicatedTags := v.getTagSpecifications() - if tagsWithoutAwsDedicatedTags != nil { + if tagsWithoutAwsDedicatedTags := v.getTagSpecifications(); tagsWithoutAwsDedicatedTags != nil { volumeInput.TagSpecifications = tagsWithoutAwsDedicatedTags } // If EBS volume is IO, let's get the IOPs parameter if strings.HasPrefix(*v.describe.VolumeType, "io") { - log.Println("--- This volumes is IO one let's set IOPs to ", *v.describe.Iops) + log.Println("---> This volumes is IO one let's set IOPs to ", *v.describe.Iops) volumeInput.Iops = aws.Int64(*v.describe.Iops) } - volume, errVolume := v.client.CreateVolume(volumeInput) - if errVolume != nil { + volume, errVolume := v.client.CreateVolume(volumeInput); if errVolume != nil { return nil, errVolume } @@ -127,12 +122,10 @@ func (v VolumeToEncrypt) IsEncrypted() bool { // New returns a well construct EC2Instance object ec2instance func New(ec2Client *ec2.EC2, volumeID string) (*VolumeToEncrypt, error) { - // Trying to describe the given ec2instance as security mechanism (ec2instance is exists ? credentials are ok ?) - input := &ec2.DescribeVolumesInput{VolumeIds: []*string{aws.String(volumeID)}} - describe, errDescribe := ec2Client.DescribeVolumes(input) - if errDescribe != nil { - log.Println("--- Cannot get information from volume " + volumeID) + volumeInput := &ec2.DescribeVolumesInput{VolumeIds: []*string{aws.String(volumeID)}} + describe, errDescribe := ec2Client.DescribeVolumes(volumeInput); if errDescribe != nil { + log.Println("---> Cannot get information from volume " + volumeID) return nil, errDescribe } diff --git a/internal/ec2instance/ec2instance.go b/internal/ec2instance/ec2instance.go index c2564b7..fb5d703 100644 --- a/internal/ec2instance/ec2instance.go +++ b/internal/ec2instance/ec2instance.go @@ -12,6 +12,8 @@ import ( "github.com/jbrt/ec2cryptomatic/internal/ebsvolume" ) +var unsupportedInstanceTypes = []string{"c1.", "m1.", "m2.", "t1."} + // Ec2Instance is the main type of that package. Will be returned by new. // It contains all data relevant for an ec2instance type Ec2Instance struct { @@ -27,8 +29,7 @@ func (e Ec2Instance) GetEBSMappedVolumes() []*ec2.InstanceBlockDeviceMapping { // GetEBSVolume returns a specific EBS volume with high level methods func (e Ec2Instance) GetEBSVolume(volumeID string) (*ebsvolume.VolumeToEncrypt, error) { - ebsVolume, volumeError := ebsvolume.New(e.ec2client, volumeID) - if volumeError != nil { + ebsVolume, volumeError := ebsvolume.New(e.ec2client, volumeID); if volumeError != nil { return nil, volumeError } return ebsVolume, nil @@ -44,7 +45,6 @@ func (e Ec2Instance) IsStopped() bool { // IsSupportsEncryptedVolumes will check if the ec2instance supports EBS encrypted volumes (not all instances types support that). func (e Ec2Instance) IsSupportsEncryptedVolumes() bool { - unsupportedInstanceTypes := []string{"c1.", "m1.", "m2.", "t1."} for _, instance := range unsupportedInstanceTypes { if strings.HasPrefix(*e.describeInstance.InstanceType, instance) { return false @@ -57,8 +57,7 @@ func (e Ec2Instance) IsSupportsEncryptedVolumes() bool { func (e Ec2Instance) StartInstance() error { log.Println("-- Start ec2instance " + *e.InstanceID) input := &ec2.StartInstancesInput{InstanceIds: []*string{aws.String(*e.InstanceID)}} - _, errStart := e.ec2client.StartInstances(input) - if errStart != nil { + if _, errStart := e.ec2client.StartInstances(input); errStart != nil { return errStart } return nil @@ -67,8 +66,7 @@ func (e Ec2Instance) StartInstance() error { //SwapBlockDevice will swap two EBS volumes from an EC2 ec2instance func (e Ec2Instance) SwapBlockDevice(source *ec2.InstanceBlockDeviceMapping, target *ec2.Volume) error { detach := &ec2.DetachVolumeInput{VolumeId: aws.String(*source.Ebs.VolumeId)} - _, errDetach := e.ec2client.DetachVolume(detach) - if errDetach != nil { + if _, errDetach := e.ec2client.DetachVolume(detach); errDetach != nil { return errDetach } @@ -88,8 +86,7 @@ func (e Ec2Instance) SwapBlockDevice(source *ec2.InstanceBlockDeviceMapping, tar VolumeId: aws.String(*target.VolumeId), } - _, errAttach := e.ec2client.AttachVolume(attach) - if errAttach != nil { + if _, errAttach := e.ec2client.AttachVolume(attach); errAttach != nil { return errAttach } @@ -110,8 +107,7 @@ func (e Ec2Instance) SwapBlockDevice(source *ec2.InstanceBlockDeviceMapping, tar requestModify, _ := e.ec2client.ModifyInstanceAttributeRequest(&attributeInput) - errorRequest := requestModify.Send() - if errorRequest != nil { + if errorRequest := requestModify.Send(); errorRequest != nil { return errorRequest } @@ -122,16 +118,15 @@ func (e Ec2Instance) SwapBlockDevice(source *ec2.InstanceBlockDeviceMapping, tar // New returns a well construct EC2Instance object ec2instance func New(session *session.Session, instanceID string) (*Ec2Instance, error) { - log.Println("Let's encrypt ec2instance " + instanceID) + log.Println("Let's encrypt EC2 instance " + instanceID) // Trying to describeInstance the given ec2instance as security mechanism (ec2instance is exists ? credentials are ok ?) ec2client := ec2.New(session) - input := &ec2.DescribeInstancesInput{InstanceIds: []*string{aws.String(instanceID)}} + ec2Input := &ec2.DescribeInstancesInput{InstanceIds: []*string{aws.String(instanceID)}} - describe, errDescribe := ec2client.DescribeInstances(input) - if errDescribe != nil { - log.Println("-- Cannot find EC2 ec2instance " + instanceID) - return &Ec2Instance{}, errDescribe + describe, errDescribe := ec2client.DescribeInstances(ec2Input); if errDescribe != nil { + log.Println("-- Cannot find EC2 instance " + instanceID) + return nil, errDescribe } instance := &Ec2Instance{