Merge "Verify docker registry certificate with system trusted CAs"

This commit is contained in:
Zuul
2020-06-01 14:54:16 +00:00
committed by Gerrit Code Review
4 changed files with 120 additions and 25 deletions
@@ -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"