[WIP] Support uploading certificates for virtual media
Change-Id: I30a5c673ab91486cafbe1383c5fe58142102a7c2
This commit is contained in:
parent
c6a42b999a
commit
0f79b690b4
|
@ -18,6 +18,7 @@ from datetime import datetime
|
|||
import functools
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import ssl
|
||||
import sys
|
||||
|
||||
|
@ -455,11 +456,11 @@ def virtual_media_patch(identity, device):
|
|||
def virtual_media_certificates(identity, device):
|
||||
location = \
|
||||
f'/redfish/v1/Managers/{identity}/VirtualMedia/{device}/Certificates'
|
||||
certificates = app.vmedia.list_certificates(identity, device)
|
||||
return flask.render_template(
|
||||
'certificate_collection.json',
|
||||
location=location,
|
||||
# TODO(dtantsur): implement
|
||||
certificates=[],
|
||||
certificates=[cert.id for cert in certificates],
|
||||
)
|
||||
|
||||
|
||||
|
@ -471,8 +472,56 @@ def virtual_media_add_certificate(identity, device):
|
|||
if not flask.request.json:
|
||||
raise error.BadRequest("Empty or malformed certificate")
|
||||
|
||||
# TODO(dtantsur): implement
|
||||
raise error.NotSupportedError("Not implemented")
|
||||
try:
|
||||
cert_string = flask.request.json['CertificateString']
|
||||
cert_type = flask.request.json['CertificateType']
|
||||
except KeyError as exc:
|
||||
raise error.BadRequest(f"Missing required parameter {exc}")
|
||||
|
||||
app.logger.debug('Adding certificate for virtual media %s at '
|
||||
'manager "%s"', device, identity)
|
||||
|
||||
app.vmedia.add_certificate(identity, device, cert_string, cert_type)
|
||||
|
||||
# TODO(dtantsur): should we return a body?
|
||||
return '', 204
|
||||
|
||||
|
||||
@app.route(
|
||||
'/redfish/v1/Managers/<identity>/VirtualMedia/<device>'
|
||||
'/Certificates/<cert_id>',
|
||||
methods=['GET'])
|
||||
@returns_json
|
||||
def virtual_media_get_certificate(identity, device, cert_id):
|
||||
location = (
|
||||
f'/redfish/v1/Managers/{identity}/VirtualMedia/{device}/Certificates/'
|
||||
+ cert_id
|
||||
)
|
||||
certificates = app.vmedia.list_certificates(identity, device)
|
||||
try:
|
||||
cert = next(c for c in certificates if c.id == cert_id)
|
||||
except StopIteration:
|
||||
raise error.NotFound()
|
||||
|
||||
return flask.render_template(
|
||||
'certificate_collection.json',
|
||||
location=location,
|
||||
cert_id=cert.id,
|
||||
cert_string=cert.string,
|
||||
cert_type=cert.type_,
|
||||
)
|
||||
|
||||
|
||||
@app.route(
|
||||
'/redfish/v1/Managers/<identity>/VirtualMedia/<device>'
|
||||
'/Certificates/<cert_id>',
|
||||
methods=['DELETE'])
|
||||
@returns_json
|
||||
def virtual_media_delete_certificate(identity, device, cert_id):
|
||||
app.logger.debug('Removing certificate %s for virtual media %s at '
|
||||
'manager "%s"', cert_id, device, identity)
|
||||
app.vmedia.delete_certificate(identity, device, cert_id)
|
||||
return '', 204
|
||||
|
||||
|
||||
@app.route('/redfish/v1/Managers/<identity>/VirtualMedia/<device>'
|
||||
|
@ -886,6 +935,49 @@ def volume(identity, stg_id, vol_id):
|
|||
raise error.NotFound()
|
||||
|
||||
|
||||
@app.route('/redfish/v1/CertificateService', methods=['GET'])
|
||||
@returns_json
|
||||
def certificate_service_resource():
|
||||
app.logger.debug('Serving certificate service')
|
||||
|
||||
return flask.render_template(
|
||||
'certificate_service.json')
|
||||
|
||||
|
||||
_VMEDIA_URI_PATTERN = re.compile(
|
||||
'/redfish/v1/Managers/([^/]+)/VirtualMedia/([^/]+)/Certificates/([^/]+)/?$'
|
||||
)
|
||||
|
||||
|
||||
@app.route('/redfish/v1/CertificateService/Actions'
|
||||
'/CertificateService.ReplaceCertificate',
|
||||
methods=['POST'])
|
||||
@ensure_instance_access
|
||||
@returns_json
|
||||
def certificate_service_replace_certificate(identity):
|
||||
if not flask.request.json:
|
||||
raise error.BadRequest("Empty or malformed certificate")
|
||||
|
||||
try:
|
||||
cert_string = flask.request.json['CertificateString']
|
||||
cert_type = flask.request.json['CertificateType']
|
||||
cert_uri = flask.request.json['CertificateUri']
|
||||
except KeyError as exc:
|
||||
raise error.BadRequest(f"Missing required parameter {exc}")
|
||||
|
||||
match = _VMEDIA_URI_PATTERN.search(cert_uri)
|
||||
if not match:
|
||||
raise error.NotFound(
|
||||
f"Certificates at URI {cert_uri} are not supported")
|
||||
|
||||
manager_id, device, cert_id = match.groups()
|
||||
|
||||
app.vmedia.replace_certificate(manager_id, device,
|
||||
cert_id, cert_string, cert_type)
|
||||
|
||||
return '', 204
|
||||
|
||||
|
||||
@app.route('/redfish/v1/Registries')
|
||||
@returns_json
|
||||
def registry_file_collection():
|
||||
|
|
|
@ -30,6 +30,11 @@ DeviceInfo = collections.namedtuple(
|
|||
'DeviceInfo',
|
||||
['image_name', 'image_url', 'inserted', 'write_protected',
|
||||
'username', 'password', 'verify'])
|
||||
Certificate = collections.namedtuple(
|
||||
'Certificate',
|
||||
['id', 'string', 'type_'])
|
||||
|
||||
_CERT_ID = "Default"
|
||||
|
||||
|
||||
class StaticDriver(base.DriverBase):
|
||||
|
@ -150,6 +155,49 @@ class StaticDriver(base.DriverBase):
|
|||
device_info['Verify'] = verify
|
||||
self._devices[(identity, device)] = device_info
|
||||
|
||||
def add_certificate(self, identity, device, cert_string, cert_type):
|
||||
device_info = self._get_device(identity, device)
|
||||
if cert_type != 'PEM':
|
||||
raise error.BadRequest(
|
||||
f"Only PEM certificates are supported, got {cert_type}")
|
||||
|
||||
if "Certificate" in device_info:
|
||||
raise error.FishyError(f"Certificate {_CERT_ID} already exists",
|
||||
code=409)
|
||||
|
||||
device_info["Certificate"] = cert_string
|
||||
self._devices[(identity, device)] = device_info
|
||||
|
||||
def replace_certificate(self, identity, device, cert_id,
|
||||
cert_string, cert_type):
|
||||
device_info = self._get_device(identity, device)
|
||||
if cert_id != _CERT_ID or "Certificate" not in device_info:
|
||||
raise error.NotFound(f"Certificate {cert_id} not found")
|
||||
|
||||
if cert_type != 'PEM':
|
||||
raise error.BadRequest(
|
||||
f"Only PEM certificates are supported, got {cert_type}")
|
||||
|
||||
device_info["Certificate"] = cert_string
|
||||
self._devices[(identity, device)] = device_info
|
||||
|
||||
def list_certificates(self, identity, device):
|
||||
device_info = self._get_device(identity, device)
|
||||
try:
|
||||
certificate = device_info["Certificate"]
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
return [Certificate(_CERT_ID, certificate, 'PEM')]
|
||||
|
||||
def delete_certificate(self, identity, device, cert_id):
|
||||
device_info = self._get_device(identity, device)
|
||||
if cert_id != _CERT_ID or "Certificate" not in device_info:
|
||||
raise error.NotFound(f"Certificate {cert_id} not found")
|
||||
|
||||
del device_info["Certificate"]
|
||||
self._devices[(identity, device)] = device_info
|
||||
|
||||
def _write_from_response(self, image_url, rsp, tmp_file):
|
||||
with open(tmp_file.name, 'wb') as fl:
|
||||
for chunk in rsp.iter_content(chunk_size=8192):
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"@odata.type": "#Certificate.v1_3_0.Certificate",
|
||||
"Id": {{ cert_id|string|json }},
|
||||
"Name": "HTTPS Certificate {{ cert_id }}",
|
||||
"CertificateString": {{ cert_string|string|json }},
|
||||
"CertificateType": {{ cert_type|string|json }},
|
||||
"Oem": {},
|
||||
"@odata.id": {{ location|string|json }},
|
||||
"@Redfish.Copyright": "Copyright 2014-2021 DMTF. For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"@odata.type": "#CertificateService.v1_0_4.CertificateService",
|
||||
"Id": "CertificateService",
|
||||
"Name": "Certificate Service",
|
||||
"Actions": {
|
||||
"#CertificateService.ReplaceCertificate": {
|
||||
"target": "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate",
|
||||
"@Redfish.ActionInfo": "/redfish/v1/CertificateService/ReplaceCertificateActionInfo"
|
||||
}
|
||||
},
|
||||
"Oem": {},
|
||||
"@odata.id": "/redfish/v1/CertificateService",
|
||||
"@Redfish.Copyright": "Copyright 2014-2021 DMTF. For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"@odata.type": "#ServiceRoot.v1_0_2.ServiceRoot",
|
||||
"@odata.type": "#ServiceRoot.v1_5_0.ServiceRoot",
|
||||
"Id": "RedvirtService",
|
||||
"Name": "Redvirt Service",
|
||||
"RedfishVersion": "1.0.2",
|
||||
"RedfishVersion": "1.5.0",
|
||||
"UUID": "85775665-c110-4b85-8989-e6162170b3ec",
|
||||
"Systems": {
|
||||
"@odata.id": "/redfish/v1/Systems"
|
||||
|
@ -13,6 +13,9 @@
|
|||
"Registries": {
|
||||
"@odata.id": "/redfish/v1/Registries"
|
||||
},
|
||||
"CertificateService": {
|
||||
"@odata.id": "/redfish/v1/CertificateService"
|
||||
},
|
||||
"@odata.id": "/redfish/v1/",
|
||||
"@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue