Merge "Verify docker registry certificate with system trusted CAs"
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user