Skip to content

Commit

Permalink
Introduce sev/fetchcertchain API endpoint for VMI
Browse files Browse the repository at this point in the history
By calling the endpoint a user can fetch the SEV platform info needed
for running the pre-attestation process.

Signed-off-by: Vasiliy Ulyanov <vulyanov@suse.de>
  • Loading branch information
vasiliy-ul committed Feb 8, 2022
1 parent 9bc0537 commit 5fb5971
Show file tree
Hide file tree
Showing 21 changed files with 413 additions and 0 deletions.
124 changes: 124 additions & 0 deletions api/openapi-spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -9187,6 +9187,47 @@
}
]
},
"/apis/subresources.kubevirt.io/v1/namespaces/{namespace:[a-z0-9][a-z0-9\\-]*}/virtualmachineinstances/{name:[a-z0-9][a-z0-9\\-]*}/sev/fetchcertchain": {
"get": {
"description": "Fetch SEV certificate chain from the node where Virtual Machine is running",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"operationId": "v1SEVFetchCertChain",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/v1.SEVPlatformInfo"
}
},
"401": {
"description": "Unauthorized"
}
}
},
"parameters": [
{
"uniqueItems": true,
"type": "string",
"description": "Name of the resource",
"name": "name",
"in": "path",
"required": true
},
{
"uniqueItems": true,
"type": "string",
"description": "Object name and auth scope, such as for teams and projects",
"name": "namespace",
"in": "path",
"required": true
}
]
},
"/apis/subresources.kubevirt.io/v1/namespaces/{namespace:[a-z0-9][a-z0-9\\-]*}/virtualmachineinstances/{name:[a-z0-9][a-z0-9\\-]*}/softreboot": {
"put": {
"description": "Soft reboot a VirtualMachineInstance object.",
Expand Down Expand Up @@ -10363,6 +10404,47 @@
}
]
},
"/apis/subresources.kubevirt.io/v1alpha3/namespaces/{namespace:[a-z0-9][a-z0-9\\-]*}/virtualmachineinstances/{name:[a-z0-9][a-z0-9\\-]*}/sev/fetchcertchain": {
"get": {
"description": "Fetch SEV certificate chain from the node where Virtual Machine is running",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"operationId": "v1alpha3SEVFetchCertChain",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/v1.SEVPlatformInfo"
}
},
"401": {
"description": "Unauthorized"
}
}
},
"parameters": [
{
"uniqueItems": true,
"type": "string",
"description": "Name of the resource",
"name": "name",
"in": "path",
"required": true
},
{
"uniqueItems": true,
"type": "string",
"description": "Object name and auth scope, such as for teams and projects",
"name": "namespace",
"in": "path",
"required": true
}
]
},
"/apis/subresources.kubevirt.io/v1alpha3/namespaces/{namespace:[a-z0-9][a-z0-9\\-]*}/virtualmachineinstances/{name:[a-z0-9][a-z0-9\\-]*}/softreboot": {
"put": {
"description": "Soft reboot a VirtualMachineInstance object.",
Expand Down Expand Up @@ -14755,6 +14837,48 @@
}
}
},
"v1.SEVPlatformInfo": {
"description": "SEVPlatformInfo contains information about the AMD SEV features for the node.",
"type": "object",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
"type": "string"
},
"cbitpos": {
"description": "The C-bit (aka encryption bit) location in guest page table entry.",
"type": "integer",
"format": "int32"
},
"certChain": {
"description": "Base64 encoded SEV certificate chain.",
"type": "string"
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
"type": "string"
},
"maxESGuests": {
"description": "Maximum number of SEV ES guests.",
"type": "integer",
"format": "int32"
},
"maxGuests": {
"description": "Maximum number of SEV guests.",
"type": "integer",
"format": "int32"
},
"pdh": {
"description": "Base64 encoded platform Diffie-Hellman key.",
"type": "string"
},
"reducedPhysBits": {
"description": "Physical address bit reduction.",
"type": "integer",
"format": "int32"
}
}
},
"v1.SMBiosConfiguration": {
"type": "object",
"properties": {
Expand Down
1 change: 1 addition & 0 deletions cmd/virt-handler/virt-handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ func (app *virtHandlerApp) runServer(errCh chan error, consoleHandler *rest.Cons
ws.Route(ws.GET("/v1/namespaces/{namespace}/virtualmachineinstances/{name}/guestosinfo").To(lifecycleHandler.GetGuestInfo).Produces(restful.MIME_JSON).Consumes(restful.MIME_JSON).Returns(http.StatusOK, "OK", v1.VirtualMachineInstanceGuestAgentInfo{}))
ws.Route(ws.GET("/v1/namespaces/{namespace}/virtualmachineinstances/{name}/userlist").To(lifecycleHandler.GetUsers).Produces(restful.MIME_JSON).Consumes(restful.MIME_JSON).Returns(http.StatusOK, "OK", v1.VirtualMachineInstanceGuestOSUserList{}))
ws.Route(ws.GET("/v1/namespaces/{namespace}/virtualmachineinstances/{name}/filesystemlist").To(lifecycleHandler.GetFilesystems).Produces(restful.MIME_JSON).Consumes(restful.MIME_JSON).Returns(http.StatusOK, "OK", v1.VirtualMachineInstanceFileSystemList{}))
ws.Route(ws.GET("/v1/namespaces/{namespace}/virtualmachineinstances/{name}/sev/fetchcertchain").To(lifecycleHandler.SEVFetchCertChainHandler).Produces(restful.MIME_JSON).Consumes(restful.MIME_JSON).Returns(http.StatusOK, "OK", v1.SEVPlatformInfo{}))
restful.DefaultContainer.Add(ws)
server := &http.Server{
Addr: fmt.Sprintf("%s:%d", app.ServiceListen.BindAddress, app.consoleServerPort),
Expand Down
5 changes: 5 additions & 0 deletions cmd/virt-launcher/node-labeller/node-labeller.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@ cp -r /usr/share/libvirt/cpu_map /var/lib/kubevirt-node-labeller

virsh domcapabilities --machine q35 --arch x86_64 --virttype $VIRTTYPE | virsh hypervisor-cpu-baseline --features /dev/stdin --machine q35 --arch x86_64 --virttype $VIRTTYPE > /var/lib/kubevirt-node-labeller/supported_features.xml
virsh capabilities > /var/lib/kubevirt-node-labeller/capabilities.xml

# FIXME need libvirt >= 8.0.0 :(
#if grep "sev supported='yes'" /var/lib/kubevirt-node-labeller/virsh_domcapabilities.xml; then
# virsh nodesevinfo > /var/lib/kubevirt-node-labeller/nodesevinfo
#fi
3 changes: 3 additions & 0 deletions manifests/generated/operator-csv.yaml.in
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,7 @@ spec:
- virtualmachineinstances/guestosinfo
- virtualmachineinstances/filesystemlist
- virtualmachineinstances/userlist
- virtualmachineinstances/sev/fetchcertchain
verbs:
- get
- apiGroups:
Expand Down Expand Up @@ -833,6 +834,7 @@ spec:
- virtualmachineinstances/guestosinfo
- virtualmachineinstances/filesystemlist
- virtualmachineinstances/userlist
- virtualmachineinstances/sev/fetchcertchain
verbs:
- get
- apiGroups:
Expand Down Expand Up @@ -931,6 +933,7 @@ spec:
- virtualmachineinstances/guestosinfo
- virtualmachineinstances/filesystemlist
- virtualmachineinstances/userlist
- virtualmachineinstances/sev/fetchcertchain
verbs:
- get
- apiGroups:
Expand Down
3 changes: 3 additions & 0 deletions manifests/generated/rbac-operator.authorization.k8s.yaml.in
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,7 @@ rules:
- virtualmachineinstances/guestosinfo
- virtualmachineinstances/filesystemlist
- virtualmachineinstances/userlist
- virtualmachineinstances/sev/fetchcertchain
verbs:
- get
- apiGroups:
Expand Down Expand Up @@ -735,6 +736,7 @@ rules:
- virtualmachineinstances/guestosinfo
- virtualmachineinstances/filesystemlist
- virtualmachineinstances/userlist
- virtualmachineinstances/sev/fetchcertchain
verbs:
- get
- apiGroups:
Expand Down Expand Up @@ -833,6 +835,7 @@ rules:
- virtualmachineinstances/guestosinfo
- virtualmachineinstances/filesystemlist
- virtualmachineinstances/userlist
- virtualmachineinstances/sev/fetchcertchain
verbs:
- get
- apiGroups:
Expand Down
15 changes: 15 additions & 0 deletions pkg/virt-api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,17 @@ func (app *virtAPIApp) composeSubresources() {
Returns(http.StatusOK, "OK", "").
Returns(http.StatusBadRequest, httpStatusBadRequestMessage, ""))

// AMD SEV endpoints
subws.Route(subws.GET(rest.NamespacedResourcePath(subresourcesvmiGVR)+rest.SubResourcePath("sev/fetchcertchain")).
To(subresourceApp.SEVFetchCertChainRequestHandler).
Param(rest.NamespaceParam(subws)).Param(rest.NameParam(subws)).
Consumes(restful.MIME_JSON).
Produces(restful.MIME_JSON).
Operation(version.Version+"SEVFetchCertChain").
Doc("Fetch SEV certificate chain from the node where Virtual Machine is running").
Writes(v1.SEVPlatformInfo{}).
Returns(http.StatusOK, "OK", v1.SEVPlatformInfo{}))

// Return empty api resource list.
// K8s expects to be able to retrieve a resource list for each aggregated
// app in order to discover what resources it provides. Without returning
Expand Down Expand Up @@ -540,6 +551,10 @@ func (app *virtAPIApp) composeSubresources() {
Name: "virtualmachineinstances/removevolume",
Namespaced: true,
},
{
Name: "virtualmachineinstances/sev/fetchcertchain",
Namespaced: true,
},
}

response.WriteAsJson(list)
Expand Down
1 change: 1 addition & 0 deletions pkg/virt-api/rest/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ go_library(
"//pkg/controller:go_default_library",
"//pkg/monitoring/api:go_default_library",
"//pkg/rest:go_default_library",
"//pkg/util:go_default_library",
"//pkg/util/status:go_default_library",
"//pkg/virt-config:go_default_library",
"//staging/src/kubevirt.io/api/core/v1:go_default_library",
Expand Down
20 changes: 20 additions & 0 deletions pkg/virt-api/rest/subresource.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import (
"kubevirt.io/client-go/kubecli"
"kubevirt.io/client-go/log"
"kubevirt.io/kubevirt/pkg/controller"
"kubevirt.io/kubevirt/pkg/util"
virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
)

Expand All @@ -57,6 +58,7 @@ const (
patchingVMStatusFmt = "Patching VM status: %s"
vmiNotRunning = "VMI is not running"
vmiGuestAgentErr = "VMI does not have guest agent connected"
vmiNoAttestation = "Pre-attestation not requested for VMI"
prepConnectionErrFmt = "Cannot prepare connection %s"
getRequestErrFmt = "Cannot GET request %s"
defaultProfilerComponentPort = 8443
Expand Down Expand Up @@ -1256,3 +1258,21 @@ func (app *SubresourceAPIApp) VMIAddVolumeRequestHandler(request *restful.Reques
func (app *SubresourceAPIApp) VMIRemoveVolumeRequestHandler(request *restful.Request, response *restful.Response) {
app.removeVolumeRequestHandler(request, response, true)
}

func (app *SubresourceAPIApp) SEVFetchCertChainRequestHandler(request *restful.Request, response *restful.Response) {
validate := func(vmi *v1.VirtualMachineInstance) *errors.StatusError {
if !vmi.IsScheduled() && !vmi.IsRunning() {
return errors.NewConflict(v1.Resource("virtualmachineinstance"), vmi.Name, fmt.Errorf(vmiNotRunning))
}
if !util.IsPreAttestationRequested(vmi) {
return errors.NewConflict(v1.Resource("virtualmachineinstance"), vmi.Name, fmt.Errorf(vmiNoAttestation))
}
return nil
}

getURL := func(vmi *v1.VirtualMachineInstance, conn kubecli.VirtHandlerConn) (string, error) {
return conn.SEVFetchCertChainURI(vmi)
}

app.httpGetRequestHandler(request, response, validate, getURL, v1.SEVPlatformInfo{})
}
39 changes: 39 additions & 0 deletions pkg/virt-api/rest/subresource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1778,6 +1778,45 @@ var _ = Describe("VirtualMachineInstance Subresources", func() {
)
})

Context("Subresource api - AMD SEV pre-attestation", func() {
withPreAttestation := func(vmi *v1.VirtualMachineInstance) {
vmi.Spec.Domain.LaunchSecurity = &v1.LaunchSecurity{
SEV: &v1.SEV{
PreAttestation: true,
},
}
}

It("Should allow to fetch certificates chain when VMI with requested pre-attestation is running", func() {
backend.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/v1/namespaces/default/virtualmachineinstances/testvmi/sev/fetchcertchain"),
ghttp.RespondWithJSONEncoded(http.StatusOK, v1.SEVPlatformInfo{}),
),
)
response.SetRequestAccepts(restful.MIME_JSON)

expectVMI(Running, UnPaused, withPreAttestation)
app.SEVFetchCertChainRequestHandler(request, response)
Expect(response.Error()).ToNot(HaveOccurred())
Expect(response.StatusCode()).To(Equal(http.StatusOK))
})

It("Should fail to fetch certificates chain when pre-attestation is not requested", func() {
expectVMI(Running, UnPaused)
app.SEVFetchCertChainRequestHandler(request, response)
Expect(response.Error()).To(HaveOccurred())
Expect(response.StatusCode()).To(Equal(http.StatusInternalServerError))
})

It("Should fail to fetch certificates chain when VMI is not running", func() {
expectVMI(NotRunning, UnPaused)
app.SEVFetchCertChainRequestHandler(request, response)
Expect(response.Error()).To(HaveOccurred())
Expect(response.StatusCode()).To(Equal(http.StatusInternalServerError))
})
})

AfterEach(func() {
backend.Close()
disableFeatureGates()
Expand Down
24 changes: 24 additions & 0 deletions pkg/virt-handler/rest/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"

v1 "kubevirt.io/api/core/v1"
v12 "kubevirt.io/api/core/v1"
"kubevirt.io/client-go/log"
cmdclient "kubevirt.io/kubevirt/pkg/virt-handler/cmd-client"
Expand Down Expand Up @@ -336,3 +337,26 @@ func (lh *LifecycleHandler) GetFilesystems(request *restful.Request, response *r

response.WriteEntity(fsList)
}

func (lh *LifecycleHandler) SEVFetchCertChainHandler(request *restful.Request, response *restful.Response) {
vmi, code, err := getVMI(request, lh.vmiInformer)
if err != nil {
log.Log.Object(vmi).Reason(err).Error(failedRetrieveVMI)
response.WriteError(code, err)
return
}

log.Log.Object(vmi).Infof("Retreiving SEV certificate chain for %s", vmi.Name)

// TODO fetch proper certificates from the node
sevPlatformInfo := v1.SEVPlatformInfo{
PDH: "AAABBBCCC",
CertChain: "CCCEEEFFF",
Cbitpos: 11,
ReducedPhysBits: 33,
MaxGuests: 100,
MaxESGuests: 200,
}

response.WriteEntity(sevPlatformInfo)
}
5 changes: 5 additions & 0 deletions pkg/virt-operator/resource/generate/rbac/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const (
VMInstancesGuestOSInfo = "virtualmachineinstances/guestosinfo"
VMInstancesFileSysList = "virtualmachineinstances/filesystemlist"
VMInstancesUserList = "virtualmachineinstances/userlist"

VMInstancesSEVFetchCertChain = "virtualmachineinstances/sev/fetchcertchain"
)

func GetAllCluster() []runtime.Object {
Expand Down Expand Up @@ -142,6 +144,7 @@ func newAdminClusterRole() *rbacv1.ClusterRole {
VMInstancesGuestOSInfo,
VMInstancesFileSysList,
VMInstancesUserList,
VMInstancesSEVFetchCertChain,
},
Verbs: []string{
"get",
Expand Down Expand Up @@ -267,6 +270,7 @@ func newEditClusterRole() *rbacv1.ClusterRole {
VMInstancesGuestOSInfo,
VMInstancesFileSysList,
VMInstancesUserList,
VMInstancesSEVFetchCertChain,
},
Verbs: []string{
"get",
Expand Down Expand Up @@ -401,6 +405,7 @@ func newViewClusterRole() *rbacv1.ClusterRole {
VMInstancesGuestOSInfo,
VMInstancesFileSysList,
VMInstancesUserList,
VMInstancesSEVFetchCertChain,
},
Verbs: []string{
"get",
Expand Down

0 comments on commit 5fb5971

Please sign in to comment.