Skip to content

Commit 48245d2

Browse files
committed
Add Cloud KMS encrypted disk lifecycle test
1 parent df44c92 commit 48245d2

File tree

4 files changed

+231
-21
lines changed

4 files changed

+231
-21
lines changed

test/e2e/tests/multi_zone_e2e_test.go

+37-19
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ limitations under the License.
1515
package tests
1616

1717
import (
18+
"fmt"
1819
"path/filepath"
1920
"strings"
2021

@@ -143,68 +144,85 @@ var _ = Describe("GCE PD CSI Driver Multi-Zone", func() {
143144

144145
})
145146

146-
func testAttachWriteReadDetach(volID string, volName string, instance *remote.InstanceInfo, client *remote.CsiClient, readOnly bool) {
147+
func testAttachWriteReadDetach(volID string, volName string, instance *remote.InstanceInfo, client *remote.CsiClient, readOnly bool) error {
147148
var err error
148149

149150
glog.Infof("Starting testAttachWriteReadDetach with volume %v node %v with readonly %v\n", volID, instance.GetNodeID(), readOnly)
150151
// Attach Disk
151152
err = client.ControllerPublishVolume(volID, instance.GetNodeID())
152-
Expect(err).To(BeNil(), "ControllerPublishVolume failed with error for disk %v on node %v", volID, instance.GetNodeID())
153+
if err != nil {
154+
return fmt.Errorf("ControllerPublishVolume failed with error for disk %v on node %v: %v", volID, instance.GetNodeID(), err)
155+
}
153156

154157
defer func() {
155-
156158
// Detach Disk
157-
err = client.ControllerUnpublishVolume(volID, instance.GetNodeID())
158-
Expect(err).To(BeNil(), "ControllerUnpublishVolume failed with error")
159+
_ = client.ControllerUnpublishVolume(volID, instance.GetNodeID())
159160
}()
160161

161162
// Stage Disk
162163
stageDir := filepath.Join("/tmp/", volName, "stage")
163-
client.NodeStageVolume(volID, stageDir)
164-
Expect(err).To(BeNil(), "NodeStageVolume failed with error")
164+
err = client.NodeStageVolume(volID, stageDir)
165+
if err != nil {
166+
return fmt.Errorf("NodeStageVolume failed with error: %v", err)
167+
}
165168

166169
defer func() {
167170
// Unstage Disk
168-
err = client.NodeUnstageVolume(volID, stageDir)
169-
Expect(err).To(BeNil(), "NodeUnstageVolume failed with error")
170-
err = testutils.RmAll(instance, filepath.Join("/tmp/", volName))
171-
Expect(err).To(BeNil(), "Failed to remove temp directory")
171+
_ = client.NodeUnstageVolume(volID, stageDir)
172+
_ = testutils.RmAll(instance, filepath.Join("/tmp/", volName))
172173
}()
173174

174175
// Mount Disk
175176
publishDir := filepath.Join("/tmp/", volName, "mount")
176177
err = client.NodePublishVolume(volID, stageDir, publishDir)
177-
Expect(err).To(BeNil(), "NodePublishVolume failed with error")
178+
if err != nil {
179+
return fmt.Errorf("NodePublishVolume failed with error: %v", err)
180+
}
178181
err = testutils.ForceChmod(instance, filepath.Join("/tmp/", volName), "777")
179-
Expect(err).To(BeNil(), "Chmod failed with error")
182+
if err != nil {
183+
return fmt.Errorf("Chmod failed with error: %v", err)
184+
}
180185
testFileContents := "test"
181186
if !readOnly {
182187
// Write a file
183188
testFile := filepath.Join(publishDir, "testfile")
184189
err = testutils.WriteFile(instance, testFile, testFileContents)
185-
Expect(err).To(BeNil(), "Failed to write file")
190+
if err != nil {
191+
return fmt.Errorf("Failed to write file: %v", err)
192+
}
186193
}
187194

188195
// Unmount Disk
189196
err = client.NodeUnpublishVolume(volID, publishDir)
190-
Expect(err).To(BeNil(), "NodeUnpublishVolume failed with error")
197+
if err != nil {
198+
return fmt.Errorf("NodeUnpublishVolume failed with error: %v", err)
199+
}
191200

192201
// Mount disk somewhere else
193202
secondPublishDir := filepath.Join("/tmp/", volName, "secondmount")
194203
err = client.NodePublishVolume(volID, stageDir, secondPublishDir)
195-
Expect(err).To(BeNil(), "NodePublishVolume failed with error")
204+
if err != nil {
205+
return fmt.Errorf("NodePublishVolume failed with error: %v", err)
206+
}
196207
err = testutils.ForceChmod(instance, filepath.Join("/tmp/", volName), "777")
197-
Expect(err).To(BeNil(), "Chmod failed with error")
208+
if err != nil {
209+
return fmt.Errorf("Chmod failed with error: %v", err)
210+
}
198211

199212
// Read File
200213
secondTestFile := filepath.Join(secondPublishDir, "testfile")
201214
readContents, err := testutils.ReadFile(instance, secondTestFile)
202-
Expect(err).To(BeNil(), "ReadFile failed with error")
215+
if err != nil {
216+
return fmt.Errorf("ReadFile failed with error: %v", err)
217+
}
203218
Expect(strings.TrimSpace(string(readContents))).To(Equal(testFileContents))
204219

205220
// Unmount Disk
206221
err = client.NodeUnpublishVolume(volID, secondPublishDir)
207-
Expect(err).To(BeNil(), "NodeUnpublishVolume failed with error")
222+
if err != nil {
223+
return fmt.Errorf("NodeUnpublishVolume failed with error: %v", err)
224+
}
208225

209226
glog.Infof("Completed testAttachWriteReadDetach with volume %v node %v\n", volID, instance.GetNodeID())
227+
return nil
210228
}

test/e2e/tests/setup_e2e_test.go

+7
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ limitations under the License.
1515
package tests
1616

1717
import (
18+
"context"
1819
"flag"
1920
"fmt"
2021
"math/rand"
2122
"testing"
2223
"time"
2324

25+
cloudkms "cloud.google.com/go/kms/apiv1"
2426
"github.com/golang/glog"
2527
. "github.com/onsi/ginkgo"
2628
. "github.com/onsi/gomega"
@@ -39,6 +41,7 @@ var (
3941
testContexts = []*remote.TestContext{}
4042
computeService *compute.Service
4143
betaComputeService *computebeta.Service
44+
kmsClient *cloudkms.KeyManagementClient
4245
)
4346

4447
func TestE2E(t *testing.T) {
@@ -62,6 +65,10 @@ var _ = BeforeSuite(func() {
6265
betaComputeService, err = remote.GetBetaComputeClient()
6366
Expect(err).To(BeNil())
6467

68+
// Create the KMS client.
69+
kmsClient, err = cloudkms.NewKeyManagementClient(context.Background())
70+
Expect(err).To(BeNil())
71+
6572
if *runInProw {
6673
*project, *serviceAccount = testutils.SetupProwConfig("gce-project")
6774
}

test/e2e/tests/single_zone_e2e_test.go

+169-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ limitations under the License.
1515
package tests
1616

1717
import (
18+
"context"
19+
"fmt"
1820
"strings"
1921
"time"
2022

@@ -26,6 +28,10 @@ import (
2628
csi "github.com/container-storage-interface/spec/lib/go/csi"
2729
. "github.com/onsi/ginkgo"
2830
. "github.com/onsi/gomega"
31+
32+
"google.golang.org/api/iterator"
33+
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
34+
fieldmask "google.golang.org/genproto/protobuf/field_mask"
2935
)
3036

3137
const (
@@ -77,7 +83,8 @@ var _ = Describe("GCE PD CSI Driver", func() {
7783
}()
7884

7985
// Attach Disk
80-
testAttachWriteReadDetach(volID, volName, instance, client, false /* readOnly */)
86+
err = testAttachWriteReadDetach(volID, volName, instance, client, false /* readOnly */)
87+
Expect(err).To(BeNil(), "Failed to go through volume lifecycle")
8188

8289
})
8390

@@ -151,7 +158,8 @@ var _ = Describe("GCE PD CSI Driver", func() {
151158
}()
152159

153160
// Attach Disk
154-
testAttachWriteReadDetach(underSpecifiedID, volName, instance, client, false /* readOnly */)
161+
err = testAttachWriteReadDetach(underSpecifiedID, volName, instance, client, false /* readOnly */)
162+
Expect(err).To(BeNil(), "Failed to go through volume lifecycle")
155163

156164
})
157165

@@ -294,6 +302,165 @@ var _ = Describe("GCE PD CSI Driver", func() {
294302
}()
295303
})
296304

305+
It("Should create CMEK key, go through volume lifecycle, validate behavior on key revoke and restore", func() {
306+
ctx := context.Background()
307+
Expect(testContexts).ToNot(BeEmpty())
308+
testContext := getRandomTestContext()
309+
310+
controllerInstance := testContext.Instance
311+
controllerClient := testContext.Client
312+
313+
p, z, _ := controllerInstance.GetIdentity()
314+
locationID := "global"
315+
316+
// The resource name of the key rings.
317+
parentName := fmt.Sprintf("projects/%s/locations/%s", p, locationID)
318+
keyRingId := "gce-pd-csi-test-ring"
319+
320+
// Create KeyRing
321+
ringReq := &kmspb.CreateKeyRingRequest{
322+
Parent: parentName,
323+
KeyRingId: keyRingId,
324+
}
325+
keyRing, err := kmsClient.CreateKeyRing(ctx, ringReq)
326+
if !gce.IsGCEError(err, "alreadyExists") {
327+
getKeyRingReq := &kmspb.GetKeyRingRequest{
328+
Name: fmt.Sprintf("%s/keyRings/%s", parentName, keyRingId),
329+
}
330+
keyRing, err = kmsClient.GetKeyRing(ctx, getKeyRingReq)
331+
332+
}
333+
Expect(err).To(BeNil(), "Failed to create or get key ring %v", keyRingId)
334+
335+
// Create CryptoKey in KeyRing
336+
keyId := "test-key-" + string(uuid.NewUUID())
337+
keyReq := &kmspb.CreateCryptoKeyRequest{
338+
Parent: keyRing.Name,
339+
CryptoKeyId: keyId,
340+
CryptoKey: &kmspb.CryptoKey{
341+
Purpose: kmspb.CryptoKey_ENCRYPT_DECRYPT,
342+
VersionTemplate: &kmspb.CryptoKeyVersionTemplate{
343+
Algorithm: kmspb.CryptoKeyVersion_GOOGLE_SYMMETRIC_ENCRYPTION,
344+
},
345+
},
346+
}
347+
key, err := kmsClient.CreateCryptoKey(ctx, keyReq)
348+
if !gce.IsGCEError(err, "alreadyExists") {
349+
getKeyReq := &kmspb.GetCryptoKeyRequest{
350+
Name: fmt.Sprintf("%s/cryptoKeys/%s", keyRing.Name, keyId),
351+
}
352+
key, err = kmsClient.GetCryptoKey(ctx, getKeyReq)
353+
354+
}
355+
Expect(err).To(BeNil(), "Failed to create crypto key %v in key ring %v", keyId, keyRing.Name)
356+
357+
keyVersions := []string{}
358+
keyVersionReq := &kmspb.ListCryptoKeyVersionsRequest{
359+
Parent: key.Name,
360+
}
361+
362+
it := kmsClient.ListCryptoKeyVersions(ctx, keyVersionReq)
363+
364+
for {
365+
keyVersion, err := it.Next()
366+
if err == iterator.Done {
367+
break
368+
}
369+
Expect(err).To(BeNil(), "Failed to list crypto key versions")
370+
371+
keyVersions = append(keyVersions, keyVersion.Name)
372+
}
373+
374+
// Defer deletion of all key versions
375+
// https://cloud.google.com/kms/docs/destroy-restore
376+
defer func() {
377+
for _, keyVersion := range keyVersions {
378+
destroyKeyReq := &kmspb.DestroyCryptoKeyVersionRequest{
379+
Name: keyVersion,
380+
}
381+
_, err = kmsClient.DestroyCryptoKeyVersion(ctx, destroyKeyReq)
382+
Expect(err).To(BeNil(), "Failed to destroy crypto key version: %v", keyVersion)
383+
}
384+
385+
}()
386+
387+
// Go through volume lifecycle using CMEK-ed PD
388+
// Create Disk
389+
volName := testNamePrefix + string(uuid.NewUUID())
390+
volID, err := controllerClient.CreateVolume(volName, map[string]string{
391+
common.ParameterKeyDiskEncryptionKmsKey: key.Name,
392+
}, defaultSizeGb,
393+
&csi.TopologyRequirement{
394+
Requisite: []*csi.Topology{
395+
{
396+
Segments: map[string]string{common.TopologyKeyZone: z},
397+
},
398+
},
399+
})
400+
Expect(err).To(BeNil(), "CreateVolume failed with error: %v", err)
401+
402+
// Validate Disk Created
403+
cloudDisk, err := computeService.Disks.Get(p, z, volName).Do()
404+
Expect(err).To(BeNil(), "Could not get disk from cloud directly")
405+
Expect(cloudDisk.Type).To(ContainSubstring(standardDiskType))
406+
Expect(cloudDisk.Status).To(Equal(readyState))
407+
Expect(cloudDisk.SizeGb).To(Equal(defaultSizeGb))
408+
Expect(cloudDisk.Name).To(Equal(volName))
409+
410+
defer func() {
411+
// Delete Disk
412+
controllerClient.DeleteVolume(volID)
413+
Expect(err).To(BeNil(), "DeleteVolume failed")
414+
415+
// Validate Disk Deleted
416+
_, err = computeService.Disks.Get(p, z, volName).Do()
417+
Expect(gce.IsGCEError(err, "notFound")).To(BeTrue(), "Expected disk to not be found")
418+
}()
419+
420+
// Test disk works
421+
err = testAttachWriteReadDetach(volID, volName, controllerInstance, controllerClient, false /* readOnly */)
422+
Expect(err).To(BeNil(), "Failed to go through volume lifecycle before revoking CMEK key")
423+
424+
// Revoke CMEK key
425+
// https://cloud.google.com/kms/docs/enable-disable
426+
for _, keyVersion := range keyVersions {
427+
disableReq := &kmspb.UpdateCryptoKeyVersionRequest{
428+
CryptoKeyVersion: &kmspb.CryptoKeyVersion{
429+
Name: keyVersion,
430+
State: kmspb.CryptoKeyVersion_DISABLED,
431+
},
432+
UpdateMask: &fieldmask.FieldMask{
433+
Paths: []string{"state"},
434+
},
435+
}
436+
_, err = kmsClient.UpdateCryptoKeyVersion(ctx, disableReq)
437+
Expect(err).To(BeNil(), "Failed to disable crypto key")
438+
}
439+
440+
// Make sure attach of PD fails
441+
err = testAttachWriteReadDetach(volID, volName, controllerInstance, controllerClient, false /* readOnly */)
442+
Expect(err).ToNot(BeNil(), "Volume lifecycle should have failed, but succeeded")
443+
444+
// Restore CMEK key
445+
for _, keyVersion := range keyVersions {
446+
enableReq := &kmspb.UpdateCryptoKeyVersionRequest{
447+
CryptoKeyVersion: &kmspb.CryptoKeyVersion{
448+
Name: keyVersion,
449+
State: kmspb.CryptoKeyVersion_ENABLED,
450+
},
451+
UpdateMask: &fieldmask.FieldMask{
452+
Paths: []string{"state"},
453+
},
454+
}
455+
_, err = kmsClient.UpdateCryptoKeyVersion(ctx, enableReq)
456+
Expect(err).To(BeNil(), "Failed to enable crypto key")
457+
}
458+
459+
// Make sure attach of PD succeeds
460+
err = testAttachWriteReadDetach(volID, volName, controllerInstance, controllerClient, false /* readOnly */)
461+
Expect(err).To(BeNil(), "Failed to go through volume lifecycle after restoring CMEK key")
462+
})
463+
297464
It("Should create and delete snapshot for RePD in two zones ", func() {
298465
Expect(testContexts).ToNot(BeEmpty())
299466
testContext := getRandomTestContext()

test/e2e/utils/utils.go

+18
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
cloudresourcemanager "google.golang.org/api/cloudresourcemanager/v1"
2828
boskosclient "k8s.io/test-infra/boskos/client"
2929
"k8s.io/test-infra/boskos/common"
30+
"k8s.io/utils/exec"
3031
remote "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/test/remote"
3132
)
3233

@@ -128,6 +129,23 @@ func SetupProwConfig(resourceType string) (project, serviceAccount string) {
128129
// [PROJECT_NUMBER][email protected]
129130
serviceAccount = fmt.Sprintf("%[email protected]", resp.ProjectNumber)
130131
glog.Infof("Using project %v and service account %v", project, serviceAccount)
132+
133+
// Set Cloud KMS permissions on compute service account
134+
// TODO: Use the API to make this call instead of exec-ing gcloud command
135+
computeSystemSA := fmt.Sprintf("service-%[email protected]", resp.ProjectNumber)
136+
_, err = exec.New().Command(
137+
"gcloud",
138+
"projects",
139+
"add-iam-policy-binding",
140+
project,
141+
"--member",
142+
fmt.Sprintf("serviceAccount:%v", computeSystemSA),
143+
"--role",
144+
"roles/cloudkms.cryptoKeyEncrypterDecrypter").CombinedOutput()
145+
if err != nil {
146+
glog.Fatalf("Failed to set iam policy binding roles/cloudkms.cryptoKeyEncrypterDecrypter on %s: %v", computeSystemSA, err)
147+
}
148+
131149
return project, serviceAccount
132150
}
133151

0 commit comments

Comments
 (0)