mirror of
https://github.com/projectatomic/atomic.git
synced 2026-02-06 21:45:24 +01:00
The previous change where we removed the tag from the write file location impacted the signing because the tag was also being omitted. This ensures the tag is omitted from the file path but included in the call skopeo standalone-sign. Closes: #741 Approved by: rhatdan
159 lines
7.0 KiB
Python
159 lines
7.0 KiB
Python
from . import util
|
|
from . import Atomic
|
|
from . import discovery
|
|
import os
|
|
import tempfile
|
|
|
|
try:
|
|
from urlparse import urlparse #pylint: disable=import-error
|
|
except ImportError:
|
|
from urllib.parse import urlparse #pylint: disable=no-name-in-module,import-error
|
|
|
|
|
|
ATOMIC_CONFIG = util.get_atomic_config()
|
|
READ_URIS = ['file', 'http', 'https']
|
|
WRITE_URIS = ['file']
|
|
|
|
def cli(subparser):
|
|
# atomic sign
|
|
signer = util.get_atomic_config_item(['default_signer'])
|
|
gnupghome = util.getgnuhome()
|
|
|
|
signp = subparser.add_parser("sign",
|
|
help="Sign an image",
|
|
epilog="Create a signature for an image which can be "
|
|
"used later to verify it.")
|
|
signp.set_defaults(_class=Sign, func="sign")
|
|
signp.add_argument("images", nargs="*", help=_("images to sign"))
|
|
signp.add_argument("--sign-by", dest="sign_by", default=signer,
|
|
help=_("Name of the signing key. Currently %s, "
|
|
"default can be defined in /etc/atomic.conf" % signer))
|
|
signp.add_argument("-d", "--directory",
|
|
default=None,
|
|
dest="signature_path",
|
|
help=_("Define an alternate directory to store signatures"))
|
|
signp.add_argument("-g", "--gnupghome",
|
|
default=gnupghome,
|
|
dest="gnupghome",
|
|
help=_("Set the GNUPGHOME environment variable to "
|
|
"use an alternate user's GPG keyring. "
|
|
"Useful when running with sudo, "
|
|
"e.g. set to '~/.gnupg'. "
|
|
"Default is %s" % gnupghome
|
|
))
|
|
|
|
class Sign(Atomic):
|
|
def __init__(self):
|
|
super(Sign, self).__init__()
|
|
|
|
def sign(self, in_signature_path=None, images=None):
|
|
def no_reg_no_default_error(image, registry_path):
|
|
return "Unable to associate {} with configurations in {} and " \
|
|
"no 'default_store' is defined.".format(image,
|
|
registry_path)
|
|
|
|
if in_signature_path is None and getattr(self.args, 'signature_path', None) is not None:
|
|
in_signature_path = self.args.signature_path
|
|
|
|
if images is None:
|
|
images = self.args.images
|
|
|
|
if self.args.debug:
|
|
util.write_out(str(self.args))
|
|
|
|
signer = self.args.sign_by
|
|
|
|
if self.args.sign_by is None:
|
|
raise ValueError("No default identity (default_signer) was defined in /etc/atomic.conf "
|
|
"and no --sign-by identity was provided. You must provide an identity")
|
|
registry_config_path = util.get_atomic_config_item(["registry_confdir"], ATOMIC_CONFIG, '/etc/containers/registries.d')
|
|
registry_configs, default_store = util.get_registry_configs(registry_config_path)
|
|
|
|
# we honor GNUPGHOME if set, override with atomic.conf, arg overrides all
|
|
if self.args.gnupghome:
|
|
os.environ['GNUPGHOME'] = self.args.gnupghome
|
|
|
|
for sign_image in images:
|
|
registry, repo, image, tag, _ = util.Decompose(sign_image).all
|
|
ri = discovery.RegistryInspect(registry, repo, image, tag, debug=self.args.debug, orig_input=sign_image)
|
|
ri.ping()
|
|
ri.get_manifest()
|
|
manifest = ri.rc.orig_manifest
|
|
|
|
try:
|
|
manifest_file = tempfile.NamedTemporaryFile(mode="wb", delete=False)
|
|
manifest_file.write(manifest)
|
|
manifest_file.close()
|
|
manifest_hash = str(util.skopeo_manifest_digest(manifest_file.name))
|
|
expanded_image_name = ri.assemble_fqdn(include_tag=True)
|
|
|
|
if in_signature_path:
|
|
if not os.path.exists(in_signature_path):
|
|
raise ValueError("The path {} does not exist".format(in_signature_path))
|
|
signature_path = in_signature_path
|
|
|
|
else:
|
|
reg, repo, _, _, _ = util.Decompose(expanded_image_name).all
|
|
if not registry_configs and not default_store:
|
|
raise ValueError(no_reg_no_default_error(sign_image, registry_config_path))
|
|
reg_info = util.have_match_registry("{}/{}".format(reg, repo), registry_configs)
|
|
if not reg_info:
|
|
reg_info = default_store
|
|
|
|
signature_path = util.get_signature_write_path(reg_info)
|
|
if signature_path is None:
|
|
raise ValueError("No write path for {}/{} was "
|
|
"found in {}".format(reg, repo, registry_config_path))
|
|
elif urlparse(signature_path).scheme not in WRITE_URIS:
|
|
raise ValueError("Writing to {} is not supported. Use a supported scheme {} "
|
|
"instead.".format(urlparse(signature_path).scheme, WRITE_URIS))
|
|
|
|
# Deal with write path prepends
|
|
if urlparse(signature_path).scheme in WRITE_URIS:
|
|
signature_path = urlparse(signature_path).path
|
|
|
|
# Make sure signature path exists
|
|
if not os.path.exists(signature_path):
|
|
raise ValueError("The signature path {} does not exist".format(signature_path))
|
|
|
|
sigstore_path = "{}/{}@{}".format(signature_path, expanded_image_name.rsplit(':', 1)[0], manifest_hash)
|
|
self.make_sig_dirs(sigstore_path)
|
|
sig_name = self.get_sig_name(sigstore_path)
|
|
fq_sig_path = os.path.join(sigstore_path, sig_name)
|
|
if os.path.exists(fq_sig_path):
|
|
raise ValueError("The signature {} already exists. If you wish to "
|
|
"overwrite it, please delete this file first")
|
|
|
|
util.skopeo_standalone_sign(expanded_image_name, manifest_file.name,
|
|
self.get_fingerprint(signer), fq_sig_path, debug=self.args.debug)
|
|
util.write_out("Created: {}".format(fq_sig_path))
|
|
|
|
finally:
|
|
os.remove(manifest_file.name)
|
|
|
|
@staticmethod
|
|
def get_fingerprint(signer):
|
|
cmd = ['gpg2', '--no-permission-warning', '--with-colons', '--fingerprint', signer]
|
|
stdout = util.check_output(cmd)
|
|
for line in stdout.splitlines():
|
|
if line.startswith('fpr:'):
|
|
return line.split(":")[9]
|
|
|
|
@staticmethod
|
|
def make_sig_dirs(sig_path):
|
|
if not os.path.exists(sig_path):
|
|
# TODO
|
|
# perhaps revisit directory permissions
|
|
# when complete use-cases are known
|
|
os.makedirs(sig_path)
|
|
|
|
@staticmethod
|
|
def get_sig_name(sig_path):
|
|
sig_files = set(os.listdir(sig_path))
|
|
sig_int = 1
|
|
while True:
|
|
name = "signature-{}".format(sig_int)
|
|
if name not in sig_files:
|
|
return name
|
|
sig_int += 1
|