feat(host): create disk from encrypted snapshot (#14256)

support create disk from encrypted snapshot, new disk encrypt info must
same with snapshot, and should used with libqemuio2.12.

area: host,host-image,fetcherfs
Signed-off-by: wanyaoqi <wanyaoqi1995@163.com>
This commit is contained in:
wanyaoqi
2022-05-16 00:34:37 +08:00
committed by GitHub
parent 68b517b1ab
commit 913aec9f7f
7 changed files with 158 additions and 35 deletions

View File

@@ -63,7 +63,7 @@ func initFetcherFs() (*FetcherFs, error) {
url: opt.Url,
}
if err := fetcherFs.fetchMetaInfo(); err != nil {
return nil, err
return nil, errors.Wrapf(err, "fetch meta info")
}
segs := strings.Split(opt.Url, "/")
if len(segs[len(segs)-1]) == 0 {
@@ -271,5 +271,11 @@ func (fs *FetcherFs) destory() error {
func NewRequestHeader() http.Header {
header := http.Header{}
header.Set("X-Auth-Token", opt.Token)
if len(opt.EncryptKey) > 0 {
header.Set("X-Encrypt-Key", opt.EncryptKey)
}
if len(opt.EncryptAlg) > 0 {
header.Set("X-Encrypt-Alg", opt.EncryptAlg)
}
return header
}

View File

@@ -31,6 +31,10 @@ type Options struct {
MountPoint string `help:"mount path of fuse fs" required:"true"`
Debug bool `help:"enable debug go fuse"`
Foreground bool `help:"run in foreground"`
// image encrypt info
EncryptAlg string `help:"image encrypt alg"`
EncryptKey string `help:"image encrypt password"`
}
var opt = &Options{}

View File

@@ -28,6 +28,7 @@ import (
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/apis"
"yunion.io/x/onecloud/pkg/appctx"
"yunion.io/x/onecloud/pkg/appsrv"
app_common "yunion.io/x/onecloud/pkg/cloudcommon/app"
@@ -35,6 +36,7 @@ import (
common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/util/seclib2"
)
type SHostImageOptions struct {
@@ -162,7 +164,7 @@ func closeImage(ctx context.Context, w http.ResponseWriter, r *http.Request) {
} else {
f = &SQcow2Image{}
}
err = f.Load(imagePath, true)
err = f.Load(imagePath, true, false)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
@@ -178,20 +180,35 @@ func getImage(ctx context.Context, w http.ResponseWriter, r *http.Request) {
return
}
var f IImage
var startPos, endPos int64
var rateLimit int64 = -1
var (
f IImage
startPos, endPos int64
rateLimit int64 = -1
encryptInfo *apis.SEncryptInfo
)
if r.Header.Get("X-Read-File") == "true" {
f = &SFile{}
} else {
f = &SQcow2Image{}
}
if err = f.Open(imagePath, true); err != nil {
log.Errorf("Open image error: %s", err)
httperrors.GeneralServerError(ctx, w, err)
return
err = f.Load(imagePath, true, true)
if err != nil {
encryptKey := r.Header.Get("X-Encrypt-Key")
if len(encryptKey) > 0 {
encryptInfo = new(apis.SEncryptInfo)
encryptInfo.Key = encryptKey
encryptInfo.Alg = seclib2.TSymEncAlg(r.Header.Get("X-Encrypt-Alg"))
}
if err = f.Open(imagePath, true, encryptInfo); err != nil {
log.Errorf("Open image error: %s", err)
httperrors.GeneralServerError(ctx, w, err)
return
}
}
defer f.Close()
endPos = f.Length() - 1
@@ -293,16 +310,34 @@ func getImageMeta(ctx context.Context, w http.ResponseWriter, r *http.Request) {
return
}
var f IImage
var (
f IImage
encryptInfo *apis.SEncryptInfo
)
if r.Header.Get("X-Read-File") == "true" {
f = &SFile{}
} else {
f = &SQcow2Image{}
}
if err = f.Open(imagePath, true); err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
log.Infof("open image %s", imagePath)
err = f.Load(imagePath, true, true)
if err != nil {
encryptKey := r.Header.Get("X-Encrypt-Key")
if len(encryptKey) > 0 {
encryptInfo = new(apis.SEncryptInfo)
encryptInfo.Key = encryptKey
encryptInfo.Alg = seclib2.TSymEncAlg(r.Header.Get("X-Encrypt-Alg"))
}
if err = f.Open(imagePath, true, encryptInfo); err != nil {
log.Errorf("Open image error: %s", err)
httperrors.GeneralServerError(ctx, w, err)
return
}
}
defer f.Close()
w.Header().Set("Content-Length", fmt.Sprintf("%d", f.Length()))
w.Header().Set("Content-Type", "application/octet-stream")

View File

@@ -27,12 +27,16 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"sync"
"sync/atomic"
"unsafe"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/apis"
"yunion.io/x/onecloud/pkg/util/qemuimg"
)
var qemuBlkCache sync.Map
@@ -40,6 +44,7 @@ var qemuBlkCache sync.Map
type QemuioBlkDev struct {
imagePath string
readonly bool
encrypted bool
refCount int32
blk *C.struct_QemuioBlk
@@ -63,20 +68,44 @@ func (qb *QemuioBlkDev) ReadQcow2(offset int64, count int64) ([]byte, int64) {
}
}
func OpenQcow2(imagePath string, readonly bool) *QemuioBlkDev {
key := fmt.Sprintf("%s_%v", imagePath, readonly)
func OpenQcow2(disk *qemuimg.SImageInfo, readonly bool) *QemuioBlkDev {
key := fmt.Sprintf("%s_%v", disk.Path, readonly)
if blk, ok := qemuBlkCache.Load(key); ok {
qb := blk.(*QemuioBlkDev)
atomic.AddInt32(&qb.refCount, 1)
return qb
}
qb := &QemuioBlkDev{
imagePath: imagePath,
imagePath: disk.Path,
readonly: readonly,
}
cImagePath := C.CString(imagePath)
blk := C.open_qcow2(cImagePath, C.bool(readonly))
C.free(unsafe.Pointer(cImagePath))
diskPath := C.CString(disk.Path)
imageOpts := C.CString(disk.ImageOptions())
// sec options
var secretOpts *C.char
secOpt := disk.SecretOptions()
if len(secOpt) > 0 {
secretOpts = C.CString(secOpt)
qb.encrypted = true
}
// qemu io open image
blk := C.open_qcow2(diskPath, imageOpts, secretOpts, C.bool(readonly))
C.free(unsafe.Pointer(diskPath))
C.free(unsafe.Pointer(imageOpts))
if secretOpts != nil {
C.free(unsafe.Pointer(secretOpts))
}
if blk == nil {
// failed open qemu image
return nil
}
qb.blk = blk
qb.refCount = 1
qemuBlkCache.Store(key, qb)
@@ -101,16 +130,26 @@ func (qb *QemuioBlkDev) Qcow2GetLength() int64 {
func (qb *QemuioBlkDev) CloseQcow2() {
if qb.blk != nil && atomic.AddInt32(&qb.refCount, -1) == 0 {
C.close_qcow2(qb.blk)
var secId *C.char
if qb.encrypted {
secId = C.CString(filepath.Base(qb.imagePath))
}
C.close_qcow2(qb.blk, secId)
qemuBlkCache.Delete(fmt.Sprintf("%s_%v", qb.imagePath, qb.readonly))
if secId != nil {
C.free(unsafe.Pointer(secId))
}
}
}
type IImage interface {
// Open image file and its backing file (if have)
Open(imagePath string, readonly bool) error
Open(imagePath string, readonly bool, encryptInfo *apis.SEncryptInfo) error
// load opend qcow2 img form qemu blk cache
Load(imagePath string, readonly bool) error
Load(imagePath string, readonly, reference bool) error
// Close may not really close image file handle, just reudce ref count
Close()
@@ -126,8 +165,22 @@ type SQcow2Image struct {
fd *QemuioBlkDev
}
func (img *SQcow2Image) Open(imagePath string, readonly bool) error {
fd := OpenQcow2(imagePath, readonly)
func (img *SQcow2Image) newQemuImage(imagePath string, encryptInfo *apis.SEncryptInfo) *qemuimg.SImageInfo {
info := &qemuimg.SImageInfo{
Path: imagePath,
}
if encryptInfo != nil {
info.SetSecId(filepath.Base(imagePath))
info.Password = encryptInfo.Key
info.EncryptAlg = encryptInfo.Alg
}
return info
}
func (img *SQcow2Image) Open(imagePath string, readonly bool, encryptInfo *apis.SEncryptInfo) error {
disk := img.newQemuImage(imagePath, encryptInfo)
fd := OpenQcow2(disk, readonly)
if fd == nil {
return fmt.Errorf("open image %s failed", imagePath)
} else {
@@ -136,11 +189,12 @@ func (img *SQcow2Image) Open(imagePath string, readonly bool) error {
}
}
func (img *SQcow2Image) Load(imagePath string, readonly bool) error {
func (img *SQcow2Image) Load(imagePath string, readonly, reference bool) error {
fd := LoadQcow2(imagePath, readonly)
if fd == nil {
return fmt.Errorf("image %s readonly: %v not found", imagePath, readonly)
} else {
atomic.AddInt32(&fd.refCount, 1)
img.fd = fd
return nil
}
@@ -167,7 +221,7 @@ type SFile struct {
fd *os.File
}
func (f *SFile) Open(imagePath string, readonly bool) error {
func (f *SFile) Open(imagePath string, readonly bool, encryptInfo *apis.SEncryptInfo) error {
var mode = os.O_RDWR
if readonly {
mode = os.O_RDONLY
@@ -181,7 +235,7 @@ func (f *SFile) Open(imagePath string, readonly bool) error {
}
}
func (f *SFile) Load(imagePath string, readonly bool) error {
func (f *SFile) Load(imagePath string, readonly, reference bool) error {
return fmt.Errorf("File don't support load")
}

View File

@@ -209,16 +209,22 @@ func (d *SLocalDisk) CreateFromImageFuse(ctx context.Context, url string, size i
}
}
if !newImg.IsValid() || newImg.IsChained() {
if err := fuseutils.MountFusefs(options.HostOptions.FetcherfsPath, url, localPath,
auth.GetTokenString(), mntPath, options.HostOptions.FetcherfsBlockSize); err != nil {
if err := fuseutils.MountFusefs(
options.HostOptions.FetcherfsPath, url, localPath,
auth.GetTokenString(), mntPath, options.HostOptions.FetcherfsBlockSize, encryptInfo,
); err != nil {
log.Errorln(err)
return err
}
}
if !newImg.IsValid() {
if err := newImg.CreateQcow2(0, false, contentPath, "", "", ""); err != nil {
log.Errorln(err)
return err
if encryptInfo != nil {
err = newImg.CreateQcow2(0, false, contentPath, encryptInfo.Key, qemuimg.EncryptFormatLuks, encryptInfo.Alg)
} else {
err = newImg.CreateQcow2(0, false, contentPath, "", "", "")
}
if err != nil {
return errors.Wrapf(err, "create from fuse")
}
}

View File

@@ -24,11 +24,15 @@ import (
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/apis"
"yunion.io/x/onecloud/pkg/util/fileutils2"
"yunion.io/x/onecloud/pkg/util/procutils"
)
func MountFusefs(fetcherfsPath, url, tmpdir, token, mntpath string, blocksize int) error {
func MountFusefs(
fetcherfsPath, url, tmpdir, token, mntpath string,
blocksize int, encryptInfo *apis.SEncryptInfo,
) error {
var metaPath = path.Join(mntpath, "meta")
if f, err := os.OpenFile(metaPath, os.O_RDONLY, 0644); err == nil {
f.Close()
@@ -64,6 +68,14 @@ func MountFusefs(fetcherfsPath, url, tmpdir, token, mntpath string, blocksize in
"--blocksize", strconv.Itoa(blocksize),
"--mount-point", mntpath,
}
if encryptInfo != nil && len(encryptInfo.Key) > 0 {
// TODO: base64 encrypt key
cmd = append(cmd, "--encrypt-key", encryptInfo.Key)
}
if encryptInfo != nil && len(encryptInfo.Alg) > 0 {
cmd = append(cmd, "--encrypt-alg", string(encryptInfo.Alg))
}
log.Infof("%s", strings.Join(cmd, " "))
out, err := procutils.NewRemoteCommandAsFarAsPossible(cmd[0], cmd[1:]...).Output()
if err != nil {

View File

@@ -211,6 +211,10 @@ type SImageInfo struct {
secId string
}
func (info *SImageInfo) SetSecId(id string) {
info.secId = id
}
func (info SImageInfo) ImageOptions() string {
opts := make([]string, 0)
format := info.Format
@@ -238,8 +242,8 @@ func (info SImageInfo) ImageOptions() string {
}
func (info SImageInfo) SecretOptions() string {
opts := make([]string, 0)
if info.Encrypted() {
opts := make([]string, 0)
secId := info.secId
if len(secId) == 0 {
secId = "sec0"
@@ -248,8 +252,10 @@ func (info SImageInfo) SecretOptions() string {
opts = append(opts, fmt.Sprintf("id=%s", secId))
opts = append(opts, fmt.Sprintf("data=%s", info.Password))
opts = append(opts, "format=base64")
return strings.Join(opts, ",")
}
return strings.Join(opts, ",")
return ""
}
func (info SImageInfo) Encrypted() bool {