Exemple #1
0
async def test_process_chain_of_trust(
    monkeypatch,
    sample_nv1,
    m_request,
    m_trust_data,
    m_expiry,
    count_loaded_delegations,
    image: str,
    delegations: list,
    key: str,
    targets: list,
    delegation_count: int,
    exception,
):
    monkeypatch.setenv("DELEGATION_COUNT", "0")
    with exception:
        with aioresponses() as aio:
            aio.get(re.compile(r".*"),
                    callback=fix.async_callback,
                    repeat=True)
            signed_targets = (
                await sample_nv1._NotaryV1Validator__process_chain_of_trust(
                    Image(image), delegations, key))

            assert signed_targets == targets
            assert delegation_count == int(os.getenv("DELEGATION_COUNT"))
Exemple #2
0
def test_get_matching_rule_error(pol, mock_policy):
    p = pol.ImagePolicy()
    p.policy["rules"] = p.policy["rules"][1:]
    with pytest.raises(BaseConnaisseurException) as err:
        p.get_matching_rule(Image("reg.io/image"))
    assert ("no matching rule for image "
            '"reg.io/image:latest" could be found.') in str(err.value)
def test_get_trusted_digest(
    mock_trust_data,
    mock_keystore,
    mock_request,
    image: str,
    policy_rule: dict,
    digest: str,
):
    assert val.get_trusted_digest("host", Image(image), policy_rule) == digest
Exemple #4
0
def test_process_chain_of_trust(
    napi,
    mock_keystore,
    mock_request,
    mock_trust_data,
    image: str,
    req_delegations: dict,
    targets: list,
):
    assert napi.process_chain_of_trust("host", Image(image), req_delegations) == targets
Exemple #5
0
async def test_update_with_delegation_trust_data(
    m_request,
    m_trust_data,
    m_expiry,
    alice_key_store,
    sample_nv1,
    delegations,
):
    assert (
        await sample_nv1._NotaryV1Validator__update_with_delegation_trust_data(
            {}, delegations, alice_key_store, Image("alice-image")) is None)
Exemple #6
0
def get_trusted_digest(host: str, image: Image, policy_rule: dict):
    """
    Searches in given notary server(`host`) for trust data, that belongs to the
    given `image`, by using the notary API. Also checks whether the given
    `policy_rule` complies.

    Returns the signed digest, belonging to the `image` or throws if validation fails.
    """
    # prepend `targets/` to the required delegation roles, if not already present
    req_delegations = list(
        map(normalize_delegation, policy_rule.get("delegations", []))
    )

    # get list of targets fields, containing tag to signed digest mapping from
    # `targets.json` and all potential delegation roles
    signed_image_targets = process_chain_of_trust(host, image, req_delegations)

    # search for digests or tag, depending on given image
    search_image_targets = (
        search_image_targets_for_digest
        if image.has_digest()
        else search_image_targets_for_tag
    )

    # filter out the searched for digests, if present
    digests = list(map(lambda x: search_image_targets(x, image), signed_image_targets))

    # in case certain delegations are needed, `signed_image_targets` should only
    # consist of delegation role targets. if searched for the signed digest, none of
    # them should be empty
    if req_delegations and not all(digests):
        raise NotFoundException(
            'not all required delegations have trust data for image "{}".'.format(
                str(image)
            )
        )

    # filter out empty results and squash same elements
    digests = set(filter(None, digests))

    # no digests could be found
    if not digests:
        raise NotFoundException(
            'could not find signed digest for image "{}" in trust data.'.format(
                str(image)
            )
        )

    # if there is more than one valid digest in the set, no decision can be made, which
    # to chose
    if len(digests) > 1:
        raise AmbiguousDigestError("found multiple signed digests for the same image.")

    return digests.pop()
def test_process_chain_of_trust_error(
    mock_keystore,
    mock_request,
    mock_trust_data,
    image: str,
    req_delegations: list,
    error: str,
):
    with pytest.raises(BaseConnaisseurException) as err:
        val.process_chain_of_trust("host", Image(image), req_delegations)
    assert error in str(err.value)
def test_process_chain_of_trust(
    monkeypatch,
    mock_keystore,
    mock_request,
    mock_trust_data,
    image: str,
    req_delegations: dict,
    targets: list,
    root_pub: str,
):
    if root_pub:
        monkeypatch.setenv("ROOT_PUB", root_pub)
    assert val.process_chain_of_trust("host", Image(image), req_delegations) == targets
    async def validate(self,
                       image: Image,
                       trust_root: str = None,
                       delegations: list = None,
                       **kwargs):  # pylint: disable=arguments-differ
        if delegations is None:
            delegations = []
        # get the public root key
        pub_key = self.notary.get_key(trust_root)
        # prepend `targets/` to the required delegation roles, if not already present
        req_delegations = list(
            map(NotaryV1Validator.__normalize_delegation, delegations))

        # get list of targets fields, containing tag to signed digest mapping from
        # `targets.json` and all potential delegation roles
        signed_image_targets = await self.__process_chain_of_trust(
            image, req_delegations, pub_key)

        # search for digests or tag, depending on given image
        search_image_targets = (
            NotaryV1Validator.__search_image_targets_for_digest
            if image.has_digest() else
            NotaryV1Validator.__search_image_targets_for_tag)
        # filter out the searched for digests, if present
        digests = list(
            map(lambda x: search_image_targets(x, image),
                signed_image_targets))

        # in case certain delegations are needed, `signed_image_targets` should only
        # consist of delegation role targets. if searched for the signed digest, none of
        # them should be empty
        if req_delegations and not all(digests):
            msg = "Not all required delegations have trust data for image {image_name}."
            raise InsufficientTrustDataError(message=msg,
                                             image_name=str(image))

        # filter out empty results and squash same elements
        digests = set(filter(None, digests))

        # no digests could be found
        if not digests:
            msg = "Unable to find signed digest for image {image_name}."
            raise NotFoundException(message=msg, image_name=str(image))

        # if there is more than one valid digest in the set, no decision can be made,
        # which to chose
        if len(digests) > 1:
            msg = "Found multiple signed digests for image {image_name}."
            raise AmbiguousDigestError(message=msg, image_name=str(image))

        return digests.pop()
def test_get_trusted_digest_error(
    monkeypatch,
    mock_trust_data,
    mock_keystore,
    mock_request,
    image: str,
    policy: dict,
    error: str,
    root_pub: str,
):
    if root_pub:
        monkeypatch.setenv("ROOT_PUB", root_pub)
    with pytest.raises(BaseConnaisseurException) as err:
        val.get_trusted_digest("host", Image(image), policy)
    assert error in str(err.value)
Exemple #11
0
async def test_get_delegation_trust_data(
    monkeypatch,
    sample_notaries,
    m_request,
    m_trust_data,
    index,
    image,
    output,
    exception,
    log_lvl,
):
    monkeypatch.setenv("LOG_LEVEL", log_lvl)
    with exception:
        with aioresponses() as aio:
            aio.get(re.compile(r".*"), callback=fix.async_callback)
            no = notary.Notary(**sample_notaries[index])
            td = await no.get_delegation_trust_data(Image(image),
                                                    "targets/phbelitz")
            assert output is bool(td)
Exemple #12
0
async def test_get_trust_data(
    sample_notaries,
    m_request,
    m_trust_data,
    index,
    image,
    role,
    output,
    exception,
):
    with exception:
        with aioresponses() as aio:
            aio.get(re.compile(r".*"),
                    callback=fix.async_callback,
                    repeat=True)
            no = notary.Notary(**sample_notaries[index])
            td = await no.get_trust_data(Image(image), role)
            assert td.signed == output["signed"]
            assert td.signatures == output["signatures"]
Exemple #13
0
async def test_validate(
    sample_nv1,
    m_trust_data,
    m_request,
    m_expiry,
    image: str,
    key: str,
    delegations: list,
    digest: str,
    exception,
):
    with exception:
        with aioresponses() as aio:
            aio.get(re.compile(r".*"),
                    callback=fix.async_callback,
                    repeat=True)
            signed_digest = await sample_nv1.validate(Image(image), key,
                                                      delegations)
            assert signed_digest == digest
def test_get_matching_rule_error():
    with pytest.raises(exc.NoMatchingPolicyRuleError):
        c = co.Config()
        c.policy = c.policy[1:]
        assert c.get_policy_rule(Image("reg.io/image"))
def test_get_policy_rule(image: str, rule):
    c = co.Config()
    assert str(c.get_policy_rule(Image(image))) == rule
def test_get_trust_data_error(napi, mock_request, mock_trust_data):
    with pytest.raises(BaseConnaisseurException) as err:
        napi.get_trust_data("host", Image("empty.io/image:tag"),
                            TUFRole("targets"))
    assert 'no trust data for image "empty.io/image:tag".' in str(err.value)
def test_get_trust_data(napi, mock_request, mock_trust_data, image: str,
                        role: str, out: dict):
    trust_data_ = napi.get_trust_data("host", Image(image), TUFRole(role))
    assert trust_data_.signed == out["signed"]
    assert trust_data_.signatures == out["signatures"]
def test_search_image_targets_for_tag(napi, image: str, digest: str):
    data = trust_data("tests/data/sample_releases.json")["signed"]["targets"]
    assert napi.search_image_targets_for_tag(data, Image(image)) == digest
Exemple #19
0
def admit(request: dict):
    """
    Admits a request, parses all image names from it, validates the images,
    optionally mutates the request and sends back a response. The request is
    only allowed, if all images are successfully validated.
    """
    uid = request["request"]["uid"]
    request_object = request["request"]["object"]
    object_kind = request_object["kind"]

    # container specifications differ from deployment objects,
    # Pod/Deployment/Job/...
    containers = get_container_specs(request_object)
    patches = []

    # child resources have mutated image names, as their parents got mutated
    # before their creation. this may result in mismatch of rules or duplicate
    # lookups for already approved images. so child resources are automatically
    # approved without further check ups, when their parents were approved
    # earlier.
    acceptable_images = []
    owner_references = request_object["metadata"].get("ownerReferences", [])
    namespace = request_object["metadata"].get("namespace",
                                               request["request"]["namespace"])
    if owner_references:
        for index, owner in enumerate(owner_references):
            if owner["kind"] in (
                    "Pod",
                    "Deployment",
                    "ReplicationController",
                    "ReplicaSet",
                    "DaemonSet",
                    "StatefulSet",
                    "Job",
                    "CronJob",
            ):
                acceptable_images += get_parent_images(request, index,
                                                       namespace)

    policy = ImagePolicy()

    # validate all images from the request
    for index, container in enumerate(containers):
        try:
            logging_context = create_logging_context(request,
                                                     container["image"])

            # child approval
            if container["image"] in acceptable_images:
                msg = 'automatic child approval for "{}".'.format(
                    container["image"])
                logging.info(str({"message": msg, "context": logging_context}))
                continue

            image = Image(container["image"])

            policy_rule = policy.get_matching_rule(image)
            verify = policy_rule.get("verify", True)

            # if image doesn't need verification, continue
            if not verify:
                msg = 'no verification for image "{}".'.format(str(image))
                logging.info(str({"message": msg, "context": logging_context}))
                continue

            msg = 'start verification of image "{}".'.format(str(image))
            logging.debug(
                str({
                    "message":
                    msg,
                    "context":
                    dict(logging_context,
                         matching_rule=policy_rule.get("pattern")),
                }))

            # get signed digest and update image reference with the digest
            trusted_digest = get_trusted_digest(
                os.environ.get("NOTARY_SERVER"), image, policy_rule)
            image.set_digest(trusted_digest)
            patches += [
                get_json_patch(object_kind=object_kind,
                               index=index,
                               image_name=str(image))
            ]

            msg = 'successful verification of image "{}"'.format(str(image))
            logging.info(str({"message": msg, "context": logging_context}))
        except BaseConnaisseurException as err:
            err.context.update(logging_context)
            raise err

    return get_admission_review(uid, True, patch=patches)
 def containers(self):
     return {(container_type, index): Image(container["image"])
             for container_type in ["containers", "initContainers"]
             for index, container in enumerate(
                 self.spec.get(container_type, []))}
Exemple #21
0
def test_search_image_targets_for_tag(sample_nv1, image: str, digest: str):
    data = fix.get_td("sample_releases")["signed"]["targets"]
    assert (sample_nv1._NotaryV1Validator__search_image_targets_for_tag(
        data, Image(image)) == digest)
    root_cert_pem = base64.b64decode(bytearray(root_cert_base64, "utf-8"))
    root_cert = x509.load_pem_x509_certificate(root_cert_pem,
                                               default_backend())
    root_public_bytes = root_cert.public_key().public_bytes(
        Encoding.PEM, PublicFormat.SubjectPublicKeyInfo)
    root_public_key = root_public_bytes.decode("utf-8")

    return root_key_id, root_public_key


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description=("Gets the root public key and key ID "
                     "from a notary server for a specific image."))
    parser.add_argument(
        "--server",
        "-s",
        help="address of the notary server",
        type=str,
        default="notary.docker.io",
    )
    parser.add_argument("--image",
                        "-i",
                        help="name of the image",
                        type=str,
                        required=True)
    args = parser.parse_args()
    root_key_id, root_key = asyncio.run(
        get_pub_root_key(args.server, Image(args.image)))
    print(f"KeyID: {root_key_id}\nKey: {root_key}")
Exemple #23
0
def test_get_matching_rule(pol, mock_policy, image: str, rule: dict):
    p = pol.ImagePolicy()
    assert p.get_matching_rule(Image(image)) == rule
Exemple #24
0
async def test_validate(approve, out, exception):
    with exception:
        val = st.StaticValidator("sample", approve)
        assert await val.validate(Image("sample")) == out
        assert obj.kind == static_k8s[index]["kind"]
        assert obj.api_version == static_k8s[index]["apiVersion"]
        assert obj.namespace == static_k8s[index]["namespace"]
        assert obj.name == static_k8s[index]["name"]


@pytest.mark.parametrize(
    "index, parent_list, exception",
    [
        (0, {}, fix.no_exc()),
        (
            1,
            {
                ("containers", 0):
                Image("securesystemsengineering/charlie-image@sha256"
                      ":91ac9b26df583762234c1cdb2fc930364754ccc59bc7"
                      "52a2bfe298d2ea68f9ff"),
            },
            fix.no_exc(),
        ),
        (2, {}, pytest.raises(exc.ParentNotFoundError)),
        (3, {}, fix.no_exc()),
    ],
)
def test_k8s_object_parent_containers(adm_req_sample_objects, m_request, index,
                                      parent_list, exception):
    obj = wl.WorkloadObject(adm_req_sample_objects[index], "default")
    with exception:
        assert obj.parent_containers == parent_list