Skip to content
This repository was archived by the owner on Oct 10, 2020. It is now read-only.
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Atomic/pull.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def pull_image(self):
be_utils.message_backend_change('docker', 'ostree')
else:
remote_image_obj = None
be.pull_image(self.args.image, remote_image_obj, debug=self.args.debug)
be.pull_image(self.args.image, remote_image_obj, debug=self.args.debug, assumeyes=self.args.assumeyes)
except ValueError as e:
write_out("Failed: {}".format(e))
return 1
Expand Down
98 changes: 64 additions & 34 deletions Atomic/trust.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
import yaml
import requests
import collections
from base64 import b64encode

TRANSPORT_TYPES = ["docker", "atomic", "dir"]

def cli(subparser):
# atomic trust
pubkeys_dir = util.get_atomic_config_item(['pubkeys_dir'])

trustp = subparser.add_parser("trust",
help=_("Manage system container trust policy"),
epilog="Manages the trust policy of the host system. "
Expand All @@ -29,10 +28,16 @@ def cli(subparser):
atomic: openshift-based atomic registry"""
commonp.add_argument("-k", "--pubkeys", nargs='?', default=[],
action="append", dest="pubkeys",
help=_("Absolute path of installed public key(s) to trust for TARGET. "
help=_("Local path or URL of public key(s) to trust for TARGET. "
"Keys are parsed and encoded into policy.json. "
"May used multiple times to define multiple public keys. "
"File(s) must exist before using this command."))
commonp.add_argument("-f", "--pubkeysfile", nargs='?', default=[],
Copy link
Member

Choose a reason for hiding this comment

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

Shoudl --pubkeys and --pubkeysfile be mutually exlusive? Or would a user specify both on the same command line?

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's possible and valid to use both commands, although unexpected. No reason to restrict it, IMO.

Copy link
Member

Choose a reason for hiding this comment

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

ok

action="append", dest="pubkeysfile",
help=_("Path of installed public key(s) to trust for TARGET. "
"Absolute path to keys is added to policy.json. "
"May used multiple times to define multiple public keys. "
"File(s) must exist before using this command. "
"Default directory is %s" % pubkeys_dir))
"File(s) must exist before using this command."))
commonp.add_argument("--sigstoretype", dest="sigstoretype", default="web",
choices=['atomic', 'local', 'web'],
help=sigstore_help)
Expand Down Expand Up @@ -82,13 +87,20 @@ def __init__(self, policy_filename="/etc/containers/policy.json"):
super(Trust, self).__init__()
self.policy_filename = os.environ.get('TRUST_POLICY', policy_filename)
self.atomic_config = util.get_atomic_config()
class Args():
def __init__(self):
self.debug = None
self.assumeyes = None
Copy link
Member

Choose a reason for hiding this comment

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

Why do I need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This can be called from Atomic/pull.py (the auto-discover trust workflow), in which case it fails to pass in global opts. @baude we discussed this. Not sure if there is a more elegant workaround. It works but does seem silly.

Copy link
Member

Choose a reason for hiding this comment

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

Ok

self.pubkeysfile = None
self.set_args(Args())

def add(self, registry=None, pubkeys=None, sigstore=None, sigstoretype=None, keytype=None, trust_type=None):
def add(self, registry=None, pubkeys=None, pubkeysfile=None, sigstore=None, sigstoretype=None, keytype=None, trust_type=None):
"""
Add trust to policy.json file and optionally update registries.d registry configuration
:param sigstoretype: string, human-readable sigstore type, one of "atomic", "web", "local"
:param registry: the registry[/repo] to add policy for
:param pubkeys: list of pubkeys to add with trust_type "signedBy"
:param pubkeys: list of pubkeys to encode in policy.json for trust_type "signedBy"
:param pubkeysfile: list of pubkey paths in policy.json for trust_type "signedBy"
:param keytype: string, "GPGKeys"
:param trust_type: string, one of "signedBy", "insecureAcceptAnything", "reject"
:param sigstore: string, URL of signature server
Expand All @@ -97,6 +109,8 @@ def add(self, registry=None, pubkeys=None, sigstore=None, sigstoretype=None, key
registry=self.args.registry
if pubkeys is None:
pubkeys=self.args.pubkeys
if pubkeysfile is None:
pubkeysfile=self.args.pubkeysfile
if sigstoretype is None:
sigstoretype=self.args.sigstoretype
if keytype is None:
Expand All @@ -123,15 +137,22 @@ def add(self, registry=None, pubkeys=None, sigstore=None, sigstoretype=None, key
policy["transports"][sstype] = {}

payload = []
for k in pubkeys:
if not os.path.exists(k):
raise ValueError("The public key file %s was not found. This file must exist to proceed." % k)
payload.append({ "type": trust_type, "keyType": keytype, "keyPath": k })
if pubkeys:
for k in pubkeys:
payload.append({ "type": trust_type, "keyType": keytype, "keyData": self.get_pubkey_data(k) })
if pubkeysfile:
for f in pubkeysfile:
if not os.path.exists(f):
raise ValueError("The public key file %s was not found. This file must exist to proceed." % f)
else:
payload.append({ "type": trust_type, "keyType": keytype, "keyPath": os.path.abspath(f) })
if trust_type == "signedBy":
if len(pubkeys) == 0:
if len(pubkeys) == 0 and len(pubkeysfile) == 0:
raise ValueError("At least one public key must be defined for type 'signedBy'")
else:
payload.append({ "type": trust_type })
if self.args.debug:
util.write_out(str(payload))
policy["transports"][sstype][registry] = payload
policy_file.seek(0)
json.dump(policy, policy_file, indent=4)
Expand Down Expand Up @@ -181,29 +202,39 @@ def modify_default(self):
json.dump(policy, policy_file, indent=4)
policy_file.truncate()

def install_pubkey(self, key_name, key_url):
"""
Installs remote public key to system config directory
:param key_name: id of key used as filename
:param key_url: download URI of public key
:return: pubkey path string or False
"""
pubkeys_dir = util.get_atomic_config_item(['pubkeys_dir'], util.get_atomic_config())
pubkey_file = "%s/%s" % (pubkeys_dir, key_name)
if not os.path.exists(pubkeys_dir):
os.mkdir(pubkeys_dir)
if os.path.exists(pubkey_file):
util.write_out("Public key %s already installed at %s" % (key_name, pubkey_file))
def get_pubkey_data(self, key_reference):
"""
Get public key base64 encoded string to embed as keyData in policy.json
:param key_reference: local file or download URI of public key
:return: encoded pubkey string or False
"""
try:
from urlparse import urlparse #pylint: disable=import-error
except ImportError:
from urllib.parse import urlparse #pylint: disable=no-name-in-module,import-error

keydata = None
token = urlparse(key_reference)
if not token.scheme or not token.netloc:
if not os.path.exists(key_reference):
raise ValueError("The public key file %s was not found. This file must exist to proceed." % key_reference)
else:
with open(key_reference, 'r') as f:
keydata = f.read()
else:
r = requests.get(key_url)
if token.scheme != "https":
if not self.args.assumeyes:
confirm = util.input("Are you sure you want to download this public key using insecure '%s' transport? (y/N) " % token.scheme)
if not "y" in confirm.lower():
raise ValueError("Aborting 'trust add' due to insecure download of public key from %s." % key_reference)
if self.args.debug:
util.write_out("Downloading key from %s" % key_reference)
r = requests.get(key_reference)
if r.status_code == 200:
with open(pubkey_file, 'w') as pubfile:
pubfile.write(r.content)
util.write_out("Installed public key %s" % pubkey_file)
keydata = r.content
else:
util.write_out("WARNING: Could not download public key using URL %s." % key_url)
util.write_out("Download the public key manually and install as %s" % pubkey_file)
return pubkey_file
raise ValueError("Could not download public key from %s. Status code %s." % (key_reference, r.status_code))
return b64encode(keydata)

def check_policy(self, policy, sstype):
"""
Expand Down Expand Up @@ -292,11 +323,10 @@ def discover_sigstore(self, pull_image):
sigstore_labels = self.get_sigstore_image_metadata(registry)
if self._validate_sigstore_labels(sigstore_labels):
if self.prompt_trust(sigstore_labels):
pubkey_path = self.install_pubkey(sigstore_labels['pubkey-id'], sigstore_labels['pubkey-url'])
explicit_sigstoretype = "web"
if "sigstore-type" in sigstore_labels:
explicit_sigstoretype = sigstore_labels['sigstore-type']
self.add(registry=scope, trust_type="signedBy", sigstoretype=explicit_sigstoretype, keytype="GPGKeys", pubkeys=[pubkey_path], sigstore=sigstore_labels['sigstore-url'])
self.add(registry=scope, trust_type="signedBy", sigstoretype=explicit_sigstoretype, keytype="GPGKeys", pubkeys=[sigstore_labels['pubkey-url']], sigstore=sigstore_labels['sigstore-url'])

def get_sigstore_image_metadata(self, registry, repo=None):
"""
Expand Down
1 change: 0 additions & 1 deletion atomic.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ default_docker: docker
registry_confdir: /etc/containers/registries.d/
discover_sigstores: true
sigstore_metadata_image: sigstore
pubkeys_dir: /etc/pki/containers


# Default storage backend [ostree, docker]
Expand Down
1 change: 1 addition & 0 deletions bash/atomic
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,7 @@ _atomic_trust_default() {
_atomic_trust_add() {
local options_with_args="
--pubkeys -k
--pubkeysfile -f
--sigstoretype
--type -t
--keytype
Expand Down
17 changes: 14 additions & 3 deletions docs/atomic-trust.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ atomic-trust - Manage system container trust policy
[**-j**|**--json**]
[**--raw**]
[**-k**|**--pubkeys** KEY1 [**-k**|**--pubkeys** KEY2,...]]
[**-f**|**--pubkeysfile** KEY1 [**f**|**--pubkeysfile** KEY2,...]]
[**--keytype** GPGKeys]
[**-t**|**--type** signedBy|insecureAcceptAnything|reject]
[**-s**|**--sigstore** https://URL[:PORT][/PATH]|file:///PATH]
Expand Down Expand Up @@ -49,8 +50,18 @@ testing.
Print usage statement.

**-k** **--pubkeys**
An absolute path to installed public key. May be used multiple times. Required
for **signedBy** type.
A reference to a local file or download URL to an exported public key. Keys
will be parsed and encoded inline with policy.json. Option may be used
multiple times to require an image be sigend by multiple keys. One of
**--pubkeys** or **--pubkeysfile** is required for **signedBy** type. This
option is recommended over **--pubkeysfile**.

**-f** **--pubkeysfile**
A path to an exported public key on the local system. Key paths
will be referenced in policy.json. Any path may be used but path
**/etc/pki/containers** is recommended. Option may be used multiple times to
require an image be sigend by multiple keys. One of **--pubkeys** or
**--pubkeysfile** is required for **signedBy** type.

**--keytype**
The public key type. Default: GPGKeys (only supported value)
Expand Down Expand Up @@ -107,8 +118,8 @@ Modify a trust scope, adding a second public key and changing
the sigstore web server

atomic trust add \
--pubkeys https://example.com/keys/example.pub \
--pubkeys /etc/pki/containers/foo@example.com \
--pubkeys /etc/pki/containers/bar@example.com \
--sigstore https://server.example.com/foobar/sigstore/ \
docker.io/foobar

Expand Down
1 change: 1 addition & 0 deletions tests/unit/test_pull.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class TestAtomicPullByDigest(unittest.TestCase):
class Args():
def __init__(self):
self.debug = None
self.assumeyes = None

def test_pull_by_digest(self):
image_name = "docker.io/busybox@sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912"
Expand Down
7 changes: 4 additions & 3 deletions tests/unit/test_trust.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ class Args():
def __init__(self):
self.sigstoretype = "atomic"
self.registry = "docker.io"
self.pubkeys = [os.path.join(FIXTURE_DIR, "key1.pub")]
self.pubkeys = []
self.pubkeysfile = [os.path.join(FIXTURE_DIR, "key1.pub")]
self.sigstore = "https://sigstore.example.com/sigs"
self.trust_type = "signedBy"
self.keytype = "GPGKeys"
Expand Down Expand Up @@ -103,7 +104,7 @@ def test_add_trust_keys(self):
def test_modify_trust_2_keys(self):
args = self.Args()
args.sigstore = None
args.pubkeys = [os.path.join(FIXTURE_DIR, "key1.pub"), os.path.join(FIXTURE_DIR, "key2.pub")]
args.pubkeysfile = [os.path.join(FIXTURE_DIR, "key1.pub"), os.path.join(FIXTURE_DIR, "key2.pub")]
testobj = Trust(policy_filename = TEST_POLICY)
testobj.atomic_config = util.get_atomic_config(atomic_config = os.path.join(FIXTURE_DIR, "atomic.conf"))
testobj.set_args(args)
Expand Down Expand Up @@ -184,7 +185,7 @@ def test_trust_gpg_email_id(self):
testobj = Trust(policy_filename = os.path.join(FIXTURE_DIR, "show_policy.json"))
testobj.atomic_config = util.get_atomic_config(atomic_config = os.path.join(FIXTURE_DIR, "atomic.conf"))
testobj.set_args(args)
actual = testobj.get_gpg_id(args.pubkeys)
actual = testobj.get_gpg_id(args.pubkeysfile)
self.assertEqual("security@redhat.com", actual)

def test_trust_gpg_noemail_id(self):
Expand Down