Repository: nanovms/ops
Pull request #981 (2021-04-04 11:02:34)

Hyper-V/Azure: add support for UEFI-based Generation-2 instances
When creating an image for Hyper-V or Azure, the `--type` command line flag (or alternatively, the "ImageType" attribute under "CloudConfig" in the configuration file) can be used to specify a Generation-2 image type, that can run on Generation-2 instances (as well as Generation-1 instances, except for some types of Azure VMs).
In Azure, the VM generation when creating an instance is chosen automatically based on the image type; the default instance flavor has been changed from A1V2 to B1s, because A1V2 instances don't support Generation-2 images.
To specify the VM generation when creating a Hyper-V instance, the `-f` command line flag (or alternatively, the Flavor attribute under "CloudConfig" in the configuration file) can be used.
Configuration file example for Hyper-V:
```
{
    "CloudConfig": {
        "ImageType":"gen2",
        "Flavor": "gen2"
    }
}
```
Images created with the "gen2" option have an additional partition containing the UEFI bootloader file (which will be part of future Nanos releases).

am11: For gen1 image, we can only create gen1 instance; for gen2, we can create gen2 and gen1 instances.

if we can skip or rename `Flavor` (which sounds a bit too generic) to accompany `ImageType`, that would be cool. e.g. `"DeployAsGen2": true` with cmd flag `--deploy-as-gen2`
`"PreferGen2": true` with cmd flag `--prefer-gen2`
`"ImageTypeForDeployment": "gen2"` with cmd arg `--deployment-type gen2`

Patch:
diff --git a/azure/azure.go b/azure/azure.go
index e135a28..4d66694 100644
--- a/azure/azure.go
+++ b/azure/azure.go
@@ -42,6 +42,7 @@ type Azure struct {
 	locationDefault string
 	groupName       string
 	storageAccount  string
+	hyperVGen       compute.HyperVGenerationTypes
 
 	authorizer *autorest.Authorizer
 }
diff --git a/azure/azure_image.go b/azure/azure_image.go
index 127e4ae..42871db 100644
--- a/azure/azure_image.go
+++ b/azure/azure_image.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"strings"
 
 	"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute"
 	"github.com/Azure/go-autorest/autorest/to"
@@ -16,6 +17,17 @@ import (
 // BuildImage to be upload on Azure
 func (a *Azure) BuildImage(ctx *lepton.Context) (string, error) {
 	c := ctx.Config()
+	a.hyperVGen = compute.HyperVGenerationTypesV1
+	imageType := strings.ToLower(c.CloudConfig.ImageType)
+	if imageType != "" {
+		if imageType == "gen1" {
+		} else if imageType == "gen2" {
+			c.Uefi = true
+			a.hyperVGen = compute.HyperVGenerationTypesV2
+		} else {
+			return "", fmt.Errorf("invalid image type '%s'; available types: 'gen1', 'gen2'", c.CloudConfig.ImageType)
+		}
+	}
 	err := lepton.BuildImage(*c)
 	if err != nil {
 		return "", err
@@ -99,7 +111,7 @@ func (a *Azure) CreateImage(ctx *lepton.Context, imagePath string) error {
 					OsState: compute.Generalized,
 				},
 			},
-			HyperVGeneration: compute.HyperVGenerationTypesV1,
+			HyperVGeneration: a.hyperVGen,
 		},
 	}
 
diff --git a/azure/azure_instance.go b/azure/azure_instance.go
index cb36b3c..568f6a8 100644
--- a/azure/azure_instance.go
+++ b/azure/azure_instance.go
@@ -142,7 +142,7 @@ func (a *Azure) CreateInstance(ctx *lepton.Context) error {
 	var flavor compute.VirtualMachineSizeTypes
 	flavor = compute.VirtualMachineSizeTypes(ctx.Config().CloudConfig.Flavor)
 	if flavor == "" {
-		flavor = compute.VirtualMachineSizeTypesStandardA1V2
+		flavor = compute.VirtualMachineSizeTypesStandardB1s
 	}
 
 	tags := getAzureDefaultTags()
diff --git a/cmd/flags_build_image.go b/cmd/flags_build_image.go
index c11b99d..9e03fb0 100644
--- a/cmd/flags_build_image.go
+++ b/cmd/flags_build_image.go
@@ -15,6 +15,7 @@ import (
 
 // BuildImageCommandFlags consolidates all command flags required to build an image in one struct
 type BuildImageCommandFlags struct {
+	Type       string
 	CmdArgs    []string
 	CmdEnvs    []string
 	ImageName  string
@@ -39,6 +40,10 @@ func (flags *BuildImageCommandFlags) MergeToConfig(c *types.Config) (err error)
 		c.TargetRoot = flags.TargetRoot
 	}
 
+	if flags.Type != "" {
+		c.CloudConfig.ImageType = flags.Type
+	}
+
 	if flags.ImageName != "" {
 		c.RunConfig.Imagename = flags.ImageName
 	}
@@ -91,6 +96,11 @@ func NewBuildImageCommandFlags(cmdFlags *pflag.FlagSet) (flags *BuildImageComman
 	var err error
 	flags = &BuildImageCommandFlags{}
 
+	flags.Type, err = cmdFlags.GetString("type")
+	if err != nil {
+		exitWithError(err.Error())
+	}
+
 	flags.CmdEnvs, err = cmdFlags.GetStringArray("envs")
 	if err != nil {
 		exitWithError(err.Error())
@@ -121,6 +131,7 @@ func NewBuildImageCommandFlags(cmdFlags *pflag.FlagSet) (flags *BuildImageComman
 
 // PersistBuildImageCommandFlags append a command the required flags to run an image
 func PersistBuildImageCommandFlags(cmdFlags *pflag.FlagSet) {
+	cmdFlags.String("type", "", "image type (target platform-specific)")
 	cmdFlags.StringArrayP("envs", "e", nil, "env arguments")
 	cmdFlags.StringP("target-root", "r", "", "target root")
 	cmdFlags.StringP("imagename", "i", "", "image name")
diff --git a/cmd/flags_nightly.go b/cmd/flags_nightly.go
index abb3f8b..8559a3f 100644
--- a/cmd/flags_nightly.go
+++ b/cmd/flags_nightly.go
@@ -64,6 +64,8 @@ func updateNanosToolsPaths(c *types.Config, version string) {
 		c.Boot = path.Join(api.GetOpsHome(), version, "boot.img")
 	}
 
+	c.UefiBoot = api.GetUefiBoot(version)
+
 	if c.Kernel == "" {
 		c.Kernel = path.Join(api.GetOpsHome(), version, "kernel.img")
 	}
diff --git a/fs/mkfs.go b/fs/mkfs.go
index 402f921..ef7936a 100644
--- a/fs/mkfs.go
+++ b/fs/mkfs.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"io"
 	"os"
+	"path/filepath"
 	"strconv"
 	"strings"
 	"unicode"
@@ -22,17 +23,115 @@ const regionFilesystem = 12
 
 const partitionEntrySize = 16
 
-const (
-	partitionBootFS = 0
-	partitionRootFS = 1
-)
-
 const klogDumpSize = 4 * 1024
+const uefiFSSize = 33 * 1024 * 1024
 const bootFSSize = 12 * 1024 * 1024
 
+/* Volume Boot Record */
+var uefiVBR = []byte{
+	0xEB, 0x58, 0x90, 0x6D, 0x6B, 0x66, 0x73, 0x2E, 0x66, 0x61, 0x74, 0x00, 0x02, 0x01, 0x20, 0x00,
+	0x02, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xCC, 0x06, 0x01, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+	0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x80, 0x00, 0x29, 0x9E, 0xF9, 0x43, 0xDF, 0x4E, 0x4F, 0x20, 0x4E, 0x41, 0x4D, 0x45, 0x20, 0x20,
+	0x20, 0x20, 0x46, 0x41, 0x54, 0x33, 0x32, 0x20, 0x20, 0x20, 0x0E, 0x1F, 0xBE, 0x77, 0x7C, 0xAC,
+	0x22, 0xC0, 0x74, 0x0B, 0x56, 0xB4, 0x0E, 0xBB, 0x07, 0x00, 0xCD, 0x10, 0x5E, 0xEB, 0xF0, 0x32,
+	0xE4, 0xCD, 0x16, 0xCD, 0x19, 0xEB, 0xFE, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6E,
+	0x6F, 0x74, 0x20, 0x61, 0x20, 0x62, 0x6F, 0x6F, 0x74, 0x61, 0x62, 0x6C, 0x65, 0x20, 0x64, 0x69,
+	0x73, 0x6B, 0x2E, 0x20, 0x20, 0x50, 0x6C, 0x65, 0x61, 0x73, 0x65, 0x20, 0x69, 0x6E, 0x73, 0x65,
+	0x72, 0x74, 0x20, 0x61, 0x20, 0x62, 0x6F, 0x6F, 0x74, 0x61, 0x62, 0x6C, 0x65, 0x20, 0x66, 0x6C,
+	0x6F, 0x70, 0x70, 0x79, 0x20, 0x61, 0x6E, 0x64, 0x0D, 0x0A, 0x70, 0x72, 0x65, 0x73, 0x73, 0x20,
+	0x61, 0x6E, 0x79, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x72, 0x79, 0x20, 0x61,
+	0x67, 0x61, 0x69, 0x6E, 0x20, 0x2E, 0x2E, 0x2E, 0x20, 0x0D, 0x0A,
+}
+
+/* FSInfo Structure */
+var uefiFSInfo = []byte{
+	0x52, 0x52, 0x61, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x72, 0x72, 0x41, 0x61, 0xAE, 0x01, 0x01, 0x00, 0xF3,
+}
+
+/* First entries of File Allocation Table */
+var uefiFAT = []byte{
+	0xF8, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F,
+	0xFF, 0xFF, 0xFF, 0x0F,
+}
+
+/* "EFI" entry in root directory */
+var uefiDirEfi = []byte{
+	0x45, 0x46, 0x49, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x00, 0x00, 0x60, 0x39,
+	0x7C, 0x52, 0x7C, 0x52, 0x00, 0x00, 0x60, 0x39, 0x7C, 0x52, 0x03,
+}
+
+/* "EFI" directory entries with "Boot" subfolder */
+var uefiDirBoot = []byte{
+	0x2E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x00, 0x00, 0x8F, 0x9C,
+	0x7B, 0x52, 0x7B, 0x52, 0x00, 0x00, 0x8F, 0x9C, 0x7B, 0x52, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x2E, 0x2E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x00, 0x00, 0x8F, 0x9C,
+	0x7B, 0x52, 0x7B, 0x52, 0x00, 0x00, 0x8F, 0x9C, 0x7B, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x41, 0x42, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x74, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xDD, 0xFF, 0xFF,
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+	0x42, 0x4F, 0x4F, 0x54, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x00, 0x00, 0x8F, 0x9C,
+	0x7B, 0x52, 0x7B, 0x52, 0x00, 0x00, 0x8F, 0x9C, 0x7B, 0x52, 0x04,
+}
+
+/* "EFI/Boot" directory entries with "bootx64.efi" file */
+var uefiFileBootx64 = []byte{
+	0x2E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x00, 0x00, 0x8F, 0x9C,
+	0x7B, 0x52, 0x7B, 0x52, 0x00, 0x00, 0x8F, 0x9C, 0x7B, 0x52, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x2E, 0x2E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x00, 0x00, 0x8F, 0x9C,
+	0x7B, 0x52, 0x7B, 0x52, 0x00, 0x00, 0x8F, 0x9C, 0x7B, 0x52, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x41, 0x62, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x74, 0x00, 0x78, 0x00, 0x0F, 0x00, 0x1D, 0x36, 0x00,
+	0x34, 0x00, 0x2E, 0x00, 0x65, 0x00, 0x66, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+	0x42, 0x4F, 0x4F, 0x54, 0x58, 0x36, 0x34, 0x20, 0x45, 0x46, 0x49, 0x20, 0x00, 0x00, 0x8F, 0x9C,
+	0x7B, 0x52, 0x7B, 0x52, 0x00, 0x00, 0x8F, 0x9C, 0x7B, 0x52, 0x05, 0x00,
+}
+
+/* "EFI/Boot" directory entries with "bootaa64.efi" file */
+var uefiFileBootaa64 = []byte{
+	0x2E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x00, 0x00, 0x8F, 0x9C,
+	0x7B, 0x52, 0x7B, 0x52, 0x00, 0x00, 0x8F, 0x9C, 0x7B, 0x52, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x2E, 0x2E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x00, 0x00, 0x8F, 0x9C,
+	0x7B, 0x52, 0x7B, 0x52, 0x00, 0x00, 0x8F, 0x9C, 0x7B, 0x52, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x41, 0x62, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x74, 0x00, 0x61, 0x00, 0x0F, 0x00, 0x54, 0x61, 0x00,
+	0x36, 0x00, 0x34, 0x00, 0x2E, 0x00, 0x65, 0x00, 0x66, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00,
+	0x42, 0x4F, 0x4F, 0x54, 0x41, 0x41, 0x36, 0x34, 0x45, 0x46, 0x49, 0x20, 0x00, 0x64, 0x4B, 0x9C,
+	0x7B, 0x52, 0x7B, 0x52, 0x00, 0x00, 0x4B, 0x9C, 0x7B, 0x52, 0x05, 0x00,
+}
+
 // MkfsCommand wraps mkfs calls
 type MkfsCommand struct {
 	bootPath string
+	uefiPath string
 	label    string
 	manifest *Manifest
 	size     int64
@@ -44,6 +143,7 @@ type MkfsCommand struct {
 func NewMkfsCommand(m *Manifest) *MkfsCommand {
 	return &MkfsCommand{
 		bootPath: "",
+		uefiPath: "",
 		label:    "",
 		manifest: m,
 		size:     0,
@@ -91,6 +191,11 @@ func (m *MkfsCommand) SetBoot(boot string) {
 	m.bootPath = boot
 }
 
+// SetUefi sets path of UEFI bootloader
+func (m *MkfsCommand) SetUefi(uefi string) {
+	m.uefiPath = uefi
+}
+
 // SetFileSystemPath add argument that sets file system path
 func (m *MkfsCommand) SetFileSystemPath(fsPath string) {
 	m.outPath = fsPath
@@ -106,6 +211,9 @@ func (m *MkfsCommand) Execute() error {
 	if m.outPath == "" {
 		return fmt.Errorf("output image file path not set")
 	}
+	if (m.bootPath == "") && (m.uefiPath != "") {
+		return fmt.Errorf("UEFI loader can only be supplied together with boot image")
+	}
 	var outFile *os.File
 	var err error
 	outFile, err = os.Create(m.outPath)
@@ -135,13 +243,19 @@ func (m *MkfsCommand) Execute() error {
 			}
 			outOffset += uint64(n)
 		}
+		outOffset += klogDumpSize
+		if m.uefiPath != "" {
+			outOffset, err = writeUefiPart(outFile, outOffset, m.uefiPath)
+			if err != nil {
+				return fmt.Errorf("cannot write UEFI partition: %v", err)
+			}
+		}
 	}
 	manifest := m.manifest
 	var root map[string]interface{}
 	if manifest != nil {
 		manifest.finalize()
 		if manifest.boot != nil {
-			outOffset += klogDumpSize
 			_, err = tfsWrite(outFile, outOffset, bootFSSize, "", manifest.boot)
 			if err != nil {
 				return fmt.Errorf("cannot write boot filesystem: %v", err)
@@ -170,7 +284,7 @@ func (m *MkfsCommand) Execute() error {
 		}
 	}
 	if m.bootPath != "" {
-		err = writeMBR(outFile)
+		err = writeMBR(outFile, m.uefiPath != "")
 		if err != nil {
 			return fmt.Errorf("cannot write MBR: %v", err)
 		}
@@ -204,7 +318,150 @@ func getRootDir(root map[string]interface{}) map[string]interface{} {
 	return root["children"].(map[string]interface{})
 }
 
-func writeMBR(imgFile *os.File) error {
+/* Creates the EFI System Partition, i.e. a FAT32 filesystem with the UEFI
+loader file in the EFI/Boot directory. */
+func writeUefiPart(imgFile *os.File, offset uint64, uefiPath string) (uint64, error) {
+	uefiLoader, err := os.Open(uefiPath)
+	if err != nil {
+		return offset, fmt.Errorf("cannot open UEFI loader file: %v", err)
+	}
+	defer uefiLoader.Close()
+	_, err = imgFile.Seek(int64(offset), io.SeekStart)
+	if err != nil {
+		return offset, err
+	}
+	err = writeBlobPadded(imgFile, uefiVBR, true)
+	if err != nil {
+		return offset, err
+	}
+	/* offset 0x200 */
+	err = writeBlobPadded(imgFile, uefiFSInfo, true)
+	if err != nil {
+		return offset, err
+	}
+	/* offset 0x400 */
+	_, err = imgFile.Seek(4*sectorSize, io.SeekCurrent)
+	if err != nil {
+		return offset, err
+	}
+	/* offset 0xC00 */
+	err = writeBlobPadded(imgFile, uefiVBR, true)
+	if err != nil {
+		return offset, err
+	}
+	/* offset 0xE00 */
+	_, err = imgFile.Seek(25*sectorSize, io.SeekCurrent)
+	if err != nil {
+		return offset, err
+	}
+	/* offset 0x4000 */
+	_, err = imgFile.Write(uefiFAT)
+	if err != nil {
+		return offset, err
+	}
+	var fileInfo os.FileInfo
+	fileInfo, err = uefiLoader.Stat()
+	if err != nil {
+		return offset, fmt.Errorf("failed to get UEFI loader file info: %v", err)
+	}
+	firstCluster := uint32(0x06)
+	lastCluster := firstCluster + uint32((fileInfo.Size()+sectorSize-1)/sectorSize)
+	var valArray [4]byte
+	for cluster := firstCluster; cluster <= lastCluster; cluster++ {
+		_ = binary.Write(bytes.NewBuffer(valArray[0:0]), binary.LittleEndian, cluster)
+		_, err = imgFile.Write(valArray[:])
+		if err != nil {
+			return offset, err
+		}
+	}
+	/* end of cluster chain */
+	_ = binary.Write(bytes.NewBuffer(valArray[0:0]), binary.LittleEndian, uint32(0x0FFFFFFF))
+	_, err = imgFile.Write(valArray[:])
+	if err != nil {
+		return offset, err
+	}
+	clusterChainSize := len(uefiFAT) + int(lastCluster-firstCluster+2)*len(valArray)
+	_, err = imgFile.Seek(int64(0x81800-clusterChainSize), io.SeekCurrent)
+	if err != nil {
+		return offset, err
+	}
+	/* offset 0x85800 */
+	err = writeBlobPadded(imgFile, uefiDirEfi, false)
+	if err != nil {
+		return offset, err
+	}
+	/* offset 0x85A00 */
+	err = writeBlobPadded(imgFile, uefiDirBoot, false)
+	if err != nil {
+		return offset, err
+	}
+	/* offset 0x85C00 */
+	fileName := filepath.Base(uefiPath)
+	if fileName == "bootx64.efi" {
+		_, err = imgFile.Write(uefiFileBootx64)
+	} else if fileName == "bootaa64.efi" {
+		_, err = imgFile.Write(uefiFileBootaa64)
+	} else {
+		return offset, fmt.Errorf("invalid UEFI loader file name '%s'", fileName)
+	}
+	if err != nil {
+		return offset, err
+	}
+	/* offset 0x85C7C */
+	_ = binary.Write(bytes.NewBuffer(valArray[0:0]), binary.LittleEndian, uint32(fileInfo.Size()))
+	_, err = imgFile.Write(valArray[:])
+	if err != nil {
+		return offset, err
+	}
+	/* offset 0x85C80 */
+	_, err = imgFile.Seek(0x180, io.SeekCurrent)
+	if err != nil {
+		return offset, err
+	}
+	/* offset 0x85E00 */
+	b := make([]byte, 8192)
+	for {
+		var n int
+		n, err = uefiLoader.Read(b)
+		if err == io.EOF {
+			break
+		} else if err != nil {
+			return offset, fmt.Errorf("cannot read UEFI loader file: %v", err)
+		}
+		_, err = imgFile.Write(b[:n])
+		if err != nil {
+			return offset, err
+		}
+	}
+	return offset + uefiFSSize, nil
+}
+
+func writeBlobPadded(imgFile *os.File, blob []byte, withTrailer bool) error {
+	blobSize := len(blob)
+	_, err := imgFile.Write(blob)
+	if err != nil {
+		return err
+	}
+	trailer := [...]byte{0x55, 0xAA}
+	var paddedSize int
+	if withTrailer {
+		paddedSize = sectorSize - len(trailer)
+	} else {
+		paddedSize = sectorSize
+	}
+	if blobSize < paddedSize {
+		_, err = imgFile.Seek(int64(paddedSize-blobSize), io.SeekCurrent)
+		if err != nil {
+			return err
+		}
+		if withTrailer {
+			_, err = imgFile.Write(trailer[:])
+		}
+	}
+	return err
+}
+
+func writeMBR(imgFile *os.File, uefi bool) error {
 	info, err := imgFile.Stat()
 	if err != nil {
 		return fmt.Errorf("cannot get size of file: %v", err)
@@ -230,13 +487,16 @@ func writeMBR(imgFile *os.File) error {
 	_ = binary.Read(bytes.NewBuffer(mbr[parts-12:parts-4]), binary.LittleEndian, &fsRegionLength)
 
 	fsOffset := sectorSize + fsRegionLength + klogDumpSize
-	bootFSPartEntry := parts + partitionBootFS*partitionEntrySize
-	mbrBootFSPart := mbr[bootFSPartEntry : bootFSPartEntry+partitionEntrySize]
-	writePartition(mbrBootFSPart, true, 0x83, fsOffset, bootFSSize)
+	partNum := 0
+	if uefi {
+		writePartition(mbr, partNum, true, 0xEF, fsOffset, uefiFSSize)
+		partNum++
+		fsOffset += uefiFSSize
+	}
+	writePartition(mbr, partNum, true, 0x83, fsOffset, bootFSSize)
+	partNum++
 	fsOffset += bootFSSize
-	rootFSPartEntry := parts + partitionRootFS*partitionEntrySize
-	mbrRootFSPart := mbr[rootFSPartEntry : rootFSPartEntry+partitionEntrySize]
-	writePartition(mbrRootFSPart, true, 0x83, fsOffset, uint64(info.Size())-fsOffset)
+	writePartition(mbr, partNum, true, 0x83, fsOffset, uint64(info.Size())-fsOffset)
 
 	// Write MBR
 	n, err = imgFile.WriteAt(mbr, 0)
@@ -247,7 +507,9 @@ func writeMBR(imgFile *os.File) error {
 	return nil
 }
 
-func writePartition(part []byte, active bool, pType uint8, offset uint64, size uint64) {
+func writePartition(mbr []byte, index int, active bool, pType uint8, offset uint64, size uint64) {
+	partEntry := sectorSize - 2 - (4-index)*partitionEntrySize
+	part := mbr[partEntry : partEntry+partitionEntrySize]
 	if active {
 		part[0] = 0x80
 	} else {
diff --git a/hyperv/hyperv_api.go b/hyperv/hyperv_api.go
index e018534..592a5e5 100644
--- a/hyperv/hyperv_api.go
+++ b/hyperv/hyperv_api.go
@@ -65,7 +65,11 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD
 `
 		var ps PowerShellCmd
 		err := ps.Run(script, vmName, path, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName, strconv.FormatInt(int64(generation), 10))
-		return err
+		if err != nil {
+			return err
+		}
+
+		return SetVMSecureBoot(vmName, false)
 	}
 
 	var script = `
@@ -95,7 +99,11 @@ New-VM -Name $vmName -NoVHD -MemoryStartupBytes $memoryStartupBytes -Generation
 `
 		var ps PowerShellCmd
 		err := ps.Run(script, vmName, strconv.FormatInt(ram, 10), strconv.FormatInt(int64(generation), 10))
-		return err
+		if err != nil {
+			return err
+		}
+
+		return SetVMSecureBoot(vmName, false)
 	}
 
 	var script = `
@@ -128,27 +136,56 @@ Hyper-V\Get-VMDvdDrive -VMName $vmName | Hyper-V\Remove-VMDvdDrive
 // Due to issues on using vhdx files located inside WSL it copies the image to a user directory
 // located at `~/vhdx-images` before attaching to the virtual machine.
 // See more details on https://github.com/MicrosoftDocs/windows-powershell-docs/issues/2286
-func AddVirtualMachineHardDiskDrive(vmName string, path string) error {
+func AddVirtualMachineHardDiskDrive(vmName string, path string, hdController string, setBootDev bool) error {
 
 	var script = `
-param([string]$vmName, [string]$path)
+param([string]$vmName, [string]$path, [string]$hdController)
 New-Item -ItemType Directory -Force -Path ~/vhdx-images
 $vhdx = $vmName + '.vhdx'
 $newPath = Join-Path -Path ~/vhdx-images -ChildPath $vhdx
 cp $path $newPath
 $newPath = Convert-Path $newPath
-Add-VMHardDiskDrive -VMName $vmName -Path $newPath -ControllerType IDE -ControllerNumber 0
+Add-VMHardDiskDrive -VMName $vmName -Path $newPath -ControllerType $hdController -ControllerNumber 0
 `
 
 	var ps PowerShellCmd
-	err := ps.Run(script, vmName, path)
+	err := ps.Run(script, vmName, path, hdController)
 	if err != nil {
 		return err
 	}
 
+	if setBootDev {
+		script = `
+param([string]$vmName, [string]$hdController)
+$hdDrive = Get-VMHardDiskDrive -VMName $vmName -ControllerType $hdController -ControllerNumber 0
+Set-VMFirmware -VMName $vmName -FirstBootDevice $hdDrive
+`
+		err = ps.Run(script, vmName, hdController)
+		if err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
+// SetVMSecureBoot enables or disables secure boot on a VM
+func SetVMSecureBoot(vmName string, enable bool) error {
+	var onOff string
+	if enable {
+		onOff = "On"
+	} else {
+		onOff = "Off"
+	}
+	var script = `
+param([string]$vmName, [string]$onOff)
+Set-VMFirmware -VMName $vmName -EnableSecureBoot $onOff
+`
+	var ps PowerShellCmd
+	err := ps.Run(script, vmName, onOff)
+	return err
+}
+
 // SetVMComPort sets com port with a named pipe with the same name of the vm
 func SetVMComPort(vmName string) error {
 
diff --git a/hyperv/hyperv_image.go b/hyperv/hyperv_image.go
index 8bc8073..9a8e29f 100644
--- a/hyperv/hyperv_image.go
+++ b/hyperv/hyperv_image.go
@@ -2,6 +2,7 @@ package hyperv
 
 import (
 	"errors"
+	"fmt"
 	"os"
 	"os/exec"
 	"path"
@@ -20,6 +21,15 @@ var (
 // BuildImage creates and converts a raw image to vhdx
 func (p *Provider) BuildImage(ctx *lepton.Context) (string, error) {
 	c := ctx.Config()
+	imageType := strings.ToLower(c.CloudConfig.ImageType)
+	if imageType != "" {
+		if imageType == "gen1" {
+		} else if imageType == "gen2" {
+			c.Uefi = true
+		} else {
+			return "", fmt.Errorf("invalid image type '%s'; available types: 'gen1', 'gen2'", c.CloudConfig.ImageType)
+		}
+	}
 	err := lepton.BuildImage(*c)
 	if err != nil {
 		return "", err
diff --git a/hyperv/hyperv_instance.go b/hyperv/hyperv_instance.go
index a246673..22d9b01 100644
--- a/hyperv/hyperv_instance.go
+++ b/hyperv/hyperv_instance.go
@@ -26,12 +26,24 @@ func (p *Provider) CreateInstance(ctx *lepton.Context) error {
 		return fmt.Errorf(`image with name "%s" not found`, c.CloudConfig.ImageName)
 	}
 
+	var vmGeneration uint = 1
+	hdController := "IDE"
+	flavor := strings.ToLower(c.CloudConfig.Flavor)
+	if flavor != "" {
+		if flavor == "gen1" {
+		} else if flavor == "gen2" {
+			vmGeneration = 2
+			hdController = "SCSI"
+		} else {
+			return fmt.Errorf("invalid instance flavor '%s'; available flavors: 'gen1', 'gen2'", c.CloudConfig.Flavor)
+		}
+	}
 	windowsImagePath, err := convertPathFromWSLtoWindows(imagePath)
 	if err != nil {
 		return err
 	}
 
-	err = CreateVirtualMachineWithNoHD(vmName, int64(1024*math.Pow10(6)), 1)
+	err = CreateVirtualMachineWithNoHD(vmName, int64(1024*math.Pow10(6)), vmGeneration)
 	if err != nil {
 		return err
 	}
@@ -54,7 +66,7 @@ func (p *Provider) CreateInstance(ctx *lepton.Context) error {
 		return err
 	}
 
-	err = AddVirtualMachineHardDiskDrive(vmName, windowsImagePath)
+	err = AddVirtualMachineHardDiskDrive(vmName, windowsImagePath, hdController, vmGeneration == 2)
 	if err != nil {
 		return err
 	}
diff --git a/lepton/const.go b/lepton/const.go
index 6742194..c3b7dc0 100644
--- a/lepton/const.go
+++ b/lepton/const.go
@@ -219,6 +219,27 @@ func getKlibsDir(nightly bool) string {
 	return getLastReleaseLocalFolder() + "/klibs"
 }
 
+// GetUefiBoot retrieves UEFI bootloader file path, if found
+func GetUefiBoot(version string) string {
+	folder := getReleaseLocalFolder(version)
+	f, err := os.Open(folder)
+	if err != nil {
+		return ""
+	}
+	defer f.Close()
+	var files []os.FileInfo
+	files, err = f.Readdir(0)
+	if err != nil {
+		return ""
+	}
+	for _, file := range files {
+		if file.Mode().IsRegular() && strings.HasSuffix(file.Name(), ".efi") {
+			return path.Join(folder, file.Name())
+		}
+	}
+	return ""
+}
+
 const (
 	commonArchive = "https://storage.googleapis.com/nanos/common/common.tar.gz"
 	libDNS        = "/lib/x86_64-linux-gnu/libnss_dns.so.2"
diff --git a/lepton/image.go b/lepton/image.go
index 7ac9fe6..568ed10 100644
--- a/lepton/image.go
+++ b/lepton/image.go
@@ -377,6 +377,12 @@ func createImageFile(c *types.Config, m *fs.Manifest) error {
 	}
 
 	mkfsCommand.SetBoot(c.Boot)
+	if c.Uefi {
+		if c.UefiBoot == "" {
+			return errors.New("this Nanos version does not support UEFI, consider changing image type")
+		}
+		mkfsCommand.SetUefi(c.UefiBoot)
+	}
 	mkfsCommand.SetFileSystemPath(c.RunConfig.Imagename)
 
 	err = mkfsCommand.Execute()
diff --git a/types/config.go b/types/config.go
index 5604ea5..ab85801 100644
--- a/types/config.go
+++ b/types/config.go
@@ -12,6 +12,12 @@ type Config struct {
 	// Boot
 	Boot string
 
+	// Boot path of UEFI bootloader file
+	UefiBoot string
+
+	// Uefi indicates whether image should support booting via UEFI
+	Uefi bool
+
 	// BuildDir
 	BuildDir string
 
@@ -96,6 +102,9 @@ type ProviderConfig struct {
 	// Flavor
 	Flavor string `cloud:"flavor"`
 
+	// ImageType
+	ImageType string `cloud:"imagetype"`
+
 	// ImageName
 	ImageName string `cloud:"imagename"`
 


Test patch:
diff --git a/cmd/flags_nightly_test.go b/cmd/flags_nightly_test.go
index 8f7eb51..f5976b1 100644
--- a/cmd/flags_nightly_test.go
+++ b/cmd/flags_nightly_test.go
@@ -42,6 +42,7 @@ func TestNightlyFlagsMergeToConfig(t *testing.T) {
 		c := &types.Config{}
 		expected := &types.Config{
 			Boot:         path.Join(nightlyPath, "boot.img"),
+			UefiBoot:     path.Join(nightlyPath, "bootx64.efi"),
 			Kernel:       path.Join(nightlyPath, "/kernel.img"),
 			CloudConfig:  types.ProviderConfig{},
 			RunConfig:    types.RunConfig{},
@@ -69,6 +70,7 @@ func TestNightlyFlagsMergeToConfig(t *testing.T) {
 		}
 		expected := &types.Config{
 			Boot:         nightlyPath + "/boot.img",
+			UefiBoot:     path.Join(nightlyPath, "bootx64.efi"),
 			Kernel:       nightlyPath + "/kernel.img",
 			CloudConfig:  types.ProviderConfig{},
 			RunConfig:    types.RunConfig{},