Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add encrypted scratch space for container sandbox #2212

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/podvm_mkosi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,12 @@ jobs:
ARCH: ${{ inputs.arch }}

- name: Build mkosi debug image
if: ${{ inputs.debug == 'true' }}
if: ${{ inputs.debug == true }}
working-directory: src/cloud-api-adaptor/podvm-mkosi
run: make image-debug

- name: Build mkosi image
if: ${{ inputs.debug != 'true' }}
if: ${{ inputs.debug != true }}
working-directory: src/cloud-api-adaptor/podvm-mkosi
run: make image

Expand Down
1 change: 1 addition & 0 deletions src/cloud-api-adaptor/cmd/cloud-api-adaptor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ func (cfg *daemonConfig) Setup() (cmd.Starter, error) {
flags.StringVar(&cfg.serverConfig.Initdata, "initdata", "", "Default initdata for all Pods")
flags.BoolVar(&cfg.serverConfig.EnableCloudConfigVerify, "cloud-config-verify", false, "Enable cloud config verify - should use it for production")
flags.IntVar(&cfg.serverConfig.PeerPodsLimitPerNode, "peerpods-limit-per-node", 10, "peer pods limit per node (default=10)")
flags.Uint64Var(&cfg.serverConfig.RootVolumeSize, "root-volume-size", 0, "Root volume size in GB. Default is 0, which implies the default image disk size")
mkulke marked this conversation as resolved.
Show resolved Hide resolved

cloud.ParseCmd(flags)
})
Expand Down
1 change: 1 addition & 0 deletions src/cloud-api-adaptor/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ azure() {
[[ "${TAGS}" ]] && optionals+="-tags ${TAGS} " # Custom tags applied to pod vm
[[ "${ENABLE_SECURE_BOOT}" == "true" ]] && optionals+="-enable-secure-boot "
[[ "${USE_PUBLIC_IP}" == "true" ]] && optionals+="-use-public-ip "
[[ "${ROOT_VOLUME_SIZE}" ]] && optionals+="-root-volume-size ${ROOT_VOLUME_SIZE} " # OS disk size in GB

set -x
exec cloud-api-adaptor azure \
Expand Down
20 changes: 16 additions & 4 deletions src/cloud-api-adaptor/pkg/adaptor/cloud/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type ServerConfig struct {
SecureCommsPpOutbounds string
SecureCommsKbsAddress string
PeerPodsLimitPerNode int
RootVolumeSize uint64
}

var logger = log.New(log.Writer(), "[adaptor/cloud] ", log.LstdFlags|log.Lmsgprefix)
Expand Down Expand Up @@ -221,17 +222,20 @@ func (s *cloudService) CreateVM(ctx context.Context, req *pb.CreateVMRequest) (r
instanceType := util.GetInstanceTypeFromAnnotation(req.Annotations)

// Get Pod VM cpu and memory from annotations
vcpus, memory, gpus := util.GetPodvmResourcesFromAnnotation(req.Annotations)
resources := util.GetPodVMResourcesFromAnnotation(req.Annotations)

// Set caa-wide root volume size to resources if the storage has not been configured yet
if resources.Storage == 0 && s.serverConfig.RootVolumeSize > 0 {
resources.Storage = int64(s.serverConfig.RootVolumeSize)
}

// Get Pod VM image from annotations
image := util.GetImageFromAnnotation(req.Annotations)

// Pod VM spec
vmSpec := provider.InstanceTypeSpec{
InstanceType: instanceType,
VCPUs: vcpus,
Memory: memory,
GPUs: gpus,
Resources: resources,
Image: image,
}

Expand Down Expand Up @@ -320,6 +324,14 @@ func (s *cloudService) CreateVM(ctx context.Context, req *pb.CreateVMRequest) (r
},
}

if s.serverConfig.RootVolumeSize > 0 {
// Write an empty file to indicate that we want to use available space as sandbox storage
cloudConfig.WriteFiles = append(cloudConfig.WriteFiles, cloudinit.WriteFile{
Path: UseScratchPath,
Content: "",
})
}

if authJSON != nil {
if len(authJSON) > cloudinit.DefaultAuthfileLimit {
logger.Printf("Credentials file is too large to be included in cloud-config")
Expand Down
1 change: 1 addition & 0 deletions src/cloud-api-adaptor/pkg/paths/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ const (
AgentCfgPath = "/run/peerpod/agent-config.toml"
ForwarderCfgPath = "/run/peerpod/daemon.json"
UserDataPath = "/media/cidata/user-data"
UseScratchPath = "/run/peerpod/mount-scratch"
)
2 changes: 1 addition & 1 deletion src/cloud-api-adaptor/pkg/userdata/provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const (
)

var logger = log.New(log.Writer(), "[userdata/provision] ", log.LstdFlags|log.Lmsgprefix)
var WriteFilesList = []string{AACfgPath, CDHCfgPath, ForwarderCfgPath, AuthFilePath, InitDataPath}
var WriteFilesList = []string{AACfgPath, CDHCfgPath, ForwarderCfgPath, AuthFilePath, InitDataPath, UseScratchPath}
var InitdDataFilesList = []string{AACfgPath, CDHCfgPath, PolicyPath}

type Config struct {
Expand Down
31 changes: 16 additions & 15 deletions src/cloud-api-adaptor/pkg/util/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strconv"
"strings"

provider "github.com/confidential-containers/cloud-api-adaptor/src/cloud-providers"
cri "github.com/containerd/containerd/pkg/cri/annotations"
hypannotations "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/annotations"
)
Expand Down Expand Up @@ -44,48 +45,48 @@ func GetImageFromAnnotation(annotations map[string]string) string {
}

// Method to get vCPU, memory and gpus from annotations
func GetPodvmResourcesFromAnnotation(annotations map[string]string) (int64, int64, int64) {

var vcpuInt, memoryInt, gpuInt int64
func GetPodVMResourcesFromAnnotation(annotations map[string]string) provider.PodVMResources {
var vcpus, gpus, memory int64
var err error

vcpu, ok := annotations[hypannotations.DefaultVCPUs]
if ok {
vcpuInt, err = strconv.ParseInt(vcpu, 10, 64)
vcpus, err = strconv.ParseInt(vcpu, 10, 64)
if err != nil {
fmt.Printf("Error converting vcpu to int64. Defaulting to 0: %v\n", err)
vcpuInt = 0
vcpus = 0
}
} else {
vcpuInt = 0
vcpus = 0
}

memory, ok := annotations[hypannotations.DefaultMemory]
mem, ok := annotations[hypannotations.DefaultMemory]
if ok {
// Use strconv.ParseInt to convert string to int64
memoryInt, err = strconv.ParseInt(memory, 10, 64)
memory, err = strconv.ParseInt(mem, 10, 64)
if err != nil {
fmt.Printf("Error converting memory to int64. Defaulting to 0: %v\n", err)
memoryInt = 0
memory = 0
}

} else {
memoryInt = 0
memory = 0
}

gpu, ok := annotations[hypannotations.DefaultGPUs]
if ok {
gpuInt, err = strconv.ParseInt(gpu, 10, 64)
gpus, err = strconv.ParseInt(gpu, 10, 64)
if err != nil {
fmt.Printf("Error converting gpu to int64. Defaulting to 0: %v\n", err)
gpuInt = 0
gpus = 0
}
} else {
gpuInt = 0
gpus = 0
}

// Return vCPU, memory and GPU
return vcpuInt, memoryInt, gpuInt
storage := int64(0)
stevenhorsman marked this conversation as resolved.
Show resolved Hide resolved

return provider.PodVMResources{VCPUs: vcpus, Memory: memory, GPUs: gpus, Storage: storage}
}

// Method to get initdata from annotation
Expand Down
3 changes: 2 additions & 1 deletion src/cloud-api-adaptor/pkg/util/cloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ func TestGetPodvmResourcesFromAnnotation(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1, got2 := GetPodvmResourcesFromAnnotation(tt.args.annotations)
r := GetPodVMResourcesFromAnnotation(tt.args.annotations)
got, got1, got2 := r.VCPUs, r.Memory, r.GPUs
if got != tt.want {
t.Errorf("GetPodvmResourcesFromAnnotation() got = %v, want %v", got, tt.want)
}
Expand Down
1 change: 1 addition & 0 deletions src/cloud-api-adaptor/podvm-mkosi/Dockerfile.mkosi
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ COPY mkosi.presets /image/mkosi.presets
COPY mkosi.profiles /image/mkosi.profiles
COPY mkosi.skeleton /image/mkosi.skeleton
COPY mkosi.skeleton-debug /image/mkosi.skeleton-debug
COPY mkosi.skeleton-rootfs /image/mkosi.skeleton-rootfs
COPY mkosi.workspace /image/mkosi.workspace
COPY resources /image/resources
COPY mkosi.conf /image/mkosi.conf
Expand Down
2 changes: 2 additions & 0 deletions src/cloud-api-adaptor/podvm-mkosi/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ else
--allow security.insecure \
.
qemu-img convert -f raw -O qcow2 build/system.raw build/podvm-$(PODVM_DISTRO)-$(ARCH).qcow2
qemu-img resize build/podvm-$(PODVM_DISTRO)-$(ARCH).qcow2 +100M
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any significance to +100M, or is it just a starter amount of space?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it needs to have a bit of size, so repart will not stumble over it at launch, attempting to create a partition without space. not sure if needs to be +100m, could just also work with +10m, have to test.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I don't have an issue with the 100M, just wanted to check if there was special logic behind it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only being done on the x86 path here as only Azure is supporting this scratch space at the moment I assume? Do you see any obstacles in the code for s390x support if/when needed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know much about partitioning schemes on s390x. if systemd-repart works in the same way, it should work ootb. The cloud-provider code would need to consider the .storage property when provisioning a VM and then repart would grab whatever it available to allocate it to the scratch space.

endif

PHONY: image-debug
Expand All @@ -112,6 +113,7 @@ else
--allow security.insecure \
.
qemu-img convert -f raw -O qcow2 build/system.raw build/podvm-$(PODVM_DISTRO)-$(ARCH).qcow2
qemu-img resize build/podvm-$(PODVM_DISTRO)-$(ARCH).qcow2 +100M
endif

PHONY: image-container
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Release=39

[Content]
CleanPackageMetadata=true
SkeletonTrees=../../resources/binaries-tree
SkeletonTrees=../../mkosi.skeleton-rootfs,../../resources/binaries-tree,
Packages=
kernel
kernel-core
Expand All @@ -23,6 +23,7 @@ Packages=
iptables
afterburn
neofetch
e2fsprogs

RemoveFiles=/etc/issue
RemoveFiles=/etc/issue.net
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
scratch /dev/disk/by-label/scratch - try-empty-password
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[Service]
ExecStartPre=sh -c '[[ ! -f /run/peerpod/mount-scratch ]] || mount /dev/mapper/scratch /run/kata-containers'
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[Partition]
Type=linux-generic
Label=scratch
Encrypt=key-file
Format=ext4
bpradipt marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 3 additions & 1 deletion src/cloud-providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,10 @@ func (p *awsProvider) updateInstanceTypeSpecList() error {
if err != nil {
return err
}
resources := provider.NewPodVMResources(vcpus, memory)
resources.GPUs = gpuCount
instanceTypeSpecList = append(instanceTypeSpecList,
provider.InstanceTypeSpec{InstanceType: instanceType, VCPUs: vcpus, Memory: memory, GPUs: gpuCount})
provider.InstanceTypeSpec{InstanceType: instanceType, Resources: resources})
}

// Sort the instanceTypeSpecList and update the serviceConfig
Expand Down
78 changes: 43 additions & 35 deletions src/cloud-providers/azure/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"log"
"net/netip"
"os"
"path/filepath"
"regexp"
"strings"

Expand All @@ -35,24 +34,44 @@ const (
type azureProvider struct {
azureClient azcore.TokenCredential
serviceConfig *Config
sshKey armcompute.SSHPublicKey
}

func NewProvider(config *Config) (provider.Provider, error) {

logger.Printf("azure config %+v", config.Redact())

// Clean the config.SSHKeyPath to avoid bad paths
config.SSHKeyPath = filepath.Clean(config.SSHKeyPath)

azureClient, err := NewAzureClient(*config)
if err != nil {
logger.Printf("creating azure client: %v", err)
return nil, err
}

// The podvm disk doesn't support sshd logins. keys can be baked
// into a debug-image. The ARM api mandates a pubkey, though.
sshPublicKeyPath := os.ExpandEnv(config.SSHKeyPath)
var pubkeyBytes []byte
if _, err := os.Stat(sshPublicKeyPath); err == nil {
pubkeyBytes, err = os.ReadFile(sshPublicKeyPath)
if err != nil {
err = fmt.Errorf("reading ssh public key file: %w", err)
logger.Printf("%v", err)
return nil, err
}
} else {
err = fmt.Errorf("ssh public key: %w", err)
logger.Printf("%v", err)
return nil, err
}
dummySSHKey := armcompute.SSHPublicKey{
Path: to.Ptr(fmt.Sprintf("/home/%s/.ssh/authorized_keys", config.SSHUserName)),
KeyData: to.Ptr(string(pubkeyBytes)),
}

provider := &azureProvider{
azureClient: azureClient,
serviceConfig: config,
sshKey: dummySSHKey,
}

if err = provider.updateInstanceSizeSpecList(); err != nil {
Expand Down Expand Up @@ -218,28 +237,12 @@ func (p *azureProvider) CreateInstance(ctx context.Context, podName, sandboxID s
diskName := fmt.Sprintf("%s-disk", instanceName)
nicName := fmt.Sprintf("%s-net", instanceName)

// require ssh key for authentication on linux
sshPublicKeyPath := os.ExpandEnv(p.serviceConfig.SSHKeyPath)
var sshBytes []byte
if _, err := os.Stat(sshPublicKeyPath); err == nil {
sshBytes, err = os.ReadFile(sshPublicKeyPath)
if err != nil {
err = fmt.Errorf("reading ssh public key file: %w", err)
logger.Printf("%v", err)
return nil, err
}
} else {
err = fmt.Errorf("ssh public key: %w", err)
logger.Printf("%v", err)
return nil, err
}

if spec.Image != "" {
logger.Printf("Choosing %s from annotation as the Azure Image for the PodVM image", spec.Image)
p.serviceConfig.ImageId = spec.Image
}

vmParameters, err := p.getVMParameters(instanceSize, diskName, cloudConfigData, sshBytes, instanceName, nicName)
vmParameters, err := p.getVMParameters(instanceSize, diskName, cloudConfigData, instanceName, nicName, spec.Resources.Storage)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -350,7 +353,10 @@ func (p *azureProvider) updateInstanceSizeSpecList() error {
}
for _, vmSize := range nextResult.VirtualMachineSizeListResult.Value {
if util.Contains(instanceSizes, *vmSize.Name) {
instanceSizeSpecList = append(instanceSizeSpecList, provider.InstanceTypeSpec{InstanceType: *vmSize.Name, VCPUs: int64(*vmSize.NumberOfCores), Memory: int64(*vmSize.MemoryInMB)})
vcpus, memory := int64(*vmSize.NumberOfCores), int64(*vmSize.MemoryInMB)
resources := provider.NewPodVMResources(vcpus, memory)
instanceSizeSpec := provider.InstanceTypeSpec{InstanceType: *vmSize.Name, Resources: resources}
instanceSizeSpecList = append(instanceSizeSpecList, instanceSizeSpec)
}
}
}
Expand All @@ -371,7 +377,7 @@ func (p *azureProvider) getResourceTags() map[string]*string {
return tags
}

func (p *azureProvider) getVMParameters(instanceSize, diskName, cloudConfig string, sshBytes []byte, instanceName, nicName string) (*armcompute.VirtualMachine, error) {
func (p *azureProvider) getVMParameters(instanceSize, diskName, cloudConfig, instanceName, nicName string, diskSize int64) (*armcompute.VirtualMachine, error) {
userDataB64 := base64.StdEncoding.EncodeToString([]byte(cloudConfig))

// Azure limits the base64 encrypted userData to 64KB.
Expand Down Expand Up @@ -416,6 +422,18 @@ func (p *azureProvider) getVMParameters(instanceSize, diskName, cloudConfig stri

networkConfig := p.buildNetworkConfig(nicName)

osDisk := armcompute.OSDisk{
Name: to.Ptr(diskName),
CreateOption: to.Ptr(armcompute.DiskCreateOptionTypesFromImage),
Caching: to.Ptr(armcompute.CachingTypesReadWrite),
DeleteOption: to.Ptr(armcompute.DiskDeleteOptionTypesDelete),
ManagedDisk: managedDiskParams,
}

if diskSize > 0 {
osDisk.DiskSizeGB = to.Ptr(int32(diskSize))
}

vmParameters := armcompute.VirtualMachine{
Location: to.Ptr(p.serviceConfig.Region),
Properties: &armcompute.VirtualMachineProperties{
Expand All @@ -424,25 +442,15 @@ func (p *azureProvider) getVMParameters(instanceSize, diskName, cloudConfig stri
},
StorageProfile: &armcompute.StorageProfile{
ImageReference: imgRef,
OSDisk: &armcompute.OSDisk{
Name: to.Ptr(diskName),
CreateOption: to.Ptr(armcompute.DiskCreateOptionTypesFromImage),
Caching: to.Ptr(armcompute.CachingTypesReadWrite),
DeleteOption: to.Ptr(armcompute.DiskDeleteOptionTypesDelete),
ManagedDisk: managedDiskParams,
},
OSDisk: &osDisk,
},
OSProfile: &armcompute.OSProfile{
AdminUsername: to.Ptr(p.serviceConfig.SSHUserName),
ComputerName: to.Ptr(instanceName),
LinuxConfiguration: &armcompute.LinuxConfiguration{
DisablePasswordAuthentication: to.Ptr(true),
//TBD: replace with a suitable mechanism to use precreated SSH key
SSH: &armcompute.SSHConfiguration{
PublicKeys: []*armcompute.SSHPublicKey{{
Path: to.Ptr(fmt.Sprintf("/home/%s/.ssh/authorized_keys", p.serviceConfig.SSHUserName)),
KeyData: to.Ptr(string(sshBytes)),
}},
PublicKeys: []*armcompute.SSHPublicKey{&p.sshKey},
},
},
},
Expand Down
Loading
Loading