Skip to content

Commit

Permalink
Added Volume encryption support to broker and poollet
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasfrank committed Dec 11, 2023
1 parent b4730b5 commit 90e55d6
Show file tree
Hide file tree
Showing 6 changed files with 465 additions and 128 deletions.
3 changes: 2 additions & 1 deletion api/storage/v1alpha1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ const (
// BucketPoolUserNamePrefix is the prefix all bucket pool users should have.
BucketPoolUserNamePrefix = "storage.ironcore.dev:system:bucketpool:"

SecretTypeVolumeAuth = corev1.SecretType("storage.ironcore.dev/volume-auth")
SecretTypeVolumeAuth = corev1.SecretType("storage.ironcore.dev/volume-auth")
SecretTypeVolumeEncryption = corev1.SecretType("storage.ironcore.dev/volume-encryption")
)

// VolumePoolCommonName constructs the common name for a certificate of a volume pool user.
Expand Down
63 changes: 54 additions & 9 deletions broker/machinebroker/server/machine_volume_attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ type IronCoreVolumeEmptyDiskConfig struct {
}

type IronCoreVolumeRemoteConfig struct {
Driver string
Handle string
Attributes map[string]string
SecretData map[string][]byte
Driver string
Handle string
Attributes map[string]string
SecretData map[string][]byte
EncryptionData map[string][]byte
}

func (s *Server) getIronCoreVolumeConfig(volume *iri.Volume) (*IronCoreVolumeConfig, error) {
Expand All @@ -55,10 +56,11 @@ func (s *Server) getIronCoreVolumeConfig(volume *iri.Volume) (*IronCoreVolumeCon
}
case volume.Connection != nil:
remote = &IronCoreVolumeRemoteConfig{
Driver: volume.Connection.Driver,
Handle: volume.Connection.Handle,
Attributes: volume.Connection.Attributes,
SecretData: volume.Connection.SecretData,
Driver: volume.Connection.Driver,
Handle: volume.Connection.Handle,
Attributes: volume.Connection.Attributes,
SecretData: volume.Connection.SecretData,
EncryptionData: volume.Connection.EncryptionData,
}
default:
return nil, fmt.Errorf("unrecognized volume %#v", volume)
Expand All @@ -82,8 +84,32 @@ func (s *Server) createIronCoreVolume(
var ironcoreVolumeSrc computev1alpha1.VolumeSource
switch {
case cfg.Remote != nil:
log.V(1).Info("Creating ironcore volume")

log.V(1).Info("Creating ironcore encryption secret")
remote := cfg.Remote
var (
encryptionSecret *corev1.Secret
)
if encryptionData := remote.EncryptionData; encryptionData != nil {
log.V(1).Info("Creating ironcore encryption secret")
encryptionSecret = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: s.cluster.Namespace(),
Name: s.cluster.IDGen().Generate(),
Labels: map[string]string{
machinebrokerv1alpha1.ManagerLabel: machinebrokerv1alpha1.MachineBrokerManager,
},
},
Type: storagev1alpha1.SecretTypeVolumeEncryption,
Data: encryptionData,
}
if err := s.cluster.Client().Create(ctx, encryptionSecret); err != nil {
return nil, nil, fmt.Errorf("error creating ironcore encryption secret: %w", err)
}
c.Add(cleaner.CleanupObject(s.cluster.Client(), encryptionSecret))
}

log.V(1).Info("Creating ironcore volume")
ironcoreVolume := &storagev1alpha1.Volume{
ObjectMeta: metav1.ObjectMeta{
Namespace: s.cluster.Namespace(),
Expand All @@ -100,11 +126,30 @@ func (s *Server) createIronCoreVolume(
ClaimRef: s.optionalLocalUIDReference(optIronCoreMachine),
},
}
if encryptionSecret != nil {
ironcoreVolume.Spec.Encryption = &storagev1alpha1.VolumeEncryption{
SecretRef: corev1.LocalObjectReference{Name: encryptionSecret.Name},
}
}
if err := s.cluster.Client().Create(ctx, ironcoreVolume); err != nil {
return nil, nil, fmt.Errorf("error creating ironcore volume: %w", err)
}
c.Add(cleaner.CleanupObject(s.cluster.Client(), ironcoreVolume))

if encryptionSecret != nil {
log.V(1).Info("Patching owner ref of ironcore encryption secret")
baseEncryptionSecret := encryptionSecret.DeepCopy()
encryptionSecret.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
metautils.MakeControllerRef(
storagev1alpha1.SchemeGroupVersion.WithKind("Volume"),
ironcoreVolume,
),
}
if err := s.cluster.Client().Patch(ctx, encryptionSecret, client.MergeFrom(baseEncryptionSecret)); err != nil {
return nil, nil, fmt.Errorf("error patching ironcore volume status: %w", err)
}
}

var (
secretRef *corev1.LocalObjectReference
accessSecret *corev1.Secret
Expand Down
86 changes: 86 additions & 0 deletions broker/machinebroker/server/machine_volume_attach_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
. "sigs.k8s.io/controller-runtime/pkg/envtest/komega"
)

var _ = Describe("AttachVolume", func() {
Expand Down Expand Up @@ -99,4 +100,89 @@ var _ = Describe("AttachVolume", func() {
Expect(secret.Type).To(Equal(storagev1alpha1.SecretTypeVolumeAuth))
Expect(secret.Data).To(Equal(map[string][]byte{"key": []byte("supersecret")}))
})

It("should correctly attach an encrypted volume", func(ctx SpecContext) {
By("creating a machine")
createMachineRes, err := srv.CreateMachine(ctx, &iri.CreateMachineRequest{
Machine: &iri.Machine{
Spec: &iri.MachineSpec{
Power: iri.Power_POWER_ON,
Image: &iri.ImageSpec{
Image: "example.org/foo:latest",
},
Class: machineClass.Name,
},
},
})
Expect(err).NotTo(HaveOccurred())
machineID := createMachineRes.Machine.Metadata.Id

By("attaching a volume")
Expect(srv.AttachVolume(ctx, &iri.AttachVolumeRequest{
MachineId: machineID,
Volume: &iri.Volume{
Name: "my-volume",
Device: "oda",
Connection: &iri.VolumeConnection{
Driver: "ceph",
Handle: "mycephvolume",
Attributes: map[string]string{
"foo": "bar",
},
SecretData: map[string][]byte{
"key": []byte("supersecret"),
},
EncryptionData: map[string][]byte{
"encryption": []byte("supersecret2"),
},
},
},
})).Error().ShouldNot(HaveOccurred())

By("getting the ironcore machine")
ironcoreMachine := &computev1alpha1.Machine{}
ironcoreMachineKey := client.ObjectKey{Namespace: ns.Name, Name: machineID}
Expect(k8sClient.Get(ctx, ironcoreMachineKey, ironcoreMachine)).To(Succeed())

By("inspecting the ironcore machine's volumes")
Expect(ironcoreMachine.Spec.Volumes).To(ConsistOf(MatchAllFields(Fields{
"Name": Equal("my-volume"),
"Device": PointTo(Equal("oda")),
"VolumeSource": MatchFields(IgnoreExtras, Fields{
"VolumeRef": PointTo(MatchAllFields(Fields{
"Name": Not(BeEmpty()),
})),
}),
})))

By("getting the corresponding ironcore volume")
volume := &storagev1alpha1.Volume{}
volumeName := ironcoreMachine.Spec.Volumes[0].VolumeRef.Name
volumeKey := client.ObjectKey{Namespace: ns.Name, Name: volumeName}
Expect(k8sClient.Get(ctx, volumeKey, volume)).To(Succeed())

By("inspecting the ironcore volume")
Expect(volume).To(SatisfyAll(
HaveField("Spec.Encryption.SecretRef.Name", Not(BeEmpty())),
HaveField("Status.Access.SecretRef.Name", Not(BeEmpty())),
HaveField("Status.Access.Driver", Equal("ceph")),
HaveField("Status.Access.Handle", Equal("mycephvolume")),
HaveField("Status.Access.VolumeAttributes", Equal(map[string]string{
"foo": "bar",
})),
))

By("fetching the corresponding ironcore volume encryption secret")
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: volume.Spec.Encryption.SecretRef.Name,
Namespace: ns.Name,
},
}
Expect(Object(secret)()).To(SatisfyAll(
HaveField("Type", Equal(storagev1alpha1.SecretTypeVolumeEncryption)),
HaveField("Data", Equal(map[string][]byte{"encryption": []byte("supersecret2")})),
Satisfy(func(o *corev1.Secret) bool { return metav1.IsControlledBy(o, volume) }),
))
})
})
Loading

0 comments on commit 90e55d6

Please sign in to comment.