Verify docker registry certificate with system trusted CAs
The issue was caused by sysinv verifying the registry certificate with only itself. This works with the self-signed certificate installed by the system but not if the user installs a new certificate. This commit now checks the registry certificate against system trusted CAs in addition to against itself. This ensures system registry commands work with both the self-signed certificates and certificates installed by the user that are signed by an actual CA. Exception handling has also been improved so that sysinv can detect an exception thrown while communicating with the registry. This is now logged and returned to the user as opposed to hanging the sysinv registry commands. Closes-Bug: 1863624 Change-Id: I1170e296d5037716f2f800eede32e15861d038ce Signed-off-by: Jerry Sun <jerry.sun@windriver.com>
This commit is contained in:
parent
c1566745d7
commit
1cc5d0dbb3
|
@ -15,6 +15,7 @@ from sysinv._i18n import _
|
|||
from sysinv.api.controllers.v1 import base
|
||||
from sysinv.api.controllers.v1 import collection
|
||||
from sysinv.common import utils as cutils
|
||||
from sysinv.openstack.common.rpc import common as rpc_common
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
@ -70,13 +71,20 @@ class RegistryImageController(rest.RestController):
|
|||
|
||||
@wsme_pecan.wsexpose(RegistryImageCollection, wtypes.text)
|
||||
def get_all(self, image_name=None):
|
||||
try:
|
||||
# no image_name provided, list images
|
||||
if image_name is None:
|
||||
images = pecan.request.rpcapi.docker_registry_image_list(
|
||||
pecan.request.context)
|
||||
# image_name provided, list tags of provided image
|
||||
else:
|
||||
images = pecan.request.rpcapi.docker_registry_image_tags(
|
||||
pecan.request.context, image_name)
|
||||
# DockerRegistrySSLException and DockerRegistryAPIException
|
||||
# come in as RemoteError from the RPC handler
|
||||
except rpc_common.RemoteError as e:
|
||||
raise wsme.exc.ClientSideError(_(e.value))
|
||||
|
||||
# no image_name provided, list images
|
||||
if image_name is None:
|
||||
images = pecan.request.rpcapi.docker_registry_image_list(pecan.request.context)
|
||||
# image_name provided, list tags of provided image
|
||||
else:
|
||||
images = pecan.request.rpcapi.docker_registry_image_tags(pecan.request.context, image_name)
|
||||
return RegistryImageCollection.convert_with_links(images)
|
||||
|
||||
@cutils.synchronized(LOCK_NAME)
|
||||
|
@ -88,13 +96,26 @@ class RegistryImageController(rest.RestController):
|
|||
"""
|
||||
|
||||
if len(image_name_and_tag.split(":")) != 2:
|
||||
raise wsme.exc.ClientSideError(_("Image name and tag must be of form name:tag"))
|
||||
raise wsme.exc.ClientSideError(_("Image name and tag must be of "
|
||||
"form name:tag"))
|
||||
|
||||
return pecan.request.rpcapi.docker_registry_image_delete(pecan.request.context, image_name_and_tag)
|
||||
try:
|
||||
return pecan.request.rpcapi.docker_registry_image_delete(
|
||||
pecan.request.context, image_name_and_tag)
|
||||
# DockerRegistrySSLException and DockerRegistryAPIException
|
||||
# come in as RemoteError from the RPC handler
|
||||
except rpc_common.RemoteError as e:
|
||||
raise wsme.exc.ClientSideError(_(e.value))
|
||||
|
||||
@cutils.synchronized(LOCK_NAME)
|
||||
@wsme_pecan.wsexpose(None, wtypes.text)
|
||||
def post(self, garbage_collect=None):
|
||||
"""Run the registry garbage collector"""
|
||||
if garbage_collect is not None:
|
||||
pecan.request.rpcapi.docker_registry_garbage_collect(pecan.request.context)
|
||||
try:
|
||||
pecan.request.rpcapi.docker_registry_garbage_collect(
|
||||
pecan.request.context)
|
||||
# DockerRegistrySSLException and DockerRegistryAPIException
|
||||
# come in as RemoteError from the RPC handler
|
||||
except rpc_common.RemoteError as e:
|
||||
raise wsme.exc.ClientSideError(_(e.value))
|
||||
|
|
|
@ -894,6 +894,15 @@ class DockerRegistryCredentialNotFound(NotFound):
|
|||
"for user %(name)s could not be found.")
|
||||
|
||||
|
||||
class DockerRegistrySSLException(SysinvException):
|
||||
message = _("Registry certificate signed by an unknown CA. "
|
||||
"Install a trusted CA with 'system certificate-install -m ssl_ca'")
|
||||
|
||||
|
||||
class DockerRegistryAPIException(SysinvException):
|
||||
message = _("Error communicating with the docker registry")
|
||||
|
||||
|
||||
class SDNNotEnabled(SysinvException):
|
||||
message = _("SDN configuration is not enabled.")
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@ import requests
|
|||
from sysinv.common import constants
|
||||
from sysinv.common import exception
|
||||
|
||||
CERT_PATH = '/etc/ssl/private/registry-cert.crt'
|
||||
DOCKER_CERT_PATH = '/etc/ssl/private/registry-cert.crt'
|
||||
SYSTEM_CERT_PATH = '/etc/ssl/certs/ca-bundle.crt'
|
||||
KEYRING_SERVICE = 'CGCS'
|
||||
REGISTRY_USERNAME = 'admin'
|
||||
REGISTRY_BASEURL = 'https://%s/v2/' % constants.DOCKER_REGISTRY_SERVER
|
||||
|
@ -68,10 +69,19 @@ def docker_registry_authenticate(www_authenticate):
|
|||
# are passed as params
|
||||
auth_string = base64.b64encode("%s:%s" % (REGISTRY_USERNAME, get_registry_password()))
|
||||
token_server_request_headers = {"authorization": "Basic %s" % auth_string}
|
||||
token_server_response = requests.get(auth_headers['realm'], verify=CERT_PATH,
|
||||
params=auth_headers,
|
||||
headers=token_server_request_headers)
|
||||
|
||||
# we need try twice.
|
||||
# SYSTEM_CERT_PATH if the docker registry cert is signed by a trusted CA
|
||||
# DOCKER_CERT_PATH if the registry certificate is self-signed
|
||||
try:
|
||||
token_server_response = requests.get(auth_headers['realm'],
|
||||
verify=SYSTEM_CERT_PATH,
|
||||
params=auth_headers,
|
||||
headers=token_server_request_headers)
|
||||
except requests.exceptions.SSLError:
|
||||
token_server_response = requests.get(auth_headers['realm'],
|
||||
verify=DOCKER_CERT_PATH,
|
||||
params=auth_headers,
|
||||
headers=token_server_request_headers)
|
||||
if token_server_response.status_code == 200:
|
||||
auth_headers['Authorization'] = "Bearer %s" % token_server_response.json().get("access_token")
|
||||
|
||||
|
@ -82,13 +92,23 @@ def docker_registry_get(path, registry_url=REGISTRY_BASEURL):
|
|||
# we need to have this header to get the correct digest when giving the tag
|
||||
headers = {"Accept": "application/vnd.docker.distribution.manifest.v2+json"}
|
||||
|
||||
resp = requests.get("%s%s" % (registry_url, path), verify=CERT_PATH, headers=headers)
|
||||
try:
|
||||
resp = requests.get("%s%s" % (registry_url, path),
|
||||
verify=SYSTEM_CERT_PATH, headers=headers)
|
||||
except requests.exceptions.SSLError:
|
||||
resp = requests.get("%s%s" % (registry_url, path),
|
||||
verify=DOCKER_CERT_PATH, headers=headers)
|
||||
|
||||
# authenticated registry, need to do auth with token server
|
||||
if resp.status_code == 401:
|
||||
auth_headers = docker_registry_authenticate(resp.headers["Www-Authenticate"])
|
||||
headers.update(auth_headers)
|
||||
resp = requests.get("%s%s" % (registry_url, path), verify=CERT_PATH, headers=headers)
|
||||
try:
|
||||
resp = requests.get("%s%s" % (registry_url, path),
|
||||
verify=SYSTEM_CERT_PATH, headers=headers)
|
||||
except requests.exceptions.SSLError:
|
||||
resp = requests.get("%s%s" % (registry_url, path),
|
||||
verify=DOCKER_CERT_PATH, headers=headers)
|
||||
|
||||
return resp
|
||||
|
||||
|
@ -96,12 +116,23 @@ def docker_registry_get(path, registry_url=REGISTRY_BASEURL):
|
|||
def docker_registry_delete(path, registry_url=REGISTRY_BASEURL):
|
||||
headers = {}
|
||||
|
||||
resp = requests.delete("%s%s" % (registry_url, path), verify=CERT_PATH, headers=headers)
|
||||
try:
|
||||
resp = requests.delete("%s%s" % (registry_url, path),
|
||||
verify=SYSTEM_CERT_PATH, headers=headers)
|
||||
except requests.exceptions.SSLError:
|
||||
resp = requests.delete("%s%s" % (registry_url, path),
|
||||
verify=DOCKER_CERT_PATH, headers=headers)
|
||||
|
||||
# authenticated registry, need to do auth with token server
|
||||
if resp.status_code == 401:
|
||||
auth_headers = docker_registry_authenticate(resp.headers["Www-Authenticate"])
|
||||
headers.update(auth_headers)
|
||||
resp = requests.delete("%s%s" % (registry_url, path), verify=CERT_PATH, headers=headers)
|
||||
|
||||
try:
|
||||
resp = requests.delete("%s%s" % (registry_url, path),
|
||||
verify=SYSTEM_CERT_PATH, headers=headers)
|
||||
except requests.exceptions.SSLError:
|
||||
resp = requests.delete("%s%s" % (registry_url, path),
|
||||
verify=DOCKER_CERT_PATH, headers=headers)
|
||||
|
||||
return resp
|
||||
|
|
|
@ -37,6 +37,7 @@ import hashlib
|
|||
import math
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
import shutil
|
||||
import socket
|
||||
import tempfile
|
||||
|
@ -1443,7 +1444,15 @@ class ConductorManager(service.PeriodicService):
|
|||
self._config_apply_runtime_manifest(context, config_uuid, config_dict)
|
||||
|
||||
def docker_registry_image_list(self, context):
|
||||
image_list_response = docker_registry.docker_registry_get("_catalog")
|
||||
try:
|
||||
image_list_response = docker_registry.docker_registry_get("_catalog")
|
||||
except requests.exceptions.SSLError:
|
||||
LOG.exception("Failed to get docker registry catalog")
|
||||
raise exception.DockerRegistrySSLException()
|
||||
except Exception:
|
||||
LOG.exception("Failed to get docker registry catalog")
|
||||
raise exception.DockerRegistryAPIException()
|
||||
|
||||
if image_list_response.status_code != 200:
|
||||
LOG.error("Bad response from docker registry: %s"
|
||||
% image_list_response.status_code)
|
||||
|
@ -1465,8 +1474,15 @@ class ConductorManager(service.PeriodicService):
|
|||
return images
|
||||
|
||||
def docker_registry_image_tags(self, context, image_name):
|
||||
image_tags_response = docker_registry.docker_registry_get(
|
||||
"%s/tags/list" % image_name)
|
||||
try:
|
||||
image_tags_response = docker_registry.docker_registry_get(
|
||||
"%s/tags/list" % image_name)
|
||||
except requests.exceptions.SSLError:
|
||||
LOG.exception("Failed to get docker registry image tags")
|
||||
raise exception.DockerRegistrySSLException()
|
||||
except Exception:
|
||||
LOG.exception("Failed to get docker registry image tags")
|
||||
raise exception.DockerRegistryAPIException()
|
||||
|
||||
if image_tags_response.status_code != 200:
|
||||
LOG.error("Bad response from docker registry: %s"
|
||||
|
@ -1494,8 +1510,17 @@ class ConductorManager(service.PeriodicService):
|
|||
image_name_and_tag = image_name_and_tag.split(":")
|
||||
|
||||
# first get the image digest for the image name and tag provided
|
||||
digest_resp = docker_registry.docker_registry_get("%s/manifests/%s"
|
||||
% (image_name_and_tag[0], image_name_and_tag[1]))
|
||||
try:
|
||||
digest_resp = docker_registry.docker_registry_get("%s/manifests/%s"
|
||||
% (image_name_and_tag[0], image_name_and_tag[1]))
|
||||
except requests.exceptions.SSLError:
|
||||
LOG.exception("Failed to delete docker registry image %s" %
|
||||
image_name_and_tag)
|
||||
raise exception.DockerRegistrySSLException()
|
||||
except Exception:
|
||||
LOG.exception("Failed to delete docker registry image %s" %
|
||||
image_name_and_tag)
|
||||
raise exception.DockerRegistryAPIException()
|
||||
|
||||
if digest_resp.status_code != 200:
|
||||
LOG.error("Bad response from docker registry: %s"
|
||||
|
@ -1505,8 +1530,17 @@ class ConductorManager(service.PeriodicService):
|
|||
image_digest = digest_resp.headers['Docker-Content-Digest']
|
||||
|
||||
# now delete the image
|
||||
image_delete_response = docker_registry.docker_registry_delete(
|
||||
"%s/manifests/%s" % (image_name_and_tag[0], image_digest))
|
||||
try:
|
||||
image_delete_response = docker_registry.docker_registry_delete(
|
||||
"%s/manifests/%s" % (image_name_and_tag[0], image_digest))
|
||||
except requests.exceptions.SSLError:
|
||||
LOG.exception("Failed to delete docker registry image %s" %
|
||||
image_name_and_tag)
|
||||
raise exception.DockerRegistrySSLException()
|
||||
except Exception:
|
||||
LOG.exception("Failed to delete docker registry image %s" %
|
||||
image_name_and_tag)
|
||||
raise exception.DockerRegistryAPIException()
|
||||
|
||||
if image_delete_response.status_code != 202:
|
||||
LOG.error("Bad response from docker registry: %s"
|
||||
|
|
Loading…
Reference in New Issue