Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add registry sidecar support #146

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .virtualenv.requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ lxml
requests
dnspython
M2Crypto
toml
2 changes: 2 additions & 0 deletions cloud-regionsrv-client.spec
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Requires: python3-lxml
Requires: python3-requests
Requires: python3-urllib3
Requires: python3-zypp-plugin
Requires: python3-toml
Requires: regionsrv-certs
Requires: zypper
BuildRequires: systemd
Expand All @@ -56,6 +57,7 @@ BuildRequires: python3-lxml
BuildRequires: python3-requests
BuildRequires: python3-setuptools
BuildRequires: python3-zypp-plugin
BuildRequires: python3-toml
BuildRequires: systemd-rpm-macros
BuildRoot: %{_tmppath}/%{name}-%{version}-build

Expand Down
162 changes: 155 additions & 7 deletions lib/cloudregister/registerutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import subprocess
import sys
import time
import toml

from lxml import etree
from pathlib import Path
Expand All @@ -45,6 +46,9 @@
REGISTRATION_DATA_DIR = '/var/cache/cloudregister/'
REGISTERED_SMT_SERVER_DATA_FILE_NAME = 'currentSMTInfo.obj'
RMT_AS_SCC_PROXY_MARKER = 'rmt_is_scc_proxy'
DOCKER_REGISTRY_CREDENTIALS_PATH = '.docker/config.json'
jesusbv marked this conversation as resolved.
Show resolved Hide resolved
DOCKER_CONFIG_PATH = '/etc/docker/daemon.json'
REGISTRIES_CONF_PATH = '/etc/containers/registries.conf'


# ----------------------------------------------------------------------------
Expand All @@ -56,10 +60,12 @@ def add_hosts_entry(smt_server):
smt_ip = smt_server.get_ipv4()
if has_ipv6_access(smt_server):
smt_ip = smt_server.get_ipv6()
entry = '%s\t%s\t%s\n' % (
entry = '%s\t%s\t%s\n%s\t%s\n' % (
smt_ip,
smt_server.get_FQDN(),
smt_server.get_name()
smt_server.get_name(),
smt_ip,
smt_server.get_registry_FQDN(),
)
with open('/etc/hosts', 'a') as hosts_file:
hosts_file.write(smt_hosts_entry_comment)
Expand All @@ -86,23 +92,30 @@ def add_region_server_args_to_URL(api, cfg):


# ----------------------------------------------------------------------------
def clean_hosts_file(domain_name):
def clean_hosts_file(domain_name, registry_name):
"""Remove the smt server entry from the /etc/hosts file"""
if isinstance(domain_name, str):
domain_name = domain_name.encode()
if isinstance(registry_name, str):
registry_name = registry_name.encode()
new_hosts_content = []
# Handle entries as bytes,
# Yes, users put non ascii characters into /etc/hosts
with open(HOSTSFILE_PATH, 'rb') as hosts_file:
content = hosts_file.readlines()

smt_announce_found = None
smt_domain_found = None
for entry in content:
if b'# Added by SMT' in entry:
smt_announce_found = True
continue
if smt_announce_found and domain_name in entry:
smt_announce_found = False
smt_domain_found = True
continue
if smt_domain_found and registry_name in entry:
smt_domain_found = False
continue
new_hosts_content.append(entry)

Expand Down Expand Up @@ -228,7 +241,9 @@ def fetch_smt_data(cfg, proxies, quiet=False):
logging.error('Unable to obtain SMT server information, exiting')
sys.exit(1)
smt_info = json.loads(response.text)
expected_entries = ('fingerprint', 'SMTserverIP', 'SMTserverName')
expected_entries = (
'fingerprint', 'SMTserverIP', 'SMTserverName', 'SMTregistryName'
)
smt_info_xml = '<regionSMTdata><smtInfo '
for attr in expected_entries:
value = smt_info.get(attr)
Expand Down Expand Up @@ -503,6 +518,55 @@ def get_credentials(credentials_file):


# ----------------------------------------------------------------------------
def set_registry_config(registry_fqdn, username, password):
registry_credentials_paths = [
os.path.join(
os.path.expanduser('~'), DOCKER_REGISTRY_CREDENTIALS_PATH
),
os.path.join(os.sep, 'root', DOCKER_REGISTRY_CREDENTIALS_PATH),
]
for cfg_path in registry_credentials_paths:
set_registry_credentials(registry_fqdn, username, password, cfg_path)
set_registry_order_search(registry_fqdn)


# ----------------------------------------------------------------------------
def set_registry_credentials(registry_fqdn, username, password, cfg_path):
"""Set the auth token to pull images from SUSE registry."""
auth_token = base64.b64encode('{username}:{password}'.format(
username=username,
password=password
).encode()).decode()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We carry the username and password information as base64 for obfuscation reasons right ? not for security, right ? Just asking because there is no security with b64encode

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not because of security, it is because we have to, docker|podman dont like anything that is not base64

> cat ${XDG_RUNTIME_DIR}/containers/auth.json
{
	"auths": {
		"registry.suse-local.com:5000": {
			"auth": "SCC_foo:password_bar"
		}
	}
}
> podman login  --tls-verify=false
Error: get credentials: 1 error occurred:
	* illegal base64 data at input byte 72
> cat ~/.docker/config.json
{
        "auths": {
                "registry.suse-local.com:5000": {
                        "auth": "SCC_foo:password_bat"
                }
        }
}
> docker login registry.suse-local.com:5000
WARNING: Error loading config file: ~/.docker/config.json: illegal base64 data at input byte 3
Username:

from the podman doc

[...] Podman creates ${XDG_RUNTIME_DIR}/containers/auth.json (if the file does not exist) and then stores the username and password from STDIN as a base64 encoded string in it

from the docker doc

 [...]
 If none of these binaries are present, it stores the credentials (i.e. password) in base64 encoding in the config files described above

registry_credentials = {}
registry_credentials[registry_fqdn] = {'auth': auth_token}

config_json = {}
try:
with open(cfg_path, 'r') as cred_json:
config_json = json.load(cred_json)
# file exists
# set the new registry credentials,
# independently of what that content was
config_json['auths'].update(registry_credentials)
except (FileNotFoundError, KeyError):
# config file does not exist or "auths" key is not set
os.makedirs(os.path.dirname(cfg_path), exist_ok=True)
config_json.update({'auths': registry_credentials})

with open(cfg_path, 'w') as cred_json_file:
json.dump(config_json, cred_json_file)

logging.info(
'Credentials for the registry added in %s' % ' '.join(cfg_path)
)


# ----------------------------------------------------------------------------
def set_registry_order_search(registry_fqdn):
_set_registry_order_search_podman(registry_fqdn)
_set_registry_order_search_docker(registry_fqdn)


def refresh_registry_credentials():
"""Refresh registry credentials."""
# to silence InsecureRequestWarning
Expand Down Expand Up @@ -786,7 +850,7 @@ def get_smt(cache_refreshed=None):
'"%s"' % str((server.get_ipv4(), server.get_ipv6()))
)
# Assume the new server is in the same domain
clean_hosts_file(server.get_FQDN())
clean_hosts_file(server.get_FQDN(), server.get_registry_FQDN())
add_hosts_entry(server)
set_as_current_smt(server)
return server
Expand Down Expand Up @@ -1274,7 +1338,7 @@ def remove_registration_data():
logging.warning('Unable to remove client registration from server')
logging.warning(e)
logging.info('Continue with local artifact removal')
clean_hosts_file(domain_name)
clean_hosts_file(domain_name, smt.get_registry_FQDN())
__remove_repo_artifacts(server_name)
os.unlink(smt_data_file)
if is_scc_connected():
Expand Down Expand Up @@ -1308,7 +1372,7 @@ def remove_registration_data():

# ----------------------------------------------------------------------------
def replace_hosts_entry(current_smt, new_smt):
clean_hosts_file(current_smt.get_FQDN())
clean_hosts_file(current_smt.get_FQDN(), current_smt.get_registry_FQDN())
add_hosts_entry(new_smt)


Expand Down Expand Up @@ -1651,3 +1715,87 @@ def __replace_url_target(config_files, new_smt):
current_service_server,
new_smt.get_FQDN())
)


# ----------------------------------------------------------------------------
def _set_registry_order_search_podman(registry_fqdn):
public_registry_fqdn = 'registry.suse.com'
private_registry = {'location': registry_fqdn, 'insecure': False}
public_registry = {'location': public_registry_fqdn, 'insecure': False}
registries_conf = {}
registries_search = []
registries = []
try:
registries_conf = _get_registry_conf_file(
REGISTRIES_CONF_PATH, 'podman'
)
registries_search = registries_conf.get(
'unqualified-search-registries', []
)
registries = registries_conf.get('registry', [])
if public_registry_fqdn not in registries_search:
registries_search.insert(0, public_registry_fqdn)
if public_registry not in registries:
registries.insert(0, public_registry)
if private_registry not in registries:
registries.insert(0, private_registry)
except FileNotFoundError:
# file does not exist, create the file
logging.info(
'Configuration file for Podman not found in %s' %
REGISTRIES_CONF_PATH
)
os.makedirs(os.path.dirname(REGISTRIES_CONF_PATH), exist_ok=True)
registries_search.append(public_registry_fqdn)
registries = [private_registry, public_registry]

registries_conf['unqualified-search-registries'] = registries_search
registries_conf['registry'] = registries

with open(REGISTRIES_CONF_PATH, 'w') as registries_conf_file:
toml.dump(registries_conf, registries_conf_file)


# ----------------------------------------------------------------------------
def _get_registry_conf_file(container_path, container):
if container == 'podman':
with open(container_path, 'r') as registries_conf_file:
registries_conf = toml.load(registries_conf_file)
if container == 'docker':
with open(container_path, 'r') as registries_conf_file:
registries_conf = json.load(registries_conf_file)

return registries_conf


# ----------------------------------------------------------------------------
def _set_registry_order_search_docker():
# search is disabled for Docker server side for private registry
public_registry_fqdn = 'https://registry.suse.com'
docker_cfg_json = {}
secure_registries = []
registry_mirrors = []
try:
docker_cfg_json = _get_registry_conf_file(DOCKER_CONFIG_PATH, 'docker')
secure_registries = docker_cfg_json.get('secure-registries', [])
registry_mirrors = docker_cfg_json.get('registry-mirrors', [])
if public_registry_fqdn not in secure_registries:
secure_registries.insert(0, public_registry_fqdn)
if public_registry_fqdn not in registry_mirrors:
registry_mirrors.insert(0, public_registry_fqdn)
except FileNotFoundError:
# config file does not exist
logging.info(
'Configuration file for Docker not found in %s' %
DOCKER_CONFIG_PATH
)
os.makedirs(os.path.dirname(DOCKER_CONFIG_PATH), exist_ok=True)
secure_registries.append(public_registry_fqdn)
registry_mirrors.append(public_registry_fqdn)

docker_cfg_json['registry-mirrors'] = registry_mirrors
docker_cfg_json['secure-registries'] = secure_registries
with open(DOCKER_CONFIG_PATH, 'w') as docker_config_file_json:
json.dump(docker_cfg_json, docker_config_file_json)

logging.info('Config for the registry added in %s' % DOCKER_CONFIG_PATH)
7 changes: 7 additions & 0 deletions lib/cloudregister/smt.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def __init__(self, smtXMLNode, https_only=False):
except KeyError:
self._region = 'unknown'
self._fqdn = smtXMLNode.attrib['SMTserverName']
self._registry_fqdn = smtXMLNode.attrib['SMTregistryName']
self._fingerprint = smtXMLNode.attrib['fingerprint']
self._cert = None
self._cert_names = ('smt.crt', 'rmt.crt')
Expand All @@ -56,6 +57,7 @@ def __eq__(self, other_smt):
self.get_ipv4() == other_smt.get_ipv4() and
self.get_ipv6() == other_smt.get_ipv6() and
self.get_FQDN() == other_smt.get_FQDN() and
self.get_registry_FQDN() == other_smt.get_registry_FQDN() and
self.get_fingerprint() == other_smt.get_fingerprint() and
self.get_region() == other_smt.get_region()
):
Expand Down Expand Up @@ -94,6 +96,11 @@ def get_FQDN(self):
"""Return the fully qualified domain name"""
return self._fqdn

# --------------------------------------------------------------------
def get_registry_FQDN(self):
"""Return the fully qualified domain registry name"""
return self._registry_fqdn

# --------------------------------------------------------------------
def get_name(self):
"""Return the name"""
Expand Down
5 changes: 5 additions & 0 deletions tests/data/registry_conf.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
unqualified-search-registries = ["foo.com", "bar.registry.com", "docker.io"]

[[registry]]
location = "foo.com"
insecure = true
4 changes: 4 additions & 0 deletions tests/data/unconfigured_registry.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
search-registries = ["docker.io"]

[[no-registry]]
location = "foo"