1
0
mirror of https://github.com/projectatomic/atomic.git synced 2026-02-06 21:45:24 +01:00
Files
atomic/Atomic/sign.py
Brent Baude 6f8a60eacc Atomic/sign.py: Ensure image tag is included in signing.
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
2016-10-31 15:45:08 +00:00

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