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

Enable automatic token generation for VirtualMachineExport objects #8418

Merged
merged 7 commits into from Sep 24, 2022

Conversation

alromeros
Copy link
Contributor

What this PR does / why we need it

This PR introduces a new mechanism in the export controller so that when a VMExport with no tokenSecretRef is created, a corresponding secret is made using a name specific to the current object.

Which issue(s) this PR fixes: https://issues.redhat.com/browse/CNV-20651

Special notes for your reviewer: Work in progress

Release note:

Enable automatic token generation for VirtualMachineExport objects

@kubevirt-bot
Copy link
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@kubevirt-bot kubevirt-bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. dco-signoff: yes Indicates the PR's author has DCO signed all their commits. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/M labels Sep 6, 2022
Copy link
Member

@mhenriks mhenriks left a comment

Choose a reason for hiding this comment

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

Great start!

// If no tokenSecretRef has been specified, we use one specific to the current export object
tokenSecretRef := vmExport.Spec.TokenSecretRef
if tokenSecretRef == "" {
tokenSecretRef = getDefaultTokenSecretName(vmExport)
Copy link
Member

Choose a reason for hiding this comment

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

I think it would be useful to store the token secret name in the VM export status. So refer to that in this function instead of calling getDefaultTokenSecretName again?

// The secret name is constructed so it can be specific to the current vmExport object
tokenSecretName := getDefaultTokenSecretName(vmExport)

token := rand.String(secretTokenLenght)
Copy link
Member

@mhenriks mhenriks Sep 7, 2022

Choose a reason for hiding this comment

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

I think we have to use a more cryptographically secure rng for the token. Looks like this package uses "math/rand" which "implements pseudo-random number generators unsuitable for security-sensitive work".

Copy link
Contributor Author

@alromeros alromeros Sep 7, 2022

Choose a reason for hiding this comment

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

hmmm I'm honestly surprised by that since I thought k8s.io/apimachinery/pkg/util/rand was our standard for this kind of thing. I see that k8s.io/client-go/util/cert uses crypto/rand, so I'll find a way to use that one instead (also in virtctl vmexport).

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, math/rand is fine for naming things but not for secrets

@kubevirt-bot kubevirt-bot added size/L and removed size/M labels Sep 7, 2022
token := make([]byte, secretTokenLenght)
if _, err := cryptorand.Read(token); err != nil {
return err
}
Copy link
Member

Choose a reason for hiding this comment

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

We still want this to be string. The value has to be included in an http header. So maybe do something like this: https://gist.github.com/dopey/c69559607800d2f2f90b1b1ed4e550fb#file-main-go-L47-L59

Basically whatever the k8s library function was doing but with a better random source

// +optional
// DefaultSecretExportName is the name of an optional secret specifically created for the current VirtualMachineExport object
// in case no other secret name is specified when creating the object
DefaultSecretExportName string `json:"defaultSecretExportName,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

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

I would just call this TokenSecretRef and set it to whatever the user set explicitly or the one we create. One source of truth to get the secret name

@alromeros alromeros marked this pull request as ready for review September 9, 2022 16:18
@kubevirt-bot kubevirt-bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Sep 9, 2022
TokenSecretRef string `json:"tokenSecretRef"`
// +optional
// TokenSecretRef is the name of the custom-defined secret that contains the token used by the export server pod
TokenSecretRef string `json:"tokenSecretRef,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

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

Should probably be a pointer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Out of curiosity, I'm seeing that we use regular strings for all other fields in the export API (even in the Spec's TokenSecretRef). Why we'd want this one to be a pointer?

Copy link
Contributor

Choose a reason for hiding this comment

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

I have seen CSI APIs use type LocalObjectReference struct for SecretRef but then on the other hand we have CDI's which are strings (type DataVolumeSourceRegistry struct for example)

Copy link
Member

Choose a reason for hiding this comment

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

Out of curiosity, I'm seeing that we use regular strings for all other fields in the export API (even in the Spec's TokenSecretRef). Why we'd want this one to be a pointer?

we are specifically talking about the spec.tokenSecretRef

Since this field is optional, it should be a pointer per kubernetes api conventions. https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#optional-vs-required

Of course we are not always consistent about this

}

// If not, the secret name is constructed so it can be specific to the current vmExport object
vmExport.Status.TokenSecretRef = getDefaultTokenSecretName(vmExport)
Copy link
Member

Choose a reason for hiding this comment

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

I would first check if vmExport.Status.TokenSecretRef is set and use that value if so. This way if, in the unlikely case that we change the naming scheme, existing exports will still work.

@@ -105,6 +106,11 @@ const (

kvm = 107

// secretTokenLenght is the lenght of the randomly generated token
secretTokenLenght = 20
Copy link
Contributor

Choose a reason for hiding this comment

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

typo?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch!

@@ -703,6 +724,7 @@ var _ = SIGDescribe("Export", func() {
Skip("Skip test when Filesystem storage is not present")
}
vmExport := createRunningPVCExport(sc, k8sv1.PersistentVolumeFilesystem)
Expect(vmExport.Spec.TokenSecretRef).To(Equal(vmExport.Status.TokenSecretRef))
Copy link
Contributor

Choose a reason for hiding this comment

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

mby should also ensure it has some reasonable length so its not empty?

@alromeros alromeros force-pushed the make-export-secret-optional branch 3 times, most recently from 71424ec to 4d7e05a Compare September 15, 2022 13:02
@alromeros
Copy link
Contributor Author

/retest-required

This commit introduces a new mechanism in the export controller so, when a VMExport with no tokenSecretRef is created, a corresponding secret is created using a name specific to the current object.

This commit also modifies the export API to make the tokenSecretRef field optional.

Signed-off-by: Alvaro Romero <alromero@redhat.com>
…nts:

* The randomly generated token for the default export secret is now created using the more secure package crypto/rand
* The default secret name is now stored in the VirtualMachineExport status

Signed-off-by: Alvaro Romero <alromero@redhat.com>
* Fixed error in export pod error handling, so if the pod creation fails, now the controller returns the error appropiately.
* The name generation mechanism of the default export secret has been fixed, so no names longer than 63 characters are created.

Signed-off-by: Alvaro Romero <alromero@redhat.com>
@alromeros
Copy link
Contributor Author

/retest-required

Copy link
Member

@mhenriks mhenriks left a comment

Choose a reason for hiding this comment

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

Looks great, just a couple minor comments

@@ -599,6 +609,65 @@ func (ctrl *VMExportController) createCertSecretManifest(vmExport *exportv1.Virt
}, nil
}

// handleVMExportSecret checks if a secret has been specified for the current export object and, if not, creates one specific to it
func (ctrl *VMExportController) handleVMExportSecret(vmExport *exportv1.VirtualMachineExport) error {
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be called handleVMExportToken or handleVMExportTokenGeneration

@@ -609,6 +678,11 @@ func (ctrl *VMExportController) getExportSecretName(ownerPod *corev1.Pod) string
return certSecretName
}

// getDefaultTokenSecretName returns a secret name specifically created for the current export object
func getDefaultTokenSecretName(vme *exportv1.VirtualMachineExport) string {
return naming.GetName("export-secret", vme.Name, validation.DNS1035LabelMaxLength)
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be export-token or vmexport-token

Expect(err).ToNot(HaveOccurred())
Expect(token.Name).To(Equal(*export.Status.TokenSecretRef))
Expect(*export.Status.TokenSecretRef).ToNot(BeEmpty())
})
Copy link
Member

Choose a reason for hiding this comment

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

How about downloading something with generated token?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea, I'll add a new test in the virtctl vmexport section.

@alromeros
Copy link
Contributor Author

/retest-required

@mhenriks
Copy link
Member

/approve

@kubevirt-bot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: mhenriks

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubevirt-bot kubevirt-bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Sep 19, 2022
@mhenriks
Copy link
Member

@awels how about giving this a once over?

@awels
Copy link
Member

awels commented Sep 19, 2022

Sure let me take a look

Copy link
Member

@awels awels left a comment

Choose a reason for hiding this comment

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

looks good a few minor things


// If a tokenSecretRef has been specified, we assume that the corresponding
// secret has already been created and managed appropiately by the user
if isTokenSpecified := vmExport.Spec.TokenSecretRef != nil; isTokenSpecified {
Copy link
Member

Choose a reason for hiding this comment

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

Can't you just write:

if vmExport.Spec.TokenSecretRef != nil {
  vmExport.Status.TokenSecretRef = vmExport.Spec.TokenSecretRef
  return nil
}

func (ctrl *VMExportController) handleVMExportToken(vmExport *exportv1.VirtualMachineExport) error {
if vmExport.Status == nil {
vmExport.Status = &exportv1.VirtualMachineExportStatus{
Phase: exportv1.Pending,
Copy link
Member

Choose a reason for hiding this comment

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

Since we are always setting this here, do we still need to set Pending and the conditions in updateCommonVMExportStatusFields?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess we don't, however, I like how we check before dereferencing it. It shouldn't be necessary anyway, so I can remove the one in updateCommonVMExportStatusFields.

@@ -989,7 +1068,7 @@ func createVMVMExport() *exportv1.VirtualMachineExport {
Kind: "VirtualMachine",
Name: testVmName,
},
TokenSecretRef: "token",
TokenSecretRef: &token,
Copy link
Member

Choose a reason for hiding this comment

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

What you did is not wrong, this is just an alternative:

 TokenSecretRef: pointer.StringPtr("token")

Saves you from having to create the variable at the top.

if vmExport.Status != nil && vmExport.Status.TokenSecretRef != nil {
tokenSecretRef = *vmExport.Status.TokenSecretRef
}

Copy link
Member

Choose a reason for hiding this comment

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

is it possible that tokenSecretRef is blank here? Not sure if we want to create the pod with a blank secret name?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would say it's impossible (handleVMExportToken should always populate vme.Status.TokenSecretRef), but still wanted to check before dereferencing the pointer.

* This commit introduces a new functional test so we cover the download of an export created with user-defined TokenSecretRef
* It also renames several token-related items in the export controller

Signed-off-by: Alvaro Romero <alromero@redhat.com>
@alromeros
Copy link
Contributor Author

/test pull-kubevirt-e2e-kind-1.23-vgpu-nonroot

@alromeros alromeros requested review from awels and removed request for kbidarkar, jean-edouard, EdDev and maiqueb September 20, 2022 16:14
@awels
Copy link
Member

awels commented Sep 20, 2022

/hold
/lgtm
Putting hold to give others chance to review, if no reviews in a few days I will remove the hold

@kubevirt-bot kubevirt-bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Sep 20, 2022
@kubevirt-bot kubevirt-bot added the lgtm Indicates that a PR is ready to be merged. label Sep 20, 2022
@awels
Copy link
Member

awels commented Sep 23, 2022

/hold cancel

@kubevirt-bot kubevirt-bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Sep 23, 2022
@alromeros
Copy link
Contributor Author

/retest-required

@alromeros
Copy link
Contributor Author

/test pull-kubevirt-e2e-k8s-1.24-sig-storage

@kubevirt-bot
Copy link
Contributor

kubevirt-bot commented Sep 24, 2022

@alromeros: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
pull-kubevirt-e2e-k8s-1.23-sig-storage-nonroot c794f87 link unknown /test pull-kubevirt-e2e-k8s-1.23-sig-storage-nonroot
pull-kubevirt-e2e-k8s-1.22-sig-storage c794f87 link unknown /test pull-kubevirt-e2e-k8s-1.22-sig-storage
pull-kubevirt-e2e-k8s-1.23-sig-storage c794f87 link unknown /test pull-kubevirt-e2e-k8s-1.23-sig-storage

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

@kubevirt-commenter-bot
Copy link

/retest-required
This bot automatically retries required jobs that failed/flaked on approved PRs.
Silence the bot with an /lgtm cancel or /hold comment for consistent failures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. dco-signoff: yes Indicates the PR's author has DCO signed all their commits. lgtm Indicates that a PR is ready to be merged. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/L
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants